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.(Handler.java:200) at android.os.Handler.(Handler.java:114) at com.sensetime.myapplication.MainActivity$LooperThread$1.(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 sThreadLocal = new ThreadLocal();
下面终于要说要离用户更近的方法了,看看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机制理清楚了。
更多相关文章
- Flutter(三):实现Flutter代码调用Android原生代码(创建WebView Plu
- Android事件处理之使用异步任务执行下载
- android使用handlerthread创建线程示例
- Android属性动画完全解析(中),ValueAnimator和ObjectAnimator的高
- Android(安卓)Context简介
- 【Android(安卓)Linux内存及性能优化】(五) 进程内存的优化 - 线
- Android中关于SQLite数据库的一些知识
- Android超精准计步器开发-Dylan计步
- Android(安卓)Activity生命周期