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

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

1、概念介紹android

  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實現的異步任務。ide

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

2、下載分析:this

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

  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返回使用發送信息,就能順利的跨過空指針異常了。

3、總結

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

  AsyncTask是做爲異步任務,執行除了UI界面更新的任務以外的其餘耗時操做的。UI界面的更新是在主線程,也就是UI線程中執行的,而在這個異步任務中,開啓了一個工做線程來執行耗時操做。而這個工做線程和UI線程的執行順序是不一樣步的,也就是說只有執行完工做線程中的下載以後,纔會調用UI線程中的onPostExecute()執行後續UI操做,這樣就實現了異步下載。若是UI線程銷燬以後工做線程再發送下載結束的信息,因爲工做線程再使用過程當中是與AsyncTask綁定的,因此他也會隨着當前AsyncTask的銷燬而銷燬,不會執行後續的下載操做,天然也不會執行發送下載結束的信息。

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

相關文章
相關標籤/搜索