zoukankan      html  css  js  c++  java
  • 移动开发iOS&Android对比学习--异步处理

    在移动开发里很多时候需要用到异步处理。Android的主线程如果等待超过一定时间的时候直接出现ANR(对不熟悉Android的朋友这里需要解释一下什么叫ANR。ANR就是Application Not Responding,应用无响应的意思。系统在应用一段时间无响应的时候会弹出这个对话框。用户可以选择继续等待或者强制关闭)。这些还是次要的,最主要的还是心急的用户。让用户长时间等待是得罪他们的最好办法!

    Android有一个很简单的办法实现异步处理:AnsyncTask。使用的时候你需要继承一个基类

    public abstract class AsyncTask<Params, Progress, Result>

    对java不熟的同学这里需要说明,尖括号里的是类型参数。这是java的一个语法,泛型。这三个参数分别指定的是输入参数的类型、任务执行进度值的类型和任务执行结果的类型。并不是所有的类型都被使用,不用的时候指定为void类型。

    最简单的使用就是继承这个类以后Override方法doInBackground(Params... params)。使用的时候实例化你的AsyncTask实现,调用execute(Params... params)方法就开启了异步操作。例如:

    布局代码:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        
        <Button  
            android:id="@+id/executeButton"  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:text=" Start "/>   
        <ScrollView  
            android:layout_width="fill_parent"   
            android:layout_height="wrap_content">  
            <TextView  
                android:id="@+id/textView"  
                android:layout_width="fill_parent"   
                android:layout_height="wrap_content"/>  
        </ScrollView> 
    
    </LinearLayout>

    java代码:

    import android.app.Activity;
    import android.os.AsyncTask;
    import android.os.Bundle;
    import android.util.Log;
    import android.view.View;
    import android.view.View.OnClickListener;
    import android.widget.Button;
    import android.widget.TextView;
    
    public class AsyncTaskDemo extends Activity {
        
        private Button executeButton;   
        private TextView textView; 
        
        @Override
        public void onCreate(Bundle savedInstanceState){
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_demo);
            
            executeButton = (Button)findViewById(R.id.executeButton);
            textView = (TextView)findViewById(R.id.textView);
             
            executeButton.setOnClickListener(new OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    Log.i("-task-","Task started.");
                    new MyAsyncTask().execute(" on click");
                }
            });
        }
        
        class MyAsyncTask extends AsyncTask<String, Integer, String>{
            
            @Override
            public String doInBackground(String... params){
                Log.i("-task-","doInBackground is called" + params[0]);
                try {
                    // 线程暂停2秒模拟后台任务
                    Thread.sleep(2000);
                } catch (Exception e) {
                    return "";
                }
                return "Hello World from AsyncTask";
            }
            
            // 在doInBackground方法执行之后调用这个方法
            public void onPostExecute(String result){
                Log.i("-task-", "doPostExecute is called");
                textView.setText(result);
            }
        }
    }

    运行效果:

    看到这里读者应该明白,doInBackground方法就是在后台执行的那个异步的方法。

    但是AsyncTask的妙处不至于此。大家都知道,在UI线程意外不可以更新UI组件。如果后台线程执行完后一定要通知UI线程,由UI线程根据后台线程的执行结果更新UI组件。AsyncTask的妙处就在于,在它的实现中实现某些方法就可以直接更新UI。当然这些方法是在UI线程中的。这些方法有:

    1. onPreExecute() 在execute方法调用后立即执行,在doInBackground执行前执行。相当于在后台操作开始前做一些准备工作。
    2. doInBackground(Params... params) 在onPreExecute之后执行,处理需要在后台操作的任务。在其中可以通过publishProgress(Progress... values)通知UI线程更新进度。
    3. onProgressUpdate(Progress... values)在调用publishProgress后此方法执行。将进度更新到UI组件上。
    4. onPostExecute(Result result) 在doInBackground执行结束之后,后台执行结果作为参数传入这个方法并根据这个结果更新UI组件。
    5. onCancelled() 取消后台操作的时候调用方法cancel(boolean endTaskDuringExecuting),这个时候onCancelled方法会被调用。同事,isCancelled()方法返回true。在后台任务的执行中需要检查是否已经被取消,尤其是循环中。这样可以保证后台任务可以尽快退出。

    下面就依据上面所说更新代码了:

    layout:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
        
        <Button  
            android:id="@+id/executeButton"  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:text=" Start "/>   
        <Button  
            android:id="@+id/cancelButton"  
            android:layout_width="fill_parent"  
            android:layout_height="wrap_content"  
            android:enabled="false"  
            android:text=" cancel "/>  
        <ProgressBar   
            android:id="@+id/progressBar"   
            android:layout_width="fill_parent"   
            android:layout_height="wrap_content"   
            android:progress="0"  
            android:max="100"  
            style="?android:attr/progressBarStyleHorizontal"/>
        <ScrollView  
            android:layout_width="fill_parent"   
            android:layout_height="wrap_content">  
            <TextView  
                android:id="@+id/textView"  
                android:layout_width="fill_parent"   
                android:layout_height="wrap_content"/>  
        </ScrollView> 
    
    </LinearLayout>

    java:

    public class AsyncTaskDemo extends Activity {
        
        private Button executeButton; 
        private Button cancelButton;
        private TextView textView; 
        private ProgressBar progressBar;
        
        private MyAsyncTask myAsyncTask;
        
        @Override
        public void onCreate(Bundle savedInstanceState){
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_demo);
            
            executeButton = (Button)findViewById(R.id.executeButton);
            cancelButton = (Button)findViewById(R.id.cancelButton);
            textView = (TextView)findViewById(R.id.textView);
            progressBar = (ProgressBar)findViewById(R.id.progressBar);
             
            executeButton.setOnClickListener(new OnClickListener() {
                
                @Override
                public void onClick(View v) {
                    Log.i("-task-","Task started.");
                    
                    // 每次开始的时候必须初始化一次。
                    myAsyncTask = new MyAsyncTask();
                    myAsyncTask.execute(" on click");
                    
                    cancelButton.setEnabled(true);
                    executeButton.setEnabled(false);
                }
            });
            
            cancelButton.setOnClickListener(new OnClickListener() {
                
                @Override
                public void onClick(View arg0) {
                    Log.i("-task", "Task cancelled");
                    
                    // 取消任务
                    myAsyncTask.cancel(true);
                }
            });
        }
        
        class MyAsyncTask extends AsyncTask<String, Integer, String>{
            
            @Override
            protected void onPreExecute() {
                Log.i("-task-", "onPreExecute is called");
                textView.setText("Mission is about to start dude...");
            }
            
            @Override
            public String doInBackground(String... params){
                Log.i("-task-","doInBackground is called" + params[0]);
                
                if (isCancelled()) {
                    return "cancelled";
                }
                try {
                    // 开始进度是0.
                    publishProgress(0);
                    Thread.sleep(2000);
                    publishProgress(30);
                    
                    // 如果有循环的话需要在循环中每次都检查是否任务已经被取消。
                    if (isCancelled()) {
                        return "cancelled";
                    }
                    
                    // 线程暂停2秒模拟后台任务
                    Thread.sleep(2000);
                    publishProgress(100);
                } catch (Exception e) {
                    return "";
                }
                return "Hello World from AsyncTask";
            }
            
            @Override
            protected void onProgressUpdate(Integer...progresses){
                Log.i("-task-", "onProgressUpdate is called");
                
                progressBar.setProgress(progresses[0]);
                textView.setText(progresses[0] + "% is handled...");
            }
            
            // 在doInBackground方法执行之后调用这个方法
            @Override
            protected void onPostExecute(String result){
                Log.i("-task-", "doPostExecute is called");
                textView.setText(result);
                
                executeButton.setEnabled(true);
                cancelButton.setEnabled(false);
            }
            
            @Override
            protected void onCancelled(){
                Log.i("-task-", "onCancelled is called");
                
                progressBar.setProgress(0);
                textView.setText("Cancelled");
                
                executeButton.setEnabled(true);
                cancelButton.setEnabled(false);
            }
        }
    }

    执行结果:

    1. 没有取消的

    2. 取消了的

    这里有一点需要注意。AsyncTask对象,一个只跑一次任务!所以,这里你可以看到,每次在执行一个后台任务的时候都要初始化一次。从这里看到,AsyncTask非常好用。只要把后台任务放到doInBackground方法里,其他的在UI线程上执行的只要按照需要放在其他的方法中就可以了。简直是随用随调,方便已用。

    在iOS里最方便的就是GCD了。GCD,Grand Central Dispatch。是OSX10.6 iOS4.0引入的,是一套新的编写并行程序的方法,是基于c开发的开源框架。GCD的便携很大程度上依赖于block。block就类似于lambda表达式。所以GCD的写法非常简单。GCD的异步操作是基于线程的,只不过是有GCD去管理这些线程,以发挥多核心CPU的优势。开发者就不用花时间在具体的线程管理的细节上。

    block的例子:

    void (^blockDemo)(void) = ^{
          // do something here...  
    }

    关于block更详细的知识,请到这里自行补脑。

    如果说block是异步任务的子弹的话,那么那把枪就是dispatch queue。dispatch queue可以是串行执行的也可以是并行执行的。串行dispatch queue按照先进先出的顺序执行任务,并行的dispatch queue只是按照这样的方式开始任务,至于具体的执行顺序会随具体的执行条件变化。

    GCD包括三中queue:

    1. Main queue:系统定义,用dispatch_get_main_queue获取。提交到这个queue的任务会在UI线程中执行。
    2. Global queue:系统定义,用dispatch_get_global_queue获取。global queue就是并行queue,一共有四种优先级。
    3. Private queue:用 dispatch_queue_create创建。默认为串行执行。可以用DISPATCH_QUEUE_CONCURRENT指定为并行队列(只在iOS5或以后的系统中有效)。

    下面通过代码展示使用GCD实现异步操作有多简单:

    // 创建一个private queue
    dispatch_queue_t myBackgroundQueue;
    myBackgroundQueue = dispatch_queue_create("com.company.subsystem.task", NULL);
     
    // 异步处理
    // Dispatch a private queue
    dispatch_async(myBackgroundQueue, ^(void) {
        // do some time consuming things here
    });
    
    // Release queue。iOS6以后系统自动管理内存。
    dispatch_release(myBackgroundQueue);
    
    // 获取global queue
    dispatch_queue_t myGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // 异步操作
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
    , ^(void) {
        // do some time consuming things here
    });

    只要给你的queue dispatch_async一个任务(block)就可以。

    由此可见,用dispatch queue实现异步比Android的AsyncTask简单了很多。只需要简单的几句话就可以实现。不用实现抽象类什么的繁琐操作。当你调用dipatch_async后,代码立即返回分发出去的任务由系统分配资源在后台异步执行。

    参考上面Android的例子,在dispatch queue执行的过程中要更新异步任务执行进度怎么办呢,要在界面上现实执行了百分之多少,progress bar要现实对应的进度怎么办呢?UI组件的更新需要在UI线程中执行。这个时候就需要用到上面说的main queue了。我们上面已经说过使用dispatch_get_main_queue方法来获取main queue也就是UI线程的queue。下面通过一个简单的例子说明在dispatch queue异步操作中如何更新UI组件。

    dispatch_async(myBackgroundQueue, ^(void) {
     
    // 异步操作在这里。。。
     
        dispatch_async(dispatch_get_main_queue(), ^{
     
            // 操作UI组件等。。。
        });
    });

    另外需要说明一点:如果一个变量需要在queue中操作,准确的说是在block中操作的话,需要有一个特别的声明:在常用的声明前加__block。

    那么上面在Android中示例用ObjC的GCD该如何实现呢?下面给出答案。

    界面布局:

    头文件代码:

    #import <UIKit/UIKit.h>
    
    @interface CMRootViewController : UIViewController{
    @private
        __block BOOL _cancelTask;
    }
    
    @property (assign, nonatomic, readonly) __block BOOL cancelled;
    @property (strong, nonatomic) dispatch_queue_t taskQueue;
    
    @property (weak, nonatomic) IBOutlet UIProgressView *progressBar;
    @property (weak, nonatomic) IBOutlet UILabel *progressLabel;
    @property (weak, nonatomic) IBOutlet UIButton *startButton;
    @property (weak, nonatomic) IBOutlet UIButton *cancelButton;
    
    - (IBAction)startAction:(id)sender;
    - (IBAction)cancelAction:(id)sender;
    
    @end

    源文件:

    #import "CMRootViewController.h"
    
    @implementation CMRootViewController
    
    - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
    {
        self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
        if (self) {
            // 初始化线程
            self.taskQueue = dispatch_queue_create("com.queue.demo", NULL);
            _cancelTask = NO;
            _cancelled = NO;
            
        }
        return self;
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.title = @"Queue Demo";
        self.progressBar.progress = .0f;
        self.progressLabel.text = @"0%";
    }
    
    - (IBAction)startAction:(id)sender {
        NSLog(@"Start button is clicked.");
        
        _startButton.enabled = NO;
        _cancelButton.enabled = YES;
        _cancelled = NO;
        _cancelTask = NO;
        
        // 这里用sleep方法暂停线程,代表那些费时的操作。
        // 在任务执行的过程中check是否已经取消了操作,尤其在循环里。这样可以尽快停止后台操作
        dispatch_async(self.taskQueue, ^{
            if (_cancelTask) {
                NSLog(@"Task cancelled");
                _cancelButton.enabled = NO;
                _startButton.enabled = YES;
                _cancelled = YES;
                return;
            }
            sleep(2);
            NSLog(@"Task: 10%% is completed.");
            dispatch_async(dispatch_get_main_queue(), ^{
                self.progressBar.progress = .1f;
                self.progressLabel.text = @"10%";
            });
            
            if (_cancelTask) {
                NSLog(@"Task cancelled");
                _cancelButton.enabled = NO;
                _startButton.enabled = YES;
                _cancelled = YES;
                return;
            }
            sleep(3);
            NSLog(@"Task: 60%% is completed.");
            dispatch_async(dispatch_get_main_queue(), ^{
                self.progressBar.progress = .6f;
                self.progressLabel.text = @"60%";
            });
            
            if (_cancelTask) {
                NSLog(@"Task cancelled");
                _cancelButton.enabled = NO;
                _startButton.enabled = YES;
                _cancelled = YES;
                return;
            }
            sleep(2);
            NSLog(@"Task: 80%% is completed.");
            dispatch_async(dispatch_get_main_queue(), ^{
                self.progressBar.progress = .8f;
                self.progressLabel.text = @"80%";
            });
            
            if (_cancelTask) {
                NSLog(@"Task cancelled");
                _cancelButton.enabled = NO;
                _startButton.enabled = YES;
                _cancelled = YES;
                return;
            }
            sleep(1);
            NSLog(@"Task: 100%% is completed.");
            dispatch_async(dispatch_get_main_queue(), ^{
                self.progressBar.progress = 1.f;
                self.progressLabel.text = @"100%";
            });
            
        });
    }
    
    - (IBAction)cancelAction:(id)sender {
        NSLog(@"Cancel button is clicked.");
        _cancelTask = YES;
        
        _startButton.enabled = YES;
        _cancelButton.enabled = NO;
    }
    
    @end

    Android和iOS对比着学习可以对这两个平台的各种技术留下深刻的印象。毕竟都是移动平台,大多数的语言之外的东西有很多东西都是想通的。Android有着广大的用户群体,而且在不断的进步。所以不必把两个平台完全独立或者对立起来。对于开发者来说得到实惠才是最重要的!

  • 相关阅读:
    SQL Server IF Exists 判断数据库对象是否存在的用法
    C# RDLC报表不出现预览窗体直接输出到打印机
    C# 未安装Office环境下使用NPOI导出Excel文件
    C# 键盘中的按键对应KeyValue
    微信小程序下可以使用的MD5以及AES加密(通用)
    SQL Server 根据树状结构表生成以/号分割的路由字符串
    C# Winform下一个热插拔的MIS/MRP/ERP框架16(窗体基类场景2)
    WEB H5 JS QRCode二维码快速自动生成
    C# Winform 小技巧(Datagridview某一列按状态显示不同图片)
    获取请求地址的IP地址
  • 原文地址:https://www.cnblogs.com/sunshine-anycall/p/3411112.html
Copyright © 2011-2022 走看看