AsyncTask 必知必会

概要

AsyncTask 在后台执行操作,并把结果发布到 UI 线程,避免手动调用 Thread 和 Handler.

官网提到 AsyncTask 适用于短时间操作,最多几秒时间。why? 后面源码分析说明。

官网还提到,如果执行长时间操作,强烈推荐我们使用并发包下的 Executor, ThreadPoolExecutor, FutureTask。 这个怎么玩呢? 后面举例。

例子

我把官网的小例子改编了下

 private class DownloadFilesTask extends AsyncTask<URL, Void, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
         }
         return totalSize;
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

执行 AsyncTask 代码如下

new DownloadFilesTask().execute(url1, url2, url3);

构造方法

给开发者都调用的构造方法只有无参的构造方法

    /**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        this((Looper) null);
    }

从注释看,构造方法必须在 UI thread 中调用,why? 然后调用了带 Looper 参数的构造方法

    public AsyncTask(@Nullable Looper callbackLooper) {
        mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
            ? getMainHandler()
            : new Handler(callbackLooper);

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    postResult(result);
                }
                return result;
            }
        };

        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 occurred while executing doInBackground()",
                            e.getCause());
                } catch (CancellationException e) {
                    postResultIfNotInvoked(null);
                }
            }
        };
    }

这个构造函数只初始化了三个变量 mHandler, mWorker, mFuture

mWorker 类型是 Callable,是用来执行的任务。

mFuture 最主要的作用是用来获取执行的结果,例如 AsyncTaskget() 方法就是用 mFuture 来获取结果的。这里还复写了 done() 方法,也就是任务 mWorker 任务执行完毕后,会调用的,这里只是简单保证了结果会被发送到 UI Thread 中。

mHandler 因为传入的参数为 null,所以它的值为 getMainHandler()

    private static Handler getMainHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler(Looper.getMainLooper());
            }
            return sHandler;
        }
    }

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @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;
            }
        }
    }    

也就是说 mHandler 也就是用主线程 Looper 创建的一个 Handler 对象, 用来传递结果,以及更新进度。
这也就回答了为何要在 UI thread 中创建 AsyncTask 对象,因为要保证用 Handler 能把消息发送回 UI thread

execute()

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

    @MainThread
    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)");
            }
        }

        // 更新 asyncTask 状态
        mStatus = Status.RUNNING;

        // 后台任务执行前调用
        onPreExecute();

        mWorker.mParams = params;
        //执行任务
        exec.execute(mFuture);

        return this;
    }    

execute() 方法实际是调用了 executeOnExecutor() 方法,这两个方法都加上了 @MainThread 注解,告诉这两个方法需要在 main thread 中调用,原理与前面一样。

executeOnExecutor() 中通过对 mStatus 的判断的那段代码,就可以知晓为何一个 AsyncTask 对象只能调用一次 execute() 方法了。

executeOnExecutor() 最后用 sDefaultExecutorexecute() 方法执行了 mFuture 任务。看看 sDefaultExecutor

    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    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 是静态的对象,也就说所有的 AsyncTask 共享一个 sDefaultExecutor,那么如果创建了 N 个 AsyncTask 对象并且调用 execute() 方法,都会用 sDefaultExecutor 执行任务。

sDefaultExecutor 决定了 AsyncTask 执行任务的顺序,是顺序执行任务还是并行执行任务。 默认的是用的 SerialExecutor ,它保证任务的顺序执行,怎么保证了?

SerialExecutorexecute()scheduleNext() 方法都添加了 synchronized 关键字,也就是任意时刻,只有一个任务能提交,而且从代码逻辑 看,提交之后,必须执行完,才能提交第二个任务并执行,如此循环下去,就保证了任务的顺序执行。

OK, 现在回到正题, 现在是用 sDefaultExecutor 提交了 mFuture, 而 mFuture 是由 mWorker 构造,那么就会执行 mWorkercall() 方法

        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                // 设置调用状态 
                mTaskInvoked.set(true);
                Result result = null;
                try {
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    //noinspection unchecked
                    result = doInBackground(mParams);
                    Binder.flushPendingCommands();
                } catch (Throwable tr) {
                    // 发生异常,设置为取消状态
                    mCancelled.set(true);
                    throw tr;
                } finally {
                    // 发送后台线程执行结果
                    postResult(result);
                }
                return result;
            }
        };

call() 方法中调用了喜闻乐见的 doInBackground() 方法,注意,这个方法是在后台线程中执行的,由 sDefaultExecutor 保证的。

然后在异常处理的代码中发现,在发生异常的时候,首先会把任务设置为取消状态,然后再抛出。

finally 中,用 postResult(result) 把结果发送出去,最后会传递到 UI thread 中,代码如下

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

    private static class InternalHandler extends Handler {
        public InternalHandler(Looper looper) {
            super(looper);
        }

        @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;
            }
        }
    } 

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

finish() 方法中可以看到,如果任务取消了(前面提到的异常 或者 手动调用 asyncTask.cancel(boolean)),就会调用 onCancelled() 回调,如果是正常流程,就执行 onPostExecute() 回调。

并发执行任务

默认情况下,AsyncTask 是顺序执行任务,那么可不可以并行执行任务呢?我们只要改变执行任务的 Executor 就行了。 setDefaultExecutor() 方法可以改变默认的 Executor,但是对开发者来说这个方法是不可以的

    /** @hide */
    public static void setDefaultExecutor(Executor exec) {
        sDefaultExecutor = exec;
    }

这个方法是隐藏的,why? 因为一旦开发者改变了 AsyncTasksDefaultExecutor,一会并行执行一会顺序执行,那岂不是乱套了~

那么利用 executeOnExecutor() 方法自己传入 Executor

    @MainThread
    public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {}

AsyncTask 同时也提供了一个线程池用来并行执行任务

    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

也就是说 new MyAsyncTask().execute(THREAD_POOL_EXECUTOR, url1, url2, url3) 就可以并行执行多个任务。

那有人会问,既然可以并行,为何默认要顺序执行? 问题就出在 ThreadPoolExecutor 上,如果任务的处理速度超过了任务的提交速度,那么并行完全 OK。 然而,如果提交的任务的速度远远大于任务的处理速度,那么 CPU 超负荷,性能得不到保障。因此,如果想要并行,就要考虑如何构造 ThreadPoolExecutor 对象。

进度更新

到现在为止,我还没有提到如何更新进度,官方的例子如下

 private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
     protected Long doInBackground(URL... urls) {
         int count = urls.length;
         long totalSize = 0;
         for (int i = 0; i < count; i++) {
             totalSize += Downloader.downloadFile(urls[i]);
             publishProgress((int) ((i / (float) count) * 100));
             // Escape early if cancel() is called
             if (isCancelled()) break;
         }
         return totalSize;
     }

     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }

     protected void onPostExecute(Long result) {
         showDialog("Downloaded " + result + " bytes");
     }
 }

从例子中可以看到,在 doInBackground() 方法中,调用了 publishProgress() 方法来发布进度,源码如下


    @WorkerThread
    protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }

    private static class InternalHandler extends Handler {
        // ...

        @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;
            }
        }
    }

publishProgress() 是通过 mHandler 把消息传回 UI thread,然后回调了 onProgressUpdate()(这个方法在例子中复写了)。 但是我们要注意,只有在这个任务没有被取消的情况下,才能发布进度。

如何正确取消任务

可能通过 AsyncTaskcancel() 方法取消任务

    public final boolean cancel(boolean mayInterruptIfRunning) {
        mCancelled.set(true);
        return mFuture.cancel(mayInterruptIfRunning);
    }

从源码实现看,参数 mayInterruptIfRunning 代表是否中断线程。如果线程不能响应中断,那么这种方式是无法取消任务的,只能等后台任务执行完毕,然后回调 onCancelled() 而不是 onPostExecute()

哪些情况线程能响应中断,请参考 Thread 类的 interrupt() 方法.

例子中,在 doInBackground() 方法中,用 for 循环,进行了多个下载任务,需要注意的是在 publishProgress() 方法后,会调用 isCancelled() 来判断任务是否取消,如果取消了就不再进行下一个下载任务。 这给了我们一个很好的示范,如果在 doInbackground() 中通过循环来处理一些事情,需要在恰当的位置判断任务是否中断,从而决定任务是否进行下一次循环。

执行长时间任务

开头提到,官网说如果运行的不是几秒的任务,而是长时间的任务,就不要用 AsyncTask,而是用 Executor, ThreadPoolExecutor, FutureTask。 其实这是因为 AsyncTask 默认顺序执行任务,如果某一个任务执行时间过长,会导致后面的任务延迟太长,而如果你很有把握,可以并发执行,也就不存在所谓的不能执行长时间任务了。

那么,既然官网都说了这茬,那么如何做呢? 如果你对 Java 并发的这些类掌握得比较好的话,就很容易了。

demo 代码如下

public class TestActivity extends AppCompatActivity {
    private TextView mTextView;
    private Handler mUIHandler;
    private FutureTask<String> mFutureTask;
    private ExecutorService mService;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test_acrtivity);
        mTextView = findViewById(R.id.tv);
        mService = Executors.newCachedThreadPool();
        mFutureTask = new FutureTask<String>(new Task()) {
            @Override
            protected void done() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mUIHandler.sendEmptyMessage(666);
                    }
                });
            }
        };
        mUIHandler = new Handler(getMainLooper()) {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 666) {
                    try {
                        mTextView.setText(mFutureTask.get());
                    } catch (InterruptedException | ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
    }

    @Override
    protected void onResume() {
        super.onResume();
        mService.execute(mFutureTask);
    }

    private static class Task implements Callable<String> {
        @Override
        public String call() throws Exception {
            Log.d("david", "doInBackground ... ");
            // 假如这里3秒代表的是30秒,甚至更长时间运行的任务
            TimeUnit.SECONDS.sleep(3);
            return "Download has completed!";
        }
    }

}

原理与 AsyncTask 一致,我就不献丑解释代码了。

那么,你可能有疑问,这明明可以通过 ThreadHandler 实现的,为何要搞这么复杂。 简单的理由有几个

  1. Thread 类执行任务无法查询执行的结果,也没有 API 调用来取消任务。
  2. 并发任务需要用 ThreadPoolExecutor,而 Thread 类无法做到这一点。

其实把代码做下封装,基本上就是 AsyncTask 了。

发布了44 篇原创文章 · 获赞 30 · 访问量 400万+

猜你喜欢

转载自blog.csdn.net/zwlove5280/article/details/52623728