AsyncTask浅析

本文深入剖析了AsyncTask的实现原理,从简单的BgTask逐步演进,探讨了线程池、WorkerRunnable、FutureTask以及AsyncTask的状态管理。在过程中揭示了AsyncTask的坑,如cancel方法的限制和线程池的演变,帮助读者理解如何更好地使用和定制异步任务处理。
摘要由CSDN通过智能技术生成

AsyncTask 实现原理并不复杂,无非是使用 ThreadHandler,将耗时操作从UI线程中转移到后台线程中,同时便于后台线程向UI线程返回执行结果。但具体实现细节却有不少技巧。下面我们从一个简单的 AsyncTask 原型,逐步完善、丰富,最后对比下它跟Android SDK 中的 AsyncTask 有什么不足,学习 AsyncTask 中的各种技巧。

BgTask

BgTask v0.1

下面是最初的实现。我们有时会直接在 Activity 中使用 ThreadHandler 来完成一些耗时操作(但通常执行时间最多十几秒而已),如果将这部分代码简单封装下,它大概是这个样子:

/**
 * v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
 * 
 * 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
 *
 * @param <T>
 */
public abstract class BgTask<T> {

    protected static final int FINISH = 1000;
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {

            switch (msg.what) {
            case FINISH:
                onPostExecute();
                break;

            default:
                break;
            }
        };
    };

    public BgTask<T> execute(final T param) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                doInBackground(param);
                mHandler.obtainMessage(FINISH).sendToTarget();
            }
        });

        onPreExecute();
        thread.start();
        return this;
    }

    protected void onPreExecute() {

    }

    protected void onPostExecute() {

    }

    protected abstract void doInBackground(T param);
}

使用一个简单的 Activity 不难验证这段代码的确可以避免耗时操作阻塞UI线程。它的用法跟 AsyncTask 很像:

  1. 继承 BgTask,至少要实现 doInBackground 方法,该方法在一个新建的后台线程中运行
  2. onPreExecute 用于耗时操作开始前的准备工作, onPostExecute 用于耗时操作结束后的处理工作, 这两个方法都在 UI 线程运行

考虑这样一种情形,使用 BgTask 从网络下载一个文件,需要向用户展示下载进度。很明显我们没法实现这一需求,所以 BgTask 的主要问题之一是: 未实现 publishProgress, 无法向UI发布后台线程进度。

BgTask v0.2

这个版本中我们要解决”无法向UI线程发布后台线程进度”的问题。解决的办法其实很简单,增加如下两个方法:

    protected void onProgress(K progress) {

    }

    protected void publicProgress(K progress) {
        Message msg = mHandler.obtainMessage(PROGRESS);
        msg.obj = progress;
        msg.sendToTarget();
    }

完整的代码如下:

/**
 * v0.2 增加 publicProgress 方法和 onProgress 回调
 * 
 * 主要问题: 后台线程结束后没有返回结果, 考虑这个情形:后台线程发一个http请求, 完成后该线程居然没法将http响应结果返回给UI
 * 
 * ---
 * 
 * v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
 * 
 * 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
 *
 * @param <T>
 */
public abstract class BgTask2<T, K> {

    private static final int FINISH = 1000;
    private static final int PROGRESS = 1001;
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {

            switch (msg.what) {
            case FINISH:
                onPostExecute();
                break;
            case PROGRESS:
                onProgress((K)msg.obj);
                break;

            default:
                break;
            }
        };
    };

    public BgTask2<T, K> execute(final T param) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                doInBackground(param);
                mHandler.obtainMessage(FINISH).sendToTarget();
            }
        });

        onPreExecute();
        thread.start();
        return this;
    }

    protected void onPreExecute() {

    }

    protected void onPostExecute() {

    }

    protected void onProgress(K progress) {

    }

    protected void publicProgress(K progress) {
        Message msg = mHandler.obtainMessage(PROGRESS);
        msg.obj = progress;
        msg.sendToTarget();
    }

    protected abstract void doInBackground(T param);
}

onProgress 跟其它 onXXX 回调类似,它在这里用于UI线程接收后台线程进度通知。而 publishProgress 则向 Handler 发送 Message,用于通知当前进度。 HandlerpublishProgressonProgress 之间的纽带。有人会问,这真的实现后台线程进度问题了吗?其实还没完全解决,这里有一个隐含的假设:就是继承 BgTask 实现其 doInBackground 方法时你需要在恰当的时机调用 publishProgress,不然 onProgress 不会被回调,UI 线程也无法接收到进度通知。

下面演示了该如何调用 publishProgress

    private void runTask() {
        mTask = new BgTask2<String, Integer>() {

            private int mProgress = 0;

            @Override
            protected void doInBackground(String param) {
                int timeout = 5;
                while (timeout > 0) {
                    try {
                        Thread.sleep(1000);
                        publicProgress(mProgress + 20 * (5 - timeout));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    timeout--;
                }
            }

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                mStatus.setText("onPreExecute\n");
            }

            @Override
            protected void onPostExecute() {
                super.onPostExecute();
                mStatus.append("onPostExecute");
            }

            @Override
            protected void onProgress(Integer progress) {
                super.onProgress(progress);
                mStatus.append("Progress: " + progress);
                mStatus.append("\n");
            }
        };
        mTask.execute("hello, AsyncTask");
    }

但新的问题又来了,虽然我可以知道进度了,比如进度为100代表执行完成,那我怎么知道这时的执行结果呢?显然,这里还存在问题, 后台线程结束后无法向UI线程返回结果。

BgTask v0.3

onPostExecute 用于向UI线程通知结果最合理,所以我们的做法是给它加上一个泛型参数。修改后的代码如下:

/**
 * v0.3 doInBackground 方法增加返回值, 可以将后台线程的最终结果返回给UI线程
 * 
 * 主要问题: 无法暂停, 无法在运行过程中获取结果
 * 
 * ---
 * 
 * v0.2 增加 publicProgress 方法和 onProgress 回调
 *
 * 主要问题: 后台线程结束后没有返回结果, 考虑这个情形:后台线程发一个http请求, 完成后该线程居然没法将http响应结果返回给UI
 *
 * ---
 *
 * v0.1 最原始的AsyncTask, 初步简化异步任务, 避免耗时操作阻塞UI.
 *
 * 主要问题: 未实现 publishProgress, 无法向UI发布后台线程进度
 *
 * @param <T>
 */
public abstract class BgTask3<T, K, V> {

    private static final int FINISH = 1000;
    private static final int PROGRESS = 1001;
    private Handler mHandler = new Handler() {
        public void handleMessage(android.os.Message msg) {

            switch (msg.what) {
            case FINISH:
                onPostExecute((V) msg.obj);
                break;
            case PROGRESS:
                onProgress((K) msg.obj);
                break;

            default:
                break;
            }
        };
    };

    public BgTask3<T, K, V> execute(final T param) {
        Thread thread = new Thread(new Runnable() {

            @Override
            public void run() {
                V result = doInBackground(param);
                Message msg = mHandler.obtainMessage(FINISH);
                msg.obj = result;
                msg.sendToTarget();
            }
        });

        onPreExecute();
        thread.start();
        return this;
    }

    protected void onPreExecute() {

    }

    protected void onPostExecute(V result) {

    }

    protected void onProgress(K progress) {

    }

    protected void publicProgress(K progress) {
        Message msg = mHandler.obtainMessage(PROGRESS);
        msg.obj = progress;
        msg.sendToTarget();
    }

    protected abstract V doInBackground(T param);
}

看看还有什么问题。哦,貌似只能调用 BgTask3.execute 来开始执行,但如果突然想取消执行呢?

BgTask v0.4

考虑如何实现 cancel 方法。 我们可以添加一个 boolean 成员变量,cancel 后将其置为 true。但这种简单的做法无法 interrupt 一些可中断的 Thread,比如处于 sleep 状态的 Thread (线程的几种状态)。所以倒不如使用 Future。当然,如果使用 Future,得引入线程池了。代码如下:

    private static ExecutorService SERVICE = Executors.newFixedThreadPool(3);
    private Future<V> mFuture;
    public BgTask4<T, K, V> execute(final T ...param) {
//      Thread thread = new Thread(new Runnable() {
//
//          @Override
//          public void run() {
//              V result = doInBackground(param);
//              Message msg = mHandler.obtainMessage(FINISH);
//              msg.obj = result;
//              msg.sendToTarget();
//          }
//      });


        Callable<V> c = new Callable<V>() {

            @Override
            public V call() throws Exception {
                V result = doInBackground(param);
                Message msg = mHandler.obtainMessage(FINISH);
                msg.obj = result;
                msg.sendToTarget();
                return result;
            }
        };

        onPreExecute();
//      thread.start();
        mFuture = SERVICE.submit(c);
        return this;
    }

    public void cancel() {
        if (mFuture != null) {
            boolean res = mFuture.cancel(true);
            Log.i("BgTask4", "Cancel future: " + res);
        }
    }

    public V get() throws InterruptedException, ExecutionException {
        if (mFuture != null) {
            return mFuture.get();
        }
        return null;
    }

AsyncTask

我们实现了一个基本的 BgTask,功能类似于 AsyncTask,完成耗时操作时不必直接操作 ThreadHandler。 但它还有很多不完善的地方。

  1. cancel 方法线程不安全
  2. 无法区分是正常结束还是 cancel
  3. 无法知道 AsyncTask 当前执行状态
  4. 泛型参数定义得很不清晰

看看 Android SDK 中的 AsyncTask 是怎么实现的吧。 注意:不同 Android 版本中,AsyncTask 源码有不小变化,这里以 android 4.4 (android-19) 为准。

线程池

AsyncTask中的线程池在不同版本的Android中并不相同,是个不小的坑。

    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    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>(128);

    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR
            = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                    TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

    /**
     * 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();

我们逐一分析:

  • CPU_COUNT - CPU核心数
  • CORE_POOL_SIZE - the number of threads to keep in the pool, even if they are idle, unless allowCoreThreadTimeOut is set。这里的大小是 CPU_COUNT 加 1
  • MAXIMUM_POOL_SIZE - the maximum number of threads to allow in the pool. 线程池中允许的最大线程数, 这里的大小是 CPU_COUNT 的两倍加 1
  • KEEP_ALIVE - when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
  • sThreadFactory - 这个 ThreadFactory 没什么特别,仅用于保持线池名字以 “AsyncTask #” 的形式有序增加
  • sPoolWorkQueue - the queue to use for holding tasks before they are executed. This queue will hold only the Runnable tasks submitted by the execute method. 用于构建 THREAD_POOL_EXECUTOR
  • THREAD_POOL_EXECUTOR - 用于 AsyncTask 并发执行的线程池

比较有意思的是这里的 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);
            }
        }
    }

一开始会感觉这种用法有点奇怪,弄这么麻烦,不如直接使用一个 Executors.newSingleThreadExecutor() 来得直接。仔细分析,这里使用了装饰器模式 (也可能认为代理模式,它们太像了,装饰模式与代理模式的区别),使用 ArrayDeque 将原来的可用于并发执行的线程池装饰成一个仅能顺序执行的线程池。

如果还是觉得奇怪,不妨看看 Executor 的Java文档中的示例,官方的,绝对权威。它是这么写的(这回该能确定它是装饰器模式了吧):

 class SerialExecutor implements Executor {
     final Queue<Runnable> tasks = new LinkedBlockingQueue<Runnable>();
     final Executor executor;
     Runnable active;

     SerialExecutor(Executor executor) {
         this.executor = executor;
     }

     public synchronized void execute(final Runnable r) {
         tasks.offer(new Runnable() {
             public void run() {
                 try {
                     r.run();
                 } finally {
                     scheduleNext();
                 }
             }
         });
         if (active == null) {
             scheduleNext();
         }
     }

     protected synchronized void scheduleNext() {
         if ((active = tasks.poll()) != null) {
             executor.execute(active);
         }
     }
 }

WorkerRunnable 与 FutureTask

WorkerRunnable 不过实现了 Callable 接口。我们知道 Callable 接口基本可认为是 Runnable 接口的带参数版本。除此之外,WorkerRunnable 还有一个 Params[] 类型的成员变量,方便保存 execute 传进来的参数。

    private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
        Params[] mParams;
    }
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
    ...

        mStatus = Status.RUNNING;

        onPreExecute();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

FutureTask 是什么?不好理解,那还是看代码吧。

public class FutureTask<V> implements RunnableFuture<V> {}

public interface RunnableFuture<V> extends Runnable, Future<V> {}

原来 FutureTask 同时实现了 Runnable 接口和 Future 接口,AsyncTask.execute 其实是将 FutureTask 提交给 Executor

分析代码可知:

  1. mTaskInvoked - 用于标识任务是否已执行,它是 AtomicBoolean 类型的,不存在线程安全问题
  2. WorkerRunnable.call - 在新的线程中运行,doInBackground 正常结束后其返回值通过 postResult 发送到UI线程
  3. FutureTask.done - 在新的线程中运行,如果 AsyncTask 不能正常结束,通过 postResultIfNotInvokedFutureTask.get 的结果发送到UI线程

以上第2条和第3条保证无论 AsyncTask 是否正常结束,都会有一个唯一确定的结果由 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;
            }
        }
    }

问题:如果直接调用 AsyncTask.get(),会发生什么情况

AsyncTask.get() 直接调用 FutureTask.get(),所以很容易这样验证:

    public static void main(String[] args) {
        FutureTask<String> f = new FutureTask<String>(new Callable<String>() {

            @Override
            public String call() throws Exception {
                return "test";
            }
        });
        // Executors.newSingleThreadScheduledExecutor().execute(f);
        try {
            System.out.println(f.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

如果我们将 Executors.newSingleThreadScheduledExecutor().execute(f); 注释掉,这个名为 f 的 FutureTask 就不会提交给线程池执行,f.get() 方法就一直阻塞在那里,等待返回结果。以下Android代码执行结果就不用解释了吧,后果你懂的。所以这种用法是错误的。

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

    private void runAsyncTaskManually() {
        AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {

            @Override
            protected String doInBackground(Void... params) {
                return "test";
            }

        };
        try {
            String result = task.get();
            Log.i("MainActivity", result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

AsyncTask的状态

AsyncTask 通过 Status 定义其状态:

    public enum Status {
        /**
         * Indicates that the task has not been executed yet.
         */
        PENDING,
        /**
         * Indicates that the task is running.
         */
        RUNNING,
        /**
         * Indicates that {@link AsyncTask#onPostExecute} has finished.
         */
        FINISHED,
    }
  • 初始时是 PENDING 状态,如果是其他状态,立即抛出 IllegalStateException
  • 调用 execute 后 立即进入 RUNNING 状态
  • InternalHandler 处理完 MESSAGE_POST_RESULT 后进入 FINISHED 状态

AsyncTask 的状态保证其 execute 方法仅能被执行一次。

AsyncTask的坑

cancel的问题

可以在任何时候调用 cancel 取消任务。调用这个方法会导致后续所有的 isCanceled 返回 true。 为保证任务尽早被取消,应该周期性地在 doInBackground 方法中检查 isCanceled 的返回值 (比如在循环当中)

注意: cancel 可能并不及时,特别是 doInBackground 没有按要求实现时。比如,根据一组url循环下载文件,每次下载前应检查 isCanceled 是否为 true。如果不这么做,cancel 肯定不生效。另一种情况是,当前 doInBackground 读取一个 InputStream 并阻塞,简单地 cancel 并不能让这个过程取消。

线程池问题

AsyncTask中提到

首次引入 AsyncTask 时,它是在唯一的后台线程中顺序执行任务的。从 DONUT 开始,单一线程改成了一个允许多个线程并发执行的线程池。 (注:线程池,corePoolSize 为 5, maximumPoolSize 为 128, keepAlive 时间为 1秒)。从 HONEYCOMB 开始,为避免并发执行引起一些常见的编程错误,又重新改成了单线程。(注:从代码来看并不是真正的单线程,但 AsyncTask 中引入 SerialExecutor 保证所有的任务是顺序执行的)。

当然,文档中还提到多线程并发执行的问题,

如果确实需要并发执行,可以使用 AsyncTask.executeOnExecutor 方法,注意第一个参数传入 THREAD_POOL_EXECUTOR 即可。


看来,Android确实在不但打磨啊。最初由单线程换成线程池,应该是前者性能不太好。现在Android手机性能基本足够了,性能不是问题,多线程反而容易引起问题,为了减少这些问题,干脆又换成单线程。反正,换或不换,总是有道理的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值