android 使用post傳值 獲取的值為null_面試官:能說說HandlerThread的原理和使用場景嗎?...
技術標籤:android 使用post傳值 獲取的值為null
初次看到HandlerThread的名字,我們可能會聯想到Handler和Thread這兩個類,沒錯,它其實就是跟Handler和Thread有莫大的關係。HandlerThread繼承自Thread,它本質上就是一個Thread,而且專門用來處理Handler的訊息。
一、HandlerThread簡介
看看官方對它的解釋:
Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.
大致就是說HandlerThread可以建立一個帶有looper的執行緒,looper物件可以用於建立Handler類來進行來進行排程,而且start()方法必須被呼叫。
在Android開發中,不熟悉多執行緒開發的人一想到要使用執行緒,可能就用new Thread(){…}.start()這樣的方式。實質上在只有單個耗時任務時用這種方式是可以的,但若是有多個耗時任務要序列執行呢?那不得要多次建立多次銷燬執行緒,這樣導致的代價是很耗系統資源,容易存在效能問題。那麼,怎麼解決呢?
我們可以只建立一個工作執行緒,然後在裡面迴圈處理耗時任務,建立過程如下:
Handler mHandler;private void createWorkerThread() { new Thread() { @Override public void run() { super.run(); Looper.prepare(); mHandler = new Handler(Looper.myLooper()) { @Override public void handleMessage(Message msg) { ...... } }; Looper.loop(); } }.start();}複製程式碼
在該工作執行緒中:
- 呼叫 Looper.prepare()建立與當前執行緒繫結的Looper例項;
- 使用上面建立的Looper生成Handler例項;
- 呼叫Looper.loop()實現訊息迴圈;
然後透過Looper的迴圈,在Handler的handlerMessage()中進行非同步任務的迴圈處理。而這也正好是HandlerThread的實現。
@Overridepublic void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1;}複製程式碼
- HandlerThread本質上是一個執行緒類,它繼承了Thread;
- HandlerThread有自己的內部Looper物件,通過Looper.loop()進行looper迴圈;
- 通過獲取HandlerThread的looper物件傳遞給Handler物件,然後在handleMessage()方法中執行非同步任務;
- 建立HandlerThread後必須呼叫HandlerThread.start()方法來啟動執行緒。
二、HandlerThread使用步驟
1、建立HandlerThread例項物件
HandlerThread handlerThread = new HandlerThread("Handler Thread");//HandlerThread handlerThread = new HandlerThread("Handler Thread",Process.THREAD_PRIORITY_DEFAULT);複製程式碼
HandlerThread預設有兩個建構函式,提供了執行緒名引數和執行緒優先順序引數的設定。
2、啟動HandlerThread執行緒
handlerThread.start();複製程式碼
通過start()方法就可以啟動一個HandlerThread了,該執行緒會不斷地迴圈執行。
3、通過Handler構建迴圈訊息處理機制
Handler workderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { //執行在工作執行緒(子執行緒)中,用於實現自己的訊息處理 return true; } });複製程式碼
通過將HandlerThread繫結的Looper物件傳遞給Handler作為引數,構建一個非同步的Handler物件,為了能實現耗時任務的非同步執行,我們重寫了Handler的Callback介面的handleMessage()方法,當然也可以不重寫該方法,而通過post()方法進行耗時任務操作。
Handler workderHandler = new Handler(handlerThread.getLooper());workderHandler.post(new Runnable() { @Override public void run() { //執行在工作執行緒(子執行緒)中,用於實現自己的訊息處理 }});複製程式碼
最後,我們就可以通過呼叫workerHandler以傳送訊息的形式傳送耗時任務到工作執行緒HandlerThread中去執行,實際上就是在Handler.Callback裡的handleMessage()中執行。
這裡要注意,在建立Handler作為HandlerThread執行緒訊息執行者的時候必須先呼叫start()方法,因為建立Handler所需要的Looper引數是從HandlerThread中獲得的,而Looper物件的賦值又是在HandlerThread的run()方法中建立。
三、HandlerThread使用例項
import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.TextView;/** * Created by Administrator on 2016/9/18. */public class HandlerThreadActivity extends Activity implements Handler.Callback { private DBHandlerThread mDBHandlerThread; private Handler mUIHandler; //與UI執行緒相關聯的Handler Button mBtnQuery; TextView mTextResult; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mBtnQuery = (Button) findViewById(R.id.buttonQuery); mTextResult = (TextView) findViewById(R.id.result); mBtnQuery.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //將非同步耗時任務傳送到HandlerThread中 //Message msg = Message.obtain(null, DBHandlerThread.MSG_QUERY_FRIENDS); //mDBHandlerThread.getWorkerHandler().sendMessage(msg); mDBHandlerThread.queryFriends(); } }); mUIHandler = new Handler(this); initWorkerThread(); } protected void initWorkerThread() { mDBHandlerThread = new DBHandlerThread("Handler Thread"); mDBHandlerThread.setUIHandlerCallBack(mUIHandler); mDBHandlerThread.start(); //start()後會執行Thread的run()方法 } @Override protected void onDestroy() { super.onDestroy(); mDBHandlerThread.setUIHandlerCallBack(null); mDBHandlerThread.quit(); mDBHandlerThread = null; } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case DBHandlerThread.MSG_QUERY_FRIENDS: // update UI break; } return false; }}複製程式碼
DBHandlerThread類如下:
import android.os.Handler;import android.os.HandlerThread;import android.os.Message;/** * Created by Administrator on 2016/9/18. */public class DBHandlerThread extends HandlerThread implements Handler.Callback { public static final int MSG_QUERY_FRIENDS = 100; private Handler mWorkerHandler; //與工作執行緒相關聯的Handler private Handler mUIHandler; //與UI執行緒相關聯的Handler public DBHandlerThread(String name) { super(name); } public DBHandlerThread(String name, int priority) { super(name, priority); } public void setUIHandlerCallBack(Handler handler) { this.mUIHandler = handler; } public Handler getWorkerHandler() { return mWorkerHandler; } @Override protected void onLooperPrepared() { mWorkerHandler = new Handler(getLooper(), this); } @Override public boolean handleMessage(Message msg) { switch (msg.what) { case MSG_QUERY_FRIENDS: //...查詢資料庫操作... Message message = Message.obtain(null, MSG_QUERY_FRIENDS); mUIHandler.sendMessage(message); //通知UI更新 break; } return true; } public void queryFriends() { Message msg = Message.obtain(null, MSG_QUERY_FRIENDS); mWorkerHandler.sendMessage(msg); }}複製程式碼
四、HandlerThread原始碼解析
HandlerThread的原始碼不多,先看下它的建構函式:
/** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. */public class HandlerThread extends Thread { int mPriority; //執行緒優先順序 int mTid = -1; //當前執行緒id //當前執行緒持有的Looper物件 Looper mLooper; public HandlerThread(String name) { //呼叫父類預設的方法建立執行緒 super(name); mPriority = Process.THREAD_PRIORITY_DEFAULT; } //帶優先順序引數的構造方法 public HandlerThread(String name, int priority) { super(name); mPriority = priority; } ...............}複製程式碼
從程式碼中得知,HandlerThread帶有兩個建構函式,可傳遞兩個引數,一個引數是name,指的是執行緒的名稱,另一個引數是priority,指的是執行緒優先順序。執行緒的優先順序的取值範圍為-20到19。優先順序高的獲得的CPU資源更多,反之則越少。-20代表優先順序最高,19最低。 我們可以根據自己的需要去設定執行緒的優先順序,也可以採用預設的優先順序,HandlerThread的預設優先順序是Process.THREAD_PRIORITY_DEFAULT,具體值為0。該優先順序是再run()方法中設定的,我們看它的run()方法:
public class HandlerThread extends 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(); //獲得當前執行緒的id Looper.prepare(); //準備迴圈條件 //通過鎖機制來獲得當前執行緒的Looper物件 synchronized (this) { mLooper = Looper.myLooper(); //喚醒等待執行緒 notifyAll(); } //設定當前執行緒的優先順序 Process.setThreadPriority(mPriority); //線上程迴圈之前做一些準備工作(子類可實現也可不實現) onLooperPrepared(); //啟動loop Looper.loop(); mTid = -1; }}複製程式碼
run()方法主要是通過Looper.prepare()和Looper.loop()構造了一個迴圈執行緒。這裡要注意,在建立HandlerThread物件後必須呼叫其start()方法才能進行run()方法體的執行。
在Looper.prepare()執行後,Looper物件會被建立,然後通過同步鎖機制,將Looper物件賦值給HandlerThread的內部變數mLooper,並通過notifyAll()方法去喚醒等待執行緒。接著為執行緒賦予優先順序,然後執行onLooperPrepared()方法,該方法是一個空實現,留給我們必要的時候去重寫的,主要用來做一些初始化工作。最後通過執行Looper.loop()線上程中啟動訊息佇列。
我們看到在run()方法中進行了喚醒等待執行緒,為什麼要這麼做呢?答案就在getLooper()方法中:
/** * This method returns the Looper associated with this thread. If this thread not been started * or for any reason is isAlive() returns false, this method will return null. If this thread * has been started, this method will block until the looper has been initialized. * @return The looper. */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(); //等待,直到另一個執行緒呼叫notify()或notifyAll()來喚醒它 } catch (InterruptedException e) { } } } return mLooper;}複製程式碼
該方法用來獲取當前子執行緒HandlerThread所關聯的Looper物件例項。首先判斷HandlerThread執行緒是否存活,如果沒有存活就直接返回null,否則繼續執行,進入同步塊並判斷Looper物件是否為空以及執行緒是否啟動,若都滿足,則呼叫wait()方法進入阻塞階段,直到Looper物件被成功建立並且通過notifyAll()方法喚醒該等待執行緒,最後才返回該Looper物件。
Looper物件的建立是在run()方法進行的,也即在子執行緒中執行的,而getLooper()方法是在UI執行緒中呼叫的,若不使用等待喚醒機制,我們就無法保證在UI執行緒中呼叫getLooper()方法時Looper物件已經被建立,會面臨一個同步的問題,所以HandlerThread就通過等待喚醒機制來解決該同步問題。
HandlerThread既然是一個迴圈執行緒,那麼怎麼退出呢?有兩種方式,分別是不安全的退出方法quit()和安全的退出方法quitSafely():
/** * Quits the handler thread's looper. *
* Causes the handler thread's looper to terminate without processing any * more messages in the message queue. *
* Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. *
* Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure * that all pending work is completed in an orderly manner. *
* * @return True if the looper has been asked to quit or false if the * thread had not yet started running. * * @see #quitSafely */public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false;}複製程式碼
/** * Quits the handler thread's looper safely. *
* Causes the handler thread's looper to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * Pending delayed messages with due times in the future will not be delivered. *
* Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. *
* If the thread has not been started or has finished (that is if * {@link #getLooper} returns null), then false is returned. * Otherwise the looper is asked to quit and true is returned. *
* * @return True if the looper has been asked to quit or false if the * thread had not yet started running. */public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false;}複製程式碼
通過檢視開發者文件知道,quit()方法在API Level 5時被新增,而quitSafely()方法在API Level 18時才被新增。
quit()方法,主要是把MessageQueue中所有的訊息全部清空,無論是延遲訊息還是非延遲訊息。
而quitSafely()方法只會清空MessageQueue中所有的延遲訊息,並將所有的非延遲訊息繼續分發出去,最後等到Handler處理完後才停止Looper迴圈。
這裡的延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法傳送的訊息。
五、HandlerThread應用場景
HandlerThread比較適用於單執行緒+非同步佇列的場景,比如IO讀寫操作資料庫、檔案等,耗時不多而且也不會產生較大的阻塞。對於網路IO操作,HandlerThread並不適合,因為它只有一個執行緒,還得排隊一個一個等著。
這裡單執行緒+非同步佇列模型的使用場景還舉幾個例子:
- 應用剛啟動時的資料初始化操作,如果開啟多個執行緒同時執行,有可能爭奪UI執行緒的CPU執行時間,造成卡頓,而使用該模型,通過設定優先順序就可以將同步工作順序的執行,而又不影響UI的初始化;
- 2.從資料庫中讀取資料展現在ListView中,通過Handler的postAtFrontOfQueue方法,快速將讀取操作加入佇列前端執行,必要時返回給主執行緒更新UI;
- 3.HandlerThread應用在IntentService中,IntentService是一個需要長時間執行任務Service,優先順序要比執行緒高。
最後
有需要更多資料的朋友可以私信我【進階】我可以分享給你。
直接點選即可領取
Android學習PDF+架構視訊+面試文件+原始碼筆記
如果你有其他需要的話,也可以在 GitHub 上檢視,下面的資料也會陸續上傳到Github
330頁PDFAndroid核心筆記