前言
在日常开发中,我们势必会使用到子线程和UI线程的通信,而起着桥梁作用的就是我们常用的Handler。但是他的内部是怎么运作的?运作的过程中存在什么问题?需要我们注意,本文将会详细讲解。
解析Handler
从图中我们就可以知道了,整个Handler
工作组成的包括了Handler
、Looper
、MessageQueue
、Message
这四个部分。
MessageQueue和Message分别只是一个队列和消息实体类,自然不再多说。
而Handler和Looper的具体是怎样的呢?
在我的模拟Handler项目中,已经比较清晰的阐述了整个框架的工作流程,接下里就是结合SDK代码的一份解析了。
整个Handler往简单了来说其实就干了两件事情:
发送消息
涉及到的三个函数sendMessage()
、enqueueMessage()
、Looper.prepareMainLooper()
。
所有事情的起源要从Looper.prepareMainLooper()
开始讲起。
这个函数处于ActivityThread
中,没有了解过这个类的读者们需要知道,java编程一定是有一个主入口的,但是我们在整个Android编程中,从来没有涉及过main()
这个函数,是因为它已经包含在了ActivityThread
这个类中,而它已经经过了复杂的封装。
接下来看下这个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();
}
}
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));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
两小段代码,里面用到了一个变量sThreadLocal
,这个变量是使用static final
修饰的,意味这全局唯一。从他主动抛出的异常我们也可以看出Looper
这个对象也是一个唯一的变量。这是我们需要掌握的第一个知识点。
接下来是关于sendMessage()
函数
这个函数其实是一个泛称,他并不单单指sendMessage()
,他还可以是sendMessageAtTime()
、sendMessageDelayed()
,他们都干了一件事情——传递消息。通过一直向下探索,你就能知道他们最后调用的都是enqueueMessage()
这个函数,也就是把消息放进了消息队列中。
没有很多的操作,就是我们熟悉的链表操作。这里没有做展示,有兴趣的朋友进到源码往下翻一点,马上就能看到了。
就这样很简单,并且很成功的让我们的消息进入了消息队列。
处理消息
接收完消息,我们要干嘛?我们为什么要发消息,因为我们要处理啊。
这里我们要遇到的函数有:Looper.loop()
、dispatchMessage()
、handleMessage()
。
用过Handler的读者们都应该知道我们是需要重写handleMessage()
这个函数的,用于对不同的消息作出响应,所以就不再多介绍。
所以第一个讲的就是Looper.loop()
这个函数。
一共两段代码,也是至关重要的一部分。
在这个代码中两个至关重要的点:
(1)首先是问题是这么一个死循环的函数,怎么就没引发ANR呢????
(2)通过dispatchMessage()
如何分发这个消息的?target是什么?
先是第一个问题的解答。
网上的解答多种多样。但是最关键的点其实是这样的,ANR
是围绕loop()
这个函数展开的,而ANR
的出现也就是loop()
的消息没有得到及时的消费。
第二个问题。
先说target
这个爆红的变量是什么。msg.target
也就是说这是Message
的一个元素,搜索Message
就能找到如下图示。
原来target
就是一个Handler
,而这个Handler
就是我们对应的主动创建Handler
。
然后就是dispatchMessage()
函数了。
* Handle system messages here.
*/
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
想来这就很清楚了,他也就是将事件分发给了handleMessage()
处理,而handleMessage()
又是我们自己来专门写的。msg.callback
是一个Runnable
对象。
害,原来就是这样啊。。
MessageQueue中消息如果为空,该咋办
其实他在MessageQueue
的next()
方法中已经有了对应的解决方案了。
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null) {
} else {
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null;
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
既然知道了IdleHandler
的存在,就看看他的具体运作方式是如何的,而最主要的就是他的接口方法queueIdle()
。
private class Idler implements MessageQueue.IdleHandler {
@Override
public final boolean queueIdle() {
ActivityClientRecord a = mNewActivities;
boolean stopProfiling = false;
if (mBoundApplication != null && mProfiler.profileFd != null
&& mProfiler.autoStopProfiler) {
stopProfiling = true;
}
if (a != null) {
mNewActivities = null;
IActivityTaskManager am = ActivityTaskManager.getService();
ActivityClientRecord prev;
do {
if (localLOGV) Slog.v(
TAG, "Reporting idle of " + a +
" finished=" +
(a.activity != null && a.activity.mFinished));
if (a.activity != null && !a.activity.mFinished) {
try {
am.activityIdle(a.token, a.createdConfig, stopProfiling);
a.createdConfig = null;
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
}
prev = a;
a = a.nextIdle;
prev.nextIdle = null;
} while (a != null);
}
if (stopProfiling) {
mProfiler.stopProfiling();
}
applyPendingProcessState();
return false;
}
}
其他讲实话,可能就是处理一系列没有优先级是那种并需要立刻处理的事情。
思考
Handler的内存泄漏实例。
为什么Handler不能在子线程创建?
为什么Handler构造方法里面的Looper不是new出来的?
问题1:Handler的内存泄漏实例。
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
startActivity(new Intent(MainActivity.this, HandlerTestActivity.class));
return false;
}
});
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(3000);
handler.sendEmptyMessage(0);
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.e("onDestroy", "已销毁");
handler.removeCallbacksAndMessages(0);
handler = null;
}
如果不加注释1和注释2,这就是一段会内存泄露的代码,看了代码,应该也清楚逻辑十分简单,就是一个跳转。推出程序后,你仍然会看到跳转,这就是Handler
的内存泄漏。
问题2: 为什么Handler不能在子线程创建?
这里需要感谢给我提出问题的大佬读者:im小野同学
这个问题其实有点问题,对于修改过底层的华为的操作系统并不存在这样的问题,但是正常的Android
原生系统就不行了,当然这针对的是无参构造函数,如果你通过传入一个Looper
来解决,像这样handler = Handler(Looper.getMainLooper(), Callback())
,也是没问题的。
代码如下
new Thread(new Runnable() {
@Override
public void run() {
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(@NonNull Message msg) {
return false;
}
});
}
}).start();
通过对报错溯源,我们就能发现这样一个问题。
他拿不到Looper
,因为他不是UI线程。
其实这就是问题所在,我们上文讲过sThreadLocal
这个变量,他通过一个get()
函数获取了Looper
。但是这里存在一个问题,这个get()
,他获取的是什么。
所以我们也就进去看看好了。
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();
}
原来,他的取法就是从一个Map
中进行获取的,而key
,就是当前线程,所以当我们在子线程中创建Handler
的时候,我们是拿不到Looper
的,因为key
并不对应。这个时候我们同样明白了Looper
也是一个唯一的,因为他不会为我们创建出来的一个子线程再添加一个Looper
,而是共用。
就这个问题,Google其实有给出解决方案,详细请看 》》知道Handler,那你知道HandlerThread吗?
问题3:为什么Handler构造方法里面的Looper不是new出来的?
这个问题的性质和问题2有点类似了,唯一性。Looper
作为一个事件处理的重要组成部分,想来我们已经看到了,就像多道程序设计技术一样,这是一个不受控制的过程,我们需要疯狂的思考安全性,同步性等问题。这也是唯一性的好处,所以事件统一处理,处理起来也就有序。至少在我们的平时使用中已经证明了这是一个可取的方法。
评论