Android消息机制
Android的消息机制主要是指的Handler
的运行机制以及Handler
所附带的MessageQueue
和Looper
的工作过程,这三者实际上是一个整体。
从开发的角度来看,Handler是消息机制的上层接口,通过它我们可以轻松的将一个任务切换到它所在的线程中去执行。以消息传递为例,在一个线程创建Handler,另外一个线程通过持有该Handler的引用调用sendMessage实现消息在线程之间的传递。MessageQueue的中文翻译是消息队列,内部采用单链表的数据结构来存储消息列表。Looper的中文翻译是循环,这里可以理解为消息循环。由于MessageQueue只是一个消息的存储单元,它不能去处理消息,而Looper就填补了这个功能,Looper会以无限循环的形式去查找是否有新消息,如果有的话就处理消息,否则就一直等待着。Looper中还有一个特殊的概念:ThreadLocal
,当我们调用Looper.prepare()
方法创建Looper时使用到它。在Handler内部就是通过ThreadLocal来获取每个线程的Looper的。
Handler的内部实现主要涉及到如下几个类: Thread、MessageQueue和Looper。这几类之间的关系可以用如下的图来简单说明:
Thread是最基础的,Looper和MessageQueue都构建在Thread之上,Handler又构建在Looper和MessageQueue之上,我们通过Handler间接地与下面这几个相对底层一点的类打交道。
下面我们来先分别介绍ThreadLocal
,MessageQueue
,Looper
, 和Handler
的工作机制,再将它们的工作流程进行一个大致的串讲。
1. ThreadLocal
1. 概述
ThreadLocal
是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
上面的话可以理解为,ThreadLocal
是一个全局变量,用来存储对应Thread
的本地变量。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。 举一个简单的例子:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//定义ThreadLocal对象
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBooleanThreadLocal.set(true);
Log.d(TAG, "[Thread#main]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
new Thread("Thread#1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG, "[Thread#1]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
}
}.start();
}
}
在上面的代码中,在主线程中设置mBooleanThreadLocal
的值为true
,在子线程1中设置mBooleanThreadLocal
值为false
,子线程2中不设置mBooleanThreadLocal
的值。然后在3个线程中分别通过get
方法获取值并打印出来。根据前面的描述,期望的打印结果应该是:主线程为true
,子线程1为false
,子线程2位null
,因为子线程2中没有设置值。实际打印结果如下:
可以看到,尽管在三个线程中访问的为同一个对象,但是ThreadLocal为他们各自维护了一个该对象的副本,所以访问到的结果不同。
2. 实现原理
ThreadLocal是一个泛型类,public class ThreadLocal<T>
,要理解它的工作原理,可以从get
和set
方法入手,先来看set
方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可见,首先通过getMap
方法来获取当前线程中的ThreadLocal
数据,如果返回的ThreadLocalMap
对象不为空,则调用set
方法添加数据,否则就创建一个新对象并把值添加进去。ThreadLocalMap
中定义了一个private Entry[] table;
来存储数据,我们可以通过set
方法将数据添加到table
数组中。
再来看看get
方法:
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
可以发现,ThreadLocal
的get
方法同样是先取出当前线程的ThreadLocalMap
对象,如果这个对象为null
就返回初始值,这个初始值由ThreadLocal
的initialValue()
方法来确定,默认情况下为null
,默认实现如下所示:
protected T initialValue() {
return null;
}
如果ThreadLocalMap
对象不为null
,那么就取出table
数组并找到相应的值返回回去。
从ThreadLocal
的set
和get
方法可以看出,它们所操作的对象都是当前线程的ThreadLocalMap
对象的table
数组,因此在不同线程中访问同一个ThreadLocal
的set
和get
方法,它们对ThreadLocal
所做的读/写操作仅限于各线程的内部,所以ThreadLocal
可以在多个线程中互不干扰地存储和修改数据。
3. 与Android消息机制的联系
当我们在一个线程中创建一个Handler,调用Looper.prepare()时通过ThreadLocal保存当前线程下的Looper对象,而所有线程的Looper都由一个ThreadLocal来维护,也就是在所有线程中创建的Looper都存放在了同一个ThreadLocal中。而Handler又与Looper协同工作,大致关系如下图:
2. MessageQueue
最基础最底层的是Thread,每个线程内部都维护了一个消息队列——MessageQueue。消息队列MessageQueue,顾名思义,就是存放消息的队列(好像是废话…)。那队列中存储的消息是什么呢?假设我们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个广播事件,那我们如何处理这两件事呢? 因为一个线程在某一时刻只能处理一件事情,不能同时处理多件事情,所以我们不能同时处理按钮的单击事件和广播事件,我们只能挨个对其进行处理,只要挨个处理就要有处理的先后顺序。 为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue里面去,即将单击按钮事件的Message入栈到消息队列中,然后再将广播事件的封装成以Message,也将其入栈到消息队列中。也就是说一个Message对象表示的是线程需要处理的一件事情,消息队列就是一堆需要处理的Message的池。线程Thread会依次取出消息队列中的消息,依次对其进行处理。MessageQueue中有两个比较重要的方法,一个是enqueueMessage方法,一个是next方法。enqueueMessage方法用于将一个Message放入到消息队列MessageQueue中,next方法是从消息队列MessageQueue中阻塞式地取出一个Message。
enqueueMessage
主要操作其实就是单链表的插入操作,这里就不再过多解释了。而next
方法是一个无限循环的方法,如果消息队列中没有消息,那么next
方法会一直阻塞在这里。当有新消息到来时,next
方法会返回这条消息并将其从单链表中移除。
3. Looper
1. Looper的创建(使用ThreadLocal存储)
Looper
在Android
的消息机制中扮演着消息循环的角色,具体来说就是它会不停地从MessageQueue
中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在哪里。首先来看一下它的构造方法,在构造方法中它会创建一个MessageQueue
即消息队列,然后将当前线程的对象保存起来,如下所示:
@UnsupportedAppUsage
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
创建looper
时调用looper.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));
}
sThreadLocal即为存储各线程Looper的ThreadLocal对象,调用其get方法判断该线程是否已经持有了Looper,如果已经持有则抛出异常,这是为了保证对于每个线程Looper的唯一性。如果没有Looper,则为该线程创建一个Looper。
2. Looper的循环
而Looper
最重要的一个方法是loop
方法,只有调用了loop
后,消息循环系统才会真正地起作用,它的实现如下:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
// Allow overriding a threshold with a system prop. e.g.
// adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
final int thresholdOverride =
SystemProperties.getInt("log.looper."
+ Process.myUid() + "."
+ Thread.currentThread().getName()
+ ".slow", 0);
boolean slowDeliveryDetected = false;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
// Make sure the observer won't change while processing a transaction.
final Observer observer = sObserver;
final long traceTag = me.mTraceTag;
long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
if (thresholdOverride > 0) {
slowDispatchThresholdMs = thresholdOverride;
slowDeliveryThresholdMs = thresholdOverride;
}
final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);
final boolean needStartTime = logSlowDelivery || logSlowDispatch;
final boolean needEndTime = logSlowDispatch;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
Object token = null;
if (observer != null) {
token = observer.messageDispatchStarting();
}
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
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);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
}
}
选取其中比较关键的部分做一下讲解:
- final MessageQueue queue = me.mQueue;
变量me是通过静态方法myLooper()获得的当前线程所绑定的Looper,me.mQueue是当前线程所关联的消息
队列。
- for (;;)
我们发现for循环没有设置循环终止的条件,所以这个for循环是个死循环。
- Message msg = queue.next(); // might block
我们通过消息队列MessageQueue的next方法从消息队列中取出一条消息,如果此时消息队列中有Message,
那么next方法会立即返回该Message,如果此时消息队列中没有Message,那么next方法就会阻塞式地等待获
取Message。
- msg.target.dispatchMessage(msg);
msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage方法让
Handler处理该Message,关于Handler的dispatchMessage方法将会在下面详细介绍。
loop
方法是一个死循环,唯一跳出循环的方式是MessageQueue
的next
方法返回了null
。
当Looper
的quit方法被调用时,Looper
就会调用MessageQueue
的quit方法或quitSafely方法来通知消息队列退出,当消息队列被标记为退出状态时,它的next
方法就会返回null
。如果MessageQueue
的next
方法返回了新消息,Looper
就会处理这条消息: msg.target.dispatchMessage(msg)
,这里的msg.target
是发送这条消息的Handler
对象,这样Handler
发送的消息最终又交给它的dispatchMessage
方法来处理了。但是这里不同的是,Handler
的dispatchMessage
方法是在创建Handler
时所使用的Looper
中执行的,这样就成功的将代码逻辑切换到指定的线程中去执行了。
4. Handler
1. 消息发送
Handler
的工作主要包含消息的发送和接收过程。消息的发送最终是通过send
的一系列方法来实现的。发送一条消息的典型过程如下所示:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
在enqueueMessage中有两件事需要注意:
- msg.target = this
该代码将Message的target绑定为当前的Handler - queue.enqueueMessage
变量queue表示的是Handler所绑定的消息队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)我们将Message放入到消息队列中。
其他的一些调用最终也都归结到上面这个流程中:
2. 消息处理
通过上文可以发现,Handler
发送消息的过程仅仅是向消息队列插入了一条消息,MessageQueue
的next
方法就会返回这条消息给Looper
,Looper
收到消息后就开始处理了,最终消息由Looper
交由Handler
处理,即Handler
的dispatchMessage
方法会被调用,这时Handler就进入了处理消息的阶段。dispatchMessage
的实现如下所示:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
可以看到,如果我们设置了callback(Runnable对象)的话,则会直接调用handleCallback方法
private static void handleCallback(Message message) {
message.callback.run();
}
即,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。比如我们经常写的runOnUiThread方法:
runOnUiThread(new Runnable() {
@Override
public void run() {
}
});
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
而如果msg.callback为空的话,会直接调用我们的mCallback.handleMessage(msg),即handler的handlerMessage方法。handlerMessage方法的执行也会在创建handler的线程中。
综上,我们可以看到Handler提供了三种途径处理Message,而且处理有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage方法处理,最后才是让Handler自身的handleMessage方法处理Message。
3. post与send的比较
上述例子都是使用send方法,这里加一个post的例子帮助理解handler对于不同类型信息的处理
首先,调用post方法时依然调用了sendMessageDelayed,但是值得注意的是这里的参数有所不同,使用了getPostMessage(r)作为参数。
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);//getPostMessage方法是两种发送消息的不同之处
}
那么我们来看一看👀这个getPostMessage方法:
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到,依然是把Runnable对象封装成了一个Message进行发送,不过这里设置了m.callback = r ,这也呼应了上文中提到的,如果我们在初始化Handler的时候设置了callback(Runnable)对象,则直接调用run方法。
4. Callback
/**
* Callback interface you can use when instantiating a Handler to avoid
* having to implement your own subclass of Handler.
*
* @param msg A {@link android.os.Message Message} object
* @return True if no further handling is desired
*/
public interface Callback {
public boolean handleMessage(Message msg);
}
Handler.Callback是用来处理Message的一种手段,如果没有传递该参数,那么就应该重写Handler的handleMessage方法,也就是说为了使得Handler能够处理Message,我们有两种办法:
- 向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage方法
- 无需向Hanlder的构造函数传入Handler.Callback对象,但是需要重写Handler本身的handleMessage方法
也就是说无论哪种方式,我们都得通过某种方式实现handleMessage方法,这点与Java中对Thread的设计有异曲同工之处。
在Java中,如果我们想使用多线程,有两种办法:
向Thread的构造函数传入一个Runnable对象,并实现Runnable的run方法
无需向Thread的构造函数传入Runnable对象,但是要重写Thread本身的run方法
5. 总结
-
在使用handler的时候,在handler所创建的线程需要维护一个唯一的Looper对象, 每个线程对应一个Looper,每个线程的Looper通过ThreadLocal来保证
-
Looper对象的内部又维护有唯一的一个MessageQueue,所以一个线程可以有多个handler,
但是只能有一个Looper和一个MessageQueue。 -
Message在MessageQueue不是通过一个列表来存储的,而是将传入的Message存入到了上一个
Message的next中,在取出的时候通过顶部的Message就能按放入的顺序依次取出Message。 -
Looper对象通过loop()方法开启了一个死循环,不断地从looper内的MessageQueue中取出Message,
然后通过handler将消息分发传回handler所在的线程。 -
handler收到消息以后,通过handleMessage进行消息处理
6. 补充:资源管理与内存泄漏
1. Handler的内存泄漏问题
Handler使用是用来进行线程间通信的,所以新开启的线程会持有Handler引用,如果在Activity等中创建Handler,并且是非静态内部类的形式,就有可能造成内存泄漏。
首先,非静态内部类是会隐式持有外部类的引用,所以当其他线程持有了该Handler,线程没有被销毁,则意味着Activity会一直被Handler持有引用而无法导致回收。
同时,MessageQueue中如果存在未处理完的Message,Message的target也是对Activity等的持有引用,也会
造成内存泄漏。
解决的办法:
-
使用静态内部类+弱引用的方式:
静态内部类不会持有外部类的的引用,当需要引用外部类相关操作时,可以通过弱引用还获取到外部类相关操作,弱引用不会造成对象该回收回收不掉的问题。
private Handler sHandler = new TestHandler(this); static class TestHandler extends Handler { private WeakReference<Activity> mActivity; TestHandler(Activity activity) { mActivity = new WeakReference<>(activity); } @Override public void handleMessage(Message msg) { super.handleMessage(msg); Activity activity = mActivity.get(); if (activity != null) { //TODO: } } }
-
在外部类对象被销毁时,将MessageQueue中的消息清空。例如,在Activity的onDestroy时将消息清空。
@Override protected void onDestroy() { handler.removeCallbacksAndMessages(null); super.onDestroy(); }
2. 创建Message时的资源管理
使用Handler.obtainMessage()来获取Message对象的,和直接new一个Message有什么差别呢?
Message message = handler.obtainMessage();
Message message = new Message();
看一看obtain的代码:
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();
}
在Message中有一个static Message变量sPool,这个变量是用于缓存Message对象的,在obtain中可以看到当需要一个Message对象时,如果sPool不为空则会返回当前sPool(Message),而将sPool指向了之前sPool的next对象,(之前讲MessageQueue时讲过Message的存储是以链式的形式存储的,通过Message的next指向下一个Message,这里就是返回了sPool当前这个Message,然后sPool重新指向了其下一个Message),然后将返回的Message的next指向置为空(断开链表),sPoolSize记录了当前缓存的Message的数量,如果sPool为空,则没有缓存的Message,则需要创建一个新的Message(new Message)。
那么,缓存中的sPool是哪里来的呢
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
recycle()是回收Message的方法,在Message处理完或者清空Message等时会调用。recycleUnchecked()方法中可以看到,将what、arg1、arg2、object等都重置了值,如果当前sPool(Message缓存池)的大小小于允许缓存的Message最大数量时,将要回收的Message的next指向sPool,将sPool指向了回收的Message对象(即将Message放到了sPool缓存池的头部)
7. 参考文章
这些是我在准备和学习过程中参考的一些博客,大家有兴趣可以自己再康康👀
-
Android消息机制: https://www.jianshu.com/p/7653adc038c6
-
post()和postDelay()方法精炼详解: https://blog.csdn.net/weixin_41101173/article/details/79701832
-
深入源码解析Android中的Handler,Message,MessageQueue,Looper:https://blog.csdn.net/iispring/article/details/47180325