Android AsyncTask完全解析,站在源码的角度上

我们都知道,Android UI是线程不安全的,如果想要在子线程里进行UI操作,就需要借助Android的异步消息处理机制。也就是Handler,Message, Thread。

不过为了更加方便我们在子线程中更新UI元素,Android从1.5版本引入了AsyncTask类,使用它我们可以更灵活方便的实现子线程更新UI元素。


AsyncTask基本用法

首先来看一下AsyncTask的基本用法,由于AsyncTask是一个抽象类,所以如果我们想使用它,就必须要创建一个子类去继承它。在继承时我们可以为AsyncTask类指定三个泛型参数,这三个参数的用途如下:

1.Params

在AsyncTask执行时传入的参数,可用于后台任务中使用

2.Progress

后台任务执行时,如果需要在UI界面上显示进度,则使用这里的泛型作为进度单位

3.Result

后台任务执行完毕后,返回的结果类型,通过这里指定的泛型作为返回值类型

因此,一个简单的异步任务现实就可以写成这样。

public class DowloadTask extends AsyncTask<String, Integer, String> {
		.....
	}

不是所有的任务都需要需要参数的,这时我们就可以使用Void类型。


AsyncTask的执行,有很重要的4个步骤。

1.onPreExecute()

方法在后台任务执行之前调用,运行于UIThread,用于一些初始化操作,如显示一个进度条对话框

2.doInBackgroud(Params...)

方法执行在onPreExecute()之后,运行于后台线程中,我们可以在这里处理耗时任务。任务结束就可以使用return语句将任务执行结果传递到最后一部。如果AsyncTask第三个参数类型为Void,那么就没有return语句了,也不需要对结果进行处理了。注意,该方法是不能进行UI操作的,如果需要更新UI元素,比如反馈任务执行的进度,可以调用publishProgress(Progress...)来完成。

3.onProgressUpdate(Progress...)

方法执行在publishProgress(Progress...)调用之后,运行于UIThread,方法中的参数就是后台任务传递的。在这个方法中,我们就可以进行UI操作了,利用参数的数值就可以对界面元素进行对应更新,如进度条进度更新。

4.onPostResult(Result)

方法执行在后台任务结束,运行在UIThread,后台任务return的数据会传递到方法中,可以利用返回的数据进行UI操作,比如关闭进度条对话框,提示任务结果等。

因此,一个比较完整的自定义AsyncTask,可以写程这样

public class DowloadTask extends AsyncTask<String, Integer, Boolean>{
	
	@Override
	protected void onPreExecute() {
		// TODO Auto-generated method stub
		mProgressDialog.show();
	}

	@Override
	protected Boolean doInBackground(String... params) {
		// TODO Auto-generated method stub
		try {
			while(true) {
				int downloadCount = doDownload(params[0]);
				publishProgress(downloadCount);
				if (downloadCount >= 100) {
					break;
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
			return false;
		}
		return true;
	}
	
	@Override
	protected void onProgressUpdate(Integer... values) {
		// TODO Auto-generated method stub
		mProgressDialog.setMessage("当前进度" + values[0] + "%");
	}
	
	@Override
	protected void onPostExecute(Boolean result) {
		// TODO Auto-generated method stub
		mProgressDialog.dismiss();
		if (result) {
			Toast.makeText(context, "下载成功", Toast.LENGTH_SHORT).show();
		} else {
			Toast.makeText(context, "下载失败", Toast.LENGTH_SHORT).show();
		}
	}

}

这里我们模拟了一个下载任务,在doInBackgroud()方法中执行具体的下载逻辑,publishProgress()方法进行下载进度的传递,progressUpdate()方法进行进度的UI显示,在onPostExecute()方法中对最后的结果进行处理。如果想要启动这个任务,只需要简单的调用下面的代码即可。

<span style="font-size:14px;">new DowloadTask().execute(url);</span>

以上就是AsyncTask的基本用法,是不是比使用Handler来说,更简单灵活了。


分析AsyncTask源码

虽然AsyncTask简单好用,但是它是如何实现的呢?那么接下来,我们来分析下它的源码,对它的原理一探究竟。注意,这里我选用的源码是Android4.4的源码,如果查看其他版本的源码,可能会有一些出入。

上面的DownloadTask代码可以看出,在启动一个任务之前,要先new出它的实例。因此,我们就来看下AsyncTask的构造函数的源码,如下所示:

/**
     * Creates a new asynchronous task. This constructor must be invoked on the UI thread.
     */
    public AsyncTask() {
        mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

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

这段代码有点长,但实际上并没有具体的逻辑会得到执行,只是初始化了两个变量,mWorker和mFuture,并在初始化mFuture时将mWorker作为参数传入。mWorker是一个Callback对象,mFuture是一个FutureTask对象,这两个对象会在后面的任务执行使用到。

注意,AsyncTask实例必须在UIThread中创建。

接着如果要启动某个任务,就需要调用该任务的execute()方法,因此我们来看一看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) {
        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)");
            }
        }

        mStatus = Status.RUNNING;

        onPreExecute();

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

        return this;
    }

果然这个方法的代码,就正常很多。可以看到onPreExecute()方法先执行了,可是接下来的代码就不明白了,怎么没见到哪里调用了doInBackgroud()方法?慢慢找,我们看到,exec.execute(mFuture)方法执行,可以看到exec对象就是上面方法传入的sDefaultExecutor变量,接下来就找一下sDefaultExecutor变量是在哪里定义的,源码如下:

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

可以看到,这里先new出了一个SERIAL_EXECUTOR常量,然后将sDefaultExecutor的值赋值为这个常量,也就是说明,刚才在executeOnExecutor()方法中调用的execute()方法,其实也就是调用的SerialExecutor类中的execute()方法。

SerialExecutor类中也有一个execute()方法,这个方法里的所有逻辑就是在子线程中执行的了,注意这个方法有一个Runnable参数,那么目前这个参数的值是什么呢?当然就是mFuture对象了,也就是说上面r.run()方法的执行,我们要调用的是FutureTask类的run()方法,那我们来看一看它的源码实现:

public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

可以看到,调用了callable的call()方法,那么这个callable对象是什么呢?其实就是在初始化mFuture对象时传入的mWorker对象了,此时调用的call()方法,也就是一开始在AsyncTask的构造函数中指定的,我们把它单独拿出来看一下,代码如下所示:

 mWorker = new WorkerRunnable<Params, Result>() {
            public Result call() throws Exception {
                mTaskInvoked.set(true);

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                //noinspection unchecked
                return postResult(doInBackground(mParams));
            }
        };

在postResult()方法参数里面,我们找到了doInBackground()方法的调用,可以看到目前代码仍然运行于子线程中,所以这也就是我们为什么在doInBackground()方法中去处理耗时的逻辑。接着doInBackground()方法返回结果作为参数传递给postResult()方法,这个方法的源码如下:

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


如果你已经熟悉了异步消息机制,这段代码对你来说就非常简单。这里使用了sHandler对象发出一条消息,消息中携带了Message_POST_RESULT常量和一个表示任务结果的AsyncTaskResult对象。这个sHandler对象是InternalHandler类的一个实例,那么稍后这条消息肯定会在InternalHandler的handleMessage()方法中被处理。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;
            }
        }
    }

这里对消息的类型进行了判断,如果这是一条MESSAGE_POST_RESULT消息,就会去执行finish()方法,如果这是一条MESSAGE_POST_PROGRESS消息,就会去执行onProgressUpdate()方法。那么finish()方法的源码如下所示:

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

可以看到,如果当前任务被取消掉了,就会调用onCancelled()方法,如果没有被取消,则调用onPostExecute()方法,这样当前任务的执行就全部结束了。

我们注意到,在刚才InternalHandler的handleMessage()方法里,还有一种MESSAGE_POST_PROGRESS的消息类型,这种消息是用于当前进度的,调用的正是onProgressUpdate()方法,那么什么时候才会发出这样一条消息呢?相信你已经猜到了,查看publishProgress()方法的源码,如下所示:

protected final void publishProgress(Progress... values) {
        if (!isCancelled()) {
            sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
                    new AsyncTaskResult<Progress>(this, values)).sendToTarget();
        }
    }
非常清晰了吧!正因如此,在doInBackground()方法中调用publishProgress()方法才可以从子线程切换到UI线程,从而完成对UI元素的更新操作。其实也没有什么神秘的,因为说到底,AsyncTask也是使用的异步消息处理机制,只是做了非常好的封装而已。

读到这里,相信你对AsyncTask中的每个回调方法的作用、原理、以及何时会被调用都已经搞明白了吧。


AsyncTask的一些其他特征

在上面,刚才我们在分析源码SerialExecutor的时候,其实并没有分析的很仔细,仅仅只是关注了它会调用mFuture中的run()方法,但是至于什么时候会调用我们并没有进一步地研究。其实SerialExecutor也是AsyncTask在3.0版本以后做了最主要的修改的地方,它在AsyncTask中是以常量的形式被使用的,因此在整个应用程序中的所有AsyncTask实例都会共用同一个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);
            }
        }
    }
可以看到,SerialExecutor是使用了ArrayDeque这个队列来管理Runnable对象的,如果我们一次性启动了很多任务,首先在第一次执行execute()方法的时候,会调用ArrayDeque的offer()方法将传入Runnable对象添加到队列的尾部,然后判断mActive对象是否为null,第一次运行当然是null,于是调用scheduleNext()方法。在这个方法中会从队列的头部取值,并赋值给mActive对象,然后调用

THREAD_POOL_EXECUTOR去执行取出的Runnable对象。之后如果又有新的任务被执行,同样还会调用offer()方法将传入的Runnable添加到队列的尾部,但是再去给mActive对象做非空检查的时候就会发现mActive对象已经不再是null了,于是就不会再调用scheduleNext()方法。

那么后面添加的任务岂不是永远得不到处理了?当然不是,看一看offer()方法里传入的Runnable匿名类,这里使用了一个try finally代码块,并在finally中调用了scheduleNext()方法,保证无论发生什么情况,这个方法都会被调用。也就是说,每次当一个任务执行完毕后,下一个任务才会得到执行,SerialExecutor模仿的是单一线程池的效果,如果我们快速地启动了很多任务,同一时刻只会有一个线程正在执行,其余的均处于等待状态。这个就是AsyncTask多任务按照顺序执行了

当然AsyncTask源码中也不是只有SerialExecutor,还实现了THREAD_POOL_EXECUTOR对象,来实现多任务并行处理。源码如下:

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

可以看到,这里规定同一时刻能够运行的线程数为CPU_COUNT+1个,线程池总大小为CUP_COUNT*2+1。也就是说当我们启动了多个任务时,只有CPU_COUNT + 1个任务能够立刻执行,另外的任务则需要等待,当有一个任务执行完毕后,那么下个任务才会启动,以此类推。而线程池中最大能存放的线程数是CPU_COUNT*2+1个,当我们尝试去添加的任务超过它时,程序就会崩溃。

这样,我们完全可以自定义ThreadPool来替换系统的。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值