摘要:異步任務的構造方法主要用于初始化線程池先關的成員變量創建一個新的異步任務。所以,我們是必須確保在銷毀活動之前取消任務。
目錄介紹
01.先看下AsyncTask用法
02.AsyncTask源碼深入分析
2.1 構造方法源碼分析
2.2 看execute(Params... params)方法
2.3 mWorker和mFuture的創建過程
03.異步機制的實現
04.不同的SDK版本區別
05.AsyncTask的缺陷和問題
5.1 AsyncTask對應線程池
5.2 AsyncTask生命周期問題
5.3 AsyncTask內存泄漏問題
5.4 AsyncTask結果丟失問題
5.5 AsyncTask并行還是串行問題
好消息博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計47篇[近20萬字],轉載請注明出處,謝謝!
鏈接地址:https://github.com/yangchong2...
如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!
問題答疑AsyncTask是如何實現異步機制的,底層原理是什么?
AsyncTask調用execute方法時,如果不是運行在主線程中會出現什么情況,如何解決?
為什么異步任務對象不能執行多次,即不能創建一個對象執行多次execute方法?
doInBackground這個方法可以做什么操作?它是在主線程中還是工作線程中?為什么?
AsyncTask任務是否可以被中途取消?為什么?
AsyncTask對應線程池是如何操作的?它有什么弊端,為什么現在幾乎很少用呢?
AsyncTask的執行策略是并行還是串行的?
帶著問題去看這篇文章,相信看完之后你對異常AsyncTask有了初步理解……
01.先看下AsyncTask用法
來看一下AsyncTask的基本使用,代碼如下所示
定義了自己的MyAsyncTask并繼承自AsyncTask;并重寫了其中的是哪個回調方法:onPreExecute(),onPostExecute(),doInBackground();
class MyAsyncTask extends AsyncTask{ @Override protected void onPreExecute() { super.onPreExecute(); Log.i(TAG, "onPreExecute...(開始執行后臺任務之前)"); } @Override protected void onPostExecute(Integer i) { super.onPostExecute(i); Log.i("TAG", "onPostExecute...(開始執行后臺任務之后)"); } @Override protected Integer doInBackground(Integer... params) { Log.i(TAG, "doInBackground...(開始執行后臺任務)"); return 0; } }
開始調用異步任務
new MyAsyncTask().execute();02.AsyncTask源碼深入分析 2.1 構造方法源碼分析
源代碼如下所示,主要是看AsyncTask(@Nullable Looper callbackLooper)中的代碼
這里面只是初始化了兩個成員變量:mWorker和mFuture他們分別是:WorkerRunnable和FutureTask,對于熟悉java的逗比應該知道這兩個類其實是java里面線程池先關的概念。
異步任務的構造方法主要用于初始化線程池先關的成員變量
//創建一個新的異步任務。必須在UI線程上調用此構造函數 public AsyncTask() { this((Looper) null); } //創建一個新的異步任務。必須在UI線程上調用此構造函數 public AsyncTask(@Nullable Handler handler) { this(handler != null ? handler.getLooper() : null); } public AsyncTask(@Nullable Looper callbackLooper) { mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); mWorker = new WorkerRunnable2.2 看execute(Params... params)方法() { 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; } }; mFuture = new FutureTask (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); } } }; }
看一下execute方法
發現該方法中添加一個@MainThread的注解,通過該注解,可以知道我們在執行AsyncTask的execute方法時,只能在主線程中執行
@MainThread public final AsyncTaskexecute(Params... params) { return executeOnExecutor(sDefaultExecutor, params); }
如果execute方法不是運行在主線程中會出現什么情況呢?
執行,但是并沒有什么區別,程序還是可以正常執行。但是onPreExecute方法是與開始執行的execute方法是在同一個線程中的,所以如果在子線程中執行execute方法,一定要確保onPreExecute方法不執行刷新UI的方法,否則將會拋出異常。
new Thread(new Runnable() { @Override public void run() { Log.i("tag", Thread.currentThread().getId() + ""); new MAsyncTask().execute(); } }).start(); Log.i("tag", "mainThread:" + Thread.currentThread().getId() + ""); @Override protected void onPreExecute() { super.onPreExecute(); //更新UI title.setText("瀟湘劍雨"); Log.i(TAG, "onPreExecute...(開始執行后臺任務之前)"); }
異常如下所示,在子線程中執行execute方法,那么這時候如果在onPreExecute方法中刷新UI,會報錯,即子線程中不能更新UI。
Process: com.example.aaron.helloworld, PID: 659 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
接著看看executeOnExecutor這個方法源碼
具體的內部實現方法里:首先判斷當前異步任務的狀態,其內部保存異步任務狀態的成員變量mStatus的默認值為Status.PENDING,所以第一次執行的時候并不拋出這兩個異常,那么什么時候回進入這個if判斷并拋出異常呢,通過查看源代碼可以知道,當我們執行了execute方法之后,如果再次執行就會進入這里的if條件判斷并拋出異常
在executeOnExecutor中若沒有進入異常分之,則將當前異步任務的狀態更改為Running,然后回調onPreExecute()方法,這里可以查看一下onPreExecute方法其實是一個空方法,主要就是為了用于我們的回調實現,同時這里也說明了onPreExecute()方法是與execute方法的執行在同一線程中。
然后將execute方法的參數賦值給mWorker對象那個,最后執行exec.execute(mFuture)方法,并返回自身。
模擬測試一下拋出異常的操作
看到我們定義了一個AsyncTask的對象,并且每次執行點擊事件的回調方法都會執行execute方法,當我們點擊第一次的時候程序正常執行,但是當我們執行第二次的時候,程序就崩潰了。
final MyAsyncTask mAsyncTask = new MyAsyncTask(); title.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { Log.i("tag", Thread.currentThread().getId() + ""); mAsyncTask.execute(); } }).start(); Log.i("tag", "mainThread:" + Thread.currentThread().getId() + ""); } });
若這時候第一次執行的異步任務尚未執行完成則會拋出異常:
Cannot execute task:the task is already running.
若第一次執行的異步任務已經執行完成,則會拋出異常:
Cannot execute task:the task has already been executed (a task can be executed only once)
然后看一下exec.execute(mFuture)的實現
這里的exec其實是AsyncTask定義的一個默認的Executor對象:
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
那么,SERIAL_EXECUTOR又是什么東西呢?
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
繼續查看SerialExecutor的具體實現:
private static class SerialExecutor implements Executor { final ArrayDequemTasks = new ArrayDeque (); 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() { if ((mActive = mTasks.poll()) != null) { THREAD_POOL_EXECUTOR.execute(mActive); } } }
可以發現其繼承Executor類其內部保存著一個Runnable列表,即任務列表,在剛剛的execute方法中執行的exec.execute(mFuture)方法就是執行的這里的execute方法。
這里具體看一下execute方法的實現:
1)首先調用的是mTasks的offer方法,即將異步任務保存至任務列表的隊尾
2)判斷mActive對象是不是等于null,第一次運行是null,然后調用scheduleNext()方法
3)在scheduleNext()這個方法中會從隊列的頭部取值,并賦值給mActive對象,然后調用THREAD_POOL_EXECUTOR去執行取出的取出的Runnable對象。
4)在這之后如果再有新的任務被執行時就等待上一個任務執行完畢后才會得到執行,所以說同一時刻只會有一個線程正在執行。
5)這里的THREAD_POOL_EXECUTOR其實是一個線程池對象。
2.3 構造方法中mWorker和mFuture的創建過程
看一下執行過程中mWorker的執行邏輯:
可以看到在執行線程池的任務時,我們回調了doInBackground方法,這也就是我們重寫AsyncTask時重寫doInBackground方法是后臺線程的原因。
mWorker = new WorkerRunnable() { 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的執行邏輯
mFuture = new FutureTask(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); } } };
這里具體看一下postResultIfNotInvoked方法:
private void postResultIfNotInvoked(Result result) { final boolean wasTaskInvoked = mTaskInvoked.get(); if (!wasTaskInvoked) { postResult(result); } }
其內部還是調用了postResult方法:
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; }
這里可以看到起調用了內部的Handler對象的sendToTarget方法,發送異步消息
03.異步機制的實現
看AsyncTask內部定義了一個Handler對象
內部的handleMessage方法,有兩個處理邏輯,分別是:更新進入條和執行完成,這里的更新進度的方法就是我們重寫AsyncTask方法時重寫的更新進度的方法,這里的異步任務完成的消息會調用finish方法
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; } } }
然后看看調用finish方法做了什么
首先會判斷當前任務是否被取消,若被取消的話則直接執行取消的方法,否則執行onPostExecute方法,也就是我們重寫AsyncTask時需要重寫的異步任務完成時回調的方法。
private void finish(Result result) { if (isCancelled()) { onCancelled(result); } else { onPostExecute(result); } mStatus = Status.FINISHED; }
既然有處理消息的,那么肯定有發送消息的。
可以從構造方法中看到,當通過執行doInBackground方法拿到結果后,最后在finally執行發送該消息邏輯
private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult(this, result)); message.sendToTarget(); return result; }
可以看到MESSAGE_POST_PROGRESS這個消息發送是處理進度,需要在工作線程中
@Override protected ReusableBitmap doInBackground(Void... params) { // enqueue the "onDecodeBegin" signal on the main thread publishProgress(); return decode(); } @WorkerThread protected final void publishProgress(Progress... values) { if (!isCancelled()) { getHandler().obtainMessage(MESSAGE_POST_PROGRESS, new AsyncTaskResult04.不同的SDK版本區別
調用AsyncTask的execute方法不能立即執行程序的原因析及改善方案通過查閱官方文檔發現,AsyncTask首次引入時,異步任務是在一個獨立的線程中順序的執行,也就是說一次只執行一個任務,不能并行的執行,從1.6開始,AsyncTask引入了線程池,支持同時執行5個異步任務,也就是說時只能有5個線程運行,超過的線程只能等待,等待前的線程某個執行完了才被調度和運行。換句話說,如果個進程中的AsyncTask實例個數超過5個,那么假如前5都運行很長時間的話,那么第6個只能等待機會了。這是AsyncTask的一個限制,而且對于2.3以前的版本無法解決。如果你的應用需要大量的后臺線程去執行任務,那么只能放棄使用AsyncTask,自己創建線程池來管理Thread。不得不說,雖然AsyncTask較Thread使用起來方便,但是它最多只能同時運行5個線程,這也大大局限了它的作用,你必須要小心設計你的應用,錯開使用AsyncTask時間,盡力做到分時,或者保證數量不會大于5個,否就會遇到上次提到的問題。可能是Google意識到了AsynTask的局限性了,從Android3.0開始對AsyncTask的API做出了一些調整:每次只啟動一個線程執行一個任務,完了之后再執行第二個任務,也就是相當于只有一個后臺線在執行所提交的任務。
05.AsyncTask的缺陷和問題 5.1 AsyncTask對應線程池Asynctask對應的線程池ThreadPoolExecutr都是進程范圍內共享的,都是static的,所以是Asynctask控制著進程范圍內所有的子類實例。由于這個限制的存在,當使用默認線程池時,如果線程數超過線程池的最大容量,線程池就會爆掉(3.0后默認串行執行,不會出現個問題)。針對這種情況,可以嘗試自定義線程池,配合Asynctask使用。
關于默認線程池:
AsyncTask里面線程池是一個核心線程數為CPU + 1,最大線程數為CPU * 2 + 1,工作隊列長度為128的線程池,線程等待隊列的最大等待數為28,但是可以自定義線程池。線程池是由AsyncTask來處理的,線程池允許tasks并行運行,需要注意的是并發情況下數據的一致性問題,新數據可能會被老數據覆蓋掉類似volatile變量。所以希望tasks能夠串行運行的話,使用SERIAL_EXECUTOR。
5.2 AsyncTask生命周期問題很多開發者會認為一個在Activity中創建的AsyncTask隨著Activity的銷毀而銷毀。然而事實并非如此。AsynTask會一直執行,直到doInBackground()方法執行完畢,然后,如果cancel(boolean)被調用,那么onCancelled(Result result)方法會被執行;否則,執行onPostExecuteResult result)方法。如果我們的Activity銷毀之前,沒有取消AsyncTask,這有可能讓我們的應用崩潰(crash)。因為它想要處理的view已經不存在了。所以,我們是必須確保在銷毀活動之前取消任務。總之,我們使用AsyncTask需要確保AsyncTask正確的取消。
5.3 AsyncTask內存泄漏問題如果AsyncTask被聲明為Activity的非靜態的內部類,那么AsyncTask會保留一個對Activity的引用。如果Activity已經被銷毀,AsyncTask的后臺線程還在執行,它將續在內存里保留這個引用,導致Activity無法被回收,引起內存泄漏。
5.4 AsyncTask結果丟失問題屏幕旋轉或Activity在后臺被系統殺掉等情況會導致Actvity的重新創建,之前運行的AsyncTask會持有一個之前Activity的引用,這個引用已經無效,這時調用onPostExecute()再去更新界面將不再生效。
5.5 AsyncTask并行還是串行問題在Android1.6之前的版本,AsyncTask是串行的,在1.6-2.3的版本,改成了并行的。在2.3之后的版本又做了修改,可以支持并行和串行,當想要串行執行時,直接行execute()方法,如果需要并行執行時,執行executeOnExecutor(Executor)。
關于其他內容介紹 01.關于博客匯總鏈接1.技術博客匯總
2.開源項目匯總
3.生活博客匯總
4.喜馬拉雅音頻匯總
5.其他匯總
02.關于我的博客我的個人站點:www.yczbj.org,www.ycbjie.cn
github:https://github.com/yangchong211
知乎:https://www.zhihu.com/people/...
簡書:http://www.jianshu.com/u/b7b2...
csdn:http://my.csdn.net/m0_37700275
喜馬拉雅聽書:http://www.ximalaya.com/zhubo...
開源中國:https://my.oschina.net/zbj161...
泡在網上的日子:http://www.jcodecraeer.com/me...
郵箱:yangchong211@163.com
阿里云博客:https://yq.aliyun.com/users/a... 239.headeruserinfo.3.dT4bcV
segmentfault頭條:https://segmentfault.com/u/xi...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71940.html
摘要:在這里又調用我們發現在線程先調用了,將傳入的參數賦值給,然后調用了參數的方法,并且將作為參數傳入,這里就設計到了三個對象在中傳入,來看看它們的賦值在哪里我們發現的賦值默認就是,也就是一個順序執行的線程池,內部實現有一個任務隊列。 在之前的文章深入探究了Handler,我們知道Android的消息機制主要靠Handler來實現,但是在Handler的使用中,忽略內存泄露的問題,不管是代碼...
閱讀 997·2023-04-25 14:20
閱讀 1875·2021-11-24 10:20
閱讀 3770·2021-11-11 16:55
閱讀 2911·2021-10-14 09:42
閱讀 3470·2019-08-30 15:56
閱讀 1156·2019-08-30 15:55
閱讀 1069·2019-08-30 15:44
閱讀 780·2019-08-29 11:28