handler是安卓中通信的常用东西,虽然常用明白其中的原理相当重要,本文记录方便后面自己巩固!
基本使用
/**
* 防止内存泄漏
*/
open class ChildHandler : Handler()
/**
* 一般的消息发送
*/
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("消息结果:", "$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的使用其实很简单,可以发送的类别基本如下:
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的区别就是:
obtainmessage()是从消息池中拿来一个msg 不需要另开辟空间new
new需要重新申请,效率低,obtianmessage可以循环利用
上面在一个子线程中有这样一句代码:
handler.post { toast("我是子线程中的post") }
}).start()
为什么可以在子线程更新UI呢?看起代码似乎是在子线程,其实不然,整个更新UI的操作还是在主线程,有兴趣的可以参考这篇文章,总结的很好:
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
发送消息
/**
* 主线程通信 handler属于主线程
*/
findViewById<AppCompatButton>(R.id.sendMessage).setOnClickListener {
Thread(Runnable {
SystemClock.sleep(5000)
handler!!.sendEmptyMessage(0)
}).start()
}
这里是点击按钮后开启一个子线程,并且在子线程沉睡5秒后发送一个消息
结果
按照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秒后给子线程发送一个消息,并且显示出消息
结果
正确的显示出消息
注意其中有句代码
//(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并不是线程,它的作用是可以在每个线程中存储数据,这里储存的就是一个Looper。
我们继续分析looper,当上面的sThreadLocal.set(new Looper(quitAllowed))执行时候,他会走到这里:
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()这个方法是在当前的线程中获取到是否有looper对象,如果没有的话就重新设置一个looper到当前的对象,并将这个对象与当前线程进行绑定,其中looper对象里面包含了一个MessageQueue(消息队列)
Looper.loop()
我们继续分析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就是前面我们调用Looper.prepare()设置的looper,并且将当前的这个looper中的MessageQueue(消息队列)对象拿了出来,再往下走是一个死循环代码:
for (;;) { ...省略代码...}
这个循环里做的事其实就是用当前looper中去循环的取出MessageQueue中的Message,并将这个message交给相应的handler进行处理,其他线程传过来的消息是放在message中的,这里再对相应的几个对象做下说明
Handler:线程间通信的方式,主要用来发送消息及处理消息 ,消息的发送是不区分线程的,但是消息的接受是要区分线程的
Looper:为线程运行消息循环的类,循环取出MessageQueue中的Message;消息派发,将取出的Message交付给相应的Handler。
MessageQueue:存放通过Handler发过来的消息,遵循先进先出原则。
Message:消息,线程间通信通讯携带的数据。
结论:
一个线程需要接受其他线程传递过来的消息,必须其中有一个与当前线程进行绑定的looper消息循环器和一个处理消息的handler,这个looper中包含了一个MessageQueue,这个MessageQueue中包含的就是其他线程传递过来的Message消息对象,这个looper是一直在循环的取出消息队列中的消息,并将这个消息信息传递给当前线程中的handler对象进行处理,handler消息的发送是不区分线程的,但是消息的接受是要区分线程的,当前handler在哪个线程中就在哪个线程中处理消息!
主线程中使用handler为什么代码中不用写Looper.prepare(),Looper.loop()?
我们找到整个项目的函数入口,代码如下:
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));
}
这里就很清楚了,这里其实就已经准备好了looper,再进入上边的myLooper():
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
其实这里返回的就是上边准备的looper,主函数入口处并且有了Looper.loop(),这里大家就清楚了为什么主线程不需要操作looper了吧,因为主线程已经准备好了looper,并且准备的方式跟我们上面再子线程中准备的方式是一样的!
网友评论