是否了解HandlerThread的使用?
是否了解HandlerThread的原理?
考察的知識點HandlerThread的使用
HandlerThread的原理
考生應該如何回答1、首先,我們可以圍繞HandlerThread是什麼,什麼場景下會用HandlerThread,HandlerThread怎麼用這幾個話題來概述一下。
只要有過Android開發經驗的同學都知道,在Android中,執行耗時任務的場景特別常見,比如圖片資源的下載、資料庫的增刪改查、文件IO等。為了保證APP的流暢性,這些耗時操作一般都需要放入子線程處理。這裡做法就比較多了,也是體現程式設計師級別的地方。最簡單粗暴的方法就是每次new出一個Thread去執行耗時任務,執行完任務這個線程也就銷毀了,這種做法是最不應當採取的,消耗太大。
第二種普遍的做法就是使用線程池,耗時任務來了直接丟到線程池處理,但線程池整體量級有些偏重,在項目中維持一個性能較優的線程池並非易事,它需要根據項目實際情況去調整線程池的各種參數。介於這兩者之間,Google工程師又貼心的為我們提供了一個輕量級的HandlerThread,可以用來執行多個耗時操作,而不需要多次開啟、關閉線程,內部是將Thread和Handler進行封裝實現。話不多說,直接上代碼,使用HandlerThread來執行耗時任務,看看是否優雅。
// 定義異步任務消息private final int MSG_EXECUTE_TASK = 100;
/** * 創建HandlerThread */private void initHandlerThread() { // 必須按照以下三個步驟: // 1、創建HandlerThread的實例對象 mHandlerThread = new HandlerThread("HandlerThread"); // 2、啟動我們創建的HandlerThread線程,要先start,為什麼?下面源碼會解釋 mHandlerThread.start(); // 3、將handlerThread與Handler綁定在一起。 mHandler = new Handler(mHandlerThread.getLooper()) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); // 接收到MSG_EXECUTE_TASK消息時,處理耗時任務 if (msg.what == MSG_EXECUTE_TASK) { try { Log.i(TAG, "執行耗時任務->thread:" + Thread.currentThread().getName()); //模擬耗時任務,當前線程睡3s Thread.sleep(3000); Log.i(TAG, "耗時任務執行結束->thread:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } };}
/** * 發送耗時任務消息 * @param view */public void doDBOperation(View view) { // 發送一次任務消息 mHandler.sendEmptyMessage(MSG_EXECUTE_TASK); // 10s後再發送一次任務消息 mHandler.sendEmptyMessageDelayed(MSG_EXECUTE_TASK, 10000);}
// logcat輸出如下,注意下面的時間戳// 2020-09-29 17:09:37.295 I/HandlerThreadActivity: 執行耗時任務 -> thread:HandlerThread// 2020-09-29 17:09:40.297 I/HandlerThreadActivity: 耗時任務執行結束 -> thread:HandlerThread// 2020-09-29 17:09:47.302 I/HandlerThreadActivity: 執行耗時任務 -> thread:HandlerThread// 2020-09-29 17:09:50.303 I/HandlerThreadActivity: 耗時任務執行結束 -> thread:HandlerThread例子很簡單,執行兩次耗時3s的任務,中間相隔10s,從Logcat中列印的日誌可以看出,兩次耗時任務依次都執行在HandlerThread線程,並沒有去創建新線程,符合期望。怎麼樣,使用起來是不是感覺還可以,核心步驟就是將Thread與Handler綁定起來,接下來所有操作就跟我們平時大量使用的handler一模一樣了,即發送消息與處理消息。
另外還有一點需要注意,在HandlerThread不再使用的時候需要手動關閉,調用quit()或者quitSafe()方法。
if (mHandlerThread != null) { mHandlerThread.quit(); mHandlerThread = null;}2、知其然知其所以然。從事軟體開發的我們,絕不能僅僅停留在API使用層面,必須得進入源碼,一探究竟,看看HandlerThread內部到底幹了什麼,這裡有一點需要注意,看HandlerThread的源碼之前,你必須得先搞懂Handler哦,在分析過程中有關handler的機制不會贅述了。
public class HandlerThread extends Thread { }可以看出HandlerThread繼承自Thread,所以說最終也是一個線程,當調用start方法後,便會處於就緒狀態,等待CPU的調度。
public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1;}HandlerThread的run方法其實就是創建了一個looper,然後通過looper去循環消息,等待消息的到來。到目前可以解釋上面代碼中留的一個問題了。
使用HandlerThread時,為什麼要先調用start方法?
因為在HandlerThread的run方法裡才會初始化Looper,所以如果不先讓子線程start起來,那麼下一步創建主線程的handler時,獲取looper肯定為空。
當然這裡有人肯定還有另外的疑問,上面的代碼中,出現了synchronized、notifyAll這些並發編程裡的概念,是做什麼的?不用懷疑,肯定有用處。
mHandler = new Handler(mHandlerThread.getLooper())創建Handler時,傳入的looper參數是通過mHandlerThread.getLooper()獲取的。
public Looper getLooper() { if (!isAlive()) { return null; } synchronized (this) { while (isAlive() && mLooper == null) { try { wait(); } catch (InterruptedException e) { } } } return mLooper;}可以看到,這裡是獲取Looper,與上面初始化Looper正好對應。因為mLooper在HandlerThread中執行,而我們的handler是在UI線程初始化的,也就是說,獲取looper的時候,looper有可能還沒創建好,所以通過線程的wait()與notify()的協作解決這兩個線程的同步問題。如果mLooper為null,並且線程alive的話,獲取線程會處於等待狀態,當初始化looper完成後,調用notifyAll喚醒等待線程,這樣便自動完成獲取操作了。
3、最後,總結一下,其實HandlerThread的源碼並不難,代碼量也不多,核心就是Looper的創建與獲取,只要能把上面的源碼吃透了,相信關於HandlerThread的問題肯定難不住聰明的你了。