AsyncTask

AsyncTask,Android 开发者应该都知道,可以方便的实现后台任务并回调结果给 UI Thread。对这个类稍微熟悉点的应该还知道 AsyncTask 只适用于短时后台任务,容易造成内存泄漏甚至 Crash。但这是为什么呢,为什么只适用于短时后台任务,又为什么容易造成内存泄漏?简单来说就是Google 给它的功能定位就是这样的,所以在实现上就有了一定的限制。我们看文档

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

文档中说的很清楚,AsyncTask 是封装了 Thread 和 Handler 的一个辅助类,不适合用来实现通用的线程模型,适合用于最多耗时几秒钟的后台操作。如果需要实现长时间的后台任务,建议使用 JDK 提供的并发 API。

关于执行策略,文档中是这么说的:

When first introduced, AsyncTasks were executed serially on a single background thread. Starting with DONUT, this was changed to a pool of threads allowing multiple tasks to operate in parallel. Starting with HONEYCOMB, tasks are executed on a single thread to avoid common application errors caused by parallel execution.

If you truly want parallel execution, you can invoke executeOnExecutor(java.util.concurrent.Executor, Object[]) withTHREAD_POOL_EXECUTOR.

  • Android-1.5 (API level 3):串行执行,即只能执行完一个任务再继续下一个任务;

  • Android-3.0 (API level 11) 之前:线程池并发执行;

  • Android-3.0 开始,默认串行,但可以通过 executeOnExecutor(java.util.concurrent.Executor, Object[]) 配合 THREAD_POOL_EXECUTOR 参数实现并行。

但这里文档和实际代码有点出入,从代码中看应该是 "After HONEYCOMB" 而不是 "Starting with HONEYCOMB"。

我们看 HONEYCOMB (android-3.2.4, API lever 13) 的代码:

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>Note: this function schedules the task on a queue for a single background
     * thread or pool of threads depending on the platform version.  When first
     * introduced, AsyncTasks were executed serially on a single background thread.
     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
     * to a pool of threads allowing multiple tasks to operate in parallel.  After
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this
     * back to a single thread to avoid common application errors caused
     * by parallel execution.  If you truly want parallel execution, you can use
     * the {@link #executeOnExecutor} version of this method
     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings on
     * its use.
     *
     * <p>This method must be invoked on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     */
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(THREAD_POOL_EXECUTOR, params);
    }

代码里默认其实还是 Thread poll 并发执行的,而且注释里面也说了:

After {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this back to a single thread to avoid common application errors caused by parallel execution.

计划在 HONEYCOMB 之后改为单线程。再看看 Ice Cream Sandwich (android-4.0.1, API level 14) 源码:

    /**
     * Executes the task with the specified parameters. The task returns
     * itself (this) so that the caller can keep a reference to it.
     * 
     * <p>Note: this function schedules the task on a queue for a single background
     * thread or pool of threads depending on the platform version.  When first
     * introduced, AsyncTasks were executed serially on a single background thread.
     * Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
     * to a pool of threads allowing multiple tasks to operate in parallel.  After
     * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, it is planned to change this
     * back to a single thread to avoid common application errors caused
     * by parallel execution.  If you truly want parallel execution, you can use
     * the {@link #executeOnExecutor} version of this method
     * with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings on
     * its use.
     *
     * <p>This method must be invoked on the UI thread.
     *
     * @param params The parameters of the task.
     *
     * @return This instance of AsyncTask.
     *
     * @throws IllegalStateException If {@link #getStatus()} returns either
     *         {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
     */
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

这里引入了一个新的静态变量 sDefaultExecutor :

    /**
    * An {@link Executor} that executes tasks one at a time in serial
    * order.  This serialization is global to a particular process.
    */
    public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
    private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
    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);
            }
        }
    }

所以这里虽然最终还是用 Thread pool 执行,但通过 SerialExecutor 转换成串行执行。

通过上面这些还可以大概解释为什么AsyncTask 只适合做短时后台任务,因为是串行,所有如果有耗时很长的 AsyncTask 任务,那他后面的其它任务就会被阻塞。那我们可能又会想到不用默认的 Executor,而改用 THREAD_POOL_EXECUTOR 呢?其实去看源码里面,AsyncTask 里面的这个 THREAD_POOL_EXECUTOR 它的 CORE_POOL_SIZE,即会长时间保留的空闲线程数量是比较少的,这样的话,如果有长时间后台任务,那势必长时间占用线程池中的线程,后续的任务如果较多的话可能就要频繁的创建和销毁线程,我们知道创建和销毁线程是开销比较大的,所以这里会有一定的性能问题。

还有一个问题,容易造成内存泄漏甚至 Crash,这个其实是使用不当造成的。我们看 AsyncTask 中一些关键的方法:

  • onPreExecute(): 运行在 UI 线程,任务开始执行前的回调,可以用来在界面上显示一个 progress bar 之类的操作
  • doInBackground(Params...): 运行在后台线程,也就是我们需要在后台运行的任务代码
  • onProgressUpdate(Progress...): 运行在 UI 线程,在进度有更新的时候回调
  • onPostExecute(Result): 运行在 UI 线程,后台任务完成之后的结果回调

上面这几个方法也说明的 AsyncTask 的一个主要功能特点,就是简化了后台任务和 UI 线程的通信操作,不用我们手动写 Thread、Handler 相关代码。因为 AsyncTask 通常都会需要操作一些 UI 元素,比如显示及更新 progress bar,或者把结果更新到界面等,为了简化代码,我们很可能就直接在 Activity 里面创建一个继承 AsyncTask 的内部类,这样就能直接使用 Activity 里面的 UI 元素,但这样有一个问题就是因为在内部类里面会持有 Activity 实例的引用,这样就导致假如在 AsyncTask 执行期间 Activity 被销毁(比如用户按了返回键),但因为 AsyncTask 还在运行,它又持有 Activity 引用,所以这个 Activity 不能及时被 GC 回收。更严重的是因为这个时候 View 已经从 Window 上面移除了,所以后续在 onProgressUpdate(Progress...) 或者 onPostExecute(Result) 中对 View 的操作可能触发异常,从而导致 App crash,这个问题即使不用内部类的形式也同样会有。但如果代码逻辑比较严谨,有相应的防范措施,AsyncTask 还是可以放心使用的。

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

推荐阅读更多精彩内容