開發Android app的時候通常將耗時的操作放在單獨的線程中執行,避免其佔用主線程(主要負責更新UI)而給用戶帶來不良用戶體驗。所以Android提供了一個Handler類在子線程完成任務後異步通知UI線程,主線程(UI線程)收到消息更新UI界面,呈現給用戶。比較好的解決了子線程更新UI的問題。但是費時的任務操作總會啓動一些匿名的子線程,太多的子線程給系統帶來巨大的負擔,隨之帶來一些性能問題。因此Android提供了一個工具類AsyncTask。這個AsyncTask生來就是處理一些後臺的比較耗時的任務,給用戶帶來良好用戶體驗的,從編程的語法上顯得優雅了許多,不再處理子線程和Handler(AsyncTask源碼最終還是使用Handler機制)就可以完成異步操作並且刷新用戶界面。
使用方法
AsyncTask是一個抽象類,必須要創建一個子類去繼承它纔可以使用。在繼承時我們可以爲AsyncTask類指定三個泛型參數:
- Params:這個泛型指定在執行AsyncTask時需要傳入的參數,可用於在後臺任務中使用;比如HTTP請求的URL。
- Progress:異步任務在執行的時候將執行的進度返回給UI線程的參數的類型,則使用這裏指定的泛型作爲進度單位。
- Result:異步任務執行完後返回給UI線程的結果的類型。
使用AsyncTask的一般步驟
本文中的實例 DownLoadTask 來自Android官方文檔。
1.繼承AsyncTask
//無傳入參數
//使用整型數據來作爲進度顯示單位
//任務執行完後返回值爲Boolean類型
class DownloadTask extends AsyncTask<Void, Integer, Boolean> {
……
}
2.重寫AsyncTask中的方法
重寫AsyncTask中的以下幾個方法才能完成對任務的定製。
- onPreExecute(): 這個方法是在執行異步任務之前的時候執行,並且是在UI Thread當中執行的,通常我們在這個方法裏做一些UI控件的初始化的操作,比如顯示一個進度條對話框或一些控件的實例化等。
- doInBackground(Params...):這個方法中的所有代碼都會在子線程中運行,應在這裏處理耗時任務。任務一旦完成就可以通過return語句來將任務的執行結果進行返回,如果AsyncTask的第三個泛型參數指定的是Void,就可以不返回任務執行結果。注意,在這個方法中是不可以進行UI操作(因爲在子線程),如果需要更新UI元素,比如說反饋當前任務的執行進度,可以調用publishProgress(Progress...)方法來完成。這個方法子類必須重寫。
- onProgressUpdate(Progress...):在UI Thread中執行的,在異步任務執行的時候,有時需要將執行的進度返回給我們的UI界面,例如下載一張網絡圖片,我們需要時刻顯示其下載的進度,就可以使用這個方法來更新我們的進度。這個方法在調用之前,我們需要在 doInBackground 方法中調用一個 publishProgress(Progress) 的方法來將我們的進度時時刻刻傳遞給 onProgressUpdate 方法來更新。
- onPostExecute(Result):在doInBackground 執行完成後,onPostExecute 方法將被UI thread調用,可以將返回的結果顯示在UI控件上。
- onCancelled():在用戶取消線程操作的時候調用。在主線程中調用onCancelled()的時候調用。
一個改自Android文檔上的例子。
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
protected void onPreExecute() {
progressDialog.show();
}
@Override
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;
}
@Override
protected void onProgressUpdate(Integer... values) {
progressDialog.setMessage("當前下載進度:" + values[0] + "%");
}
@Override
protected void onPostExecute(Long result) {
progressDialog.dismiss();
if (result) {
Toast.makeText(context, "下載成功", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(context, "下載失敗", Toast.LENGTH_SHORT).show();
}
}
我們模擬了一個下載任務,在doInBackground()方法中去執行具體的下載邏輯,在onProgressUpdate()方法中顯示當前的下載進度,在onPostExecute()方法中來提示任務的執行結果。簡單地調用以下代碼即可啓動此任務:
new DownloadTask().execute();
使用AsyncTask必須要遵循的原則
- AsyncTask的對象必須在UI Thread當中實例化
- execute方法必須在UI Thread當中調用
- 請勿手動的去調用AsyncTask的onPreExecute, doInBackground, publishProgress, onProgressUpdate, onPostExecute方法,這些都是由Android系統自動調用的
- AsyncTask任務只能被執行一次,即只能調用一次execute方法,多次調用時將會出現異常
- AsyncTask不是被設計爲解決非常耗時操作的,耗時上限爲幾秒鐘,如果要做長耗時操作,強烈建議使用Executor,ThreadPoolExecutor以及FutureTask。
注意AsyncTask不能完全取代線程,在一些邏輯較爲複雜或者需要在後臺反覆執行的邏輯就可能需要線程來實現了。
AsyncTask的源碼解析
AsyncTask的源碼基於Android4.4.4,其實源碼文檔已經說得很細了,本文分析源碼做下補充。
AsyncTask也是使用的異步消息處理機制,只是做了非常好的封裝而已。
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
//獲取當前機器的cpu核心數
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//線程池推薦容量,一般都爲處理器核心數+1(更好利用多核)
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;
//工廠方法模式,通過newThread來獲取新線程
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());
}
};
//靜態阻塞式隊列,用來存放待執行的任務,初始容量:128個
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* 靜態併發線程池,可以用來並行執行任務,AsyncTask默認是串行執行任務
* 但仍能構造出並行的AsyncTask
*/
public static final Executor THREAD_POOL_EXECUTOR
= new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
/**
* 內部實現了串行控制,循環的取出一個個任務交給上述的併發線程池去執行
*/
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
//消息類型:發送結果
private static final int MESSAGE_POST_RESULT = 0x1;
//消息類型:更新進度
private static final int MESSAGE_POST_PROGRESS = 0x2;
/*靜態Handler,用來發送上述兩種通知,採用UI線程的Looper來處理消息
* 這就是爲什麼AsyncTask必須在UI線程調用,因爲子線程
* 默認沒有Looper無法創建下面的Handler,程序會直接Crash
*/
private static final InternalHandler sHandler = new InternalHandler();
//默認任務執行器,被賦值爲串行任務執行器,就是它,AsyncTask變成串行的了
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
//任務的狀態 默認爲掛起, volatile經典用法之一:當做標識
private volatile Status mStatus = Status.PENDING;
//標識任務是否被取消
private final AtomicBoolean mCancelled = new AtomicBoolean();
//標識任務是否被執行過
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
/*串行執行器的實現,asyncTask.execute(Params ...)實際上會調用
*SerialExecutor的execute方法;即當你的asyncTask執行的時候,
*首先你的task會被加入到任務隊列,然後排隊,一個個執行
*/
private static class SerialExecutor implements Executor {
//線性雙向隊列,用來存儲所有的AsyncTask任務
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
//當前正在執行的AsyncTask任務
Runnable mActive;
public synchronized void execute(final Runnable r) {
////將新的AsyncTask任務加入到雙向隊列中
mTasks.offer(new Runnable() {
public void run() {
try {
//執行AsyncTask任務
//其實爲mFuture對象,調用mFuture對象的run方法
//run調用Sync內部類的innerRun(),在調用call()方法
//call方法裏有postResult(doInBackground(mParams));
//所以doInBackground最終是在這裏執行(畢竟新開啓了一個線程)
r.run();
} finally {
//當前AsyncTask任務執行完畢後,如果還有未執行任務 則進行下一輪執行,
//明顯體現AsyncTask是串行執行任務,總是一個任務執行完畢纔會執行下一個任務
scheduleNext();
}
}
});
//第一次運行當然是等於null了,於是會調用scheduleNext()方法
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
//從任務隊列中取出隊列頭部的任務,如果有就交給併發線程池去執行
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
/**
* 任務的三種狀態,在整個Task的生命週期中,只能被分別設置一次
*/
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,
}
/** @hide 在UI線程中調用,用來初始化Handler */
public static void init() {
sHandler.getLooper();
}
/** @hide 爲AsyncTask設置默認執行器*/
public static void setDefaultExecutor(Executor exec) {
sDefaultExecutor = exec;
}
/**
* 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);
}
}
};
}
private void postResultIfNotInvoked(Result result) {
final boolean wasTaskInvoked = mTaskInvoked.get();
if (!wasTaskInvoked) {
postResult(result);
}
}
//doInBackground執行完畢,發送消息
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = sHandler.obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
/**
* 返回任務的狀態
*
* @return The current status.
*/
public final Status getStatus() {
return mStatus;
}
/**
* 這個方法是我們必須要重寫的,用來做後臺計算
* 所在線程:後臺線程
* This method can call {@link #publishProgress} to publish updates
* on the UI thread.
*
* @param params The parameters of the task.
*
* @return A result, defined by the subclass of this task.
*
* @see #onPreExecute()
* @see #onPostExecute
* @see #publishProgress
*/
protected abstract Result doInBackground(Params... params);
/**
* R在doInBackground之前調用,用來做初始化工作
* 所在線程:UI線程
* @see #onPostExecute
* @see #doInBackground
*/
protected void onPreExecute() {
}
/**
* 在doInBackground之後調用,用來接受後臺計算結果更新UI
* 所在線程:UI線程
*
* @param result The result of the operation computed by {@link #doInBackground}.
*
* @see #onPreExecute
* @see #doInBackground
* @see #onCancelled(Object)
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void onPostExecute(Result result) {
}
/**
* 在publishProgress之後調用,用來更新計算進度
* 所在線程:UI線程
*
* @param values The values indicating progress.
*
* @see #publishProgress
* @see #doInBackground
*/
@SuppressWarnings({"UnusedDeclaration"})
protected void onProgressUpdate(Progress... values) {
}
/**
* cancel被調用並且doInBackground執行結束,會調用onCancelled,表示任務被取消
*這個時候onPostExecute不會再被調用,二者是互斥的,分別表示任務取消和任務執行完成
* 所在線程:UI線程
* @param result The result, if any, computed in
* {@link #doInBackground(Object[])}, can be null
*
* @see #cancel(boolean)
* @see #isCancelled()
*/
@SuppressWarnings({"UnusedParameters"})
protected void onCancelled(Result result) {
onCancelled();
}
/**
* <p>Applications should preferably override {@link #onCancelled(Object)}.
* This method is invoked by the default implementation of
* {@link #onCancelled(Object)}.</p>
*
* <p>Runs on the UI thread after {@link #cancel(boolean)} is invoked and
* {@link #doInBackground(Object[])} has finished.</p>
*
* @see #onCancelled(Object)
* @see #cancel(boolean)
* @see #isCancelled()
*/
protected void onCancelled() {
}
/**
* Returns <tt>true</tt> if this task was cancelled before it completed
* normally. If you are calling {@link #cancel(boolean)} on the task,
* the value returned by this method should be checked periodically from
* {@link #doInBackground(Object[])} to end the task as soon as possible.
*
* @return <tt>true</tt> if task was cancelled before it completed
*
* @see #cancel(boolean)
*/
public final boolean isCancelled() {
return mCancelled.get();
}
/**
* <p>Attempts to cancel execution of this task. This attempt will
* fail if the task has already completed, already been cancelled,
* or could not be cancelled for some other reason. If successful,
* and this task has not started when <tt>cancel</tt> is called,
* this task should never run. If the task has already started,
* then the <tt>mayInterruptIfRunning</tt> parameter determines
* whether the thread executing this task should be interrupted in
* an attempt to stop the task.</p>
*
* <p>Calling this method will result in {@link #onCancelled(Object)} being
* invoked on the UI thread after {@link #doInBackground(Object[])}
* returns. Calling this method guarantees that {@link #onPostExecute(Object)}
* is never invoked. After invoking this method, you should check the
* value returned by {@link #isCancelled()} periodically from
* {@link #doInBackground(Object[])} to finish the task as early as
* possible.</p>
*
* @param mayInterruptIfRunning <tt>true</tt> if the thread executing this
* task should be interrupted; otherwise, in-progress tasks are allowed
* to complete.
*
* @return <tt>false</tt> if the task could not be cancelled,
* typically because it has already completed normally;
* <tt>true</tt> otherwise
*
* @see #isCancelled()
* @see #onCancelled(Object)
*/
public final boolean cancel(boolean mayInterruptIfRunning) {
mCancelled.set(true);
return mFuture.cancel(mayInterruptIfRunning);
}
/**
* Waits if necessary for the computation to complete, and then
* retrieves its result.
*
* @return The computed result.
*
* @throws CancellationException If the computation was cancelled.
* @throws ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
*/
public final Result get() throws InterruptedException, ExecutionException {
return mFuture.get();
}
/**
* Waits if necessary for at most the given time for the computation
* to complete, and then retrieves its result.
*
* @param timeout Time to wait before cancelling the operation.
* @param unit The time unit for the timeout.
*
* @return The computed result.
*
* @throws CancellationException If the computation was cancelled.
* @throws ExecutionException If the computation threw an exception.
* @throws InterruptedException If the current thread was interrupted
* while waiting.
* @throws TimeoutException If the wait timed out.
*/
public final Result get(long timeout, TimeUnit unit) throws InterruptedException,
ExecutionException, TimeoutException {
return mFuture.get(timeout, unit);
}
/**
*這個方法如何執行和系統版本有關,在AsyncTask的使用規則裏已經說明,如果你真的想使用並行AsyncTask,
* 也是可以的,只要返回 THREAD_POOL_EXECUTOR 即可
* 必須在UI線程調用此方法
*
* @param params The parameters of the task.
*
* @return This instance of AsyncTask.
*
* @throws IllegalStateException If {@link #getStatus()} returns either
* {@link AsyncTask.Status#RUNNING} or {@link AsyncTask.Status#FINISHED}.
*
* @see #executeOnExecutor(java.util.concurrent.Executor, Object[])
* @see #execute(Runnable)
*/
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
//默認串行執行
return executeOnExecutor(sDefaultExecutor, params);
//如果我們想並行執行,返回THREAD_POOL_EXECUTOR即可
//return executeOnExecutor(THREAD_POOL_EXECUTOR, params);
}
/**
* 通過這個方法我們可以自定義AsyncTask的執行方式,串行or並行,
* 甚至可以採用自己的Executor
* 爲了實現並行,我們可以在外部這麼用AsyncTask:
* asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, Params... params);
* 必須在UI線程調用此方法
*/
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會最先執行
onPreExecute();
mWorker.mParams = params;
//然後後臺計算#doInBackground才真正開始
exec.execute(mFuture);//接着會有#onProgressUpdate被調用,最後是#onPostExecute
return this;
}
//方便我們直接執行一個runnable
public static void execute(Runnable runnable) {
sDefaultExecutor.execute(runnable);
}
//打印後臺計算進度,onProgressUpdate會被調用
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
sHandler.obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
//任務結束的時候會進行判斷,如果任務沒有被取消,則onPostExecute會被調用
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
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
//說明任務執行完成,調用finish結束任務
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {
Params[] mParams;
}
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
}