Android Handler那些事儿(二)——几个关键类之间的关系
Looper是什么?
Looper是android.os包里的一个类,看名字就知道和os相关。它和handler等配合完成android的消息机制。Looper完成线程中的消息循环,即不断地读取MessageQueue中的消息。
但是呢,在Thread中默认是没有Looper的,所以想要使用Handler,就得获取一个Looper;该类提供了静态方法Looper.prepare()来获得Looper,并通过Looper.loop()无限循环获取和分发MessageQueue中的消息。
在Android中主线程在ActivityThread中已经调用了,所以在主线程使用Handler不用显示地调用Looper的方法,但是在子线程中使用Handler是需要显示调用的。
Looper怎么用?
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
// Step 1: 创建Handler
mHandler = new Handler() {
public void handleMessage(Message msg) {
//处理即将发送过来的消息
}
};
Looper.loop();
}
}
为什么需要Looper?
我们看啊,在上面的代码中,在子线程中创建了Handler并重写了接收message的方法,但是如果没有一个无限循环的话,线程不是应该立马就结束了吗,而创建的Handler随之也应该被回收,那如何还能给子线程的Handler传递消息?
那我们写个while吧让线程不退出,这样试试。
结果是。。。。
2020-05-27 11:27:07.965 9366-9383/com.sensetime.myapplication E/AndroidRuntime: FATAL EXCEPTION: Thread-2
Process: com.sensetime.myapplication, PID: 9366
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:200)
at android.os.Handler.<init>(Handler.java:114)
at com.sensetime.myapplication.MainActivity$LooperThread$1.<init>(MainActivity.java:728)
at com.sensetime.myapplication.MainActivity$LooperThread.run(MainActivity.java:728)
还没发消息就直接崩,跟是不是无限循环没半毛钱关系,Handler在创建的时候就会检查Looper是否创建过,不然的话就直接抛异常不干了。
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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
进而还可以做实验:
1、如果不执行Loop.prepare(),直接崩溃,报错和上面一样的。说明Loop.prepare是创建Looper的方法
2、如果不执行Looper.loop(),啥也收不到,说明要能收消息,必须得开启loop循环。
现在我们可以达成一致结论,不用Looper不行。下面看一下Looper的源码,看它到底有什么玄机。
分析Looper的运作机制?怎么和Handler绑定起来的?
首先要接受,一个线程对应一个looper的结论,这个在后面的代码分析中是显而易见的。
我们先看一下Looper里面有哪些重要成员变量。首先是sThreadLocal,它是一个静态对象,我们都知道静态对象是属于类的,那么不管哪个线程的Looper,都能够访问到同一个sThreadLocal,那岂不是就乱套了吗?接下来再看它的奇妙之处。
ThreadLocal.java
先说一下ThreadLocal的特点,然后再用一个例子来说明。ThreadLocal是一个容器,那么是容器就能存储数据对吧,这是第一点;第二,这个容器有什么特殊之处呢?它能实现以线程为作用域的存储,线程之间数据隔离。也就是说,如果使用threadLocal.set分别在两个线程存储不同的东西(注意操作的是同一个对象threadlocal),再用threadlocal.get取出数据来;取出来的数据与对应线程存储的数据一致。也就是在A线程调用get,会得到在A线程set的数据,在B线程调用get,会得到在B线程set的数据,是不是很神奇。看一看set和get的源码
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(Thread t) {
return t.threadLocals;
}
那么从set原理就看出来了,首先通过Thread.currentThread()获取当前的线程,再获得Thread这个类里持有的threadLocals(ThreadLocalMap,不过创建还是由ThreadLocal的createMap完成的),所以能够限制访问域为线程,因为数据相当于存储到Thread类自身中的。
Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
threadLocals对象是由ThreadLocal类定义类型和分配空间,但它并不持有;真正持有的是Thread类,所以就能解释为什么能够做到限制访问域为线程。
Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper在创建的时候就会和当前线程绑定,所以印证了刚才说的一个线程对应一个Looper,并且创建一个消息队列实例,这个实例,其实我们在上面Handler的构造就已经见到了,Handler里面也有一个消息列队,只不过是一个引用,同时还有一个looper的引用,这里我们就看到了Handler是怎么和Looper绑定起来的了。另外,Looper也需要分发消息给Handler,这个怎么做到的接下来再分析。
//必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
mLooper = Looper.myLooper(); //从当前线程的TLS中获取Looper对象
if (mLooper == null) {
throw new RuntimeException("");
}
mQueue = mLooper.mQueue; //消息队列,来自Looper对象
下面再看一下Looper中另一个比较重要的静态方法myLooper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
这个方法有什么用呢?从本地的sThreadLocal里get出一个东东,下面看一下ThreadLocal的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();
}
和上面分析的set方法如出一辙,都是获取当前线程后从线程的map中取出数据,key正是threadLocal这个对象本身,而这个对象是谁创建的呢?正是Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<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));
}
首先,代码上可以看出来为什么Looper.prepare不能调用两次。然后new一个Looper对象放进ThreadLocal里,这样把Looper和当前线程给绑定在一起了。再看一下loop方法,必须要调用了looper方法后才能开启消息循环。
下图是各个类之间的关联关系,比较清晰地反应了是怎样联系起来的。
Looper并没有直接关联上Handler,但是,Message本身就包含了一个Handler的引用,成员变量target,所以Looper才能把相应的Message传给对应的Handler。
下面总结一下各个类主要作用:
- Message:消息分为硬件产生的消息(如按钮、触摸)和软件生成的消息;
- MessageQueue:消息队列的主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);
- Handler:消息辅助类,主要功能向消息池发送各种消息事件(Handler.sendMessage)和处理相应消息事件(Handler.handleMessage);
- Looper:不断循环执行(Looper.loop),按分发机制将消息分发给目标处理者。
消息入队与分发处理流程
刚才分析完了Handler、Looper、Message等的关系,现在我们来捋一下,当我们调用Handler的sendMessage发生了什么,最终又怎样传到了Handler的handMessage回调中。
首先从sendMessage聊起,调用栈如下所示。
Handler.java
public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(@NonNull 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(@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);
}
整体流程下来比较简单,但是需要注意几个地方。
- 1、到了sendMessageAtTime的时候,就把关联的Looper中的MessageQueue联系起来了。
- 2、到了enqueueMessage的时候把该条Message的target设置成了Handler自身,这样把该条Message和对应的Handler绑定起来了。
- 3、最后调用的是MessageQueue的enqueueMessage方法,作用是添加一条消息进Looper的消息队列我们来看一下。
boolean enqueueMessage(Message msg, long when) {
// 每一个普通Message必须有一个target,指向对应Handler
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) { //正在退出时,回收msg,加入到消息池
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
//p为null(代表MessageQueue没有消息) 或者msg的触发时间是队列中最早的, 则进入该该分支并且插队到最前面
msg.next = p;
mMessages = msg;
needWake = mBlocked; //当阻塞时需要唤醒
} else {
//将消息按时间顺序插入到MessageQueue。一般地,不需要唤醒事件队列,除非
//消息队头存在barrier,并且同时Message是队列中最早的异步消息。
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;
prev.next = msg;
}
//消息没有退出,我们认为此时mPtr != 0
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue是按照Message触发时间的先后顺序排列的,队头的消息是将要最早触发的消息。当有消息需要加入消息队列时,会从队列头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。
现在消息已经到了Looper的消息队列中了,我们下一步就是看loop到底干了什么事以及怎么分发到Handler的HandMessage中。
public static void loop() {
final Looper me = myLooper(); //获取TLS存储的Looper对象
final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列
Binder.clearCallingIdentity();
//确保在权限检查时基于本地进程,而不是调用进程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //进入loop的主循环方法
Message msg = queue.next(); //可能会阻塞
if (msg == null) { //没有消息,则退出循环
return;
}
//默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg); //用于分发Message
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//恢复调用者信息
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked(); //将Message放入消息池
}
}
可以看到,其实最重要的就是 msg.target.dispatchMessage(msg)这句话,它取出了Message对应的的Handler并且调用它的dispatchMessage方法,看一下。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//当Message存在回调方法,回调msg.callback.run()方法;
handleCallback(msg);
} else {
if (mCallback != null) {
//当Handler存在Callback成员变量时,回调方法handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler自身的回调方法handleMessage()
handleMessage(msg);
}
}
真像大白了:
- [1] 当Message的回调方法不为空时,则回调方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
- [2] 当Handler的mCallback成员变量不为空时,则回调方法mCallback.handleMessage(msg),否则进入步骤3;
- [3] 调用Handler自身的回调方法handleMessage(),该方法默认为空,Handler子类通过覆写该方法来完成具体的逻辑。
我们一般使用的是3,如果调用的是Handler的post方法传的Runnable对象则是第一种,流程和上述差不多,只是把msg的callback对象赋为了你传入的runnable而已。
注意,从这里可以看出,Looper所在的线程,才是真正执行handleMessage或者你传入的Runnable的线程,现在基本把framework层的Handler机制理清楚了。
上一篇: 小米4C刷LineageOS
下一篇: 电池报警来源 和 电池和电量关机设置修改