美文网首页Android开发Android知识程序员
Android面试系列之AsyncTask源码分析

Android面试系列之AsyncTask源码分析

作者: 人失格 | 来源:发表于2017-02-09 19:19 被阅读82次

    在Android开发道路上,有一个类你是无论如何都无法绕过去的。那就是AsyncTask,因为使用的足够简单,在面试中多线程很频繁聊到这个。

    AsyncTask的使用起始很简单,在源码中类为抽象类 我们只需要复写doInBackground方法即可完成子线程的业务编写。我们从AsyncTask中的方法 onPreExecute()doInBackground() onPostExecute()onProgressUpdate()来入手分析AsyncTask的源码

    AsyncTask的对象一般是执行execute()方法来启动,我们来看一下

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

    接着看executeOnExecutor

    @MainThread
        public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
                Params... params) {
            //此处省略一些
            mStatus = Status.RUNNING;
            onPreExecute();
            mWorker.mParams = params;
            exec.execute(mFuture);
            return this;
        }
    
    

    在方法中首先调用了onPreExecute()方法执行在主线程中,然后讲参数赋给了Worker对象,在后面我们会知道这是线程执行单位。然后调用了线程调度器来执行Future,这个后面会解释到。

    然后我们再来看一下线程池的调用情况

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

    这块代码 首先讲要执行的代码放到Tasks队列中,然后判断是否有任务执行 如果线程池空闲则调用scheduleNext();方法 这个方法中完成任务的执行

    THREAD_POOL_EXECUTOR的初始化如下

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

    可能你看到这里已经有点头晕了,但是没关系,此处我们放慢脚步来解释下几个名词

    • THREAD_POOL_EXECUTOR
      这个是用于任务执行的线程池 ,所有的任务都是在这个线程池中完成调用
    • SerialExecutor
      这个线程池用于用于任务的排队,默认实现也能看出来,它是串行执行任务。
    • FutureTask
      FutureTask一个可取消的异步计算, 使用子线程计算返回的结果Result作为泛型,并且将WorkerRunnable作为构造参数传入
    • WorkerRunnable
      真正的子线程执行单位
    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;
                }
            };
    

    首先设置 mTaskInvoked.set(true);标识任务已经执行,然后调整线程的优先级,接着调用doInBackground(mParams); 因为WorkerRunnable的call()方法会被FutureTask的run方法调用,所以doInBackground()方法最后执行在子线程中。postResult(result);最后将结果返回到UI线程。

    我们都知道Handler的作用很大程度在于线程间传输,我们首先看一下 postResult(result)方法

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

    很熟悉的代码 通过Handler将MESSAGE_POST_RESULT发送到目标来处理,我们首先看一下AsyncTask类中的Handler是如何处理的

    private static class InternalHandler extends Handler {
            public InternalHandler() {
                super(Looper.getMainLooper());
            }
            @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;
                }
            }
        }
    
    

    直接很简单的在构造函数上Looper.getMainLooper() 获取UI线程的Looper
    看到switch语句中有两种类型 1. MESSAGE_POST_RESULT: 2. MESSAGE_POST_PROGRESS 这我们在平时的使用中再熟悉不过了 一个用来在UI 线程中获取结果的 onPostExecute(),另外一个则是onProgressUpdate 用来刷新任务完成的进度的。

    接下来我们先看一下AsyncTask的finish方法

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

    简单判断下是否取消isCancelled(),如果取消则调用onCancelled(result);,否则将结果传递到onPostExecute(result);在这里我们可以得出一个结论,AsyncTask为什么不能中断线程,在AsyncTask的方法中Cancel方法的调用只是在线程执行完需要返回结果的时候做的一个标记符而已。

    PS 这里多说几句 Java最开始的时候是没有正式中断线程的办法,一般都是调用interrupt()来强制中断,然后在Run()方法中来捕获这个异常。我们在平时的开发过程中一般使用Run方法中循环 然后使用一个volatieboolean变量来提前终止掉Run方法来达到中断线程的目的。

    到这里 我们已经基本完成了AsyncTask的源码分析了,因为AsyncTask的源码是只有500行,所以看下来起始很轻松,如果有些类的代码达到了一万多行的时候,我们应该用巧方法来阅读源码,最常见的方法便是从使用的角度来反向查看代码如何实现的,去粗取精 了解到整个类的是如何设计的就行,做到看完后心中已经有了框架模样就可以了,没必要一行一行看下去也不现实。

    相关文章

      网友评论

        本文标题:Android面试系列之AsyncTask源码分析

        本文链接:https://www.haomeiwen.com/subject/wijwittx.html