對于中小專案,會經常使用的AsyncTask,并且其中包含了執行緒池機制,Handler機制,任務排隊等,設計確實比較巧妙,今天我們來進行一篇原始碼學習,這里沒有太多的個人觀點,主要針對原始碼進行說明,,,
其中會涉及到Handler相關的知識,如果對Handler了解的話應該能很容易理解,不了解的話可以讀下我之前的這篇文章:很容易理解的Android訊息機制分析,
AsyncTask入門簡介:
AsyncTask介紹
AsyncTask是一種輕量級的異步任務類,它可以執行后臺任務并把任務執行的進度和結果傳遞給主執行緒進行ui的更新,AsyncTask是一個抽象的范型類,包含Params, Progress、Result三個范型引數,其中Params表示引數型別,Progress表示后臺任務的執行進度的型別,而Result表示任務的回傳結果的型別,如下:
@MainThread
public abstract class AsyncTask<Params, Progress, Result> {
...
}
AsyncTask抽象類的方法:
-
protected void onPreExecute() { }該方法運行在主執行緒,一半用于進行一些非耗時的初始化操作,
-
@WorkerThread protected abstract Result doInBackground(Params... params);該方法在執行緒池中執行,用于執行異步任務,方法中的引數型別對應范型引數中的Params,方法的回傳值型別對應范型引數中的Result,該方法運行在執行緒池中,用于執行耗時任務,在該方法中呼叫publishProgress,就會在主執行緒中收到onProgress的呼叫,可以進行任務進度的更新,
-
@MainThread protected void onProgressUpdate(Progress... values) { }該方法在主執行緒中執行,如doInBackground方法中描述那樣,通過方法呼叫除法該方法,
-
@MainThread protected void onPostExecute(Result result) { }在主執行緒中執行,在異步任務執行完成后執行該方法,其中的型別Result對應范型引數中的result,值是方法doInBackground的回傳值,
AsyncTask的使用
再多的廢話不如代碼來的即直接,下邊通過一個簡單的小例子來說明一下AsyncTask的使用,簡易代碼,只是表達下大致使用程序,如果想直接使用執行緒的代碼可以百度,,,谷歌,,,
public class MyDownloadTask extends AsyncTask<String, Integer, Integer>{
public static final int STATUS_SUCCESS = 0;
public static final int STATUS_PAUSE = 1;
...
...
@override
protected void onPreExecute(){
//進行一些初始化操作
OKHttpClient client = new OKHttpClicent();
...
}
@override
protected Integer doInBackground(String... params){
String downloadUrl = params[0];
Request request = new Request.Builder()
.url(downloadUrl);
Response response = client.newCall(request).execute();
InputSream is = null;
try{
is = response.body().byteStream();
....此處省略很多代碼
//從stream中讀取資料,累加長度,計算progress,具體代碼省略
publishProgress(progress);
}cache(Exception e){
}
return STATUS_SUCCESS;
}
@override
protected void onProgress(Integer... values){
//更新進度回呼給下載發起者
listener.onProgress(values[0]);
}
@override
protected void onPostExecute(Integer status){
if(status == STATUS_SUCCESS){
listener.onSuccess();
} else if(status == STATUS_PAUSE){
//回呼暫停
}
回呼失敗等其他狀態,,,,
....
}
}
呼叫AsyncTask
String downloadUrl = "...";
MyDownloadTask task = new MyDownloadtask();
task.execute(downloadUrl);
AsyncTask的原理
只會用還是不夠的,作為一個有追求的碼農,,,我們還是要看下其中的原理,防止別人問起的時候出現一問三不知的尷尬,,,相信不少小伙伴一提到原始碼二字就秒慫(比如我),但是既然要學習,還是要硬著頭皮上,,,,話不多少,開始!!
execute方法做了什么
通過上邊對AsyncTask的使用的講解,可以看到在呼叫AsyncTask的時候我們直接使用的方法就是它的execute方法,
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//1. 這里是一個執行緒池型別的成員變數
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
@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;
//2. 執行onPreExecute方法
onPreExecute();
//3. 處理引數
mWorker.mParams = params;
//4. 呼叫道這個方法,exec是一個執行緒池,對應的類是mFuture是一個包裝的Runnable.
exec.execute(mFuture);
return this;
}
我們可以看到execute方法呼叫了executeOnExecutor方法,第一個引數是一個成員變數,該成員變數就是一個實體化的執行緒池,該執行緒池的execute會在注釋4處被呼叫,在4之前,我們可以看到在2處直接呼叫AsyncTask的onPreExecute方法,因此該方法是在主執行緒直接執行的,接下來我們看一下注釋4處對應的SerialExecutor對應的execute方法,
//這里從類名也反應了這個類的作用,Serial 主要用于排序
private static class SerialExecutor implements Executor {
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
//1. SerialExecutor的execute方法的主要作用是對傳進來的Runnable進行一個排序,塞到佇列
mTasks中
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
//2.在并發執行緒池中執行run方法
public void run() {
try {
//3.執行原始的任務的run方法
r.run();
} finally {
//4.并發執行緒池執行一個任務后才進行下一個任務的獲取
scheduleNext();
}
}
});
//5. 如果佇列為空,則執行scheduleNext方法
if (mActive == null) {
scheduleNext();
}
}
//6. 該方法的作用是從佇列中獲取一個Runnable交給執行緒池執行
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
從上述代碼可以看出,呼叫執行緒SerialExecutor的executor方法,主要是對任務Runnable進行一次排隊,把任務放到mTasks佇列中,然后判斷mActive是否為null,如果為null說明AysncTask執行緒池還沒有激活,則呼叫scheduleNext方法開始執行,我們可以從注釋6處看到,scheduleNext方法從佇列中獲取一個任務,交給一個叫做“TREAD_POOL_EXECUTOR”的并發執行緒池進行執行,后續程序中,如果又呼叫了一個AsyncTask的execute,則會把Runnable繼續加入到mTasks中,但是因為mActivit != null,所以不會在執行scheduleNext操作,那么新加入的執行緒是怎么執行的呢?剛才已經提到佇列中的一個任務已經交給了并發執行緒池,該執行緒池會執行任務的run方法,對應的就是注釋2處的run方法,這個run方法中,首先執行了原始任務的run方法,執行結束后通過scheduleNext方法再取出一條任務,交給并發執行執行緒執行,看到這里,相信大家應該明白過來了,這里的TREAD_POOL_EXECUTOR雖然是一個并發執行執行緒,但是它內部是完成一個任務的執行后,再通過scheduleNext方法讓AsyncTask送進來下一條任務,因此實作了任務的排隊串醒執行,
如果你能看到這里,恭喜,你已經了解了執行緒池內部串行執行的程序,不過你是否有一點疑問?為什么你創建的不同的AysncTask實體執行的任務都排到了一個佇列中?
答案很簡單:因為這里的SerialExecutor和TREAD_POOL_EXECUTOR對應的實體都是static型別的哇,所以不管你創建了幾個AsyncTask實體,在方法去中都只有一份對應的實體,所以也只有一份mTAsks任務佇列,
doInBackground執行探究
到此,我們直到我們的任務會按照排隊順序在執行緒池進行執行,那執行的到底是什么呢?我們接著往下走,
@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();
//1. 處理引數
mWorker.mParams = params;
//2. 送給佇列
exec.execute(mFuture);
return this;
}
//3. 任務
private final FutureTask<Result> mFuture;
沒錯,我們又回到了這段代碼,先看注釋1處,這里是把我們呼叫時傳入的引數賦值給mWork的mParams變數,先記住有這個東西,接著往下看,看完前邊的分析,你肯定已經知道這個的作用,這里的mFuture就是送給任務佇列的Runnable,并發執行緒池真正執行的run方法對應的原始任務就是這個mFuture,我們看下這個mFuture到底是什么東西,這里我把mFuture的宣告粘貼到了注釋3處,可以看到對應的類是FutureTask,定義如下:
public class FutureTask<V> implements RunnableFuture<V> {
//....省略大部分代碼,只看關鍵方法
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
//完結傳入的一個回呼
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public void run() {
if (state != NEW ||
!U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
return;
try {
//2. 把回呼賦值給c
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
//3.呼叫構造時傳入的callback的call方法
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);
}
}
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
我們可以看到,FutrureTask本質上就是一個Runnable,在run方法中回呼了構造Future傳入的callback,我們看看對Future的構造,位置在AsyncTask中:
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 occurred while executing doInBackground()",
e.getCause());
} catch (CancellationException e) {
postResultIfNotInvoked(null);
}
}
};
我們可以看到傳入future的是mWork這個變數,這個mWork的call方法做了什么呢?繼續往下走,,,
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
//1. 看這里看這里
result = doInBackground(mParams);
Binder.flushPendingCommands();
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
postResult(result);
}
return result;
}
};
我們可以看到,在注釋1處呼叫了AysncTak的doInBackground方法,
看到這里我們先捋一下,,,,
并發執行緒池從佇列中獲取任務---->呼叫任務的run---->呼叫work的call---->呼叫AsyncTask的doInBackgroud
看到這里你應該知道了,AsyncTask的doInBackground在子執行緒中執行,其實是因為被送到了執行緒池中執行的,
分發訊息的Handler
接下來我們看下進度更新的相關方法,在此之前,我們先看下AsyncTask中的handler成員
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
private static Handler getMainHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
//1.使用主執行緒中的Looper創建Handler,會在主執行緒中處理訊息
sHandler = new InternalHandler(Looper.getMainLooper());
}
return sHandler;
}
}
正常情況下,我們在主執行緒呼叫AsyncTask無需給構造方法傳入looper,對應的就是這里callbackLooper == null的情況,這種情況下,我們可以看到,注釋1處生成了一個使用主執行緒訊息回圈的Handler,
更新進度在這里
在前邊已經講解,我們呼叫了publishProgress會觸發onProgressUpdate,我們看下publishProgress,
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
從上述代碼中我們可以看到,通過handler把訊息拋了出去,因為handler是主執行緒中的handler,因此訊息的執行是在主執行緒中執行的,我們看下對訊息MESSAGE_POST_PROGRESS的處理,
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;
//1. 處理進度
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
我們可以看到,在注釋1處處理了進度,回呼了onProgress方法,這里的result的task對應的就是AsyncTask,不需要去深究代碼細節,
執行結束還是靠Handler
接下來,我們來看最后一個重要方法,onPostExecute,
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;
}
依然是前邊的mWorker成員變數的call方法,我們可以看到執行完doInbackgound后,在finally中執行了一個postResult方法,我們已經知道call呼叫是在執行緒池中發生的,那么接下來我們看postResult方法:
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
通過該方法的實作我們可以看到,還是通過AsyncTask的handler把訊息MESSAGE_POST_RESULT發送到了主執行緒處理,我們再來回顧下handler中的handleMessage方法,如下:
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
//1.處理結果message
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
//2.處理進度message
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
其中,注釋2處我們在前邊講解處理進度的時候已經說到了,現在的處理結果的訊息對應代碼注釋1,我們可以看到這里呼叫了AsyncTask的finish方法,finish方法是什么東西呢?我們再來挖掘一層,,,
private void finish(Result result) {
if (isCancelled()) {
//1.取消
onCancelled(result);
} else {
//2.執行完成
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
我們可以看到finish方法中,注釋1呼叫了取消后的邏輯,如果沒有取消,會正常回呼onPostExecute方法,此時是在Handler對應的訊息佇列中處理的,該Handler對應的Looper是MainLoooper,因此,該方法執行是在主執行緒執行的,
兩點補充:先上代碼:
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
//1.例外處理
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;
}
補充1:通過以上分析我們已經知道,呼叫AsyncTask的execute方法,任務進行串行執行(該機制是從3.0開始,3.0之前主要是沒有排隊的程序,因此是并行執行),那么現在我們能不能做到并行執行呢?答案是肯定的,我們只需要跳過排隊的程序,直接呼叫上邊代碼中的executeOnExecutor方法,該方法是直接在執行緒池上執行任務,
補充2:在呼叫AsyncTask的時候,如果兩次呼叫一個AsyncTask執行任務會根據當前任務的狀態拋出例外,如下:
//該代碼是錯誤示范,會導致例外
AsyncTask task = new MyAsyncTask();
task.execute("http://******.mp3");
//再次呼叫同一個task的execute
task.execute("http://*****.mp4");
該例外出現的原因可以在上邊executeOnExecutor方法中看到,呼叫同一個任務的execute會走到例外模塊,根據任務狀態拋出對應的例外,
總結:對于AsyncTask的知識點重點有一下兩點:
1、使用了SerialExecutor進行任務的排隊,并通過scheduleNext集合try finally保證了訊息串行執行,
2、使用Handler進行作業執行緒和主執行緒的切換,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/41754.html
標籤:其他
下一篇:TYC OneWeek
