handler是安卓中通信的常用東西御毅,雖然常用明白其中的原理相當重要漂洋,本文記錄方便后面自己鞏固每界!
基本使用
/**
* 防止內(nèi)存泄漏
*/
open class ChildHandler : Handler()
/**
* 一般的消息發(fā)送
*/
messageSend.setOnClickListener {
val handler = @SuppressLint("HandlerLeak")
object : ChildHandler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
val par1 = msg?.what
val par2 = msg?.arg1
val par3 = msg?.arg2
val par4 = msg?.data?.getString("key1")
val par5 = (msg?.obj as HandlerData).info
Log.e("消息結(jié)果:", "$par1 $par2 $par3 $par4 $par5")
}
}
Thread(Runnable {
handler.sendMessage(getMessage())
SystemClock.sleep(2000)
handler.postDelayed({ handler.sendMessage(getMessage()) }, 0)
SystemClock.sleep(2000)
handler.post { toast("我是子線程中的post") }
}).start()
}
}
handler的使用其實很簡單捷泞,可以發(fā)送的類別基本如下:
fun getMessage(): Message {
val msg = Message()
msg.what = 1
msg.arg1 = 2
msg.arg2 = 3
val bundle = Bundle()
bundle.putString("key1", "4")
msg.data = bundle
msg.obj = HandlerData("5")
return msg
}
獲取Message載體也可以使用下面這種方法:
fun getMessageInfo(handler: Handler): Message {
return handler.obtainMessage()
}
與new Message的區(qū)別就是:
obtainmessage()是從消息池中拿來一個msg 不需要另開辟空間new
new需要重新申請声搁,效率低黑竞,obtianmessage可以循環(huán)利用
上面在一個子線程中有這樣一句代碼:
handler.post { toast("我是子線程中的post") }
}).start()
為什么可以在子線程更新UI呢?看起代碼似乎是在子線程疏旨,其實不然很魂,整個更新UI的操作還是在主線程,有興趣的可以參考這篇文章檐涝,總結(jié)的很好:
handler.post方法的終極最直觀的理解與解釋
在主線程中使用handler
獲取handler對象
/**
* handler接受消息
*/
private fun initData() {
val handler = @SuppressLint("HandlerLeak")
object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
msg?.what?.let { Toast.makeText(this@MainActivity, "主線程接受到消息----$it", Toast.LENGTH_SHORT).show() }
}
}
}
這個handler是new在主線程中的屬于主線程的handler
發(fā)送消息
/**
* 主線程通信 handler屬于主線程
*/
findViewById<AppCompatButton>(R.id.sendMessage).setOnClickListener {
Thread(Runnable {
SystemClock.sleep(5000)
handler!!.sendEmptyMessage(0)
}).start()
}
這里是點擊按鈕后開啟一個子線程遏匆,并且在子線程沉睡5秒后發(fā)送一個消息
結(jié)果
按照Toast彈出了正確的顯示框
在子線程中使用handler
findViewById<Button>(R.id.childThread).setOnClickListener { view ->
Thread(Runnable {
// SystemClock.sleep(5000)
Looper.prepare()
mHandlerThread = @SuppressLint("HandlerLeak")
object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
msg?.what?.let {
//(view as Button).text="子線程接受到消息----$it"
Toast.makeText(this@MainActivity, "子線程接受到消息----$it", Toast.LENGTH_SHORT).show()
}
}
}
Looper.loop()
}).start()
SystemClock.sleep(5000)
mHandlerThread!!.sendEmptyMessage(0)
}
同樣是點擊一個按鈕后開啟一個線程并且獲取handler對象,在點擊按鈕后主線程沉睡5秒后給子線程發(fā)送一個消息谁榜,并且顯示出消息
結(jié)果
正確的顯示出消息
注意其中有句代碼
//(view as Button).text="子線程接受到消息----$it"
這是來檢測是否在子線程中幅聘,所以更新ui失敗證明了是在子線程中。
源碼解讀
主線程中使用handler是沒有什么問題的窃植,但是在子線程中使用handler多了兩句代碼分別是:
Looper.prepare()
Looper.loop()
Looper.prepare()
先來分析Looper.prepare()帝蒿,進入源碼可以看到:
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
再往下
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
這里我們看到了一個sThreadLocal,他的定義在里面是這樣的:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
ThreadLocal并不是線程巷怜,它的作用是可以在每個線程中存儲數(shù)據(jù)葛超,這里儲存的就是一個Looper。
我們繼續(xù)分析looper延塑,當上面的sThreadLocal.set(new Looper(quitAllowed))執(zhí)行時候巩掺,他會走到這里:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
這里獲取一個looper對象的時候需要一個MessageQueue對象,這個MessageQueue就是來獲取消息的對象和一個線程對象页畦,再看looper類的定義
public final class Looper {
//省略代碼...
final MessageQueue mQueue;
final Thread mThread;
}
到這里基本上就可以明白
使用Looper.prepare()這個方法是在當前的線程中獲取到是否有l(wèi)ooper對象胖替,如果沒有的話就重新設(shè)置一個looper到當前的對象,并將這個對象與當前線程進行綁定豫缨,其中l(wèi)ooper對象里面包含了一個MessageQueue(消息隊列)
Looper.loop()
我們繼續(xù)分析looper在線程中的作用独令,進入Looper.loop()源碼可以看到:
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...省略代碼...
for (;;) { ...省略代碼...}
}
其中的myLooper()方法是:
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
這里可以恍然大悟,這里獲取的looper就是前面我們調(diào)用Looper.prepare()設(shè)置的looper好芭,并且將當前的這個looper中的MessageQueue(消息隊列)對象拿了出來燃箭,再往下走是一個死循環(huán)代碼:
for (;;) { ...省略代碼...}
這個循環(huán)里做的事其實就是用當前l(fā)ooper中去循環(huán)的取出MessageQueue中的Message,并將這個message交給相應(yīng)的handler進行處理舍败,其他線程傳過來的消息是放在message中的招狸,這里再對相應(yīng)的幾個對象做下說明
Handler:線程間通信的方式敬拓,主要用來發(fā)送消息及處理消息 ,消息的發(fā)送是不區(qū)分線程的裙戏,但是消息的接受是要區(qū)分線程的
Looper:為線程運行消息循環(huán)的類乘凸,循環(huán)取出MessageQueue中的Message;消息派發(fā)累榜,將取出的Message交付給相應(yīng)的Handler营勤。
MessageQueue:存放通過Handler發(fā)過來的消息,遵循先進先出原則壹罚。
Message:消息葛作,線程間通信通訊攜帶的數(shù)據(jù)。
結(jié)論:
一個線程需要接受其他線程傳遞過來的消息猖凛,必須其中有一個與當前線程進行綁定的looper消息循環(huán)器和一個處理消息的handler赂蠢,這個looper中包含了一個MessageQueue,這個MessageQueue中包含的就是其他線程傳遞過來的Message消息對象辨泳,這個looper是一直在循環(huán)的取出消息隊列中的消息客年,并將這個消息信息傳遞給當前線程中的handler對象進行處理,handler消息的發(fā)送是不區(qū)分線程的漠吻,但是消息的接受是要區(qū)分線程的量瓜,當前handler在哪個線程中就在哪個線程中處理消息!
主線程中使用handler為什么代碼中不用寫Looper.prepare()途乃,Looper.loop()绍傲?
我們找到整個項目的函數(shù)入口,代碼如下:
public static void main(String[] args) {
...省略代碼...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
進入到Looper.prepareMainLooper():
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
進入 prepare(false)
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
這里就很清楚了耍共,這里其實就已經(jīng)準備好了looper烫饼,再進入上邊的myLooper():
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
其實這里返回的就是上邊準備的looper,主函數(shù)入口處并且有了Looper.loop()试读,這里大家就清楚了為什么主線程不需要操作looper了吧杠纵,因為主線程已經(jīng)準備好了looper,并且準備的方式跟我們上面再子線程中準備的方式是一樣的钩骇!