一、问题
- handler是什么?
- handler与Looper、MessageQueue、Message的关系,它们的作用分别是什么?
- handler 引发内存泄漏,怎么处理?
二、场景介绍
刚接触android时,最常用的场景就是,在主线程new 一个Handler对象,在子线程中回调中,通过handler对象发送消息,之后由handler接收处理对象。例如下面的代码
package com.lxqljc.handlerdemo2;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private static final int FLAG = 0x10;
private Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
//接收消息处理
switch (msg.what) {
case FLAG:
Log.d(TAG, "handleMessage: 消息处理");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread(new Runnable() {
@Override
public void run() {
//模拟请求网络
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Message msg = new Message();
msg.what = FLAG;
//发送消息
handler.sendMessage(msg);
}
}).start();
}
});
}
}
通过上面的代码示例,我们可以回答上面问题了。
- handler是什么?
handler是一个线程间通信媒介,因为android系统规定,ui的更新只能在主线程,防止ui在多线程下更新会造成混乱问题,统一由ui线程管理。 - handler的作用是啥?
通过代码可以知道,主要用于发送消息和接收消息处理。
三、Handler原理解析
上面只是简单的应用,你可能会好奇handler发送消息到接收消息,这中间隐藏了什么样的秘密,我们简单介绍一个流程:
- handler 发送message消息
handler.sendMessage(msg);
源码调用链
sendMessage(@NonNull Message msg) --> sendMessageDelayed(msg, 0) --> sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis) --> enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis)
通过调用链分析,最后进入了enqueueMessage,我们看看代码。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
- message 添加到MessageQueue消息队列中(链表)
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
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 {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
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;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
- 消息已经添加到了队列了,那么此时谁取消息呢??没错,就是Looper对象,Looper对象已经主线程帮我们创建好了,接下来就是通过Looper的loop()方法,循环去取message消息。
public static void loop() {
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);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
}
- message消息分发给对应的handler处理,也就是消息是谁发送的,就分发给谁处理。
//消息分发
msg.target.dispatchMessage(msg);
//这里的this, 就是handler
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
return queue.enqueueMessage(msg, uptimeMillis);
}
大概原理就是这样啦,具体去看看源码吧。
四、常见问题
-
匿名内部类引用外部对象会造成内存泄漏,编译器警告了,所以还要处理下。
那到底是哪里引用了外部对象了,msg.target 就是handler,回调的对象引用了外部对象了。
怎么处理?
① 将handler定义为静态内部类。
② 如果回调处理用到Activity,要用软引用包装,用户内存不足时,释放对象。
我们将代码改一改,此时就没有警告了,因为静态内部类的对象,完全属于外部类本身,不属于外部类某一个对象。
/**
* 静态内部类
*/
static class MyHandler extends Handler {
@Override
public void handleMessage(@NonNull Message msg) {
//接收消息处理
switch (msg.what) {
case FLAG:
Log.d(TAG, "handleMessage: 消息处理");
break;
}
}
}
- Message复用问题。
前面的例子,是直接new Message的,这样有个问题,会增加内存消耗,因为每次都是新创建对象,最好这样用:
handler.obtainMessage();
或者
Message m = obtain();
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
优先缓存中取消息复用,否则重新创建。
五、总结
- 面试一般都会问题,所以看看源码还是必要的。
- 大概只是分析了基本流程,需要更深入了解源码。