at之asynctask源码解答

来源:互联网 发布:wowhead数据库 编辑:程序博客网 时间:2024/05/27 21:48

首先从Android3.0开始,系统要求网络访问必须在子线程中进行,否则网络访问将会失败并抛出NetworkOnMainThreadException这个异常,这样做是为了避免主线程由于耗时操作所阻塞从而出现ANR现象。AsyncTask封装了线程池和Handler。AsyncTask有两个线程池:SerialExecutor和THREAD_POOL_EXECUTOR。前者是用于任务的排队,默认是串行的线程池:后者用于真正的执行任务。AsyncTask还有一个Handler,叫InternalHandler,用于将执行环境从线程池切换到主线程。AsyncTask内部就是通过InternalHandler来发送任务执行的进度以及执行结束等消息。

AsyncTask排队执行过程:系统先把参数Params封装为FutureTask对象它相当于Runnable,接着FutureTask交给SerialExcutor的execute方法,它先把FutureTask插入到任务队列tasks中,如果这个时候没有正在活动的AsyncTask任务,那么就会执行下一个AsyncTask任务,同时当一个AsyncTask任务执行完毕之后,AsyncTask会继续执行其他任务直到所有任务都被执行为止。


关于线程池,AsyncTask对应的线程池ThreadPoolExecutor都是进程范围内共享的,都是static的,所以是AsyncTask控制着进程范围内所有的子类实例。由于这个限制的存在,当使用默认线程池时,如果线程数超过线程池的最大容量,线程池就会爆掉(3.0默认串行执行,不会出现这个问题)。针对这种情况。可以尝试自定义线程池,配合AsyncTask使用。


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

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

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

接着看executeOnExecutor

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

这块代码SerialExecutor(调度器)首先讲要执行的代码放到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方法中循环 然后使用一个volatie boolean变量来提前终止掉Run方法来达到中断线程的目的。


若Activity已经销毁,此时AsynTask执行完并且返回结果,会报异常吗?

当一个App旋转时,整个Activity会被销毁和重建。当Activity重启时,AsyncTask中对该Activity的引用是无效的,因此onPostExecute()就不会起作用,若AsynTask正在执行,折会报 view not attached to window manager 异常
同样也是生命周期的问题,在 Activity 的onDestory()方法中调用Asyntask.cancal方法,让二者的生命周期同步


3.Activity销毁但Task如果没有销毁掉,当Activity重启时这个AsyncTask该如何解决?

还是屏幕旋转这个例子,在重建Activity的时候,会回掉Activity.onRetainNonConfigurationInstance()重新传递一个新的对象给AsyncTask,完成引用的更新

执行相应地任务,因为线程池的大小问题,所以 AsyncTask 只应该用来执行耗时时间较短的任务,
比如 HTTP 请求,大规模的下载和数据库的更改不适用于 AsyncTask,因为会导致线程池堵塞,没有
线程来执行其他的任务,导致的情形是会发生 AsyncTask 根本执行不了的问题


0 0
原创粉丝点击