關于Android線程的那些事兒

1. 線程的定義

線程thread)是操作系統能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。

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


2. Android的單線程模式

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

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

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

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


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

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

3.1 主線程中開啟工作線程

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

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

  • Runnable
    實現Runnable接口復寫run()然后New Thread(Runnable).start()。

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

3.2 工作線程中訪問UI線程

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

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

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


4. 線程間通信

4.1 Handler

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

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

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

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

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

class LooperThread extends Thread {
      public Handler mHandler;

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

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

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

Handler有兩個用途:
(1)安排message和runnable在將來的某個時刻得到執行
常用實現方法有
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)來實現的(需要繼承Handler的基類)。
(2)在其他線程中執行某些操作。
Handler可以在線程間傳遞消息,工作原理如下

4.1.2 工作機制

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

Handler,MessageQueue,Looper的工作流程

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

Looper, Handler and MessageQueue之間的關系

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執行完成之后調用UI線程
         showDialog("Downloaded " + result + " bytes");
     }
 } 

然后在主線程中執行:

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

一個異步任務的4步走:

  1. onPreExecute

  2. doInBackground

  3. onProgressUpdate

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

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

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

4.3 IntentService

有了intentService就可以不用自己啟動和結束線程了,IntentService內部會自動執行線程的開啟和銷毀。

4.3.1 使用步驟

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

public class myIntentService extends IntentService {

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

    @Override
    protected void onHandleIntent(Intent intent) {
        //根據Intent的不同進行不同的事務處理 
        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)啟動服務,Intent 會攜帶相關數據,onHandleIntent會對這些數據進行識別,。

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

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

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

  • intentservice的oncreate方法中會通過HandlerThread 開啟新線程,創建對應的Looper,和MessageQueue。

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

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

  • 處理完后會自動停止自己的服務Ondestroy

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

4.4. HandlerThread

HandlerThread是Thread的子類,可以創建一個帶有looper的線程,并創建與這個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也被創建了,所以不再需要我們自己去調用Looper.prepare(),Loop.loop()。直接通過thread.getLooper()就可以獲取到當前線程的looper,類似于根據looper獲取MesssageQueue的方法:Looper.MyQueue()。

5. 總結

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容