Android 進階之HandlerThread 使用場景及原始碼解析
- 眼睛困得要死,但今天的計劃不完成又怎麼能睡呢?明日復明日,明日何其多啊!
為了避免 ANR,我們常常需要線上程中做耗時操作,然後把結果拋到主執行緒進行處理。
Android 提供了多種用於這種場景的元件,其中一種就是本篇文章要介紹的 HandlerThread。
HandlerThread 簡介
我們都知道Handler 必須要和 Looper 中結合使用,尤其在子執行緒中建立 Handler 的話,需要這樣寫:
class LooperThread extends Thread { public Handler mHandler; public void run() { Looper.prepare(); mHandler = new Handler() { public void handleMessage(Message msg) { // 這裡處理訊息 } }; Looper.loop(); }
可以看到,非常繁瑣,一層套一層看著也不美觀。
HandlerThread
就是為了幫我們免去寫上面那樣的程式碼而生的。
官方文件對它的介紹:
HandlerThread 是一個包含 Looper 的 Thread,我們可以直接使用這個 Looper 建立 Handler。
有這麼神奇?去瞅瞅它的原始碼。
HandlerThread 原始碼
HandlerThread 原始碼非常簡單,看起來 so easy:
public class HandlerThread extends Thread { int mPriority; int mTid = -1; Looper mLooper; public HandlerThread(String name) { super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } //也可以指定執行緒的優先順序,注意使用的是 android.os.Process 而不是 java.lang.Thread 的優先順序! public HandlerThread(String name, int priority) { super(name); mPriority = priority; } // 子類需要重寫的方法,在這裡做一些執行前的初始化工作 protected void onLooperPrepared() { } //獲取當前執行緒的 Looper //如果執行緒不是正常執行的就返回 null //如果執行緒啟動後,Looper 還沒建立,就 wait() 等待 建立 Looper 後 notify public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) {//迴圈等待 try { wait(); } catch (InterruptedException e) { } } } return mLooper; } //呼叫 start() 後就會執行的 run() @Override public void run() { mTid = Process.myTid(); Looper.prepare();//幫我們建立了 Looepr synchronized (this) { mLooper = Looper.myLooper(); notifyAll();//Looper 已經建立,喚醒阻塞在獲取 Looper 的執行緒 } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop();//開始迴圈 mTid = -1; } 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; } public int getThreadId() { return mTid; } }
可以看到,①HandlerThread 本質還是個 Thread,建立後別忘了呼叫 start()
。
②在 run()
方法中建立了 Looper,呼叫 onLooperPrepared
後開啟了迴圈
③我們要做的就是在子類中重寫 onLooperPrepared
,做一些初始化工作
④在建立 HandlerThread 時可以指定優先順序,注意這裡的引數是 Process.XXX
而不是 Thread.XXX
Process.setThreadPriority(int) A Linux priority level, from -20 for highest scheduling priority to 19 for lowest scheduling priority.
可選的值如下:
public static final int THREAD_PRIORITY_DEFAULT = 0; public static final int THREAD_PRIORITY_LOWEST = 19; public static final int THREAD_PRIORITY_BACKGROUND = 10; public static final int THREAD_PRIORITY_FOREGROUND = -2; public static final int THREAD_PRIORITY_DISPLAY = -4; public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; public static final int THREAD_PRIORITY_AUDIO = -16;
HandlerThread 的使用場景
我們知道,HandlerThread 所做的就是在新開的子執行緒中建立了 Looper,那它的使用場景就是 Thread + Looper 使用場景的結合,即: 在子執行緒中執行耗時的、可能有多個任務的操作 。
比如說多個網路請求操作,或者多檔案 I/O 等等。
使用 HandlerThread 的典型例子就是 IntentService
,我們下篇文章介紹它。
舉個栗子
我們寫一個使用 HandlerThread
實現子執行緒完成多個下載任務的 demo。
先建立一個 HandlerThread
子類,它有兩個 Handler
型別的成員變數,一個是用於在子執行緒傳遞、執行任務,另一個用於外部傳入,在主執行緒顯示下載狀態:
/** * Description: * <br> 繼承 HandlerThread 模擬下載執行緒 * <p> * <br> Created by shixinzhang on 17/6/7. * <p> * <br> Email: [email protected] * <p> * <ahref="https://about.me/shixinzhang">About me</a> */ public class DownloadThread extends HandlerThread implements Handler.Callback { private final String TAG = this.getClass().getSimpleName(); private final String KEY_URL = "url"; public static final int TYPE_START = 1; public static final int TYPE_FINISHED = 2; private Handler mWorkerHandler; private Handler mUIHandler; private List<String> mDownloadUrlList; public DownloadThread(final String name) { super(name); } @Override protected void onLooperPrepared() {//執行初始化任務 super.onLooperPrepared(); mWorkerHandler = new Handler(getLooper(), this);//使用子執行緒中的 Looper if (mUIHandler == null) { throw new IllegalArgumentException("Not set UIHandler!"); } //將接收到的任務訊息挨個新增到訊息佇列中 for (String url : mDownloadUrlList) { Message message = mWorkerHandler.obtainMessage(); Bundle bundle = new Bundle(); bundle.putString(KEY_URL, url); message.setData(bundle); mWorkerHandler.sendMessage(message); } } public void setDownloadUrls(String... urls) { mDownloadUrlList = Arrays.asList(urls); } public Handler getUIHandler() { return mUIHandler; } //注入主執行緒 Handler public DownloadThread setUIHandler(final Handler UIHandler) { mUIHandler = UIHandler; return this; } /** * 子執行緒中執行任務,完成後傳送訊息到主執行緒 * * @param msg * @return */ @Override public boolean handleMessage(final Message msg) { if (msg == null || msg.getData() == null) { return false; } String url = (String) msg.getData().get(KEY_URL); //下載開始,通知主執行緒 Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 開始下載 @" + DateUtils.getCurrentTime() + "\n" + url); mUIHandler.sendMessage(startMsg); SystemClock.sleep(2000);//模擬下載 //下載完成,通知主執行緒 Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下載完成 @" + DateUtils.getCurrentTime() + "\n" + url); mUIHandler.sendMessage(finishMsg); return true; } @Override public boolean quitSafely() { mUIHandler = null; return super.quitSafely(); } }
可以看到, DownloadThread
中做了以下工作:
- 建立一個子執行緒
Handler
- 然後在
onLooperPrepared()
中初始化 Handler,使用的是 HandlerThread 建立的 Looper- 同時將外部傳入的下載 url 以
Message
的方式傳送到子執行緒中的MessageQueue
中
- 同時將外部傳入的下載 url 以
- 這樣當呼叫
DownloadThread.start()
時,子執行緒中的Looper
開始工作,會按順序取出訊息佇列中的佇列處理,然後呼叫子執行緒的 Handler 處理 - 也就是上面的
handleMessage()
方法,在這個方法中進行耗時任務 - 然後通過
mUIHandler
將下載狀態資訊傳遞到主執行緒
呼叫 Activity 的程式碼:
/** * Description: * <br> HandlerThread 示例程式 * <p> * <br> Created by shixinzhang on 17/6/7. * <p> * <br> Email: [email protected] * <p> * <ahref="https://about.me/shixinzhang">About me</a> */ public class HandlerThreadActivity extends AppCompatActivity implements Handler.Callback { @BindView(R.id.tv_start_msg) TextView mTvStartMsg; @BindView(R.id.tv_finish_msg) TextView mTvFinishMsg; @BindView(R.id.btn_start_download) Button mBtnStartDownload; private Handler mUIHandler; private DownloadThread mDownloadThread; @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_thread_test); ButterKnife.bind(this); init(); } private void init() { mUIHandler = new Handler(this); mDownloadThread = new DownloadThread("下載執行緒"); mDownloadThread.setUIHandler(mUIHandler); mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ", "http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?"); } @OnClick(R.id.btn_start_download) public void startDownload() { mDownloadThread.start(); mBtnStartDownload.setText("正在下載"); mBtnStartDownload.setEnabled(false); } //主執行緒中的 Handler 處理訊息的方法 @Override public boolean handleMessage(final Message msg) { switch (msg.what) { case DownloadThread.TYPE_FINISHED: mTvFinishMsg.setText(mTvFinishMsg.getText().toString() + "\n " + msg.obj); break; case DownloadThread.TYPE_START: mTvStartMsg.setText(mTvStartMsg.getText().toString() + "\n " + msg.obj); break; } return true; } }
佈局檔案:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_horizontal" android:orientation="vertical" android:padding="8dp"> <TextView android:id="@+id/tv_start_msg" android:layout_width="match_parent" android:layout_height="200dp" android:text="下載開始資訊"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorAccent"/> <TextView android:id="@+id/tv_finish_msg" android:layout_width="match_parent" android:layout_height="200dp" android:layout_marginTop="8dp" android:text="下載完成資訊"/> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="@color/colorAccent"/> <Button android:id="@+id/btn_start_download" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="開始下載"/> </LinearLayout>
重點是 init()
方法(其中有福利,你懂得):
private void init() { mUIHandler = new Handler(this); mDownloadThread = new DownloadThread("下載執行緒"); mDownloadThread.setUIHandler(mUIHandler); mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ", "http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?"); }
在這個方法中我們建立一個 DownloadThread
,也就是 HandlerThread,然後傳入 UI 執行緒中的 Handler。
最後在按鈕的點選事件中呼叫了 start()
方法。
執行結果

總結

image
上面的例子中 HandlerThread
配合一個主執行緒 Handler
完成了在子執行緒中序列執行任務,同時在主執行緒中反饋狀態的功能。
如果用一句話總結 HandlerThread
的特點:
- 它就是一個幫我們建立 Looper 的執行緒,讓我們可以直接線上程中使用 Handler 來處理非同步任務。
程式碼地址
喜歡的話請幫忙轉發一下能讓更多有需要的人看到吧,有些技術上的問題大家可以多探討一下。


以上Android資料以及更多Android相關資料及面試經驗可在QQ群裡獲取:936903570。有加群的朋友請記得備註上簡書,謝謝。