HandlerThread解析及應用
本文我們講解的是HandlerThread,可能有部分同學不瞭解或者沒用過這個類。原因很簡單,在如今大神們的框架面前,許多的原生類已經不為人所用了,然而早期開發者要想在子執行緒裡更新UI則需要在Thread裡建立一個Handler。
class MyThread : Thread() {
private lateinit var mHandler: Handler
override fun run() {
super.run()
Looper.prepare()
mHandler = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
if(msg?.what==0){
//...處理訊息
}
}
}
Looper.loop()
}
}
複製程式碼
是不是認為很難受?每次要想在子執行緒裡面處理資訊必須每次在子執行緒裡建立Handler?
然而貼心的Google工程師為我們提供了一個自帶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.
*/
public class HandlerThread extends Thread {
...
}
複製程式碼
- 由它的註釋可以看出,這是一個帶有
Looper
的Thread
,這個Looper
可以被用於建立Handler
,請注意,start()
這個方法仍然需要被呼叫。 - 總結一下,這個類就是自帶Handler的執行緒類。
構造方法
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的。
//這是可選的優先順序
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_VIDEO = -10;
public static final int THREAD_PRIORITY_AUDIO = -16;
public static final int THREAD_PRIORITY_URGENT_AUDIO = -19;
public static final int THREAD_PRIORITY_MORE_FAVORABLE = -1;
public static final int THREAD_PRIORITY_LESS_FAVORABLE = +1;
複製程式碼
其他方法
/*
在Looper.loop()之前呼叫的方法,如需要配置可重寫
*/
protected void onLooperPrepared() {
}
/*
來自Thread的run方法,呼叫與start()之後
*/
@Override
public void run() {
mTid = Process.myTid();//執行緒id
Looper.prepare();//建立子執行緒的Looper
synchronized (this) {
mLooper = Looper.myLooper();//獲取Looper例項
notifyAll();// native方法,用於喚醒所有等待獲取Looper的執行緒
}
Process.setThreadPriority(mPriority);//設定執行緒優先順序
onLooperPrepared();//上面方法的呼叫
Looper.loop();//Looper開始輪詢
mTid = -1;
}
/*
獲取Looper
*/
public Looper getLooper() {
//判斷執行緒是否啟動
if (!isAlive()) {
return null;
}
synchronized (this) {
while (isAlive() && mLooper == null) {//如果執行緒啟動,且Looper為空
try {
wait();//使其等待直至獲取到Looper
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
/*
獲取Handler
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
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不使用時記得使用 getLooper().quit()來退出這個執行緒,但需要注意的是quit和quitSafely的區別,接下來我將用較短的篇幅介紹一下這兩個方法的區別。
quit()和quitSafely()的區別
HandlerThread:
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在這兩個方法中呼叫了quit()
和quitSafely()
方法。我們點開原始碼檢視,他們本質都是在呼叫MessageQueue
中quit
方法,不同的是quit方法的引數不同:
Looper:
quitSafely:
public void quit() {
mQueue.quit(true);
}
複製程式碼
quit:
public void quit() {
mQueue.quit(false);
}
複製程式碼
如果大家對訊息機制有所瞭解,那麼一定知道MessageQueue
是用於處理Message(訊息)的佇列,如果不知道的可以去了解一下。話不多說,接下來我們將深入MessageQueue
瞭解一下quit(boolean safe)
方法是何方神聖。
MessageQueue:
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
if (safe) {//敲黑板!這就是quitSafely和quit的區別
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
複製程式碼
- 當我們在
Looper
中呼叫quitSafely()
時,在MessageQueue
裡呼叫的removeAllFutureMessagesLocked()
方法 - 當我們在
Looper
中呼叫quit()
時,在MessageQueue
裡呼叫的是removeAllMessagesLocked()
方法 - 而這兩個方法本質的區別就是在於remove訊息的範圍,下面是一段虛擬碼:
if (safe) {
只清空佇列中的延時訊息(通過postDelay傳送的),非延時訊息繼續派發,直到完成
} else {
移除所有訊息,包括延時訊息
}
複製程式碼
所以說safe的區別就是是否清除所有訊息,這兩個方法的實現我們就不深究了,畢竟本文是為了講解HandlerThread而不是資料結構,若有興趣的可以去自行研究。
應用
IntentService
的底層就是利用HandlerThread
- 舉個例子
Activity:
class MainActivity : AppCompatActivity() ,Handler.Callback{
private lateinit var mUIHandler :Handler
private lateinit var mDownloadThread: DownloadThread
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
}
private fun init(){
mUIHandler = Handler(this)
val list = mutableListOf<String>()
list.add("url1")
list.add("url2")
list.add("url3")
mDownloadThread = DownloadThread("Download")
.setUrls(list)
.setUIHandler(mUIHandler)
mDownloadThread.start()
Log.d("test","開始下載")
}
override fun handleMessage(msg: Message?): Boolean {
when(msg?.what){
DownloadThread.START->{
Log.d("test","Activity接收到了START資訊")
}
DownloadThread.FINISH->{
Log.d("test","Activity接收到了FINISH資訊")
}
}
return true
}
}
複製程式碼
DownloadThread:
class DownloadThread(name: String?) : HandlerThread(name), Handler.Callback {
private lateinit var mWorkHandler: Handler
private lateinit var mUIHandler: Handler
private lateinit var urls: List<String>
companion object {
const val START = 1
const val FINISH = 2
const val KEY = "getUrl"
}
fun setUIHandler(mUIHandler: Handler): DownloadThread {
this.mUIHandler = mUIHandler
return this
}
fun setUrls(urls: List<String>): DownloadThread {
this.urls = urls
return this
}
override fun onLooperPrepared() {
super.onLooperPrepared()
if (looper != null)
mWorkHandler = Handler(looper, this)
//在這裡傳送下載資訊
urls.forEach { url->
val message = mWorkHandler.obtainMessage()
val bundle = Bundle()
bundle.putString(KEY,url)
message.data = bundle
mWorkHandler.sendMessage(message)
}
}
//mWorkHandler的handleMessage
override fun handleMessage(msg: Message?): Boolean {
if (msg == null || msg.data == null) return false
val url = msg.data.get(KEY) as String//獲取url
val startMessage: Message = mUIHandler.obtainMessage(START)
mUIHandler.sendMessage(startMessage)
Log.d("test","$url :Thread傳送START資訊")
Thread.sleep(2000)
Log.d("test","$url :Thread執行耗時操作中...")
val finishMessage: Message = mUIHandler.obtainMessage(FINISH)
mUIHandler.sendMessage(finishMessage)
Log.d("test","$url :Thread傳送FINISH資訊")
return true
}
}
複製程式碼
結果:
2019-01-30 20:47:47.743 D/test: 開始下載
2019-01-30 20:47:47.744 D/test: url1 :Thread傳送START資訊
2019-01-30 20:47:47.776 D/test: Activity接收到了START資訊
2019-01-30 20:47:49.746 D/test: url1 :Thread執行耗時操作中...
2019-01-30 20:47:49.747 D/test: url1 :Thread傳送FINISH資訊
2019-01-30 20:47:49.747 D/test: Activity接收到了FINISH資訊
2019-01-30 20:47:49.747 D/test: url2 :Thread傳送START資訊
2019-01-30 20:47:49.747 D/test: Activity接收到了START資訊
2019-01-30 20:47:51.748 D/test: url2 :Thread執行耗時操作中...
2019-01-30 20:47:51.749 D/test: url2 :Thread傳送FINISH資訊
2019-01-30 20:47:51.750 D/test: Activity接收到了FINISH資訊
2019-01-30 20:47:51.749 D/test: url3 :Thread傳送START資訊
2019-01-30 20:47:51.750 D/test: Activity接收到了START資訊
2019-01-30 20:47:53.750 D/test: url3 :Thread執行耗時操作中...
2019-01-30 20:47:53.751 D/test: url3 :Thread傳送FINISH資訊
2019-01-30 20:47:53.751 D/test: Activity接收到了FINISH資訊
複製程式碼
總結
上面就是利用HandlerThread
在子執行緒中執行序列任務,並反饋到主執行緒的栗子。總的來說HandlerThread的知識點就是這些,若有錯誤或者遺漏歡迎指出。