前言
Android
基于JVM
部分规范定制了自己的Davik
和Art
虚拟机,Android
会调用ActivityThread
的main
开始一个app
的启动.
大多数UI
应用为了避免内存共享导致的多线程问题,都采用了消息队列维护一个Main线程进行单独控制视图,如win32
,Android
,Java Swing
.
在ActivityThread.main
便存在一个消息循环的初始化。
class ActivityThread{
public static void main(String[] args) {
//构造一个主线程对应的Looper实例,存放在Looper.sMainLooper中
Looper.prepareMainLooper();
//...
//创建一个Handler对象实例
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
//开启事件循环
Looper.loop();
}
//handler实例
final H mH = new H();
final Handler getHandler() {
return mH;
}
static volatile Handler sMainThreadHandler;
}
复制代码
Looper分析1
为避免一次性分析所有代码所带来的烦恼这chapter只解析部分
Looper.prepareMainLooper
分析
class Looper{
//用于存放线程对应Looper实例
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
@Deprecated
public static void prepareMainLooper() {
//构造了一个main线程实例
prepare(false);
synchronized (Looper.class) {
//保存mainLooper实例
sMainLooper = myLooper();
}
}
//获取当前线程对应的Looper实例
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
public static void prepare() {
prepare(true);
}
//构造lopper实例放入一个ThteadLocal中
private static void prepare(boolean quitAllowed) {
sThreadLocal.set(new Looper(quitAllowed));
}
//构造一个消息队列
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
复制代码
从上面我们可以得到Looper.prepare
构造对应调用线程的Looper
实例,然后将实例放入
一个ThreadLocal
中。
Looper.loop
分析
class Looper{
public static void loop() {
//得到当前线程对应实例
final Looper me = myLooper();
//得到Looper对应MessageQueue
final MessageQueue queue = me.mQueue;
boolean slowDeliveryDetected = false;
for (;;) {
//取出消息队列
Message msg = queue.next(); // might block
//没有消息那么就退出死循环
if (msg == null) {
return;
}
// Looper中你可以自定义一个日志器实例,然后你就可以统计
//主线程是否耗时了哦
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " "
msg.callback + ": " + msg.what);
}
//分配消息给回调
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
//可以自定义一个日志打印
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
}
}
}
复制代码
我们可以大概知道looper
的loop
函数会从MessageQueue
不断取出Message
对象,然后进行分发.
一个可以用于统计主线程耗时的技巧
class MainActivity : AppCompatActivity() {
class MyPrinter : Printer {
var startTime = System.currentTimeMillis()
override fun println(x: String) {
//事件分发处理函数开始
if (x.contains(">>>>> Dispatching to")) {
startTime = System.currentTimeMillis()
}
//事件分发处理函数开始
if (x.contains("<<<<< Finished to ")) {
val endTime = System.currentTimeMillis()
//某次主线程处理耗时为duration ms
val duration = endTime - startTime
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//得到主线程的Looper然后挂载自己的打印
var myLooper = Looper.getMainLooper();
myLooper.setMessageLogging(MyPrinter())
}
}
复制代码
MessageQueue 分析1
从上面的我得知Looper
会不断调用MessageQueue
的next
class MessageQueue{
//一个用于标识状态的类,这个标志位被native代码所使用,
//所以java
private long mPtr; // used by native code
Message next() {
// Return here if the message loop has already quit and been disposed.
//如果标志位为0那么证明消息循环已经结束那么返回null即可
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命令到内核,这里不需要关心
Binder.flushPendingCommands();
}
//调用linux的epoll函数进行休眠
//epoll是linux的下的api,这里不需要深究只需要认为object.wait 和object.notify的作用即可
//当有信息要处理或者休眠到期或被内核唤醒
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;
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 (false) Log.v("MessageQueue", "Returning message: " + msg);
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
//...略
}
}
}
复制代码
Message
作为队列节点的数据机构,我们看下他的属性:
public final class Message implements Parcelable {
//用户多定义的消息标识号
public int what;
/**
* 目标消息要送达的时间。这个时间是基于{@link SystemClock#uptimeMillis}.
*/
@UnsupportedAppUsage
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public long when;
//所对应的handler如果这个为空那么标识这个消息作为同步凭证
@UnsupportedAppUsage
/*package*/ Handler target;
//如果你希望直接回调自定义的函数那么赋值这个即可
@UnsupportedAppUsage
/*package*/ Runnable callback;
// 链表的下一个节点
@UnsupportedAppUsage
/*package*/ Message next;
}
复制代码
其内部的链表结构大致如下:
其中特别要注意的是 链表是按照优先级排序的,when
越小越在前面.(when
表示要执行的时间)
我们看下MessageQueue
入队的操作函数
class MessageQueue{
//标识消息队列在调用next函数的时候被阻塞在pollOnce()传入非零的超时参数
private boolean mBlocked;
final long ptr = mPtr; //当mPtr为0时退出消息循环,在构造MessageQueue的时候会初始化一个非0数值
//when 表示此消息要送达的时间
boolean enqueueMessage(Message msg, long when) {
//同步锁 防止多线程并发放入
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//当前队列是空的还未初始化 那么进行初始化
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//插入消息在队列中,在多数情况我们不需要唤醒队列,除非有一个同步屏障在头队列头且消息是一个最早的异步消息,这里可以简单理解为了快速响应异步消息
//同步屏障后面在细讲
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;
}
// We can assume mPtr != 0 because mQuitting is false.
//因为之前判断过当前消息队列没有退出( mQuitting is false),所以我们假设
//mPtr!=0
if (needWake) {
//mPtr==0标识已经退出
//nativeWake使用linux底层的epool唤醒队列,并且使用mPtr告知唤醒的消息队列状态
nativeWake(mPtr);
}
}
return true;
}
}
复制代码
Handler分析
为了构建一个和其他线程通信的桥梁,Android提供Handler
,方便我们使用Looper
完成线程通信和切换.
直接看如下案例(网上轮子过多不在介绍):
class MainActivity : AppCompatActivity() {
private class MyHandler(activity: MainActivity) : Handler() {
private val mTarget: WeakReference<MainActivity>
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
init {
mTarget = WeakReference<MainActivity>(activity)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
//子线程向主线程通信
// val myHandler = MyHandler(this)
// val thread = Thread(Runnable {
// myHandler.sendEmptyMessage(1)
// })
//主线程向子线程通信
var myHandler: MyHandler? = null
val thread = Thread(Runnable {
myHandler = MyHandler(this)
Looper.prepare()
Looper.loop()
}).start()
myHandler?.sendEmptyMessage(0);
//请在activity销毁或者不需要的退出,我见到好多大佬都忘了哦
myHandler?.looper?.quit()
}
fun onDestry(){
}
}
复制代码
构造函数
public interface Callback {
public boolean handleMessage(Message msg);
}
class Handler{
//callback:当消息对象Message没有设置特有回调时,就先会先用Handler.CallBack,然后再根据返回值为false再去调用Handler自身的handleMessage函数
//async:是否允许开启同步屏障 后文在讲叙
public Handler(Callback callback, boolean async)
}
复制代码
我们知道消息分发的时候会调用下面的代码:
class Looper{
public static void loop() {
//分配消息给回调
//msg是Message , target是Handler
msg.target.dispatchMessage(msg);
}
}
}
复制代码
所以我们消息最终分发到了Handler
class Handler{
public void dispatchMessage(Message msg) {
//如果Message有指定回调那么直接调用
if (msg.callback != null) {
handleCallback(msg);
} else {
//构造函数传入callback如果设置了根据返回值确定是否继续调用自己的handleMessage
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//这个一般开发者都会重写的函数
handleMessage(msg);
}
}
//调用Message定义的回调然后直接返回
private static void handleCallback(Message message) {
message.callback.run();
}
}
复制代码
所以得到以下的回调链条:
同步屏障
Android
仅能在主线程更新ui
,为了流程度我们一般会在16ms
重绘一次屏幕,但是如果此时事件循环中有无数的事件队列怎么办?这将导致UI
绘制延迟 ,严重影响用户体验问题。
考虑到这种情况Android
,把消息分为两种,同步消息和异步消息,默认情况我们的消息都是同步消息。
只需要调用Message.setAsynchronous
即可成为异步消息.
class Message{
public boolean isAsynchronous() {
return (flags & FLAG_ASYNCHRONOUS) != 0;
}
public void setAsynchronous(boolean async) {
if (async) {
flags |= FLAG_ASYNCHRONOUS;
} else {
flags &= ~FLAG_ASYNCHRONOUS;
}
}
}
复制代码
成为异步消息后,还需要插入同步屏障,这时所有的同步的消息都不会执行只会优先执行异步消息。
我们回过头看下MessageQueue
的异步处理部分
class MessageQueue{
Message next() {
//...略
//如果当前的头节点是target是null那么证明插入了一个同步屏障标志
//那么一直轮询到一个异步消息然后返回处理
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
//...略
}
}
}
复制代码
示意图:
我们看下Android
系统下如何利用这个机制进行刷新ui
的
class ViewRootImpl{
//这个函数开始便利根view致性各种绘制操作
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//放入一个屏障
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
}
复制代码
具体插入屏障代码,由于只是简单的链表操作不在解释
class Looper{
public int postSyncBarrier() {
return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
}
}
class MessageQueue(){
int enqueueSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
//移除
void removeSyncBarrier(int token) {
// Remove a sync barrier token from the queue.
// If the queue is no longer stalled by a barrier then wake it.
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();
// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}
}
复制代码
IdleHandler
MessageQueue
我们重新看下这个类的next
函数
//MessageQueue.java
Class MessageQueue{
private IdleHandler[] mPendingIdleHandlers;
Message next() {
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();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//如果有消息就分发 然后返回
}
//如果没有消息那么就调用IdleHandler集合中的对象
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
//调用函数
keep = idler.queueIdle();
//如果函数返回false,那么会将其移除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
pendingIdleHandlerCount = 0;
nextPollTimeoutMillis = 0;
}
}
}
复制代码