廢話少說,今天來分析AsyncTask的源碼,看了網上很多人都說使用AsyncTask有最大線程的約束,但是呢,自己寫了一個測試代碼,發現同時扔進去一萬多個線程也沒有問題啊,於是對自己充滿了否定,是不是我用錯了?然後給同事打電話一問,原來就是沒有那個問題了,我去,坑了半重點內容天原來網上分析的都有問題(應該是源碼更新的緣故),還是擼開袖子自己分析源碼吧!
第一.怎麼使用的
首先我們先看看谷歌給的例子,很簡單(源碼中就有)**
private class DownloadFilesTask extends AsyncTask<URL, Integer, Long>{
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;
}
protected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);
}
protected void onPostExecute(Long result) {
showDialog("Downloaded " + result + " bytes");
}
}
使用:new DownloadFilesTask().execute(url1, url2, url3);
第二.使用很簡單,看看源碼吧
首先我們先分析一下都有什麼屬性,以及作用:
1)第一個線程池構造
//獲取cup數目
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());
}
};
//線程隊列,應該是大家看到這裏就都認爲是最多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);
2)第二個線程池登場了:
/**
* 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 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) {
//這個方法是說把一個runnable對象添加到隊列的最後面
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
//第一次執行肯定是空了,以後就不會執行了,而是在上面的finally中執行
if (mActive == null) {
scheduleNext();
}
}
//操作執行下一個
protected synchronized void scheduleNext() {
//這個方法是說獲取隊列中最開始的runnable
if ((mActive = mTasks.poll()) != null) {
//重量級登場了,另一個線程池執行任務去了,所以真正執行任務是這個另一個線程池
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
3)兩個線程池分析完了,再看看handler
//handler,不用多說吧
private static InternalHandler sHandler;
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:
// 處理完的結果
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
//發佈進度
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
@SuppressWarnings({"RawUseOfParameterizedType"})
private static class AsyncTaskResult<Data> {
//這裏就是下面結束任務是調用finish的mTask
final AsyncTask mTask;
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
//這裏有個判斷,其實就再說明一件事情,就是我們設置取消,不一定會馬上取消(線程就這麼設計的),所以還需要標誌來標識是否取消了
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
4)下面就開始我們從構造器開始看吧:
//當前的工作
private final WorkerRunnable<Params, Result> mWorker;
//對任務的二次封裝
private final FutureTask<Result> mFuture;
/**
* Creates a new asynchronous task. This constructor must be invoked on the UI thread.
* 這裏說創建一個AsyncTask必須要在主線程中,對嗎?分線程不可以嗎?經過本人測試是可以的其實,至於爲
* 什麼可以,我查閱了一下網上說的爲什麼必須要在主線程,他們說其實主要是因爲handler必須在主線程初始
* 化,但是我們返回去看看這裏handler是怎麼創建的,
* public InternalHandler() {
* super(Looper.getMainLooper());
* }
* 我們可以看到,這裏本來就是得到的主線程的Looper,也就是說不論你在哪個線程創建都會在主線程處理結果
* 至於爲什麼,那就要分析handler源碼了
*/
public AsyncTask() {
//這就是一個具體的工作,傳進去參數,返回來結果
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
return postResult(result);
}
};
//對工作進行再次封裝
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);
}
}
};
}
我們創建一個AsyncTask,構造器創建了一個要執行的任務,下面就開始分析execute方法:
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
return executeOnExecutor(sDefaultExecutor, params);
}
//這裏說明一下:雖然註解註明必須要在主線程中執行,但是經過測試在子線程也可以的,不過onPeExecute
//方法就不是在主線程中執行了,而是在當前線程執行,onPostExecute方法仍然在主線程
@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 = params;
//放進線程池開始執行,注意這裏放進的是第二個線程池
exec.execute(mFuture);
return this;
}
好了,任務放到第二個線程池裏了,然後我們可以看看上面第二個線程池幹了什麼工作?將任務放到隊列裏最後面
,然後開始從頭取出任務交給第一個線程池去工作,是不是順起來了。那麼第一個線程池執行的Runnable又是什
麼呢?可以看到其實就是我們構造器中創建的那個mWorker任務,然後再去構造器中看看WorkerRunnable
裏面做了什麼:
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
mTaskInvoked.set(true);
//設置優先級
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//開始執行這個方法,具體什麼操作交由我們自己來實現
Result result = doInBackground(mParams);
Binder.flushPendingCommands();
//執行完了還是向主線程發送結果
return postResult(result);
}
};
//向主線程發送結果
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
然後又回到上面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:
// 到這裏處理完成的結果,result.mTask其實就是當前AsyncTask,看上面
result.mTask.finish(result.mData[0]);
break;
case MESSAGE_POST_PROGRESS:
result.mTask.onProgressUpdate(result.mData);
break;
}
}
}
繼續:
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
//執行結束
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
好了,至此整個執行過程全部完整分析結束了。下面開始說說都有什麼坑:
第三:使用中的一些坑
1)就是面試的時候經常會問什麼128個限制啊什麼的,經過分析默認情況下使用是沒有這個限制的,記住是默認,
但是下面這種用法就會有:
mMyAsy.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
其實這就是說我們自己傳入了一個線程池,替代了默認的那個線程池,爲什麼會有限制呢?源碼分析分析:
@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 = params;
//上面都沒變化,但是這裏我們直接使用我們傳進來的線程池去執行了,而相對於默認情況我們跳過了隊列
//這當然就會有線程池的最大線程數限制了
exec.execute(mFuture);
return this;
}
2)其實這個坑也不能說是AsyncTask中的坑,就是線程同步問題。我們做相機開發的時候由於bitmap佔用大量內
存,所以時刻需要主動去回收圖片,但是處理圖片過程還必須放在異步中執行,所以就會出現一個線程已經回收
了圖片了,另一個還在使用中...掛了。解決方法,個人是說兩個線程各持有一把鎖,如果你想要回收圖片,就
去檢查對方是否釋放了鎖(也就是處理完了),如果處理完了,則可以回收銷燬,否則我們就把我們自己所持有的
鎖釋放掉,這樣另一個線程執行完了檢查我們的鎖之後就可以回收了。
3)源碼分析的時候說了,AsyncTask不是只能在主線程中使用,分線程也可以,但是onPreExecute()方法會
在同一個線程中執行,只有onPostExecute方法會在主線程中執行,原因就是
private static class InternalHandler extends Handler {
public InternalHandler() {
//它默認就去調用主線程的Looper了,所以肯定會到主線程執行最後的結果
super(Looper.getMainLooper());
}
//省略...
}
不過好像看谷歌的英文註釋,確實是應該在主線程中初始化纔可以,不過谷歌本來就是用來否定的...
4)就是我們調用cancle()方法不一定會立即執行取消操作,因爲這是線程的一個機制,所以會造成什麼結果呢,
就是加入說我打開了一個頁面,啓動asynctask聯網,這時候呢,我手機屏幕旋轉了,頁面銷燬掉重新創建了
然後再一次聯網去了,按說頁面銷燬的時候我們會執行取消操作,但是SB不一定馬上執行啊,結果就是第一次聯
網成功了刷新頁面,頁面早就已經重建了,肯定就Crash了。