Android中使用Thread線程與AsyncTask非同步任務的區別

来源:http://www.cnblogs.com/BobGo/archive/2016/06/19/5598270.html
-Advertisement-
Play Games

最近和幾個朋友交流Android開發中的網路下載問題時,談到了用Thread開啟下載線程時會產生的Bug,其實直接用子線程開啟下載任務的確是很Low的做法,那麼原因究竟如何,而比較高大上的做法是怎樣?於是用這篇博文詳細分析記錄一下。 一、概念介紹 Thread是指在CPU運行的一個程式中,可以有多個 ...


  最近和幾個朋友交流Android開發中的網路下載問題時,談到了用Thread開啟下載線程時會產生的Bug,其實直接用子線程開啟下載任務的確是很Low的做法,那麼原因究竟如何,而比較高大上的做法是怎樣?於是用這篇博文詳細分析記錄一下。

一、概念介紹

  Thread是指在CPU運行的一個程式中,可以有多個執行路徑。運行的程式稱作進程,而這個執行路徑,就被稱為線程(如果對這兩個名詞不太理解的同學可以參考一下操作系統方面的書籍)。Java中的多線程是指多個Thread可以在一段內同步執行,這樣可以提高代碼的運行效率,Java中允許一個進程有多個線程,可以無限多,但是必須要有一個線程,也就是當前進程的主線程。

  必須要明白的一點是,Thread是Java語言下的一個底層類,而Android是使用並封裝了Java語言的系統,所以Android中的AsyncTask只是使用了Java的多線程概念並優化封裝之後的一個抽象類。所以Thread和AsyncTask完全是兩個不同層次的概念,而不是簡單的替換。

  再說說AsyncTask非同步任務,這個類是在Android中使用的,在編寫該類時就已經明確說明,“AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler} and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs”,後邊的不重要就不用粘貼了,可以看出非同步任務進行長時間操作時使用的。因為Android中對每一個App的運行都看做一個進程,而這個進程中的主線程,就是UI線程,也就是我們打開一個App時可以看到界面的這個線程。而像下載這種耗時操作,如果放到UI線程執行,則會使得UI線程負荷過大產生ANR應用無響應異常,所以創建了AsyncTask類,用來專門進行一些耗時的非UI更新操作。

  通過上面的介紹,很容易想到AsyncTask是使用了Java中的多線程技術的,但是他不是單純的Thread,具體是怎麼實現非同步任務的,我們可以看源碼比較。

   Thread類是在java.lang包下的,所以他的使用不需要另外導包,而且Thread是實現Runnable介面的類,也就是說他可以實例化;由於Thread是底層代碼,具體源碼就不再分析了,所以主要說一下AsyncTask怎麼用Thread實現的非同步任務。

  AsyncTask類是在android.os包下的抽象類,在使用之前必須導包。AsyncTask是使用線程工廠創建新的線程在後臺執行非同步任務的,之前我們說個Android中有一個UI線程作為主線程,那麼再創建的線程都是子線程了,至於新創建的這些子線程做了什麼事情,就要看我們的意願了。

二、下載分析:

  介紹了半天兩個類的對比,感覺還是直接上Demo來的快一點。下邊我分別用開啟子線程和開啟非同步兩種方式實現下載,同時簡單分析一下這兩種方式下的CPU執行順序。

  1. 在當前Activity中開啟子Thread執行下載

(1)創建下載子線程:

 1 /**
 2      * 下載線程
 3      */
 4     private Thread myThread =new Thread(new Runnable() {
 5         @Override
 6         public void run() {
 7             Object data=download(PATH);
 8             Message msg=Message.obtain();
 9             msg.what=101;
10             msg.obj=data;
11             handler.sendMessage(msg);
12         }
13     });

(2)在Handler中執行下載之後的任務:

 1 private Handler handler=new Handler(new Handler.Callback() {
 2         @Override
 3         public boolean handleMessage(Message msg) {
 4             if(msg.what==101){
 5                 data=msg.obj;
 6                 //下麵執行對data的操作
 7             }
 8             return false;
 9         }
10     });

(3)在需要下載的地方開啟當前下載線程:

 1 myThread.start(); 

  只需要上邊三步就可以輕鬆完成下載網路請求,是不是看起來很簡單?那麼問題來了,下載任務是在myThread的子線程中執行的,如果下載任務還在執行的過程中時,用戶執行了頁面跳轉的操作,也就是說當前Activity所在的UI線程已經銷毀,但是並沒有銷毀myThread子線程吧,那麼當myThread執行完下載任務download()這個方法之後,他接著調用handler來發送信息以執行data操作,而執行data操作的handler是在當前Activity中定義的,隨著當前Activity的銷毀,當前handler也跟隨著銷毀了,這樣在myThread中就無法調用執行data的handler了,那麼他必然會報NullPointException了吧。所以這樣使用子線程進行下載任務是不安全的。

  2. 使用非同步任務AsyncTask執行下載任務

  所以在Android中可以使用原生的AsyncTask進行像下載網路請求這樣的耗時操作,具體方法就是創建一個下載任務繼承AsyncTask抽象類,同時重寫該類中的doInBackground(),這個方法是在要下載的子線程中執行的,點開AsyncTask的源碼,我們可以看到在doInBackground()這個方法的前邊有個註釋@WorkThread,可以想到這個方法是在工作線程中執行的,那麼有沒有在主線程中執行的方法呢?當然是有的,我們還會看到有這樣幾個方法,他們的方法體內都沒有執行語句,說明是可以用子類來重寫這些方法的,有構造方法,execute(),onPreExecute(),onCancelled()等都是在MainThread中執行的。

  那麼可能有同學要提問了,這樣還是在子線程中執行要下載的任務,難道這樣再發生上邊我們說到的那種臨界事件,子線程下載結束之後就不會有空指針異常了嗎?

  當然可以很肯定的說,使用AsyncTask絕對不會發生上述Bug了,為什麼呢?我們接著分析。

  在工作線程中執行的除了當前執行下載的doInBackground()之外,就只有publishProgress()這一個方法了,而doInBackground()是個抽象方法,所以要想知道工作線程到底有什麼門道,只能從publicProgress()找線索了。我們知道這個方法是發佈進度的時候使用,下麵是這個方法的源碼,

1 @WorkerThread
2     protected final void publishProgress(Progress... values) {
3         if (!isCancelled()) {
4             getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
5                     new AsyncTaskResult<Progress>(this, values)).sendToTarget();
6         }
7     }

很明顯這個getHandler()就是獲得當前AsyncTask類中的Handler對象,也就是說在工作線程中發佈的進度會將信息發送到當前AsyncTask的Handler中處理,那麼我們不管工作線程中具體怎麼發佈的進度,只需要看看在當前AsyncTask中怎麼處理接收的信息就可以了。

1     private static Handler getHandler() {
2         synchronized (AsyncTask.class) {
3             if (sHandler == null) {
4                 sHandler = new InternalHandler();
5             }
6             return sHandler;
7         }
8     }

這個方法明顯是在sHandler不為空的時候返回了一個InternalHandler對象,整個過程都是對AsyncTask加鎖的,而這裡加鎖才是關鍵,畢竟要保證發送消息時的安全性,在獲得一個InternalHandler對象後,整個AsyncTask都是加鎖狀態的。那麼我們接著去看這個InternalHandler是乾什麼用的。

  首先我們可以確定這是一個繼承Handler類的子類,在他的handlerMessage()中只執行了下邊幾行代碼,這裡應該快要找到我們問題的根源了。

 1 AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
 2             switch (msg.what) {
 3                 case MESSAGE_POST_RESULT:
 4                     // There is only one result
 5                     result.mTask.finish(result.mData[0]);
 6                     break;
 7                 case MESSAGE_POST_PROGRESS:
 8                     result.mTask.onProgressUpdate(result.mData);
 9                     break;
10             }

發現這裡對發送的信息類型進行了判斷,只有兩種類型,第二種MESSAGE_POST_PROGRESS,不正是剛剛發佈進度的方法publicProgress()裡邊發送的信息類型嗎。那麼第一種MESSAGE_POST_RESULT,不難想到,這就是在工作線程中執行完doInBackground()之後的發送的信息類型了,而且人家已經有註釋說明,“There is only one result”,“只有一個唯一的結果”,在得到這個信息也就是我們的下載任務執行完成之後,會調用下邊那個方法,其實不用再往下找了,因為執行的這個方法就是當前AsyncTask自身的finish()這個方法。而這正是說明瞭在正常執行完工作線程的doInBackground()之後再在主線程中執行finish(),所以我們的思路也就理順了。

  好吧,也許看完上邊的代碼加我的分析,有些同學感覺更是雲里霧裡了,似乎這裡邊並沒有解釋中途跳轉的問題啊。那你可要仔細想想了,在之前直接開啟子線程下載之後的中途跳轉發生空指針異常的根本原因在哪裡?是在子線程中無法使用主線程中的handler對象才產生的空指針異常吧。那麼我們的非同步任務AsyncTask是怎麼解決發送信息這個handler的?

  在使用handler發送信息時,系統會先調用getHandler(),獲得一個InternalHandler對象,如果之前沒有就創建,如果有就用之前的,而且由於整個過程中當前非同步任務AsyncTask都是加鎖狀態的,所以其他線程無法使用,同樣的在使用AsyncTask的主線程中也無法隨意銷毀。這樣再將得到的handler返回使用發送信息,就能順利的跨過空指針異常了。

三、總結

  這麼解釋,相信還在摸不著頭腦的同學們應該明白一點了,下邊我再簡單做一下總結。

  AsyncTask是作為非同步任務,執行除了UI界面更新的任務之外的其他耗時操作的。UI界面的更新是在主線程,也就是UI線程中執行的,而在這個非同步任務中,開啟了一個工作線程來執行耗時操作。而這個工作線程和UI線程的執行順序是不同步的,也就是說只有執行完工作線程中的下載之後,才會調用UI線程中的onPostExecute()執行後續UI操作,這樣就實現了非同步下載。如果UI線程銷毀之後工作線程再發送下載結束的信息,由於工作線程再使用過程中是與AsyncTask綁定的,所以他也會隨著當前AsyncTask的銷毀而銷毀,不會執行後續的下載操作,自然也不會執行發送下載結束的信息。

  而簡單的開啟子線程執行下載,子線程與UI線程只是保持簡單的同步關係,所以只是單純的在子線程中執行下載耗時操作是不安全的。事實證明,儘管Java中的多線程是個很好的機制,但是在使用時要註意它的副作用,學會使用對他進行封裝之後的類和方法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在iOS學習23之事件處理中,小編詳細的介紹了事件處理,在這裡小編敘述一下它的相關原理 1、UITouch對象 在觸摸事件的處理方法中都會有一個存放著UITouch對象的集合,這個參數有什麼用呢? (1)UITouch 對象的簡介 當用戶用一根手指觸摸屏幕時,會創建一個與手指相關聯的 UITouch ...
  • 誤解一:安卓是iOS的後輩 不知不覺,安卓已經成為了世界上最流行的移動智能系統,就市場占有率來看,安卓甚至要高於引領了智能機和平板電腦革命的iOS。安卓的紅火深遠地影響了IT行業,全球最大的社交網路Facebook甚至倡議員工棄用iOS改換安卓手機以更深入地瞭解用戶體驗 但是,流行總伴隨著流言,安卓 ...
  • 【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5598451.html 】 在上一篇的fork函數中,首先一上來就調用get_free_page為新任務的數據結構申請一頁記憶體,在memory.c中: 上面有幾個指令比較陌生,先介紹repne s ...
  • WWDC 2016 大會之後,蘋果公司發佈了四個全新平臺:iOS,macOS,watchOS 和 tvOS。並且在此之後,蘋果應用商店審核條款也同時進行了更新——貌似不算進行了更新,簡直就是重寫!上個版本的 30 個章節被修改成了 5 大章節,但原版英文版字數從 5000 多個英文單詞增加到了 60 ...
  • 一.繼承關係 二.概述 TAB的容器。這個對象包含兩個子元素: 三.常用方法 四.三個內部類 TabHost.TabSpec tab(標簽)有一個indicator,content後臺tag.例如: 1.indicator 有三個重載的方法可以設置標簽的名字和圖案。返回值都是TabHost.TabS ...
  • 便利的初始化view以及設置tag值 效果 源碼 https://github.com/YouXianMing/iOS-Project-Examples 中的 SetRect 細節 需要實現協議(用NSMapTable的strongToWeakObjectsMapTable來作為存儲string - ...
  • 【版權所有,轉載請註明出處。出處:http://www.cnblogs.com/joey-hua/p/5597818.html 】 據說安卓應用里通過fork子進程的方式可以防止應用被殺,大概原理就是子進程被殺會向父進程發送信號什麼的,就不深究了。 首先fork()函數它是一個系統調用,在sys.h ...
  • 最全面的 Android 編碼規範指南 本文word文檔下載地址:http://pan.baidu.com/s/1bXT75O 1. 前言 最全面的 Android 編碼規範指南 本文word文檔下載地址:http://pan.baidu.com/s/1bXT75O 1. 前言 這份文檔參考了 Go ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...