Android HandlerThread 詳解
概述
HandlerThread 相信大家都比較熟悉了,從名字上看是一個帶有 Handler 消息循環機制的一個執行緒,比一般的執行緒多了消息循環的機制,可以說是 Handler + Thread 的結合,從源碼上看也是如此的設計,一般情況下如果需要子執行緒和主執行緒之間相互交互,可以用 HandlerThread 來設計,這比單純的 Thread 要方便,而且更容易管理,因為大家都知道Thread 的生命周期在一些情況下是不可控制的,比如直接 new Thread().start() 這種方式在項目中是不推薦使用的,實際上 Android 的源碼中也有很多地方用到了 HandlerThread,下面我將分析一下 HandlerThread 用法以及源碼解析。
使用示例
// 實例對象,參數為執行緒名字 HandlerThread handlerThread = new HandlerThread("handlerThread"); // 啟動執行緒 handlerThread.start(); // 參數為 HandlerThread 內部的一個 looper Handler handler = new Handler(handlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); } };
注意:這個使用的順序是不能更改的!!!,因為如果不先讓子執行緒 start 起來,那麼創建主執行緒的 handler 的參數 getLooper 是獲取不到的,這一點可以看源碼就清楚。
Demo 詳解
這裡模擬在子執行緒下載東西,然後和主執行緒之間進行通訊。主執行緒知道了下載開始和下載結束的時間,也就能及時改變介面 UI。
DownloadThread
類,繼承於 HandlerThread
,用於下載。
public class DownloadThread extends HandlerThread{ private static final String TAG = "DownloadThread"; public static final int TYPE_START = 2;//通知主執行緒任務開始 public static final int TYPE_FINISHED = 3;//通知主執行緒任務結束 private Handler mUIHandler;//主執行緒的Handler public DownloadThread(String name) { super(name); } /* * 執行初始化任務 * */ @Override protected void onLooperPrepared() { Log.e(TAG, "onLooperPrepared: 1.Download執行緒開始準備"); super.onLooperPrepared(); } //注入主執行緒Handler public void setUIHandler(Handler UIhandler) { mUIHandler = UIhandler; Log.e(TAG, "setUIHandler: 2.主執行緒的handler傳入到Download執行緒"); } //Download執行緒開始下載 public void startDownload() { Log.e(TAG, "startDownload: 3.通知主執行緒,此時Download執行緒開始下載"); mUIHandler.sendEmptyMessage(TYPE_START); //模擬下載 Log.e(TAG, "startDownload: 5.Download執行緒下載中..."); SystemClock.sleep(2000); Log.e(TAG, "startDownload: 6.通知主執行緒,此時Download執行緒下載完成"); mUIHandler.sendEmptyMessage(TYPE_FINISHED); } }
然後是 MainActivity
部分,UI 和處理消息。
public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; private DownloadThread mHandlerThread;//子執行緒 private Handler mUIhandler;//主執行緒的Handler @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化,參數為執行緒的名字 mHandlerThread = new DownloadThread("mHandlerThread"); //調用start方法啟動執行緒 mHandlerThread.start(); //初始化Handler,傳遞mHandlerThread內部的一個looper mUIhandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { //判斷mHandlerThread里傳來的msg,根據msg進行主頁面的UI更改 switch (msg.what) { case DownloadThread.TYPE_START: //不是在這裡更改UI哦,只是說在這個時間,你可以去做更改UI這件事情,改UI還是得在主執行緒。 Log.e(TAG, "4.主執行緒知道Download執行緒開始下載了...這時候可以更改主介面UI"); break; case DownloadThread.TYPE_FINISHED: Log.e(TAG, "7.主執行緒知道Download執行緒下載完成了...這時候可以更改主介面UI,收工"); break; default: break; } super.handleMessage(msg); } }; //子執行緒注入主執行緒的mUIhandler,可以在子執行緒執行任務的時候,隨時發送消息回來主執行緒 mHandlerThread.setUIHandler(mUIhandler); //子執行緒開始下載 mHandlerThread.startDownload(); } @Override protected void onDestroy() { //有2種退出方式 mHandlerThread.quit(); //mHandlerThread.quitSafely(); 需要API>=18 super.onDestroy(); } }
運行的Log日誌如下
源碼解析
先來看下構造函數相關的:
int mPriority;//優先順序 int mTid = -1; Looper mLooper;//自帶的Looper private @Nullable Handler mHandler; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } /** * Constructs a HandlerThread. * @param name * @param priority The priority to run the thread at. The value supplied must be from * {@link android.os.Process} and not from java.lang.Thread. */ public HandlerThread(String name, int priority) { super(name); mPriority = priority; }
HandlerThread(String name)
,一個 HandlerThread(String name, int priority)
,我們可以自己設定執行緒的名字以及優先順序。注意!是 Process 里的優先順序而不是Thread 的。/** * Call back method that can be explicitly overridden if needed to execute some * setup before Looper loops. */ protected void onLooperPrepared() { } @Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }
run
方法中首先獲取執行緒 id
,然後就調用了 Looper.prepare
方法創建一個 Looper,
接著調用了 Looper.myLooper
方法獲取到了當前執行緒的 Looper
。
接著通過 notifyAll
通知等帶喚醒,這裡的等待是在 HandlerThread
的 getLooper
方法里調用的 wait
方法,getLooper
方法是為了獲取該 HandlerThread
中的 Looper
。
如果在沒調用 HandlerThread
的 start
方法開啟執行緒前就調用 getLooper
方法就通過 wait
方法暫時先進入等待,等到 run
方法運行後再進行喚醒。喚醒之後 run
方法中繼續設置了構造函數中傳入的優先順序,接著調用了onLooperPrepared
方法,該方法是個空實現,該方法是為了在 Looper
開啟輪詢之前如果要進行某些設置,可以複寫該方法。
最後調用Looper.loop
開啟輪詢。退出的時候,將 mTid = -1;
public Looper getLooper() { if (!isAlive()) { return null; } // If the thread has been started, wait until the looper has been created. synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper; }
這個方法是獲取當前的 Looper,可以看到如果沒有獲取的時候就一直等待直到獲取,而前面也提到了獲取到了就喚醒了所有的執行緒,看來這是執行緒的等待-喚醒機制應用。
public Handler getThreadHandler() { if (mHandler == null) { mHandler = new Handler(getLooper()); } return mHandler; }
這個是獲取 HandlerThread 綁定的 Looper 執行緒的 Handler
public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false; } public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false; }
可以看到這兩個方法去退出執行緒的 Looper 循環,那麼這兩個方法有什麼區別呢,實際上都是調用了 MessageQueue 的 quit() 方法,源碼如下:
void quit(boolean safe) { if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); } synchronized (this) { if (mQuitting) { return; } mQuitting = true; if (safe) { removeAllFutureMessagesLocked(); } else { removeAllMessagesLocked(); } // We can assume mPtr != 0 because mQuitting was previously false. nativeWake(mPtr); } }
可以看到: 當我們調用 quit 方法的時候,實際上執行了 MessageQueue 中的 removeAllMessagesLocked 方法,該方法的作用是把 MessageQueue 消息池中所有的消息全部清空,無論是延遲消息(延遲消息是指通過 sendMessageDelayed 或通過 postDelayed 等方法發送的需要延遲執行的消息,只要不是立即執行的消息都是延遲消息)還是非延遲消息。
而 quitSafely 方法時,實際上執行了 MessageQueue 中的 removeAllFutureMessagesLocked 方法,通過名字就可以看出,該方法只會清空 MessageQueue 消息池中所有的延遲消息,並將消息池中所有的非延遲消息派發出去讓 Handler 去處理,quitSafely 相比於 quit 方法安全之處在於清空消息之前會派發所有的非延遲消息,一句話,就是清除未來需要執行的消息。
這兩個方法有一個共同的特點就是:Looper 不再接收新的消息了,消息循環就此結束,此時通過 Handler 發送的消息也不會在放入消息杜隊列了,因為消息隊列已經退出了。應用這2個方法的時候需要注意的是:quit 方法從 API 1 就開始存在了,比較早,而 quitSafely 直到 API 18 才添加進來.
總結
-
如果經常要開啟執行緒,接著又是銷毀執行緒,這是很耗性能的,
HandlerThread
很好的解決了這個問題; -
HandlerThread
由於非同步操作是放在Handler
的消息隊列中的,所以是串列的,但只適合併發量較少的耗時操作。
-
HandlerThread
用完記得調用退出方法。 -
注意使用 handler 避免出現記憶體泄露