引言
Hander机制是安卓开发中一个重要的消息机制,也是面试的常客。本篇文章是结合书籍和源码,进行一个梳理总结,方便大家更好的理解Hander机制。
Hander的两种用法及使用场景
这里介绍两种最为常见的用法以及使用场景
一丶使用Hander的sendMessage(Message msg)方法。
该方法对应延迟发送的方法sendMessageDelayed(Message msg,long delayMillis)
使用场景:
需要在子线程进行耗时操作,完成后,利用Hander通知UI进程也就是主线程刷新UI。
使用步骤:
步骤1. 在主线程中实例化Hander。这里介绍两种实例化方式。
private static final int UPDATE_UI = 0; /*整形常量,标识要进行的动作,设置常量的作用在于将关注点放在操作本身而不是变量的值*/
/*第一种实例化Hander的方法,重写handleMessage方法*/
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI:
/*在这里进行更新ui*/
break;
}
}
};
/*第二种实例化Hander的方法,构造函数中传入Hander.Callback接口,该接口也只有一个同名handleMessage方法,不过的多了一个Boolean类型的返回值*/
private Handler mHander = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_UI:
/*在这里进行更新ui*/
break;
}
return true;
}
});
观察这两种实例化的方式,是不是感觉有些似曾相识?我们在创建新的Thread的时候,貌似也差不多,要么通过重写Thread的run()方法,要么就是在构造方法中传入一个Runnable接口,这里估计也是借鉴的这一种思想。
同时到这里产生一个问题: 第二种实例化方法中为啥会有返回值?返回值的作用是什么?
我们查看源码Hander的dispatchMessage方法(大家先别管这个方法啥时候调用,只要知道消息经过辗转后最终会到这里进行分发就行了),源码如下:
public void dispatchMessage(Message msg) {
<!--先不管这个判断-->
if (msg.callback != null) {
handleCallback(msg);
} else {
<!--看下面这一行,mCallbck即为第二种实例方法中构造方法中传入的入Hander.Callback接口-->
if (mCallback != null) { //不为空,则执行接口的handleMessage方法
if (mCallback.handleMessage(msg)) { //如果返回值为true,直接返回
return;
}
}
<!--执行hander本身的handleMessage方法-->
handleMessage(msg);
}
}
经过查看源码,我们得知,当使用第一种方法实例化hander时,mCallback(Hander.Callback)为空,最终会调用Hander自身的handleMessage(Message msg)方法当。而使用第二种方法实例化Hander时,会首先调用传入的Hander.Callback接口的handleMeassage(Message msg)方法,而该方法如果返回值为true表示消息只由该接口处理,而如果返回false,则由该接口handleMessage(Message msg)方法处理完成后会紧接着由Hander本身的handleMessage(Message msg)方法处理。
步骤2. 在子线程中进行耗时操作,完成后利用Hander的sendMessage()方法通知主线程.
new Thread(){
@Override
public void run() {
/*模拟耗时操作*/
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*耗时操作完成,利用hander发送消息给主线程*//*
// Message msg = new Message(); 获取消息的第一种方法,直接实例化Message
// Message msg = Message.obtain(); 获取的第二种方法,从消息池中获取消息,推荐使用
Message msg = mHander.obtainMessage(); //获取消息的第三种方法,本质上就是第二种方法
msg.what = UPDATE_UI; //设置消息的参数,便于区分,如果一个参数不足以区分,Message类还提供arg1和arg2两个整形参数,以及object参数用于设置
mHander.sendMessage(msg); // hander发送消息的方法
}
}.start();
在子线程中耗时操作完成后,需要生成一个消息由主线程实例化后的Hander发出去。生成消息有三种方式,代码中也注释了,如果需要频繁的发送消息,推荐使用第二种方式和第三种方式,第三种内部也是调用的Message的obtain()方法.,该方法从Message自身维护的消息池中获取消息,比起直接实例,消息池能对消息对象进行回收利用,性能上更佳。消息发送完成后,经过一系列的辗转,主线程中Hander的handleMessage()方法便会回调,我们便能根据相关的参数判断消息的类型,从而进行相应的操作处理。
以上便是第一种用法。下面介绍第二种用法和使用场景。
二. 使用Hander的post(Runnable r)方法。
对应的延迟发送方法,postDelayed(Runnable r,long delayMillis)。
第一种使用方法是发送一个Message,而这种方法是发送一个Runnable接口出去,看上去差别挺大,实际上两者本质相同。其实,在第二种使用场景中,Hander内部会自动帮我们将Runnable接口封装在一个Message里面。
源码告诉了我们一切:
//发送Runnable接口时,hander内部的getPostMessage方法会将我们的runnable接口赋值为Message的callback属性
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
可以看到,Message内部有一个callback属性,而这个callback,类型便是我们之前post的Runnable接口.
那么问题来了?这种带Runnable接口的消息Hander是怎么处理的呢?贴上一个熟悉的方法,也就是场景一中所贴过的,消息辗转之后,最终会调用的Hander中的dispatchMessage(Message msg)方法:
public void dispatchMessage(Message msg) {
<!--先不管这个判断--> (之前的注释)
<!--之前让你们先不用管的判断,实际上就是用于处理携带runnable消息的判断,callback实际上就是post方法中的runnable参数-->
if (msg.callback != null) {
handleCallback(msg); //对于携带runnable的消息,hander直接调用该方法
} else {
<!--看下面这一行,mCallbck即为第二种实例方法中构造方法中传入的Callback-->
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { //如果返回值为true,直接返回
return;
}
}
<!--执行hander本身的handleMessage方法-->
handleMessage(msg);
}
}
可以看到,对于携带Runnable接口的消息,Hander直接调用自身的handleCallback(Message message)对其进行处理.
那么handleCallback(Message message)是怎样处理消息的呢?还是看源码:
private static void handleCallback(Message message) {
message.callback.run();
}
可以看到,只是将Runnable接口中的run()方法执行了一遍,仅此而已,虽然是Runnable接口,但和线程没有任何关系。
隐藏的三兄弟 Looper 丶MessageQueue丶ThreadLocal
在上面的文章中,我们只接触到了 Hander丶Message,似乎有了这两兄弟,Hander便可正常运转。在上面的文章,我们还说过,无论是发送有一个Message和发送一个Runnable接口,最终都是Message的方式发送,然后经过一系列辗转之后,会走到Hander的dispatchMessage(Message msg)中进行分发,处理。那么消息辗转的过程是怎么样的呢?还是接着在源码中找寻答案:我们查看Handler的sendMessage()方法:
public final boolean sendMessage(Message msg)
return sendMessageDelayed(msg, 0);
}
可以看到,该方法在内部又调用了一个sendMessageDelayed方法,根据函数我们可以知道,普通消息实际上就是一个延迟时间为0的延迟消息,继续往下看:
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
可以看到,在该方法中对延迟时间进行了一下判断之后,又进入下一个sendMessageAtTime方法。虽然由一个sendMessage()方法开始,进行了很多次的Hander内部其它函数调用,但是我们不要觉得混乱,这样做其实无非就是对消息的发送时间进行各种判断,计算而已。继续跟进:
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);
}
在这个方法中,我们看到一个新的兄弟,MessageQueue.小弟不才,这里先给上书上的解释。
MessageQueue的中文翻译是消息队列,顾名思义,它的内部存储了一组消息,以队列的形式对外提供插入和删除的工作。虽然叫消息队列,但是它的内部存储结构并不是真的队列,而是采用单链表的数据结构来存储消息列表。
看了解释,我们对MessageQueue有了大概的认识,就是一个单链表,用来存储发送出来的Message.我们看到,在上面的方法中,直接获得了成员变量mQueue的引用。那么对于Hander来说,我们并没有手动给它的mQueue赋值,那么mQueue是在哪里赋值的呢?很大概率是构造函数了,那我们去Hander的构造函数中瞅瞅。追寻源码我们发现,无论哪种方式实例化hander,最终都会到这个构造函数中进行最终的初始化。
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
我们在这里看到了mQueQue的赋值,不过,很奇怪,mQueue的引用来自于自身另一个成员变量mLooper的成员变量.而mLooper的值,是由 Looper.myLooper()这行代码赋值。这里,我们又见到另一位隐藏的兄弟,Looper.老规矩,先按照书上的话大概介绍下这位兄弟.
由于MessageQueue只是一个消息的存储单元,他不能去处理消息,而Looper就填补了这个功能。Looper会以无限循环的方式去查找是否有新的消息,如果有的话就处理消息,否者就一直等待着。 好了介绍完毕之后,我们进入
Looper.myLooper()方法中,一探究竟。
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
我们看到,myLooper()方法只是将Looper中的sThreadLocal的值返回。我们先来看看这个变量长啥样。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
看到这里,我们看到了第三个兄弟ThreadLocal,对于没接触过ThreadLocal这个类的人来说可能会一脸懵逼。先按照书上的话解释下:
ThreadLocal是一个线程内部的数据存储类,通过他可以在指定的线程中存储数据,数据存储后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
对于没接触过的人是不是看了解释还是一脸懵逼。。。先来个demo便于理解。
public class MainActivity extends AppCompatActivity {
static ThreadLocal<String> stringThreadLocal = new ThreadLocal<String>();
static ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
stringThreadLocal.set("Lesincs");
booleanThreadLocal.set(true);
Log.d("stringThreadLocal的值为", stringThreadLocal.get());
Log.d("booleanThreadLocal的值为", booleanThreadLocal.get().toString());
new Thread() {
@Override
public void run() {
stringThreadLocal.set("scniseL");
booleanThreadLocal.set(false);
Log.d("stringThreadLocal的值为", stringThreadLocal.get());
Log.d("booleanThreadLocal的值为", booleanThreadLocal.get().toString());
}
}.start();
new Thread() {
@Override
public void run() {
stringThreadLocal.set("juejin");
booleanThreadLocal.set(true);
Log.d("stringThreadLocal的值为", stringThreadLocal.get());
Log.d("booleanThreadLocal的值为", booleanThreadLocal.get().toString());
}
}.start();
}
}
运行打印结果为这样:
03-07 04:49:23.425 2096-2096/? D/stringThreadLocal的值为: Lesincs
03-07 04:49:23.425 2096-2096/? D/booleanThreadLocal的值为: true
03-07 04:49:23.425 2096-2112/? D/stringThreadLocal的值为: juejin
03-07 04:49:23.425 2096-2112/? D/booleanThreadLocal的值为: true
03-07 04:49:23.429 2096-2111/? D/stringThreadLocal的值为: scniseL
03-07 04:49:23.429 2096-2111/? D/booleanThreadLocal的值为: false
看了demo和运行的结果我们大概可以感受到ThreadLocal的用法:
- 实例的时候通过
泛型指定值的类型 - 通过
set()方法进行赋值 - 通过
get()方法获取值
特点:
- 在不同线程中,
ThreadLocal变量的值相互独立。
那么为什么ThreadLocal有这么“神奇”的特点呢?原理是什么呢?
大家去Android Studio对着Thread类按着contrl点击一下,进入Thread源码,会发现Thread类中有一个变量长这样:
ThreadLocal.ThreadLocalMap threadLocals = null;
可以看到这个变量是ThreadLocal的一个内部类ThreadLocalMap,我们把它当成一个特殊的map,它的key是ThreadLocal,而values则是对应的值。
我们查看ThreadLocal的get()方法:
public T get() {
Thread t = Thread.currentThread(); //得到当前线程
ThreadLocalMap map = getMap(t); //得到当前线程上面所说的ThreadLocalMap对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //this传入当前threadLocal对象,以该对象为键索引得到值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); //如果未被赋值 返回默认值
}
可以看到,对于ThreadLocal的get()方法,每一次取值,都会先得到当前线程的ThreadLocalMap,再传入自身的引用,作为key,得到相应的value.对于不同线程,有不同的ThreadLocalMap变量,所以即使key(ThreadLocal变量)相同,value还是可以得到独立。
set()方法同理,还是贴一下源码。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set()方法也类似,不同的在于多了一个createMap方法,这里不禁感受到源码设计的巧妙。当整个项目中没有ThreadLocal的对象时,线程中的ThreadLocalMap并没有创建,而是当第一个ThreadLoal实例化时,才开始创建,避免资源白白浪费。
到这里你应该对ThreadLocal有了一定的理解。一言以蔽之:一个ThreadLocal变量的值之所以能在不同线程中相互独立,是因为每个线程中维护了一个ThreadLocalMap,在调用ThreadLocal的get()和set()方法时,会先得到当前线程中ThreadLocal变量,然后在把ThreadLocal变量本身当成key从中赋值或者取值。
对ThreadLocal大致了解后,我们继续回到Hander机制的正题。
上面说到 Looper类中有一个静态 ThreadLocal变量,泛型指定为Looper,即这个ThreadLocal变量的值为Looper。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
而Hander的构造函数中初始化得到mLooper便是Looper的静态myLooper()方法.
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到,myLooper()方法中仅仅只是调用了sThreadLocal的get()方法得到了Looper的值,那么看过上面ThreadLocal的demo之后,我们知道,ThreadLocal的值必须先进行set()方法,在调用get()方法才能返回set()方法的值,不然,只会返回默认的值,对于Looper,默认值肯定是null。那么主线程的Looper的值是在哪里设置的呢?对于本文,也就不卖关子了。对于我们Activity运行的Thread,也就是ActivityThread。系统已将帮我们进行了Looper的创建,查看源码:
public final class ActivityThread {
public static void main(String[] args) {
....(省略无关代码)
Looper.prepareMainLooper(); // 在这儿调用 Looper.prepareMainLooper, 为应用的主线程创建Looper
ActivityThread thread = new ActivityThread();
sMainThreadHandler = thread.getHandler();
}
Looper.loop(); //进行消息循环读取
....
}
}
我们看到,调用Looper的prepareMainLooper()后,后面又调用了Looper的loop()方法。对于loop()方法,我们先不管。先看下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();
}
}
prepareMainLooper()中首先是调用了 prepare(boolean quitAllowed)方法,紧接着一个判断之后是这么一行代码:
sMainLooper = myLooper();
我们知道myLooper()方法是返回当前线程Looper的引用,可以看到在prepare(boolean quitAllowed)方法之后,又用sMainLooper保存了当前线程Looper的引用,那我们可以猜测,Looper的赋值肯定是在prepare(boolean quitAllowed)中了,我们看这个方法做了些什么呢?
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));
}
果然,prepare(boolean quitAllowed)方法的作用就是实例化Looper并放入sThreadlLocal变量中。
可以看到,prepareMainLooper()方法先是调用了prepare(boolean quitAllowed)方法,然后将当前线程的Looper实例存入了sMainLooper这个静态变量中。这样,无论在哪个线程中,都能轻易的拿到主线程的Looper引用,笔者目前还不知道有啥用,但是想到Hander有两个构造函数重载是可以传入Looper对象的,猜测可能有其它高级用法吧。
继续,我们知道MessageQueque是Looper对象中的一个变量,那么我们看看Looper的构造方法中发生了些什么。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
我们看到了在Looper的构造函数中,实例了MessageQueue对象,将他赋值给自身的成员变量mQueue,以及将当前线程的引用保存。
在上面的ActivityThread中,我们还看到了Looper.loop()方法的调用,其实很好理解.我们得知在prepare(boolean quitAllowed)方法中,仅仅只是进行了MessageQueue的创建,此时,Message发送之后,已经可以加入到MessageQueue中,但是我们还需要一个操作对Message进行读取以及处理.看源码loop()方法:
public static void loop() {
final Looper me = myLooper(); //得到当前线程的Looper对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //得到MessageQueue对象
// 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();
//无线循环读取消息队列中的消息
for (;;) {
Message msg = queue.next(); // MessageQueue中没有消息时,该方法阻塞.
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);
}
final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
final long end;
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (slowDispatchThresholdMs > 0) {
final long time = end - start;
if (time > slowDispatchThresholdMs) {
Slog.w(TAG, "Dispatch took " + time + "ms on "
+ Thread.currentThread().getName() + ", h=" +
msg.target + " cb=" + msg.callback + " msg=" + msg.what);
}
}
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();
}
}
我们可以看到loop()中有一个无限for循环,在循环体中,调用自身的MessageQueue对象的next()方法取出消息进行处理。我们看一看next()方法中干了些什么。
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//native方法,用以阻塞。当nextPollTimeoutMillis时长过去或者有新的消息来临,该函数才会返回,继而进行下面的操作。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages; //获取头结点
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg; //用preMsg保存头结点引用
msg = msg.next;// 用msg保存第二个节点的引用
} while (msg != null && !msg.isAsynchronous()); //当存在第二个消息,并且是同步消息才打断循环?对这里有点暂时还有点疑问
}
if (msg != null) {
if (now < msg.when) {
//若当msg的when属性大于当前时间,说明msg未到处理时间
// Next message is not ready. Set a timeout to wake up when it is ready.
//设置nextPollTimeoutMillis的值为msg.when和now的差值,循环之后上面的native阻塞方法会阻塞相应的时间再处理消息
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
//若当msg的when属性小于当前时间,说明msg已经可以处理
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
可以发现next()方法是一个无线循环的方法,如果消息队列中没有消息或者有消息还没到处理时间,那么next()方法会一直阻塞在这里。当有新消息到来时,next()方法会返回并将其从单链表移除。有移除,就必有插入,MessageQueue中插入消息的方法是enqueueMessage().我们看看该方法。
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; //将参数的when赋值给msg的when属性,这里不深谙其细节,我们可以推测其作用是表示msg执行的时间
Message p = mMessages;
boolean needWake;
// p为链表的头结点,p==null表示消息队列为空,,从判断我们可以得知when属性越小,则排在越前面,越早被处理.
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//满足消息队列为空或者when=0,或者when属性小于当前头结点的when属性时,将新的message插入链表头部。
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;
//如果对于一个新来的消息,其when属性位于居中的位置怎么处理呢?对于链表,肯定是查找到一个合适的位置插入.
for (;;) { //无限循环
prev = p; //利用prev保存左边的节点
p = p.next; //利用p保存右边的节点
if (p == null || when < p.when) { //到链表尾部或者找到比自己大的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;
}
对于MessageQueue的enqueueMessage(Message msg, long when)方法,看过注释之后你会大概了解它的原理。对于每个Message,有发送时机的先后,也有指定发送延迟时间的不同。系统于是统一在内部指定了一个when属性,用来表示消息处理的时间,when的值越大,表示越晚发送。这个属性在Message进入MessageQueue之前已经被计算好了.在进入MessageQueue时,MessageQueue调用enqueueMessage(Message msg, long when)进行消息插入,并且插入的规律是按照when属性的大小顺序,越大越靠近尾部。
好了,我们从Hander的sendMessageAtTime(Message msg, long uptimeMillis)方法扯了这么多,但是如果上面所说的大家都理解了的话,下面的就很容易理解了。我们回到刚才说到Hander的sendMessageAtTime(Message msg, long uptimeMillis)方法:
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);
}
我们看到拿到消息队列之后,Hander调用了自身的enqueueMessage方法.Hander也有enqueueMessage方法?excuse me?继续看源码
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
果然,自身的enqueueMessage方法内部还是调用的MessageQueue的enqueueMessage方法,将消息插入消息队列中。至此,已经没Hander啥事了,消息已经到了MessageQueue中,并由Looper的无线循环方法结合MessageQueue的next()方法进行检索。检索到消息之后看代码:
...
msg.target.dispatchMessage(msg);
...
我们看到了熟悉的dispatchMessage()方法,也就是上文说的消息经过一系列辗转之后最终会调用的方法,那么msg.target的值肯定就是我们发送它的hander啦。target是在哪里赋值的呢?还记得Hander的enqueueMessage方法吗?
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//设置消息的target属性为hander自己。谁把你发出来的,最终谁处理。
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到,msg.target的值就是在这里赋予的。
因为msg.target.dispatchMessage方法是在Looper循环方法中调用的,那么最后的handleMessage也是在Looper所在线程,也就是主线程中调用,我们成功的在主线程中处理了消息!
总体梳理
源码终于讲完了,下面还是梳理一下整个过程吧。
准备工作:
- 首先是
Looper调用prepare()方法(主线程中调用prepareMainLooper方法),实例化Looper对象,并将其值放在threadLocal类里面,保证各个线程的Looper对象互不影响。 Looper在实例化的时候,实例化MessageQueue对象并保存到自身成员变量mQueue.Looper调用其Loop()方法在主线程中进行循环调用mQueue的next()方法读取mQueue中的消息。若mQueue该队列中没有新的消息,该方法会阻塞。若读到消息,消息从队列中移除,并且调用Message的target属性(即发送它的hander对象)的dispatchMessage()方法进行消息处理.
Hander发送消息:
-
Hander在主线程中实例化,在实例化的同时,保存当前线程的Looper引用以及Looper中MessageQueue的引用mQueue。 -
Hander调用自身sendMessage方法。最终会调用自身的sendMessageAtTime方法,该方法中,又会调用自身enqueueMessage的方法,在enqueueMessage中,将自身的引用赋值给message的target属性,然后调用mQUeue的enqueueMessage方法,将消息插入消息队列。 -
消息插入队列后,请看准备工作的第三步。
-
dispctchMessage方法中会判断msg类型,是普通消息还是携带Runnable接口的消息。如果是携带Runnable接口的消息,直接调用runnable接口的run()方法。如果是普通消息。调用handleMessage方法处理之。
相关问题
-
Looper的looper()是一个无线循环方法,为什么不会造成ANR?关于这个问题,想了想是挺奇怪的,于是去网上搜索下,感觉下面这个文章的说法还挺让人信服的,有的太多于深层次,有的又太浅显,大家暂时按着下文中这样理解吧。
文/HongJay(简书作者) 原文链接:http://www.jianshu.com/p/cfe50b8b0a41
-
Hander会导致内存泄漏吗?如果会的话,怎么解决?关于这个问题,前几天看过掘友的一篇总结的很好。也贴上来吧,不要重复造轮子哈哈。
文/Carson_Ho(简书作者) 原文链接:http://www.jianshu.com/p/cfe50b8b0a41
参考
最后
这是在掘金上第二次写文章,记得写第一篇文章时,顺风顺水,洋洋洒洒几小时就捣鼓好了,还也得到了不少掘友的鼓励和喜欢,很是欣慰。而这次写得感觉有点精神分裂,文中有很多不足的地方,比如MessageQueue的next()方法,自己也理解得有点晕乎,还请见谅。因为这次的内容相比上次的要复杂得多,而且还是结合着源码在分析,看源码是件繁琐的事情,但是既然你要写文章,要对你的读者负责,你就不敢马马虎虎,这也是我想写文章的一部分原因吧,能督促自己多看看源码。建议大家没事也可以多多看看源码,对于源码中比较复杂的东西,有的可以选择性忽略,关注其中最重要的东西,让自己能够消化。祝大家心明眼亮!