Android非同步任務處理框架AsyncTask原始碼分析
引言
在平時專案開發中難免會遇到非同步耗時的任務(比如最常見的網路請求)。遇到這種問題,我們可以自己通過Handler+Message+Thread/ThreadPool來構造一個非同步耗時任務框架。當你下次專案中又遇到一個網路請求,你又不得不重寫非同步耗時任務處理框架。出於避免開發者重複搬磚工作,Google工程師給開發者搭建了一個通用的非同步耗時任務處理框架—-AsyncTask。
AsyncTask簡介
我把AsycnTask類稱之為非同步任務處理框架,為什麼這麼說?因為其內部通過Handler+Message+ThreadPool技術構建了一個非同步耗時任務處理框架。所謂框架,無非就是封裝複雜的邏輯操作,留給開發者少數介面或者方法來進行資料操作。AsyncTask類也一樣,目的是讓開發者很方便容易的在UI執行緒中處理一些非同步耗時任務。AsyncTask類中將非同步耗時任務處理放在ThreadPool執行緒池中處理,然後將處理結果通過Handler傳送訊息更新進度和結果,開發者只需要實現和重寫AsyncTask類中的幾個方法即可獲得當前非同步任務處理的進度和結果。
AsyncTask使用
由於AsyncTask是一個抽象類,所以開發者如果想使用AsyncTask類的話必須讓子類去繼承它。子類至少重寫AsyncTask類中的 doInBackground方法,一般我們也會重寫onPostExecute方法去獲取非同步任務處理結果。在使用AsyncTask類時,我們知道需要準備3個泛型引數分別是:
- Params:非同步任務執行時需要的引數型別
- Progress:非同步任務執行過程中進度更新型別
- Result:非同步任務執行結束返回的結果型別
當你在使用AsyncTask無需相應的引數時可以將對應引數設定為 Void型別。
AsyncTask使用的步驟順序可分為如下:
- onPreExecute:該方法由系統在UI執行緒中呼叫,非同步任務執行之前呼叫,做一些準備工作,比如初始化進度條。無需使用者自己去呼叫,使用者只需重寫該方法即可,當然使用者也可以不重寫該方法。
- doInBackground:該方法由系統執行於後臺執行緒中,當onPreExecute方法呼叫之後就呼叫該方法。所有非同步耗時的任務都是在該方法中實現,同樣使用者無需自己去呼叫,使用者只需重寫該方法即可,且必須重寫該方法。
- publishProgress:該方法在doInBackground方法中呼叫,用於釋出當前非同步任務執行的進度到UI執行緒中,該方法需要使用者自己在onInBackground中呼叫。
- onProgressUpdate:該方法由系統在UI執行緒中呼叫,用於更新當前非同步任務執行的進度,進而更新UI操作,該方法也無需使用者自己呼叫,使用者只需重寫該方法即可。想要在UI執行緒中獲得進度資訊的前提是在doInBackground方法中呼叫了publishProgress方法。
- onPostExecute:該方法由系統在UI執行緒中呼叫,用於非同步任務執行完成之後獲取後臺執行緒執行的結果。該方法同樣無需使用者自己呼叫,只需重寫該方法即可。
上面的解釋可能會有點抽象,現在我們拿一個例子來說明AsyncTask的使用。
public class MainActivity extends AppCompatActivity {
private ProgressBar progressBar;
private TextView value;
private TextView result;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
result = (TextView) findViewById(R.id.result);
}
//啟動一個任務
public void startTask(View view) {
String s1 = "task1";
String s2 = "task2";
String s3 = "task3";
String s4 = "task4";
String s5 = "task3";
String s6 = "task3";
String s7 = "task3";
String s8 = "task3";
new DownloadFilesTask().execute(s1, s2, s3, s4, s5, s6, s7, s8);
}
private class DownloadFilesTask extends AsyncTask<String, Integer, Long> {
//該方法執行與後臺執行緒中,所有非同步耗時的任務都在此處操作
@Override
protected Long doInBackground(String... urls) {
int count = urls.length;
long totalSize = 0;
for (int i = 0; i < count; i++) {
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
totalSize += 800;
//釋出進度資訊到UI執行緒中
publishProgress((int) ((i / (float) (count - 1)) * 100));
//為了安全起見,每次迴圈都需檢查當前任務是否被取消,如果被取消這退出迴圈
if (isCancelled()) break;
}
return totalSize;
}
//該方法在後臺任務執行之前執行,在UI執行緒中執行,用於初始化一些資訊
@Override
protected void onPreExecute() {
value = (TextView) findViewById(R.id.progress_value);
progressBar = (ProgressBar) findViewById(R.id.progress);
progressBar.setMax(100);
}
//該方法在UI執行緒中執行,用於獲取後臺任務更新的進度資訊
@Override
protected void onProgressUpdate(Integer... values) {
value.setText(values[0] + "%");
progressBar.setProgress(values[0]);
}
//該方法在UI執行緒中執行,用於獲取後臺任務執行完成之後的結果
@Override
protected void onPostExecute(Long aLong) {
result.setText("the result is" + aLong);
Toast.makeText(MainActivity.this, "the result is " + aLong, Toast.LENGTH_SHORT).show();
}
}
}
以上示例是一個最簡單的模擬非同步任務操作,我們主要的工作就是讓子類DownloadFilesTask繼承AsyncTask,然後重寫相應的方法,其中只要是重寫的方法都無需使用者去控制其呼叫邏輯,只需重寫裡面的方法實現計算邏輯。言外之意就是:AsycnTask類中的那些方法的呼叫順序是不需要使用者去控制的,其內部已經控制好這些方法的呼叫邏輯。
AsyncTask原始碼分析
AsyncTask的構造方法
public abstract class AsyncTask<Params, Progress, Result> {
//獲得當前執行狀態的cup數
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//根據當前機器CUP的個數決定執行緒池中的執行緒個數
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());
}
};
//執行緒池中的快取佇列,此處為128個
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);
/**
* An {@link Executor} that executes tasks one at a time in serial
* order. This serialization is global to a particular process.
*/
//獲得順序執行的執行緒池執行器
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物件
private static final InternalHandler sHandler = new InternalHandler();
//執行緒池中預設的執行器
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
//非同步任務回撥介面
private final WorkerRunnable<Params, Result> mWorker;
private final FutureTask<Result> mFuture;
//當前非同步任務的狀態,初始狀態為“未執行”狀態
private volatile Status mStatus = Status.PENDING;
private final AtomicBoolean mCancelled = new AtomicBoolean();
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
......................
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
*/
//建立一個新的非同步任務,該構造方法必須在UI執行緒中呼叫
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);
}
}
};
}
..................
}
分析:AsyncTask類中的所有成員變數的作用都已經添加註釋,在AsyncTask類的成員變數中根據當前系統CPU的個數來構建一個固定大小的執行緒池THREAD_POOL_EXECUTOR成員變數。然後通SerialExecutor類建立了一個順序執行的執行緒池成員變數SERIAL_EXECUTOR,這裡我們暫且不討論SerialExecutor類的具體實現,只需知道它是順序執行的一個執行緒池執行器就可,感興趣的童鞋可以深究。細心的你會發現AsyncTask的成員變數幾乎都是靜態的,也就是說:一個應用中的記憶體中只儲存有一份這些成員變數的值。
然後在構造方法中獲得了mWorker物件,並且實現了裡面的介面方法call,call方法裡面呼叫了doInBackground方法。當後臺執行緒任務被執行時,該call方法就會被呼叫。並且將mWorker作為引數傳遞給了FutureTask類來獲取mFuture物件。因此在AsyncTask的構造方法中最後獲得mFuture物件。FutureTask類也是繼承自Runnable介面的。到此,我們可以理解成mFuture物件封裝了一個後臺的非同步耗時任務,等待執行緒池執行器去處理該耗時任務。mFuture物件會作為一個執行緒介面在後面使用到。
AsycnTask的執行方法execute
由前面分析我們知道,AsyncTask處理非同步任務的邏輯都在該類的內部實現了,我們只需要重寫相應的方法即可。那麼我們就從execute非同步任務的執行方法開始跟蹤AsyncTask類內部是怎麼處理非同步任務的邏輯的。
/**
* Executes the task with the specified parameters. The task returns
* itself (this) so that the caller can keep a reference to it.
*
* <p>Note: this function schedules the task on a queue for a single background
* thread or pool of threads depending on the platform version. When first
* introduced, AsyncTasks were executed serially on a single background thread.
* Starting with {@link android.os.Build.VERSION_CODES#DONUT}, this was changed
* to a pool of threads allowing multiple tasks to operate in parallel. Starting
* {@link android.os.Build.VERSION_CODES#HONEYCOMB}, tasks are back to being
* executed on a single thread to avoid common application errors caused
* by parallel execution. If you truly want parallel execution, you can use
* the {@link #executeOnExecutor} version of this method
* with {@link #THREAD_POOL_EXECUTOR}; however, see commentary there for warnings
* on its use.
*
* <p>This method must be invoked on the UI thread.
*
* @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);
}
分析:該方法的註釋比方法的實現還多,大概意思是:傳指定引數執行非同步任務,該方法的返回值是AsyncTask物件本身,目的是讓呼叫者持有AsyncTask物件的一個引用。該方法的功能是:不同平臺利用不同方式去處理佇列中的任務,AsyncTask最初設計是單個後臺執行緒去處理佇列中的任務,到了Android1.6版本之後改為固定執行緒數的執行緒池去處理佇列中的任務,在之後到了Android3.0之後,又改回到單個後臺執行緒去處理佇列中的任務,目的是為了解決Android1.6以後如果非同步任務超過138個時AsyncTask會丟擲異常。如果在Android3.0以後你想同時執行多個非同步任務的話你可以使用AsyncTask類提供的executeOnExecutor方法實現。
其實execute方法的實現也僅僅是呼叫了executeOnExecutor方法而已。那麼我們跟蹤程式碼進入executeOnExecutor方法
/** <p>This method must be invoked on the UI thread.
*
* @param exec The executor to use. {@link #THREAD_POOL_EXECUTOR} is available as a
* convenient process-wide thread pool for tasks that are loosely coupled.
* @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 #execute(Object[])
*/
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;
}
分析:註釋解釋,該方必須在UI執行緒中呼叫。第一個引數為執行緒池執行器Executor物件。第二個引數為非同步任務執行所需引數Params。方法的返回值為AsyncTask例項物件。
1.程式碼第16-26行:判斷當前非同步任務狀態,如果不是”PENDING“未執行狀態,則會丟擲相應的異常,如果是”RUNNING“,這丟擲”Cannot execute task: the task is already running.”:當前任務正在執行;如果是”FINISHED”,則丟擲Cannot execute task: the task has already been executed (a task can be executed only once)”:該任務已經被執行。由此可知AsyncTask同一個非同步任務只能被執行一次。
2.程式碼第28行:標記當前非同步任務的狀態為”RUNNING”表示任務正在執行。
3.程式碼第30行:呼叫onPreExecute方法,該方法是個空方法,在子類中可以重寫該方法做一些初始化的工作,比如初始化進度條資訊等。該方法執行與UI執行緒中。
4.程式碼第32-33行:呼叫執行緒池執行器去處理非同步任務mFuture,此處開始執行非同步耗時任務,線上程中操作。由AsyncTask構造方法知道,mFuture是一個非同步任務的封裝。來看看FutureTask類中的run方法吧
...............
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 ||
!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);
}
}
...............
}
分析:在FutureTask的構造方法中我們看到,將AsyncTask構造方法中的mWorker變數賦值給了FutureTask類中的callable變數。我們看到程式碼第24行呼叫了callable的call方法,因此這裡呼叫如下介面方法
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));
}
};
有上面程式碼可以看出,在call方法中doInBackground方法的返回值作為引數傳遞給postResult方法,然後doInBackground是一個抽象方法,真正執行非同步耗時任務的方法,具體實現需要在AsyncTask子類中實現。那麼我們來看看postResult方法做了什麼?
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
該方法先通過getHandler方法獲得一個Handler物件,然後將非同步任務doInBackground方法返回的結果Result封裝成訊息傳送出去,由 從Handler+Message+Looper原始碼帶你分析Android系統的訊息處理機制這篇部落格我們知道,Handler訊息處理機制是誰傳送訊息誰處理訊息。那麼我們來看看getHandler方法吧
private static Handler getHandler() {
synchronized (AsyncTask.class) {
if (sHandler == null) {
sHandler = new InternalHandler();
}
return sHandler;
}
}
加鎖獲得Handler物件,目的是防止多執行緒同時持有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;
}
}
}
有上面程式碼可以看出,InternalHandler是AsyncTask類的內部類,然而InternalHandler是通過Looper.getMainLooper()UI執行緒的Looper作為引數構造的,因此InternalHandler是在UI執行緒中處理訊息的。我們看到,上面有兩條訊息分支。
1.MESSAGE_POST_RESULT:也就是呼叫了postResult方法之後傳送的訊息才走這條分支,將非同步任務處理結果推送到UI執行緒中。此處又呼叫了finish方法,進入看看其實現
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
該方法表示非同步任務處理結束,首先判斷當前非同步任務是否已經被取消,如果被取消則呼叫onCancelled方法,子類中如果想在非同步任務取消時候做一些處理的話可以重寫onCancelled方法。如果沒有取消則呼叫onPostExecute方法,該方法的實現體是一個空,需要在子類中去實現拿到非同步任務和處理結果。最後非同步任務處理結束,將當前非同步任務的狀態置為FINISHED狀態,以防止同一個任務被執行多次。
2.MESSAGE_POST_PROGRESS:該條訊息是用來發布非同步任務進度資訊的。最後會呼叫onProgressUpdate來發布進度資訊到UI執行緒中,其中該方法是一個空的,需要子類去實現獲得當前非同步任務執行進度。那什麼情況下發送了這條訊息呢?我們檢視程式碼發現,只有AsyncTask類中的publishProgress方法裡面傳送了這條資訊。那進入該方法看看
/**
* This method can be invoked from {@link #doInBackground} to
* publish updates on the UI thread while the background computation is
* still running. Each call to this method will trigger the execution of
* {@link #onProgressUpdate} on the UI thread.
*
* {@link #onProgressUpdate} will not be called if the task has been
* canceled.
*
* @param values The progress values to update the UI with.
*
* @see #onProgressUpdate
* @see #doInBackground
*/
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
方法的註釋解釋:該方法需要在doInBackground方法中呼叫,目的是去更新當前非同步任務執行進度,每呼叫一次該方法就會觸發onProgressUpdate方法的呼叫,也就是每呼叫一次publishProgress方法就會發送一個訊息碼MESSAGE_POST_PROGRESS的訊息到InternalHandler類處理。
總結:由此可知,如果你想要在UI執行緒中獲得當前非同步任務執行的進度資訊,就必須在onInBackground方法中呼叫publishProgress方法,否則就獲取不到進度資訊。
cancel取消非同步任務
在網路請求的時候,當你退出當前Activity的時候你可能也想當前Activity啟動的非同步任務AsyncTask也隨之取消,這麼一來就節省了系統資源。在AsyncTask類中提供了cancel方法用來取消當前正在執行的非同步任務。跟蹤原始碼檢視AsyncTask#cancel
/**
* <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);
}
分析:方法的結束比方法實現體長,大概意思就是:呼叫該方法可以取消當前非同步任務,如果當前的非同步任務已經結束的話,返回false,否則返回true。該方法呼叫需要在doInBackground方法之後呼叫,呼叫此方法之後,應該確保不呼叫onPostExecute方法來發布非同步任務結果到UI執行緒中。因此你應該在doInBackground方法中呼叫isCancelled方法去檢查當前任務是否被取消,如果取消則儘早結束當前任務。
有關cancel的引數問題:
- 引數為true:當前正在執行非同步任務的執行緒會立即中斷
- 引數為false:完成當前正在執行的非同步任務之後取消後面其他非同步任務。
最後給出一個AsyncTask內部邏輯流程圖,便於大家直觀的理解哪些方法執行與UI執行緒,哪些方法執行後臺工作執行緒。
AsyncTask陷阱
很多人可能都知道在Android3.0之前的AsyncTask類是有一個缺陷的,也就是當你同時執行139個非同步任務的時候就會出錯了。為什麼會是這樣子呢?所有玄機都在AsyncTask的執行方法execute裡面。我們不妨從Android2.3的AsyncTask原始碼去解答該答案,在該版本中的execute方法如下:
public final AsyncTask<Params, Progress, Result> More ...execute(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;
sExecutor.execute(mFuture);
return this;
}
有上面程式碼可以發現,Android2.3版本的execute方法實現和Android3.0以後的不一樣,也就是上面我們分析的。在2.3版本之前執行緒池執行器是這麼構造的
private static final int CORE_POOL_SIZE = 5;
private static final int MAXIMUM_POOL_SIZE = 128;
private static final int KEEP_ALIVE = 1;
private static final BlockingQueue<Runnable> sWorkQueue =
new LinkedBlockingQueue<Runnable>(10);
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread More ...newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final ThreadPoolExecutor sExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sWorkQueue, sThreadFactory);
主要是構造一個固定執行緒為5,最大執行緒為128,快取對了為10的一個執行緒池執行器。由此可以知道,當有第139個非同步任務執行的時候就超出了最大執行緒數和快取佇列的總和。因此會報錯。那麼Android3.0以後的版本是怎麼解決這個問題的呢?
在Android3.0以後的版本AsyncTask類修改了execute方法的實現
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
該方法又呼叫了executeOnExecutor方法去執行非同步任務,此處使用的執行緒池執行器是sDefaultExecutor預設的執行器,有前面我們分析可得,預設的執行緒池執行器是一個順序執行的,且快取佇列是無限大。也就是多個非同步任務是順序執行的,只有當第一個非同步任務執行完之後才能執行第二個,這麼一來不管你有多少個非同步任務,都不會報錯。那有人可能會問?如果我需要在Android3.0以後的版本上同時執行多個非同步任務怎麼辦?哈哈!騷年,不用擔心!Google Android工程師早已給你想好了。在Anddroid3.0以後的AsyncTask類給暴露出一個介面也就是上面的executeOnExecutor方法啦,我們只需要重新構造一個執行緒池執行器,比如說你可以呼叫newCachedThreadPool方法來建立一個無線大的快取執行緒池,可以同時執行無線個任務。
//構建一個快取執行緒池,用於同時執行無限多個非同步耗時任務
ExecutorService executorService = Executors.newCachedThreadPool();
asyncTask.executeOnExecutor(executorService,params);
開發者可以根據專案需求選擇自己合適的執行緒池執行器:
- Single Thread Executor : 只有一個執行緒的執行緒池,因此所有提交的任務是順序執行,程式碼: Executors.newSingleThreadExecutor()
- Cached Thread Pool : 執行緒池裡有很多執行緒需要同時執行,老的可用執行緒將被新的任務觸發重新執行,如果執行緒超過60秒內沒執行,那麼將被終止並從池中刪除,程式碼:Executors.newCachedThreadPool()
- Fixed Thread Pool : 擁有固定執行緒數的執行緒池,如果沒有任務執行,那麼執行緒會一直等待,程式碼: Executors.newFixedThreadPool()
- Scheduled Thread Pool : 用來排程即將執行的任務的執行緒池,程式碼:Executors.newScheduledThreadPool()
- Single Thread Scheduled Pool : 只有一個執行緒,用來排程執行將來的任務,程式碼:Executors.newSingleThreadScheduledExecutor()
總結: 在Android3.0之前的AsyncTask是有缺陷的,因為其內部使用了固定執行緒數和快取大小的執行緒池來執行非同步耗時任務,所以當同時有超過139個非同步耗時任務時,AsyncTask就會報錯。 然而在Android3.0以後的AsyncTask是沒有缺陷的,因為其背部使用了一個順序執行的執行緒池來執行非同步耗時任務,不論有多少個非同步任務每次都只能執行一個,所以不會報錯。且Android3.0之後的AsyncTask提供了自定義執行緒池的方法,更加方便靈活的讓開發者根據自己所需來選擇不同的執行緒池執行器來處理耗時任務。
值得注意的是:如果你要相容Android2.3以及3.0使用AsyncTask同時處理139個非同步耗時任務是不可能的,這個時候你只能自己利用 Handler+Message+ThreadPool+Exexutor自己構建一個非同步任務處理框架了。考驗你技術的時候到啦!其實你可以仿著3.0的AsyncTask寫一個就好了。
AsyncTask總結
1.AsyncTask類只能在UI執行緒中使用,為什麼?可以看 AsycnTask的執行方法execute小節,因為裡面的Handler是有MainLooper構造的。
2.初學者可能會有這個疑問?一個App應用中有很多個非同步耗時任務,每個任務都去建立一個AsyncTask物件豈不是很耗費資源?如果這麼想你就還不理解AsyncTask了。從AsyncTask的構造方法小節可以看到,裡面的絕大多數成員變數都是靜態static的。包括Executor執行器。因此整個應用都是共享且只有一個AsyncTask例項的。
3.關於AsyncTask類中的方法呼叫邏輯無需用開發者去關注,開發者只需要重寫相應的方法即可。當然如果你想在UI執行緒中獲得進度資訊,你就必須在doInBackground方法中呼叫publishProgress方法才可以觸發UI執行緒中的onProgressUpdate方法來更新進度資訊。
4.值得一提的是:在你重寫doInBackground方法執行非同步耗時任務時,你最好在適當的地方呼叫
if (isCancelled()){
//做一些任務取消的處理
}
此方法呼叫的目的是檢測在執行非同步耗時任務時該任務是否已經被取消了?如果被取消了,當前正在執行的非同步任務就沒有必要繼續執行了,節省資源。比如部落格開頭的例子:非同步任務是一個迴圈任務,如果在執行到第2個迴圈的時候非同步任務被取消了,此時就跳出迴圈結束此次的非同步任務。
5.在Android3.0之前如果你想要同時執行超過139個非同步任務的時候,AsyncTask是會報錯的,這時候你就不得不自己去重新構造一個多執行緒非同步任務處理框架了,就不能直接使用AsyncTask框架了。但是在Android3.0之後改善了這個問題,3.0之後預設是順序執行非同步任務,無論你有多少個非同步任務都不會報錯,你也可以自定義一個符合需求的執行緒池執行器。