文章目录
一.消息机制的概述
Android的消息机制主要指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。主要用于UI线程和子线程之间的交互。
一般情况下,出于安全的考虑,所有与UI控件的操作都要放在主线程及UI线程,而一些耗时操作应当放在子线程中。当在子线程中完成耗时操作并要对UI控件进行操作时,就要用Handler来控制了。
延伸:
- 系统为何不允许在子线程中访问UI?
因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预估的状态。 - 为什么系统不对UI控件的访问加上锁机制呢?
首先加上锁机制会让UI访问间的逻辑变得复杂,其次锁机制会降低UI访问的效率。
1.模型
消息机制主要包含:
方法 | 解释 |
---|---|
Message | 消息的载体:用于线程之间传递信息,在不同的线程间交换数据 |
MessageQueue | 消息队列:主要功能向消息池投递消息(MessageQueue.enqueueMessage)和取走消息池的消息(MessageQueue.next);每个线程中只有一个MessageQueue对象 |
Handler | 消息辅助类:主要功能向消息池发送各种消息事件 |
Looper | 每个线程中MessageQueue的管家,调用Looper的loop()方法之后,就会进入到一个无限循环中,每当发现MessageQueue中存在一条消息,就会将他取出,并传递到Handler的handleMessage() |
2.流程概述
当创建一个Message对象,并通过header将这条消息发送出去时,这条消息就会被添加到消息队列中等待被处理,而looper则会一直尝试从MessageQueue中取出待处理的信息,最后分发回Header的handleMessage()方法中。由于Hander是在主线程创建的,所以此时的handerMessage()方法在主线程运行
解释:
- Handler通过sendMessage()发送Message到MessageQueue队列;
- Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
- 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
3.典型实例
1.在主线程使用Handler
private Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
switch(msg.what){
case 1:{
//更新ui等操作
}
}
}
} ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread("Thread#1"){
@Override
public void run() {
Message message = new Message();
message.what = 1;
handler.sendMessage(message);
}
}.start();
}
2.子线程的使用
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
}
};
Looper.loop();
}
}
二.Android的消息机制分析
1.ThreadLocal
ThreadLocal是一个线程内部的数据存储类(Thread Local Storage,简称为TLS),通过它可以在指定的线程中存储数据,数据存储以后,只有在指定的线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
ThreadLocal在消息机制中的使用:Looper的作用域是线程并且不同线程有不同的Looper,通过ThreadLocal可以实现Looper在线程中的存取。
ThreadLocal 内部维护了一个 Map ,这个 Map 不是直接使用的 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类。而我们使用的 get()、set() 方法其实都是调用了这个ThreadLocalMap类对应的 get()、set() 方法
ThreadLocal的两个重要方法:
- ThreadLocal.set(T value):将value存储到当前线程的TLS区域,源码如下:
public void set(T value) {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //查找当前线程的本地储存区
if (map != null)
map.set(this, value);//保存数据value到当前线程this
else
//当线程本地存储区,尚未存储该线程相关信息时,
//对象创建ThreadLocalMap
createMap(t, value);
}
// ThreadLocalMap.set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
说白了,当前线程中存在一个Map变量,KEY是ThreadLocal,VALUE是你设置的值(用在消息机制中,就是当前线程的Looper)。
- ThreadLocal.get( )获取当前线程TLS区域的数据,源码如下:
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();
}
ThreadLocalMap 内部是一个entry对象, 还是 K : V 形式存在的。它用的是当前所在线程的 ThreadLocal , 作为key 要隔离的那个变量作为value。 并且 这个entry 对象 继承了 弱引用类型 这个类。
ThreadLocalMap.Entry:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
可参考的博客
ThreadLocalMap的内存泄漏问题
<一>深入理解Threadlocal的实现原理
https://cloud.tencent.com/developer/article/1393135
2. MessageQueue
消息队列,但是它的内部实现并不是队列,实际上是通过一个单链表的数据结构来维护消息队列。
2.1 创建MessageQueue
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
//通过native方法初始化消息队列,其中mPtr是供native代码使用
mPtr = nativeInit();
}
2.2 next()
从消息队列里取出一条消息并将其从消息队列中移除。
Message next() {
final long ptr = mPtr;
if (ptr == 0) { //当消息循环已经退出,则直接返回
return null;
}
int pendingIdleHandlerCount = -1; // 循环迭代的首次为-1
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//阻塞操作,当等待nextPollTimeoutMillis时长,或者消息队列被唤醒,都会返回
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//当消息的Handler为空时,则查询异步消息
if (msg != null && msg.target == null) {
//当查询到异步消息,则立刻退出循环
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//当异步消息触发时间大于当前时间,则设置下一次轮询的超时时长
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一条消息,并返回
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
//设置消息的使用状态,即flags |= FLAG_IN_USE
msg.markInUse();
return msg;//成功地获取MessageQueue中的下一条即将要执行的消息
}
} else {
//没有消息
nextPollTimeoutMillis = -1;
}
//消息正在退出,返回null
if (mQuitting) {
dispose();
return null;
}
//当消息队列为空,或者是消息队列的第一个消息时
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
//没有idle handlers 需要运行,则循环并等待。
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
//只有第一次循环时,会运行idle handlers,执行完成后,重置pendingIdleHandlerCount为0.
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; //去掉handler的引用
boolean keep = false;
try {
keep = idler.queueIdle();//idle时执行的方法
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
//重置idle handler个数为0,以保证不会再次重复运行
pendingIdleHandlerCount = 0;
//当调用一个空闲handler时,一个新message能够被分发,因此无需等待可以直接查询pending message.
nextPollTimeoutMillis = 0;
}
}
可以发现next方法是一个无限循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里。当有新消息到来时,next方法会返回这条消息并将其从单链表中删除。
2.3 enqueueMessage
往消息队列中插入一条消息。
boolean enqueueMessage(Message msg, long when) {
// 每一个普通Message必须有一个target
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {//正在退出时,回收msg,加入到消息池
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;
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; // invariant: p == prev.next
prev.next = msg;
}
//消息没有退出,我们认为此时mPtr != 0
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
MessageQueue是按照消息触发时间的先后顺序排列的,队头的消息是将要初始化触发的消息。当有消息需要加入消息发生时,会从此头开始遍历,直到找到消息应该插入的合适位置,以保证所有消息的时间顺序。
3. Looper
Looper在消息机制中扮演这消息循环的角色,具体来说就是它会不停地从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,负责会一直阻塞在那里。
2.1 prepare()
//可见sThreadLocal的get()和set()操作的类型都是Looper类型。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并保存到当前线程的TLS区域
sThreadLocal.set(new Looper(quitAllowed));
}
Looper.prepare()在每个线程只允许执行一次,该方法会创建Looper对象,再将Looper对象保存到当前线程TLS。
另外,与prepare()相近功能的,还有一个prepareMainLooper()方法,该方法主要在ActivityThread类中使用。
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
Looper还提供了一个getMainLooper方法,通过它可以在任何地方获取到主线程的Looper。
2.2 loop()
只有调用了loop()后,消息循环才会真正起作用
public static void loop() {
final Looper me = myLooper();//获取TLS存储的Looper对象
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
//确保在权限检查时基于本地进程,而不是调用进程。
final long ident = Binder.clearCallingIdentity();
for (;;) { //进入loop的主循环方法
Message msg = queue.next(); // might block
if (msg == null) {
return;
}
//默认为null,可通过setMessageLogging()方法来指定输出,用于debug功能
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
//恢复调用者信息
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}
loop方法是一个死循环,唯一跳出循环的方式是MessageQueue的next方法返回null。当Looper的quit方法被调用时,looper就会调用MeaaageQueue的quit或者quitSafely方法来通知消息队列退出,当消息队列被标为以退出状态时,它的next方法就会返回null。
2.3 quit()
public void quit() {
mQueue.quit(false); //消息移除
}
public void quitSafely() {
mQueue.quit(true); //安全地消息移除
}
消息退出的方式:
- 当safe = true时,只可移除尚未触发的所有消息,对于正在触发的消息并不可移除;
- 当safe = flase时,可移除所有的消息
4. Handler
4.1 构造方法
所有的构造方法实际调用的都是这个方法
public Handler(@Nullable 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, "");
}
}
//必须先执行Looper.prepare(),才能获取Looper对象,否则为null.
mLooper = Looper.myLooper();//从当前线程的TLS中获取Looper对象
if (mLooper == null) {
throw new RuntimeException(
"");
}
mQueue = mLooper.mQueue; //消息队列,来自Looper对象
mCallback = callback;//回调方法
mAsynchronous = async;//设置消息是否为异步处理方式
}
4.2 sendMessage方法
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);
}
可以看到通过sendMessage发送的方法最终会进入到MessageQueue中,而MessageQueue的next方法就会返回这条消息给Looper,Looper收到消息后就开始处理了,最终消息由Looper交给Handler处理。即Handler的dispatchMessage方法会调用,这时就进入了处理消息的阶段。
流程图片:
4.3 dispatchMessage方法
public void dispatchMessage(@NonNull 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);
}
}
发行消息流程:
- 当Message的某些方法不为空时,则某些方法msg.callback.run(),其中callBack数据类型为Runnable,否则进入步骤2;
- 当Handler的mCallback成员变量不为空时,则某些方法mCallback.handleMessage(msg),否则进入步骤3;
- 调用Handler本身的替代方法handleMessage(),该方法替换为空,Handler子类通过覆写该方法来完成具体的逻辑。
对于很多情况下,消息分发后的处理方法是第3种情况,即Handler.handleMessage(),一般地经常通过覆盖写该方法从而实现自己的业务逻辑。
5.主线程的消息循环
Android的主线程就是ActivityThread,主线程的入口方法为main,在mian方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()来开启主线程的消息循环。
public static void main(String[] args) {
......
Looper.prepareMainLooper(); //创建主线程的Looper以及MessageQueue
.....
Looper.loop(); //开启主线程的消息循环。
....