Android高性能编码 - 第六篇 异步任务与多线程

第六篇异步任务与多线程

6.1 ANR与异步任务

ANR是Android系统对程序无响应的异常响应,从Android系统的角度来看,发生ANR的原因主要有三个方面:

1.        Activity/Fragment在5秒内没有响应输入的事件(例如,按键按下,屏幕触摸);

2.        BroadcastReceiver在10秒内没有执行完毕;

3.        Service的生命周期方法中有20s以上耗时。

从开发编码的角度来看,主要可能发生的场景包括:

l  耗时的网络访问

l  在主线程中解析网络返回的数据

l  在主线程中进行加解密(编解码)运算

l  遍历资源文件Assert

l  大量的数据读写

l  繁重的数据库操作

l  硬件操作(比如camera)

l  service binder的数量达到上限

l  其他线程持有锁,导致主线程等待超时

以上,对于锁资源的竞争需要从具体编码审查,binder资源的系统有限制不作为本章的讨论内容,而绝大部分的场景下,ANR的发生都可以归结为主线程的耗时操作。

从严格意义上来说,所有的耗时操作,即使不会导致ANR,也需要放到工作线程中执行,即采用异步任务的方式来实现。以下将对Android中的各种异步工作线程进行阐述。

6.2 Android开发中各种异步机制

6.2.1 Thread、Runnable结合主Looper的handler

最常见最原始的异步操作方式

示例:

private void test(){
    new Thread(mRunnable).start();
}

Handler mHandler = newHandler(){
    @Override
    publicvoid handleMessage(Message msg){
        if(msg.what == 0x123){
            textView.setText("Task Done!!");
        }
    }
};

mRunnable = new Runnable() {
    @Override
    publicvoid run() {
        SystemClock.sleep(5000);    // 模拟耗时处理
        mHandler.sendEmptyMessage(0x123);  

    }
};

优点:

l 操作简单,基本无学习成本

缺点:

l 代码规范性较差,不易维护

l 每次都开辟一个线程,开启新线程的以及调度多个线程的开销是非常大的,这往往会带来严重的性能问题,在实际开发中,该方式是很不被推荐的。

6.2.2 Runnable结合View.post、Activity.runOnUIThread

这其实是第一种异步方式的扩展,差别就是这种方法不需要去定义Handler优缺点雷同

示例:

只需要将上述的

mHandler.sendEmptyMessage(0x123);

改成:

runOnUiThread(new Runnable() {
    @Override
    publicvoid run() {
        text.setText("Task Done!!");
    }
});


text.post(new Runnable(){
    @Override
    publicvoid run() {
        text.setText("Task Done!!");
    }
});

备注:根据同学的实践反馈,使用view.post()方式进行线程调度时,在个别电视机型曾出现不执行的情况,换handler后表现正常,初步推测是底层框架对该接口进行了修改,在此标记为有风险的接口,不建议使用。

6.2.3 AsyncTask

较为轻量级的异步类,封装了FutureTask的线程池、LinkedBlockingQueue和handler进行调度。

可以直接继承AsyncTask,在类中实现异步操作,并提供接口反馈当前异步执行的程度,最后反馈执行的结果给UI主线程。

基本使用方法,doInBackground(Params…) 方法里执行耗时逻辑,然后在onPostExecute(Result)中将结果更新回UI组件。

示例:

/**
 * 生成该类的对象,并调用execute方法之后  
 * 首先执行的是onProExecute方法  
 * 其次执行doInBackgroup方法  
 *
 */
public class ProgressBarAsyncTask extends AsyncTask{

    private TextViewtextView;
    private ProgressBar progressBar;


    public ProgressBarAsyncTask(TextView textView, ProgressBarprogressBar) {
        super();
        this.textView = textView;
        this.progressBar= progressBar;
    }

    /**
     * 这里的Integer参数对应AsyncTask中的第一个参数   
     * 这里的String返回值对应AsyncTask的第三个参数  
     * 该方法并不运行在UI线程当中,主要用于异步操作,所有在该方法中不能对UI当中的空间进行设置和修改  
     * 但是可以调用publishProgress方法触发onProgressUpdate对UI进行操作
     */
    @Override
    protected StringdoInBackground(Integer... params) {
        NetOperator netOperator = new NetOperator();
        int i = 0;
        for (i = 10; i<= 100; i+=10){
            netOperator.operator();
            publishProgress(i);
        }
        return i +params[0].intValue() + "";
    }
    
    /**
     * 这里的String参数对应AsyncTask中的第三个参数(也就是接收doInBackground的返回值)  
     * 在doInBackground方法执行结束之后在运行,并且运行在UI线程当中可以对UI空间进行设置  
     */
    @Override
    protected void onPostExecute(String result) {
        textView.setText("异步操作执行结束" + result);
    }
    
    //该方法运行在UI线程当中,并且运行在UI线程当中可以对UI空间进行设置
    @Override
    protected void onPreExecute() {
        textView.setText("开始执行异步线程");
    }
    
    /**
     * 这里的Intege参数对应AsyncTask中的第二个参数  
     * 在doInBackground方法当中,,每次调用publishProgress方法都会触发onProgressUpdate执行  
     * onProgressUpdate是在UI线程中执行,所有可以对UI空间进行操作  
     */
    @Override
    protected void onProgressUpdate(Integer... values) {

If(!isCancel()){
        int vlaue =values[0];
        progressBar.setProgress(vlaue);
    }
} 

优点:

l 结构清晰,功能定义明确,尤其适用后台进度任务的交互

缺点:

l 在单个后台异步处理时,显得代码过多,结构过于复杂

l Task实例只能被执行一次,否则多次调用时将会出现异常

l AsyncTask在整个Android系统中维护一个线程池,有可能被其他进程的任务抢占而降低效率。

6.2.4 HandlerThread

HandlerThread可以创建一个带有looper的线程,looper对象可以用于创建Handler类来进行来进行调度,其中创建looper时,start()方法必须先被调用。

创建出来的Handler对象,使用方法同主线程Handler使用方式一样,但此时handleMessage()方法执行在子线程中。

异步交互的一般方式是,在构造传入一个UIHandler对象,将数据返回主线程

示例:

handlerThread = newHandlerThread("MyWorkThread");//自定义线程名称  
handlerThread.start();
mWorkHandler = new Handler(handlerThread.getLooper()){
    @Override
    publicvoid handleMessage(Message msg){

        if (msg.what== 0x123){
            try{
                Log.d("HandlerThread",Thread.currentThread().getName());
                Thread.sleep(5000);//模拟耗时任务  
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
};

优点:

l 开销较少,仅一个Thread体量可以连续处理多个后台任务

l 兼有handler和Thread的优点,适合队列性中轻量的子线程任务

缺点:

l 没有提供返回主线程的接口,需要自行封装

l 通常需要交叉维护多个handler

6.2.5 IntentService

内部封装了HandlerThread的实现,同时是Service的子类,用法跟Service也差不多,实现耗时逻辑应放在onHandleIntent(Intent intent){}的方法体里,它同样有着退出启动它的Activity后不会被系统杀死的特点,而且当任务执行完后会自动停止,无须手动去终止它。

示例:

实现onHandleIntent方法即可

public class MyIntentService extends IntentService {
    private static final String TAG = MyIntentService.class.getSimpleName();
    private int count = 0;

    public MyIntentService() {
        super(TAG);
        // TODO Auto-generated constructor stub
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //每一次通过Intent发送的命令将被顺序执行
        count ++;
        Log.d(TAG, "count::" + count);
    }
}

优点:

l 使用方式熟悉

l 兼有HandlerThread和Service的优点,适合单个重度分叉性的后台任务

缺点:

l 没有提供返回主线程的接口,不适合异步交互

l 需要像Service一样在清单配置,开销体量较大

6.2.6 AsyncQueryHandler

在一般的应用中我们可以使用ContentProvider去操作其它进程的数据库,为了避免ANR,还需要配合上述的异步任务机制,但Android已经为此封装了异步查询框架AsyncQueryHandler。

AsyncQueryHandler类封装了调用者线程与工作线程的交互过程。交互的主体是两个Handler,一个运行在调用者线程中,一个运行在工作者线程中。通过提供onXXXComplete的回调接口,返回主线程实现事件的完成处理。

示例:以查询为例:

定义自己的AsyncQueryHandler,同时定义一个监听器进行设置。

/**
 * 异步的查询操作帮助类,可以处理增删改(ContentProvider提供的数据)
 */
public class CommonQueryHandler extends AsyncQueryHandler {
    public CommonQueryHandler(ContentResolver cr) {
        super(cr);
    }

    /**
     * 当查询完成后,回调的方法
     */
    @Override
    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {

        //触发监听事件
        if(cursorChangedListener!=null){
            cursorChangedListener.onCursorChanged(token, cookie, cursor);
        }
    }


    public OnCursorChangedListener getCursorChangedListener() {
        return cursorChangedListener;
    }

    public void setOnCursorChangedListener(OnCursorChangedListener cursorChangedListener) {
        this.cursorChangedListener = cursorChangedListener;
    }

    private OnCursorChangedListener cursorChangedListener;

    /**
     * 定义cursor改变时的事件监听
     * @author leo
     *
     */
    public interface OnCursorChangedListener{
        void onCursorChanged(int token, Object cookie, Cursor cursor);
    }
}

调用:

使用时直接调用startXXX方法即可。

new CommonQueryHandler(getContentResolver()).startQuery(token, cookie, uri, projections, selection selectinArgs, orderBy);

传入的通用参数如下:

l  Int token,一个令牌,主要用来标识查询,保证唯一即可。

l  Object cookie,你想传给onXXXComplete方法使用的一个对象。(没有的话传递null即可)

l  Uri uri(进行查询的通用资源标志符):

l  String[] projections 查询的列

l  String selection 限制条件

l  String[] selectionArgs 查询参数

l  String orderBy 排序条件

优点:

l 实现了异步调度的封装

l 接口简单易用

缺点:

l  封装了ContentResolver,应用场景定向,只适用于数据库操作

6.3 异步机制的选择

根据上述,我们需要根据不同异步方式优缺点和具体的使用场景进行抉择,一般来说:

1.        大量分散性的轻量化异步任务,可以考虑对Runnable进行线程池的统一管理使用;

2.        频率不高,比较重度,且有进度交互的,可以考虑使用AsyncTask,同时注意执行时,调用其并发性执行接口;

3.        中轻度队列性子任务,考虑使用HandlerThread;

4.        分叉性的重度后台任务,考虑使用IntentService;

5.        涉及ContentProvider的数据库操作,考虑使用AsyncQueryHandler。


你可能感兴趣的:(Android,Performance)