前言
Android中,線程是操作系統調度的最小單位。線程分爲主線程和子線程。主線程用來處理界面的交互,而耗時操作(網絡請求,複雜的數據庫查詢)必須在子線程中來完成。通過Handler消息機制完成主線程和子線程之間的通信。
每個任務都需要一個線程去執行,但是不可能每個任務的執行都是伴隨着線程的銷燬和重新創建,十分的耗費性能。所以用線程池去緩存一定數目的線程,由線程池來管理執行任務的線程,避免了頻繁的創建和銷燬。Android中提供了AsyncTask類,內部就是由線程池和Handler實現的。各位大佬,這篇看了包會!
(一) AsyncTask使用
public abstract class AsyncTask<Params, Progress, Result>
AsyncTask是一個抽象類,如果使用的話需要自定義一個類繼承它。它提供了4個核心的方法。這裏用一個示例來做介紹(Kotlin環境下)。
一.使用和核心方法介紹
1.1 自定義類繼承AsyncTask
/*靜態屬性聲明處,此處MyAsyncTask爲靜態類*/
companion object {
class MyAsyncTask(private var taskName: String) : AsyncTask<String, Int, String>() {
/*核心方法1*/
override fun onPreExecute() {
super.onPreExecute()
Log.d("MyAsyncTask", taskName + " OnPreExecute")
}
/*核心方法2*/
override fun doInBackground(vararg params: String?): String {
try {
Thread.sleep(5000)
} catch (e: Exception) {
e.printStackTrace()
}
/*輔助方法*/
publishProgress(50)
Log.d("MyAsyncTask", taskName + " doInBackground")
return taskName
}
/*核心方法3*/
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
Log.d("MyAsyncTask",
"I am Task" + result + " executed at " + SimpleDateFormat("yyyy-mm-dd HH:mm:ss").format(Date()))
}
/*核心方法4*/
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
Log.d("MyAsyncTask", taskName + " onProgressUpdate")
}
}
}
聲明好了靜態內部類MyAsyncTask後,小手一點Button執行異步任務。
/*execute是Button控件的id,這裏沒有使用findViewById()*/
execute!!.setOnClickListener({
MyAsyncTask("AsyncTask1").execute()
})
介紹一下上面點擊按鈕執行任務操作,在項目中是沒有寫findViewById()去初始化Button,通過Kotlin Android Extensions拓展插件直接可以使用xml佈局裏Button的id來調用相應的方法,兩個字:簡便。有興趣的同學可以去Kotlin官網瞭解一波。下面是MyAsyncTask執行的結果。
根據圖示介紹一下AsyncTask的核心方法
1-2.onPreExecute()
在主線中執行,用於異步任務執行之前做一些準備工作,比如初始化加載進度條之類。
1-3.doInBackground(vararg params: String?)
在子線程中執行,真正執行異步任務的方法,參數是異步任務輸入參數。在此方法中可以通過 publishProgress()方法來更新異步任務當前的進度,調用publishProgress()方法會同步調用onProgressUpdate()。
1-4. onProgressUpdate(vararg values: Int?)
在主線程中執行,此方法用來實時顯示當前任務的執行進度。作用是可以在此處更新UI上的進度條進度或者做其他操作。
1-5. onPostExecute(result: String?)
在主線程中執行,異步任務執行結束之後會執行此方法。參數是doInBackground()的返回值。
AsyncTask的核心方法就是上面所述,執行任務是在子線程中,而任務準備工作,進度更新等全是在主線程中完成。AsyncTask實例的創建也是要在主線程中去執行。
二.源碼解析
根據代碼執行順序,我們首先看下AsyncTask的 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)");
}
}
mStatus = Status.RUNNING;
/*執行準備工作*/
onPreExecute();
/*mWorker開啓一個線程任務,mParams 後臺執行任務所需參數*/
mWorker.mParams = params;
/*線程池執行線程任務,mFuture封裝了開啓線程的mWorker*/
exec.execute(mFuture);
return this;
}
我們看下上面executeOnExecutor()方法中幾個重要的屬性。exec →* SerialExecutor*,mWorker → WorkerRunnable和mFuture → FutureTask。
WorkerRunnable:任務執行返回的結果
這裏mWorker對象的創建是在AsyncTask的構造函數中,本質是開啓一個線程,call()方法將任務執行完的結果返回。
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
//標記任務已經被調用過
mTaskInvoked.set(true);
Result result = null;
try {
//將線程的優先級設置爲後臺,爲了執行完任務方便回收
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//後臺執行任務
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
//有異常將此任務取消
mCancelled.set(true);
throw tr;
} finally {
//任務結束
postResult(result);
}
return result;
}
};
FutureTask:線程任務(理解爲要執行的任務)
這裏將mWorker任務的參數封裝成FutureTask對象,按照中式英語理解的話就是將來要執行的任務。本質是一個Runnable對象。
mFuture = new FutureTask<Result>(mWorker) {
@Override
/*任務完成後的回調*/
protected void done() {
try {
/*調用下方postResult()方法*/
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) {
/*調用下方postResult()方法*/
postResultIfNotInvoked(null);
}
}
};
當線程任務執行結束後,調用postResult(Result result)通過InternalHandler將線程池(子線程任務)切換到主線程中。
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
getHandler()獲取的就是InternalHandler對象,這是一個靜態內部類。切換線程作用。
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;
}
}
}
exec:線程池執行類
exec是SerialExecutor串行線程池對象,主要作用是將要執行的任務(mFutureTask)通過此類被插入到任務隊列mTasks中,整合任務執行順序作用。會不斷從隊列中取出任務來執行,如果當前任務執行完畢,mTasks會查找隊列頂端是否有mFutureTask對象。有的話就執行。沒有任務結束。
/*源碼中不是這樣寫的,這裏爲了方便理解*/
public static final Executor exec = 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() {
/*隊列最上方的一個元素不爲null即隊列頂端有空閒的任務*/
if ((mActive = mTasks.poll()) != null) {
/*真正執行任務的線程池:THREAD_POOL_EXECUTOR核心類ThreadPoolExecutor對象*/
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
其實真正執行任務的線程池是:ThreadPoolExecutor核心類的對象THREAD_POOL_EXECUTOR。
THREAD_POOL_EXECUTOR.execute(mActive);
目前 AsyncTask在Android3.0以上版本的執行順序是串行的,有個多個任務添加到mTasks隊列中,執行的順序是從隊列頂端往下依次執行。如果想實現多線程的並行任務。只要將
MyAsyncTask("AsyncTask1").execute()
替換爲下述即可:
MyAsyncTask("AsyncTask1").executeOnExecutor()
結尾
Android中多線程任務雖然沒有Java中使用頻率高,但是是一個比較重要的知識點。AsyncTask可以在很多多線程任務中發揮好的作用,比如項目中的在線更新等。線程池也是特別重要的知識點,幾乎是面試必問,接下來將會介紹線程池的工作原理等相關知識。