Android开发笔记之Handler机制详解(源码、线程切换、内存泄漏问题)
程序员文章站
2024-03-23 19:25:58
...
Android开发笔记之Handler机制详解(源码、线程切换、内存泄漏问题)
- 正文
整个流程
- 整个流程请见下图
Message
-
Message
中就是一些字段,用来存储消息的,我们平时使用postXxx
或者sendXxx
方法发送消息的时候,最终的消息会被包装为一个Message
中的callback
字段 - 不过
Message
是一个链表结构(注意:这里说的链表不是指Message
身是一个链表,而是说各个Message
之间形成一个链表结构,每个Message
之间都有联系),而MessageQueue
的next()
方法返回的一个Message
对象,拿到的只是代表Message
链表的一个头节点sPool
,我们回收消息的时候也是将 头节点sPool
设置为下一个Message
的节点。- 下面是回收消息:
recycleUnchecked()
方法的部分源码。
- 下面是回收消息:
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
- 这里还要注意一点,我们创建一个
Message
有两种方式,如下:
Message message1 = new Message(); //直接 new 出一个 Message
Message message2 = Message.obtain(); //通过 obtain()方法返回一个 Message
obtain()
方法,顾名思义,获取,那么他从哪里获取的呢?那就是从一个 消息池 中得到的,这个消息池是共享的,各个线程都可以访问。可以看到下面的 obtain()
方法的源码,加了一个同步锁保证了线程安全,如果消息池中还有一个 Message
的头节点不为空,则直接将其返回,如果消息池中没有消息了,那么就创建一个新的消息,然后将其返回。
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();
}
MessageQueue
- 翻译过来是叫做 消息队列,但其实它并不单单是一个队列,可以把它理解为一个存储、管理消息的一个数据结构
- 内部提供很多方法,对
Message
进行操作,其中两个方法:enqueueMessage()
、next()
分别是插入消息、取出消息。Looper
开始循环就是不断的调用MessageQueue
的next()
方法取出消息,进行处理。 -
MeesageQueue
的创建是在Looper
的prepare()
方法中进行的
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
ThreadLocal
-
ThreadLocal
提供线程局部变量,每个线程都可以通过set()
、get()
来对这个局部变量进行操作,而且不会和其他线程的局部变量进行冲突,实现了线程的 数据隔离 而在 Handler这一套中,它的作用就是去存储Looper
,使得每一个线程都有一个Looper
。下面来看看它的两个方法set()
、get()
- 这里再多提一句:
ThreadLocalMap
是ThreadLocal
的静态内部类,用于维护线程局部值。Entry
又是ThreadLocalMap
的静态内部类,内部维护了一个 Entry 的数组。
//set() 方法就是将 value 值存入 当前线程中的 ThreadLocalMap 中的静态内部类 Entry 中去
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//下面是 ThreadLocalMap 的 getMap() 方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//get() 方法就是从 ThreadLocalMap 中的 静态内部类 Entry 中拿出来
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
Looper
-
Looper
是一个用于为线程运行消息循环的类,简单点讲,就是一个对MessageQueue
进行操作的类,Handler
处理消息的入口。一个线程只能有一个Looper
,并且是保存在当前线程的threadLocals
中的; - 首先看看两个方法:
Looper.prepare()
、Looper.loop()
,这是使用Handler
不可缺少的两个方法(尽管在主线程中使用的时候并没有要我们去设置,这是因为内部自动设置好了的,最终都会落到这两个方法上)-
Looper.prepare()
方法,里面还是调用的一个prepare()
1 方法,这个方法里面先进行了判断,从当前线程的ThreadLocal
中去拿Looper
,如果不为空则抛出异常提示当前线程中已经存在Looper
了,为空则创建一个Looper
并存入ThreadLocal
中。这样当前线程就有了Looper
了。 - 注意这里调用的
Looper
的构造方法,这个构造方法中进行了MessageQueue
的创建
-
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
-
Looper.loop()
方法,首先会调用myLooper()
方法,尝试从ThreadLocal
中取出当前线程的Looper
,如果为空,则抛出异常提示Looper
为空,这时就需要我们去调用prepare()
方法了,然后就是通过当前的Looper
去拿到MessageQueue
,接着是一个 for循环(无参数的for循环,即一个 死循环)在这个 死循环 中进行的操作就是不断的 从MessageQueue
中去拿到消息(next()
方法),然后交给dispatchMessage()
方法去处理消息,最后就是消息的回收recycleUnchecked()
。 - 上面的流程要注意的就是,
msg.target.dispatchMessage()
这个方法的调用者msg.target
这个字段其实是一个Handler
对象,在这个方法中,消息又回到了Handler
手中,即handlerMessage()
方法中(不只这种途径)。
//myLoper() 方法
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//looper() 方法
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);
if (observer != null) {
observer.messageDispatched(token, msg);
}
···
msg.recycleUnchecked();
}
}
}
Handler
- 对
Handler
这一套机制而言,重要的作用就是 线程切换,而光就Handler
而言,它的作用主要就只有两个,一是向MessageQueue
中发消息,二是对Message
进行处理。Handler
的发送消息就两种方式:postXxx()
、sendXxx()
这两类方法,查看源码我们可以发现,这两种方法最终都是调用的同一个方法enqueueMessage()
,这个方法又去调用的MessageQueue
的enqueueMessage()
方法。进行插入消息的操作。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
···
return queue.enqueueMessage(msg, uptimeMillis);
}
- 这里再提一下
Handler
的构造方法,每个简单的构造方法都会去调用另外一个复杂的构造方法,如下:在最终的构造方法中,首先去调用myLooper()
方法,拿到当前线程的Looper
对象,然后进行判断,接着就是从这个Looper
对象中拿到MessageQueue
对象。这时就创建了MessageQueue
了。
//简单构造方法
public Handler() {
this(null, false);
}
//最终调用的构造方法,当然不只这一个
public Handler(@Nullable Callback callback, boolean async) {
···
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- 而我们重写的
handleMessage()
方法就是一个空方法,最终的消息会在Looper.loop()
方法中通过dispatchMessage()
方法传到handleMessage()
方法中,我们就可以对消息进行一些处理了。 - 需要注意的地方就是
postXxx()
方法发送的Runnable
、Callback
对象最终是不会被hanldeMessage()
方法给接收到的,看下面的源码就可以知道了,如果有callback
字段,调用的就是handleCallback()
这个方法,就直接将消息执行了。
//handleCallback() 方法
private static void handleCallback(Message message) {
message.callback.run();
}
//dispatchMessage() 方法
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler是怎么做到线程切换的
- 先看看整个简单的流程吧。我们在 线程A 中创建一个
Hanlder
,那么就肯定要调用Looper
的两个方法:prepare()
、loop()
,这两个方法一调用,我们在 线程A 中完成的工作就有:创建Handler
,创建Looper
,创建MessageQueue
,并且这个Looper
是存在当前线程的threadLocals
中的,然后Looper
启动开始循环消息。接下来就是发送消息了,我们在 线程B 中调用sendXxx()
或者postXxx()
方法进行发送消息,这样消息就被存到了MessageQueue
中,这时,Looper
发现有MessageQueue
中有消息了,就会对其进行处理,这样,消息就回到了Handler
手中。这就是一个完整的 Handler消息机制。 - 也就是说,
Handler
的线程切换实际上就是 一个线程在处理MessageQueue
中的消息,然后另外的线程向MessgeQueue
中发送消息,这发送的消息最终不就被处理消息的那个线程给处理了吗?这样就很容易理解所谓的 线程切换 了。
注意Handler的内存泄露问题
- 首先需要知道的是:非静态内部类默认持有外部内的引用。我们平时创建一个
Handler
就是通过一个匿名内部类的形式进行创建的,那么,问题的根源就在这里了。在一个Activity
中通过匿名内部类的形式创建一个Handler
,那么这个Handler
就会持有Activity
的引用了,而Messgae
中有个target
字段,这个字段正是一个Hanlder
对象,那么,Message
有持有了Handler
的引用了。这三者的后两者没有被释放,那么Activity
又怎么会被释放掉呢?这就导致了 OOM 啦。
解决方法
- 因为静态内部类是不会持有外部类的引用的,所以我们可以使用 静态内部类 + 弱引用 的方法来解决
- 使用 弱引用 来存储 外部类
Activity
的引用。
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler myHandler = new MyHandler(this);
myHandler.post(MyHandler.runnable);
}
//解决Handler的内存泄漏问题
static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
MyHandler(MainActivity activity) {
mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
//拿到强引用进行判空操作
MainActivity activity = mActivity.get();
//进行判空操作
if (activity != null) {
//对消息执行一些操作
}
}
//也可以不写这一步
private static Runnable runnable = new Runnable() {
@Override
public void run() {
//写一些任务
}
};
}
}
上一篇: Java核心技术 日期和时间API