關(guān)于Android線程的那些事兒

1. 線程的定義

線程thread)是操作系統(tǒng)能夠進(jìn)行運算調(diào)度的最小單位。它被包含在進(jìn)程之中,是進(jìn)程中的實際運作單位。

一個進(jìn)程中可以并發(fā)多個線程,每條線程并行執(zhí)行不同的任務(wù)。每個線程都會分配一個私有內(nèi)存區(qū)域,該區(qū)域主要用于在執(zhí)行過程中存儲方法、局部變量和參數(shù)。一旦線程終止私有內(nèi)存區(qū)將會被銷毀。更多關(guān)于進(jìn)程與線程的區(qū)別和聯(lián)系請參照上一篇文章關(guān)于Android進(jìn)程的那些事兒,在此不再重復(fù)。


2. Android的單線程模式

Android的系統(tǒng)啟動流程和應(yīng)用啟動流程這篇文章中有提到,Android應(yīng)用啟動時,zygote進(jìn)程fork自身,開啟一個Linux進(jìn)程和一個主線程(Main Thread),主線程負(fù)責(zé)UI顯示、更新、控件交互,因此主線程又叫做UI線程。

Android中的單線程模式必須遵循兩條規(guī)則:

  • 不要阻塞 UI 線程
    原因:
    由于UI繪制和事件分發(fā)采用單線程模式,當(dāng)UI線程中執(zhí)行耗時操作(網(wǎng)絡(luò)訪問或數(shù)據(jù)庫查詢)時,UI線程會被阻塞,從而出現(xiàn)ANR(application not responding)或者類似NetworkOnMainThreadException之類的錯誤。

  • 不要在 UI 線程之外訪問 Android UI 工具包
    原因:
    UI的顯示、更新和控件交互主要通過Android UI 工具包(Android UI toolkit,包括android.widget和android.view)來實現(xiàn)的,而Android UI toolkit并非線程安全的工具包,如果我們在工作線程中刷新UI的時候,主線程也恰好在刷新UI,就會出現(xiàn)沖突。因此Android不允許在UI線程之外進(jìn)行UI的相關(guān)操作,否則會出現(xiàn)CalledFromWrongThreadException的錯誤。


3. 工作線程和UI線程(主線程)

為了遵循上述單線程模式的兩條規(guī)則,我們必須將耗時操作放在工作線程中,將UI操作放在UI線程中,同時有需要兩個線程之間的切換。

3.1 主線程中開啟工作線程

Java中提供了兩種方法:Thread和Runnable

  • Thread
    繼承Thread類覆寫run()然后thread.start()

  • Runnable
    實現(xiàn)Runnable接口復(fù)寫run()然后New Thread(Runnable).start()。

但是,在Android中這兩種方法是不值得推薦的。最好使用Android自帶的Handler,AsyncTask,IntentService, Loader,Service等方式來實現(xiàn)。下面會詳細(xì)說明。

3.2 工作線程中訪問UI線程

下面三個方法可以訪問UI線程:

  • Activity.runOnUIThread(Runable)
  • View.post(Runable)
  • View.postDelayed(Runable,long)

但是如果主進(jìn)程和工作線程之間需要更復(fù)雜的交互和操作,上面的方法就無能為力了,現(xiàn)在輪到Handler, AsyncTask, HandlerThread上場了,下面會展開詳述。


4. 線程間通信

4.1 Handler

由于Android單線程模式下的兩條原則的存在,線程間的通信顯得尤為重要,Handler正是用來解決這個問題的。消息(Message)的傳遞需要Handler,MessageQueue, Looper共同來完成。其中Looper在Handler和MessageQueue之間起到橋梁和紐帶的作用。在此之前,我們有必要了解一下相關(guān)概念

4.1.1 相關(guān)概念
  • Message
    Message類實現(xiàn)了Parcelable接口,因此它可以包含任意的數(shù)據(jù)對象,并把它傳遞給Handler。盡管Message的構(gòu)造方法是public,但是我們還是推薦使用Message.obtain()或者M(jìn)essage.obtainMessage()來得到一個Message實例。

  • MessageQueue
    MessageQueue(消息隊列)顧名思義,Message組成的一個List,但是Message并不是直接加入到MessageQueue的,而是通過與Looper綁定的Handler來實現(xiàn)的。

    獲取當(dāng)前線程的MessageQueue的方法是Looper.MyQueue()。

  • Looper
    Looper類用來操作一個線程的消息循環(huán)。用法如下:

class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();//創(chuàng)建looper

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();//
      }
  }
  • Handler
    handler跟一個線程和該線程的messagequeue綁定的,當(dāng)我們新建一個Handler的時候,它就會與當(dāng)前線程,和線程的消息隊列綁定在一起。Handler負(fù)責(zé)傳遞Message或者Runnable對象給MessageQueue,并當(dāng)他們從消息隊列出來的時候執(zhí)行相關(guān)操作。

Handler有兩個用途:
(1)安排message和runnable在將來的某個時刻得到執(zhí)行
常用實現(xiàn)方法有
post(Runnable)
[postAtTime(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postAtTime(java.lang.Runnable, long))
[postDelayed(Runnable, long)](https://developer.android.com/reference/android/os/Handler.html#postDelayed(java.lang.Runnable, long))
sendEmptyMessage(int)
sendMessage(Message)
[sendMessageAtTime(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageAtTime(android.os.Message, long))
[sendMessageDelayed(Message, long)](https://developer.android.com/reference/android/os/Handler.html#sendMessageDelayed(android.os.Message, long))
其中,Message的處理是通過Handler的 handleMessage(Message)來實現(xiàn)的(需要繼承Handler的基類)。
(2)在其他線程中執(zhí)行某些操作。
Handler可以在線程間傳遞消息,工作原理如下

4.1.2 工作機(jī)制

Handler用來發(fā)送和處理Message或者Runnable對象,每個Handler實例都會綁定到一個線程和這個線程的MessageQueue。當(dāng)創(chuàng)建Handler的時候,他會默認(rèn)綁定到創(chuàng)建它的線程上,他會給MessageQueue發(fā)送message和runnable,在Looper輪訓(xùn)到該條消息的時候,回調(diào)創(chuàng)建該消息的Handler的handlerMessage方法,處理從message queue出來的message和runnable。
具體流程如下圖:

Handler,MessageQueue,Looper的工作流程

當(dāng)應(yīng)用啟動時,新的進(jìn)程被創(chuàng)建的時候,它的主線程會運行一個message queue來管理上層應(yīng)用對象(Activity,Broadcast Receiver)和創(chuàng)建的窗口。也可以創(chuàng)建工作線程,然后創(chuàng)建Handler,在工作線程中調(diào)用postRunnable或者sendMessage類方法來傳遞消息給主線程。這時message或者Runnable對象將會在handler的message queue中排隊并在它出站時得到執(zhí)行。

Looper, Handler and MessageQueue之間的關(guān)系

4.2 AsyncTask

4.2.1 使用方法

首先,繼承AsyncTask類,至少重寫doInBackground這個方法。比如

private class DownloadFilesTask extends AsyncTask<URL, Integer, Long> {
@Override
     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;
     }
@Override
     protected void onProgressUpdate(Integer... progress) {
         setProgressPercent(progress[0]);
     }
@Override
        protected void onPreExecute() {
        }
@Override
     protected void onPostExecute(Long result) {//doInBackground執(zhí)行完成之后調(diào)用UI線程
         showDialog("Downloaded " + result + " bytes");
     }
 } 

然后在主線程中執(zhí)行:

new DownloadFilesTask().execute(url1, url2, url3);
4.2.2 執(zhí)行過程

一個異步任務(wù)的4步走:

  1. onPreExecute

  2. doInBackground

  3. onProgressUpdate

  4. onPostExecute
    從方法名可以理解這四個方法的功能和執(zhí)行順序,不再贅述。

4.2.3 注意事項
  1. 上述四個方法不可以手動調(diào)用
  2. 每個task的加載,創(chuàng)建,執(zhí)行都必須在UI線程中執(zhí)行。
  3. 該任務(wù)只能執(zhí)行一次,第二次執(zhí)行會拋異常
  4. AsyncTask開啟線程的方法asyncTask.execute()默認(rèn)是也是開啟一個線程和一個隊列的,不過也可以通過asyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, 0)開啟一個含有5個新線程的線程池,也就是說有個5個隊列了,假如說你執(zhí)行第6個耗時任務(wù)時,除非前面5個都還沒執(zhí)行完,否則任務(wù)是不會阻塞的,這樣就可以大大減少耗時任務(wù)延遲的可能性。

除了常用的Handler和AsyncTask之外,IntentService和HandlerThread也為線程間的通信提供了極大地便利

4.3 IntentService

有了intentService就可以不用自己啟動和結(jié)束線程了,IntentService內(nèi)部會自動執(zhí)行線程的開啟和銷毀。

4.3.1 使用步驟

(1)繼承IntentService類,并重寫onHandleIntent()方法,這個方法中,執(zhí)行耗時操作

public class myIntentService extends IntentService {

    public myIntentService() {
        super("myIntentService");
        // 注意構(gòu)造函數(shù)參數(shù)為空,這個字符串就是worker thread的名字
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        //根據(jù)Intent的不同進(jìn)行不同的事務(wù)處理 
        String taskName = intent.getExtras().getString("taskName");  
        switch (taskName) {
        case "task1":
            Log.i("myIntentService", "do task1");
            break;
        case "task2":
            Log.i("myIntentService", "do task2");
            break;
        default:
            break;
        }       
    }
  //--------------------用于打印生命周期--------------------    
   @Override
  public void onCreate() {
        Log.i("myIntentService", "onCreate");
    super.onCreate();
}

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i("myIntentService", "onStartCommand");
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        Log.i("myIntentService", "onDestroy");
        super.onDestroy();
    }
}

然后通過startService(Intent i)啟動服務(wù),Intent 會攜帶相關(guān)數(shù)據(jù),onHandleIntent會對這些數(shù)據(jù)進(jìn)行識別,。

//同一服務(wù)只會開啟一個worker thread,在onHandleIntent函數(shù)里依次處理intent請求。

        Intent i = new Intent("cn.scu.finch");  
        Bundle bundle = new Bundle();  
        bundle.putString("taskName", "task1");  
        i.putExtras(bundle);  
        startService(i);  
4.3.2 執(zhí)行過程

onCreate--->onStartCommand-->onHandleIntent-->onDestroy

  • intentservice的oncreate方法中會通過HandlerThread 開啟新線程,創(chuàng)建對應(yīng)的Looper,和MessageQueue。

  • 通過onStartCommand()傳遞給服務(wù)intent被依次插入到工作隊列中。工作隊列又把intent逐個發(fā)送給onHandleIntent()。

  • 通過在onHandleIntent((Intent)msg.obj)中調(diào)用你的處理程序.

  • 處理完后會自動停止自己的服務(wù)Ondestroy

4.3.3 注意事項
  • IntentService 會創(chuàng)建一個線程,來處理所有傳給onStartCommand()的Intent請求。
  • 對于startService()請求執(zhí)行onHandleIntent()中的耗時任務(wù),會生成一個消息隊列,每次只有一個Intent傳入onHandleIntent()方法并執(zhí)行。也就是同一時間只會有一個耗時任務(wù)被執(zhí)行,其他的請求還要在后面排隊, onHandleIntent()方法不會多線程并發(fā)執(zhí)行。
  • 當(dāng)所有startService()請求被執(zhí)行完成后,IntentService 會自動銷毀,所以不需要自己寫stopSelf()或stopService()來銷毀服務(wù)。
  • 提供默認(rèn)的onBind()實現(xiàn) ,即返回null,不適合綁定的 Service。采用StartService來啟動,即使啟動它的Activity被銷毀也不會影響service的生命周期
  • 提供默認(rèn)的 onStartCommand() 實現(xiàn),將intent傳入等待隊列中,然后到onHandleIntent()的實現(xiàn)。
  • Service等四大組件默認(rèn)是運行在主線程上的!沒有界面不代表不再UI線程。

4.4. HandlerThread

HandlerThread是Thread的子類,可以創(chuàng)建一個帶有l(wèi)ooper的線程,并創(chuàng)建與這個looper綁定的Handler,代碼如下

HandlerThread mThread = new HandlerThread("message");
mThread.start();
Handler mHandler = new Handler(mThread.getLooper())
        {
            @Override
            public void handleMessage(Message msg)
            {
//   to do sth     
            }
        };

可以看出,跟Handler非常類似,只是新建HandlerThread時,與該線程綁定的looper也被創(chuàng)建了,所以不再需要我們自己去調(diào)用Looper.prepare(),Loop.loop()。直接通過thread.getLooper()就可以獲取到當(dāng)前線程的looper,類似于根據(jù)looper獲取MesssageQueue的方法:Looper.MyQueue()。

5. 總結(jié)

由于Android系統(tǒng)的單線程模式,線程之間的通信必不可少,必須掌握Handler,AsyncTask, IntentService, HandlerThread的工作原理并加以熟練使用。其中Handler跟HandlerThread工作原理是一樣的,很多情況下使用HandlerThread可以簡化很多工作。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容