关于正确使用Android AsyncTask学习整理

AsyncTask异步任务,用于执行耗时任务并在UI线程中更新结果。

我们都知道,Android UI线程中不能执行耗时的任务,否则就会出现ANR。对于耗时的操作就需要放到子线程中操作,操作完成后需要通知UI线程进行更新等操作,这就需要Android的异步消息机制(创建一个Message对象,使用Handler发送出去,然后在Handler的handleMessage()方法中获得刚才发送的Message对象,然后在这里进行UI操作)。
不过本文要说的是AsyncTask,其实早在Android 1.5版本就引入这个类,所以我知道大多数人对它的用法都已经非常熟悉了。基本用法在网上搜搜就有很多教程,然而,在使用时,仍需要注意其潜在的问题以及缺陷。

[TOC]

AsyncTask 简单使用


public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final String TAG = "MainActivity";
    private ProgressDialog mDialog;
    private AsyncTask mAsyncTask;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mDialog = new ProgressDialog(this);
        mDialog.setMax(100);
        mDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        mDialog.setCancelable(false);
        mAsyncTask = new MyAsyncTask();

        findViewById(R.id.tv).setOnClickListener(this);
    }

    @Override
    public void onClick(View view) {
        mAsyncTask.execute();
    }

    private class MyAsyncTask extends AsyncTask<Void, Integer, Void> {

        @Override
        protected void onPreExecute() {
            mDialog.show();
            Log.e(TAG, Thread.currentThread().getName() + " onPreExecute ");
        }

        @Override
        protected Void doInBackground(Void... params) {

            // 模拟数据的加载,耗时的任务
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(80);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                publishProgress(i);
            }

            Log.e(TAG, Thread.currentThread().getName() + " doInBackground ");
            return null;
        }

        @Override
        protected void onProgressUpdate(Integer... values) {
            mDialog.setProgress(values[0]);
            Log.e(TAG, Thread.currentThread().getName() + " onProgressUpdate ");
        }

        @Override
        protected void onPostExecute(Void result) {
            // 进行数据加载完成后的UI操作
            mDialog.dismiss();
            Log.e(TAG, Thread.currentThread().getName() + " onPostExecute ");
        }
    }
}

如以上实例中,当UI线程中需求处理耗时的操作时,我们可以放在AsyncTask的doInBackground方法中执行,这个抽象的类,有几个方法需要我们重新,除了doInBackground,我们可以在onPreExecute中为这个耗时方法进行一些预处理操作,同时我们在onPostExecute中对UI进行更新操作。实例中的publishProgress对应的回调是onProgressUpdate,这样可以实时更新UI,提供更好的用户体验。

AsyncTask 原理

AsyncTask主要有二个部分:一个是与主线的交互,另一个就是线程的管理调度。虽然可能多个AsyncTask的子类的实例,但是AsyncTask的内部Handler和ThreadPoolExecutor都是进程范围内共享的,其都是static的,也即属于类的,类的属性的作用范围是CLASSPATH,因为一个进程一个VM,所以是AsyncTask控制着进程范围内所有的子类实例。

  • 与主线程交互
    与主线程交互是通过Handler来进行的,因为本文主要探讨AsyncTask在任务调度方面的,所以对于这部分不做细致介绍,感兴趣的朋友可以继续去看AsyncTask的源码部分。

  • 线程任务的调度
    内部会创建一个进程作用域的线程池来管理要运行的任务,也就就是说当你调用了AsyncTask#execute()后,AsyncTask会把任务交给线程池,由线程池来管理创建Thread和运行Therad。对于内部的线程池不同版本的Android的实现方式是不一样的:

AsyncTask 发展

接下来我们先简单的了解一下AsyncTask的历史

  • 首先在android 3.0之前的版本,ThreadPool的限制是5个,线程的并发量是128个,阻塞队列长度10,也就是说超过138个则会抛出异常。因此我们在使用的时候,一定要主要这部分限制,正确的使用。
  • 到了在Android 3.0之后的,也许是Google也意识到这个问题,对AsyncTask的API做了调整:
    · execute()提交的任务,按先后顺序每次只运行一个也就是说它是按提交的次序,每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务(Executors.newSingleThreadPool())。
    · 新增了接口executeOnExecutor()这个接口允许开发者提供自定义的线程池来运行和调度Thread,如果你想让所有的任务都能并发同时运行,那就创建一个没有限制的线程池(Executors.newCachedThreadPool()),并提供给AsyncTask。这样这个AsyncTask实例就有了自己的线程池而不必使用AsyncTask默认的。
    · 新增了二个预定义的线程池SERIAL_EXECUTORTHREAD_POOL_EXECUTOR。其实THREAD_POOL_EXECUTOR并不是新增的,之前的就有,只不过之前(Android 2.3)它是AsyncTask私有的,未公开而已。THREAD_POOL_EXECUTOR是一个corePoolSize为5的线程池,也就是说最多只有5个线程同时运行,超过5个的就要等待。所以如果使用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)就跟2.3版本的AsyncTask.execute()效果是一样的。而SERIAL_EXECUTOR是新增的,它的作用是保证任务执行的顺序,也就是它可以保证提交的任务确实是按照先后顺序执行的。它的内部有一个队列用来保存所提交的任务,保证当前只运行一个,这样就可以保证任务是完全按照顺序执行的,默认的execute()使用的就是这个,也就是executeOnExecutor(AsyncTask.SERIAL_EXECUTOR)与execute()是一样的。

AsyncTask 源码简析

  • 这里我们从AsyncTask的起点开始分析,主要有 execute()executeOnExecutor()
public final AsyncTask<Params, Progress, Result> execute(Params... params) {  
        return executeOnExecutor(sDefaultExecutor, params);  
}  
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,  
            Params... params) {  
        if (mStatus != Status.PENDING) {  
            switch (mStatus) {  
                case RUNNING:  
                    throw new IllegalStateException("Cannot execute task:"  
                            + " the task is already running.");  
                case FINISHED:  
                    throw new IllegalStateException("Cannot execute task:"  
                            + " the task has already been executed "  
                            + "(a task can be executed only once)");  
            }  
        }  
  
        mStatus = Status.RUNNING;  
  
        onPreExecute();  
  
        mWorker.mParams = params;  
        exec.execute(mFuture);  
  
        return this;  
    }  
  1. 从代码中可以看出,execute()其实也是通过执行executeOnExecutor()方法,只是将其中的Executor设置为默认值。
  2. executeOnExecutor()中将当前AsyncTask的状态为RUNNING,上面的switch也可以看出,每个异步任务在完成前只能执行一次。
  3. 接下来就执行了onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
  4. 将我们传入的参数赋值给了mWorker.mParams
  5. 最后exec.execute(mFuture)
    相信大家对代码中出现的mWorker,以及mFuture都会有些困惑。接下来我们来看看mWorker找到这个类:
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {  
        Params[] mParams;  
}  

可以看到是Callable的子类,且包含一个mParams用于保存我们传入的参数,下面看初始化mWorker的代码:

     public AsyncTask() {  
        mWorker = new WorkerRunnable<Params, Result>() {  
            public Result call() throws Exception {  
                mTaskInvoked.set(true);  
  
                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
                //noinspection unchecked  
                return postResult(doInBackground(mParams));  
            }  
        };  
    //...
   }  

可以看到mWorker在构造方法中完成了初始化,并且因为是一个抽象类,在这里new了一个实现类,实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,并返回Result值作为参数给postResult方法.可以看到我们的doInBackground出现了,下面继续看:

private Result postResult(Result result) {  
        @SuppressWarnings("unchecked")  
        Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,  
                new AsyncTaskResult<Result>(this, result));  
        message.sendToTarget();  
        return result;  
}  

可以看到postResult中出现了我们熟悉的异步消息机制,传递了一个消息message, message.what为MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result);

private static class AsyncTaskResult<Data> {  
       final AsyncTask mTask;  
       final Data[] mData;  
  
       AsyncTaskResult(AsyncTask task, Data... data) {  
           mTask = task;  
           mData = data;  
       }  
   }  

AsyncTaskResult就是一个简单的携带参数的对象。
看到这,我相信大家肯定会想到,在某处肯定存在一个sHandler,且复写了其handleMessage方法等待消息的传入,以及消息的处理。

private static final InternalHandler sHandler = new InternalHandler();  
    private static class InternalHandler extends Handler {  
        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})  
        @Override  
        public void handleMessage(Message msg) {  
            AsyncTaskResult result = (AsyncTaskResult) msg.obj;  
            switch (msg.what) {  
                case MESSAGE_POST_RESULT:  
                    // There is only one result  
                    result.mTask.finish(result.mData[0]);  
                    break;  
                case MESSAGE_POST_PROGRESS:  
                    result.mTask.onProgressUpdate(result.mData);  
                    break;  
            }  
        }  
}  

这里出现了我们的handleMessage,可以看到,在接收到MESSAGE_POST_RESULT消息时,执行了result.mTask.finish(result.mData[0]);其实就是我们的AsyncTask.this.finish(result),于是看finish方法

private void finish(Result result) {  
        if (isCancelled()) {  
            onCancelled(result);  
        } else {  
            onPostExecute(result);  
        }  
        mStatus = Status.FINISHED;  
    }  

可以看到,如果我们调用了cancel()则执行onCancelled回调;正常执行的情况下调用我们的onPostExecute(result);主要这里的调用是在handler的handleMessage中,所以是在UI线程中。最后将状态置为FINISHED。
mWoker看完了,应该到我们的mFuture了,依然实在构造方法中完成mFuture的初始化,将mWorker作为参数,复写了其done方法。

public AsyncTask() {  
    ...  
        mFuture = new FutureTask<Result>(mWorker) {  
            @Override  
            protected void done() {  
                try {  
                    postResultIfNotInvoked(get());  
                } catch (InterruptedException e) {  
                    android.util.Log.w(LOG_TAG, e);  
                } catch (ExecutionException e) {  
                    throw new RuntimeException("An error occured while executing doInBackground()",  
                            e.getCause());  
                } catch (CancellationException e) {  
                    postResultIfNotInvoked(null);  
                }  
            }  
        };  
}  

任务执行结束会调用:postResultIfNotInvoked(get());get()表示获取mWorker的call的返回值,即Result.然后看postResultIfNotInvoked方法

private void postResultIfNotInvoked(Result result) {  
        final boolean wasTaskInvoked = mTaskInvoked.get();  
        if (!wasTaskInvoked) {  
            postResult(result);  
        }  
}  

如果mTaskInvoked不为true,则执行postResult;但是在mWorker初始化时就已经将mTaskInvoked为true,所以一般这个postResult执行不到。好了,到了这里,已经介绍完了execute方法中出现了mWorker和mFurture,不过这里一直是初始化这两个对象的代码,并没有真正的执行。下面我们看真正调用执行的地方。execute方法中的:还记得上面的execute中的:exec.execute(mFuture)
exec为executeOnExecutor(sDefaultExecutor, params)中的sDefaultExecutor
下面看这个sDefaultExecutor

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;  
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();  
private static class SerialExecutor implements Executor {  
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();  
        Runnable mActive;  
        public synchronized void execute(final Runnable r) {  
            mTasks.offer(new Runnable() {  
                public void run() {  
                    try {  
                        r.run();  
                    } finally {  
                        scheduleNext();  
                    }  
                }  
            });  
            if (mActive == null) {  
                scheduleNext();  
            }  
        }  
        protected synchronized void scheduleNext() {  
            if ((mActive = mTasks.poll()) != null) {  
                THREAD_POOL_EXECUTOR.execute(mActive);  
            }  
        }  
}  

可以看到sDefaultExecutor其实为SerialExecutor的一个实例,其内部维持一个任务队列;直接看其execute(Runnable runnable)方法,将runnable放入mTasks队尾;再判断当前mActive是否为空,为空则调用scheduleNext。方法scheduleNext,则直接取出任务队列中的队首任务,如果不为null则传入THREAD_POOL_EXECUTOR进行执行。下面看THREAD_POOL_EXECUTOR为何方神圣:

public static final Executor THREAD_POOL_EXECUTOR  
          =new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,  
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); 

可以看到就是一个自己设置参数的线程池,参数为:

private static final int CORE_POOL_SIZE = 5;  
private static final int MAXIMUM_POOL_SIZE = 128;  
private static final int KEEP_ALIVE = 1;  
private static final ThreadFactory sThreadFactory = new ThreadFactory() {  
private final AtomicInteger mCount = new AtomicInteger(1);  
public Thread newThread(Runnable r) {  
     return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());  
    }  
 };  
private static final BlockingQueue<Runnable> sPoolWorkQueue =  
            new LinkedBlockingQueue<Runnable>(10);  

看到这里,大家可能会认为,背后原来有一个线程池,且最大支持128的线程并发,加上长度为10的阻塞队列,可能会觉得就是在快速调用138个以内的AsyncTask子类的execute方法不会出现问题,而大于138则会抛出异常。其实不是这样的,我们再仔细看一下代码,回顾一下sDefaultExecutor,真正在execute()中调用的为sDefaultExecutor.execute:

private static class SerialExecutor implements Executor {  
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();  
        Runnable mActive;  
        public synchronized void execute(final Runnable r) {  
            mTasks.offer(new Runnable() {  
                public void run() {  
                    try {  
                        r.run();  
                    } finally {  
                        scheduleNext();  
                    }  
                }  
            });  
            if (mActive == null) {  
                scheduleNext();  
            }  
        }  
        protected synchronized void scheduleNext() {  
            if ((mActive = mTasks.poll()) != null) {  
                THREAD_POOL_EXECUTOR.execute(mActive);  
            }  
        }  
}  

可以看到,如果此时有10个任务同时调用execute(s synchronized)方法,第一个任务入队,然后在mActive = mTasks.poll()) != null被取出,并且赋值给mActivte,然后交给线程池去执行。然后第二个任务入队,但是此时mActive并不为null,并不会执行scheduleNext();所以如果第一个任务比较慢,10个任务都会进入队列等待;真正执行下一个任务的时机是,线程池执行完成第一个任务以后,调用Runnable中的finally代码块中的scheduleNext,所以虽然内部有一个线程池,其实调用的过程还是线性的。一个接着一个的执行,相当于单线程。

总结:

AsyncTask在并发执行多个任务时发生异常。其实还是存在的,在3.0以前的系统中还是会以支持多线程并发的方式执行,支持并发数也是我们上面所计算的128,阻塞队列可以存放10个;也就是同时执行138个任务是没有问题的;而超过138会马上出现java.util.concurrent.RejectedExecutionException;而在在3.0以上包括3.0的系统中会为单线程执行(即我们上面代码的分析);

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容