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; }這裡面有一個方法 onLooperPrepared(),在實際中,我們可以重寫這個方法做一些初始化的操作,這個 run() 是重點。
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 避免出現記憶體洩露