Android消息机制概略
从根本上来说Android系统同windows系统一样,也属于消息驱动型系统 消息驱动型系统会有以下4大要素
- 接收消息的“消息队列”
- 阻塞式地从消息队列中接收消息并进行处理的“线程”
- 可发送的“消息的格式”
- 消息发送函数
Android系统与之对应的实现就是 (MessageQueue,Looper,Handler)
- 接收消息的“消息队列” ——【MessageQueue】主要功能是投递消息(
MessageQueue.enqueueMessage
)和取走消息池的消息(MessageQueue.next
); - 阻塞式地从消息队列中接收消息并进行处理的“线程” ——【Thread+Looper】
- 可发送的“消息的格式” ——【Message】
- “消息发送函数”——【Handler的post和sendMessage】
一个Looper
类似一个消息泵。它本身是一个死循环,不断地从MessageQueue
中提取Message
或者Runnable。而Handler
可以看做是一个Looper
的暴露接口,向外部暴露一些事件,并暴露sendMessage()
和post()
函数
Android消息机制的大致流程
因为Android的消息模型是贯穿整个App的生命周期的,所以主线程的消息模型在App启动时就被创建。
Android app的入口类是ActivityThread
,这个类中有个main
方法,这个方法是整个app的入口,
其中有下面一段代码
//创建一个消息循环
public static void main(String[] args) {
......
Looper.prepareMainLooper(); //主线程的Looper(),也是app应用主主要的消息机制的消息管理者
......
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
这段代码最后是一个异常,如果代码运行到哪里,就会导致app崩溃,既然我们的app没有崩溃,说明代码中以 hi没有执行到那里,怎么才能使那段代码不被执行呢?通过死循环
其中Looper.loop()
public static void loop() {
......
for(;;){
......
}
......
}
looper不停的去轮询消息队列,当有消息时更新,没有消息时休眠.这是我们理想中的状态,
那么如果来控制这个循环状态呢?
因为Android系统是基于Linux系统的,自然会汲取Linux成熟的一些设计。其中消息循环就是借助Linux的epoll机制。
消息循环中有两个重要的native方法 nativePollOnce
消息挂起和nativeWake
消息唤醒。
Android消息循环机制的主流程简略概括如下
主线程
- App进程创建
- 初始化一个消息队列
- 主线程死循环读取消息队列
- 消息处理完之后调用
nativePollOnce
挂起线程
其他线程
- 引用主线程的消息队列,添加消息到队列里。(由于做了线程同步这时候主线程的队列数据已经和子线程一致了)
- 调用
nativeWake
唤醒主线程
主线程
- 主线程nativePollOnce挂起取消
- 处理消息
- 消息处理完消息,nativePollOnce继续挂起等待新消息
....
细说Android消息机制的流程
上面对Android的消息机制做了一个简单的介绍,接下来跟着源码再来仔细的分析一下消息机制
先展示一个典型的关于Handler/Looper的线程
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
//TODO 定义消息处理逻辑.
}
};
Looper.loop();
}
}
//只有在子线程需要 Looper.prepare()和 Looper.loop();
//因为主线程在初始化的时候已经创建了一个Looper对象了
Looper
prepare()
对于无参的情况,默认调用prepare(true)
,表示的是这个Looper允许退出,而对于false的情况则表示当前Looper不允许退出,主线程的Looper就是不允许退出的。
private static void prepare(boolean quitAllowed) {
//每个线程只允许执行一次该方法,第二次执行时线程的TLS已有数据,则会抛出异常。
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
//创建Looper对象,并保存到当前线程的TLS区域
sThreadLocal.set(new Looper(quitAllowed));
}
//这个方法是主线程Looper初始化的代码,入口在ActivityThread中,即App初始化时就会被调用
public static void prepareMainLooper() {
prepare(false); //设置不允许退出的Looper
synchronized (Looper.class) {
//将当前的Looper保存为主Looper,每个线程只允许执行一次。
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
每个线程只能有一个Looper对象,并且与线程(Thread)绑定,在prepare
方法中Looper
会新建了Looper
对象和MessageQueue
对象
Looper
中有一个 ThreadLocal
类型的 sThreadLocal
静态字段,Looper
通过它的 get
和 set
方法来赋值和取值。
由于 ThreadLocal
是与线程绑定的,所以我们只要把 Looper
与 ThreadLocal
绑定了,那 Looper
和 Thread
也就关联上了.ThreadLocal
的分析可以看文章下面的知识补充
loop()
public static void loop() {
final Looper me = myLooper(); //获取TLS存储的Looper对象
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue; //获取Looper对象中的消息队列
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) { //死循环
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是一个Handler对象,回调Handler.dispatchMessage(msg)
msg.target.dispatchMessage(msg);
...
msg.recycleUnchecked(); //将Message放入消息池,用以复用
}
}
- 读取MessageQueue的下一条Message;
- 把Message分发给相应的Handler
- 再把分发后的Message回收到消息池,以便重复利用
quit()
public void quit() {
mQueue.quit(false); //消息移除
}
public void quitSafely() {
mQueue.quit(true); //安全地消息移除
}
----------------------------------------MessageQueue#quit();-----------------------------
void quit(boolean safe) {
// 主线程的MessageQueue.quit()行为会抛出异常,
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) { //防止多次执行退出操作
return;
}
mQuitting = true;
if (safe) {
removeAllFutureMessagesLocked(); //移除尚未触发的所有消息
} else {
removeAllMessagesLocked(); //移除所有的消息
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
其他方法
Looper.myLooper() //获取当前线程
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Looper.getMainLooper() //获取主线程
//判断当前线程是不是主线程
Looper.myLooper() == Looper.getMainLooper()
Looper.getMainLooper().getThread() == Thread.currentThread()
Looper.getMainLooper().isCurrentThread()
Handler
Handler的主要作用是什么呢?
- 跨进程通信
- 跨进程更新UI
- 与
looper
messageQueue
一起构建Android的消息循环模型
首先创建Handler
无参构造,
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
//匿名类、内部类或本地类都必须申明为static,否则会警告可能出现内存泄露,
//这个警告Android studio会提示给开发者
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());
}
}
//Handler与Looper的绑定过程,如果在绑定之前没有调用Looper.prepare()方法的话就会报错
mLooper = Looper.myLooper(); //从当前线程的TLS中获取Looper对象
if (mLooper == null) {
throw new RuntimeException("");
}
mQueue = mLooper.mQueue; //消息队列,来自Looper对象
mCallback = callback; //回调方法
mAsynchronous = async; //设置消息是否为异步处理方式
}
对于Handler的无参构造方法,默认采用当前线程TLS中的Looper对象,并且callback回调方法为null,且消息为同步处理方式。只要执行的Looper.prepare()方法,那么便可以获取有效的Looper对象。
有参构造
//Handler与传入的Looper进行绑定
public Handler(Looper looper) {
this(looper, null, false);
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
Handler类在构造方法中,可指定Looper,Callback回调方法以及消息的处理方式(同步或异步)
消息分发
在Looper.loop()中,当发现有消息时,调用消息的目标handler,执行dispatchMessage()方法来分发消息。
public void dispatchMessage(Message msg) {
//调用Handler.post系列方法时,会给Message的callback赋值,
//所以在分发消息的时候会调用handleCallback(msg)
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
//当Handler设置了回调对象Callback变量时,回调方法handleMessage();
if (mCallback.handleMessage(msg)) {
return;
}
}
//Handler自身的回调方法handleMessage() 需子类实现相应逻辑
handleMessage(msg);
}
}
消息发送
查看Handler的所有的消息发送入口,包括post()
系列方法和sendMessage()
系列方法,你会发送最终都会到MessageQueue.enqueueMessage()
;
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//设置Message的消费Handler为当前Handler
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//uptimeMillis = SystemClock.uptimeMillis() + delayMillis
//SystemClock.uptimeMillis() 代表的是自系统启动开始从0开始的到调用该方法时相差的毫秒数
//比起System.currentTimeMillis()更加可靠,因为System.currentTimeMillis()是跟系统时间强关联的,
//修改了系统时间,System.currentTimeMillis()就会发生变化.
消息回收
removeMessages
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
removeCallbacksAndMessages
public final void removeCallbacksAndMessages(Object token) {
mQueue.removeCallbacksAndMessages(this, token);
}
实质上都是调用的MessageQueue
的方法来达到目的的
MessageQueue
MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,其中比较重要两个方法
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞loop循环
private native static void nativeWake(long ptr); //唤醒loop循环
next()
提取下一条message,如果有
enqueueMessage
按照参数when
来添加消息刀队列中
removeMessages
移除消息
篇幅有限,MessageQueue的各个方法就不详细说明
消息池
在代码中,可能经常看到recycle()方法,咋一看,可能是在做虚拟机的gc()相关的工作,其实不然,这是用于把消息加入到消息池的作用。这样的好处是,当消息池不为空时,可以直接从消息池中获取Message对象,而不是直接创建,提高效率。
静态变量sPool
的数据类型为Message,通过next成员变量,维护一个消息池;静态变量MAX_POOL_SIZE
代表消息池的可用大小;消息池的默认大小为50。
其他补充
一个线程中可以有多个Handler
,但是只能有一个Looper
MessageQueue是有序的
子线程可以创建Handler对象
不可以在子线程中直接调用 Handler 的无参构造方法,因为 Handler
在创建时必须要绑定一个 Looper
对象,有两种方法绑定
- 先调用 Looper.prepare() 在当前线程初始化一个 Looper
Looper.prepare();
Handler handler = new Handler();
// ....
// 这一步必须
Looper.loop();
- 通过构造方法传入一个 Looper
Looper looper = .....; //这里可以传入主线程的looper
Handler handler = new Handler(looper);
知识补充
ThreadLocal 浅析
ThreadLocal
是一个线程内部的数据存储类Thread Local Storage,简称为TLS),作用域限制在线程之内.即A线程存储的数据,不能被B线程使用。
ThreadLocal
在Android源码中的表现主要在Looper
、ActivityThread
以及AMS
.
使用示例
public class ThreadLocalTest extends AppCompatActivity {
private static final String TAG = "ThreadLocalTest";
private ThreadLocal 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();
}
}
//输出结果
//[Thread#main]mBooleanThreadLocal=true
//[Thread#1]mBooleanThreadLocal=false
//[Thread#2]mBooleanThreadLocal=null
从结果来看,都是调用的mBooleanThreadLocal.get()
方法,得到的值却不一样,这时因为ThreadLocal
会在各自的线程分别创建一个数据存储空间(ThreadLocalMap
),分别保存各个线程中的数据.
接下里跟着源码看下ThreadLocal的实现
ThreadLocal的public方法,只有三个 set, get ,remove
ThreadLocal#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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 获取当前线程
- 获取当前线程对应的
ThreadLocalMap
,这个map是跟线程绑定的,所以存在这个map中的数据也是跟线程绑定的 - 往map中添加数据,或者重新创建一个
ThreadLocalMap
对象
至于ThreadLocalMap
如何存储数据,这里就不展开说了。
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)
return (T)e.value;
}
return setInitialValue();
}
这段代码也十分简单,无外乎将之前set进去的数据拿出来。
总之,ThreadLocal根据不同的线程分别创建数据容器,从而达到数据独立的目的
参考文章
Android消息机制1-Handler(Java层)
Android 消息机制——你真的了解Handler?
你真的懂Handler吗?Handler问答
Android消息循环机制
Android中的Thread, Looper和Handler机制
ThreadLocal 原理