Android学习之Handler
Handler内存泄露
sendMessage方法内存泄露
有这么一个需求,延迟执行一段逻辑,先看第一种方式,直接让线程sleep:
private val handler2 = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
startActivity(Intent(this@HandlerActivity, XXXActivity::class.java))
}
}
private fun test() {
thread {
val message = Message()
SystemClock.sleep(3000) //1
message.apply {
what = 3
obj = "hahaha"
}
handler2.sendMessage(message)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("XX", "onDestroy")
handler2.removeMessages(3) //2
}
从上面的代码里看到在代码1处让程序休眠3秒,然后点击返回按钮销毁此Activity,那么即使在onDestroy方法里移除掉这个message,也是没有效果的。原因在于此时这个message还没有添加到MessageQueue里,所以移除的是null。
那么该如何解决呢,使用以下代码:
private fun test() {
thread {
val message = Message()
message.apply {
what = 3
obj = "hahaha"
}
handler2.sendMessageDelayed(message,3000)
}
}
使用sendMessageDelayed方法执行延迟操作,即可以避免带来的内存泄露。
为什么不能在子线程new Handler
我们在单独的线程里初始化一个Handler
private fun test() {
thread {
Handler()
}
}
然后在onCreate方法里调用,发现会闪退,报了一个错误,如下:
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-4,5,main] that has not called Looper.prepare()
那么为什么我们在子线程中new Handler会报这个异常呢,原因是我们的Handler需要初始化一个loop对象,而我们没有做。那么为什么在Android的主线程中我们可以直接使用Handler而不报错呢,是因为在应用启动的时候已经帮我们调用了初始化loop。在ActivityThread类里main方法中已经帮我们初始化好了loop,这个loop绑定的是主线程。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
......
Looper.prepareMainLooper();
......
}
Looper.prepareMainLooper();
就是这行代码完成了loop的初始化工作。那么我们再进入到这个方法中看看:
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方法,然后初始化sMainLooper对象,并且可以看出sMainLooper只能被初始化一次,否则被抛出异常。
再进入到prepare方法里看看:
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
从代码里可以看出,prepare方法初始化了looper对象,并且在looper中绑定了当前线程和new除了一个MessageQueue。并且把这个looper对象放入了sThreadLocal中。然后通过调用myLooper()方法从sThreadLocal中取得looper对象,至此完成looper的初始化工作。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
那么,我们该如何在子线程中使用Handler呢,很简单,在我们使用Handler之前调用一下Looper的prepare方法即可:
private fun test() {
thread {
Looper.prepare();
Handler()
}
}
这样的话,这个Handler就是运行在我们new出来的子线程中了,当然这个Handler也不能去更改UI了。
更改UI只能在主线程中操作吗
我们学习Android的时候就知道,不能在非UI线程中去更新UI,否则会报错,那么真的是绝对的吗,看下面的代码:
private fun test() {
thread {
btnTxt.text = "我哦喔喔"
}
}
我们在子线程中将一个Button控件的text值修改,并且成功运行没有报错,但是在某些手机或者某些系统上就会抛出异常。那么是为什么呢?原因是我们在调用setText的时候会调用requestLayout();
方法,这个方法里又会调用ViewRootImpl的requestLayout方法:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
在这个方法中就会检查线程是否正确,即调用checkThread方法:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这里就会做线程的检查,如果不是主线程,就会抛出异常,但是在执行requestLayout方法时还会并行的执行invalidate方法,所以,有可能页面invalidate方法先执行了,然后才触发checkThread方法,那么就不会抛出异常。
我们可以修改一下上面的代码验证一下:
private fun test() {
thread {
SystemClock.sleep(1000)
btnTxt.text = "我哦喔喔"
}
}
我们让修改的代码延迟一秒执行,可以发现,程序就闪退了,并且抛出了上面的异常。
屏幕快照 2019-08-11 17.38.54.png
Handler的dispatchMessage方法分析
我们在通过Handler去发送消息,并执行的时候可以有三种方式:
//方式一
private val handler = Handler(Handler.Callback {
when (it.what) {
3 -> {}
else -> {}
}
false
})
//方式二
private val handler2 = object : Handler() {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
startActivity(Intent(this@HandlerActivity, AopActivity::class.java))
}
}
//方式三
handler.post(){
btnTxt.text = "handler"
}
那么这三种方式有什么区别呢,答案就在Handler的dispatchMessage方法源码里:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);//1
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {//2
return;
}
}
handleMessage(msg);//3
}
}
注释1处的代码
让我先看注释1处的代码,也就是dispatchMessage首先判断了msg里的callback是否是null,如果不为null,那么就会调用handleCallback方法。那么这个callback是什么呢,就是我们调用Handler.post时传进来的Runnable。
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到我们调用post方法时,会将我们传进来的Runnable封装到Message对象里并且返回,那么在dispatchMessage方法里这个最先被判断的就不会为空,也就会执行handleCallback方法:
private static void handleCallback(Message message) {
message.callback.run();
}
这个handleCallback方法也就是我们传进Runnable的run方法。
注释2处的代码
dispatchMessage方法里,如果callback为null了又进行判断mCallback是否为null。那这个mCallback是什么呢,就是我们初始化Handler对象是在构造方法中传进来的Handler.Callback对象,它是一个被定义在Handler中的接口:
public interface Callback {
/**
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public boolean handleMessage(Message msg);
}
如果我们在初始化中传进来这个CallBack,那么将执行它里面的handleMessage方法。
注释3处的代码
那么,如果以上这两个对象都为null的话,将调用Handler内部的handleMessage方法,这个方法是个空实现,也就是我们自己实现的handleMessage方法。
Handler的发送消息和执行消息过程
Handler的发送消息
当我们调用了Handler的sentXXX方法时,到最终都会调用enqueueMessage方法,这个方法代码如下:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//1
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//2
}
从上面的代码中可以看出,这个方法做了两件事
- 将当前的Handler对象赋值给msg.target对象
- 调用MessageQueue中的enqueueMessage方法
那么,我们再到enqueueMessage方法中看一下逻辑:
boolean enqueueMessage(Message msg, long when) {
......
synchronized (this) {
......
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
......
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
......
}
return true;
}
代码很长,其中最重要的一句就是mMessages = msg;即将传进来的msg赋值给全局的mMessages。这个过程就是Handler的发送消息的过程。
Handler的执行消息
那么我们在发送消息的时候最终将msg赋值到了MessageQueue中的全局对象mMessages中,是如何将它取出执行的呢。
首先是通过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 (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
......
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
......
}
这段代码很长,我截取了比较关键的部分,可以看出loop方法先是取出looper对象,然后从looper对象中取出MessageQueue对象,接着在一个死循环中取出queue中的msg,如果为null,就返回。否则就调用msg.target的dispatchMessage方法,那么这里的msg.target就是刚才发送消息时绑定的Handler对象,所以最终会通过Handler的dispatchMessage方法调用我们的回调方法。
至此,Handler的发送消息和执行消息就分析完了。