本篇是由我学习并总结,有不对的地方多多指正。
Handler的使用
Handler的使用很简单,有匿名内部类的方式,静态内部类的方式等等。
常见的大概就是这两种吧,一会也将对比他们之间的不同
private val handler = object :Handler(Looper.getMainLooper()){
}
private val handler2 = MainActivity.TestHandler(this)
class TestHandler(activity: Activity) : Handler(Looper.getMainLooper()) {
private val weakActivity: WeakReference<Activity> = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
此时我们已经获取到了Handler,那么就可以post发送一个Runnable,或者sendMessage来发送消息,或从子线程中更新UI了。
handler.sendMessage(Message())
handler.post {
gotoDo()
}
既然已经了解到使用了,那为什么handler就可以将线程切换到主线程?
Handler的消息处理分析
首先要清楚Handler的组成,分别有Looper,MessageQueue,Message以及Handler。 他们之间的持有关系则是 Looper->MessageQueue->Message->Handler,那handler既然是发送数据的,为什么Message会持有Handler的引用?这是接下来要说的。
如何获取到Looper
仔细查看Looper的源码,会发现Looper的实例化其实是私有的,也就是外界并没有实例化Looper的办法。
Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
那就得看官方提供的有哪些方法可以实例化,毕竟无法实例化那怎么处理数据,在观察后发现,Looper提供了静态方法Looper.prepare用于初始化Looper。
Looper.java
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
//当该线程已经存在Looper执行者,那就会抛出异常
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
其中有一个静态的sThreadLocal来保证每个线程只有一个Looper,来保持消息处理无误。并在上面Looper的实例化中看到,实例化了MessageQueue消息队列。 此刻可能还会有疑惑,那既然Looper需要初始化才可以进行处理消息,为什么我们并没有做任何操作,主线程的Looper就已经存在了?
ActivityThread.java
public static void main(String[] args) {
Looper.prepareMainLooper();
.....
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
.....
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
看了ActivityThread的main方法就懂了,Android程序与Java程序其实并无差别,都需要一个main方法才可以运行,而Android只是进行了层层封装,使得我们可以不用管这些,只需要处理好自己的业务逻辑
Handler发送消息或post一个runnable到了哪?
现在追踪sendMessage的方法,来看看我们的message具体到了哪
Handler.java
public final boolean sendMessage(@NonNull Message msg) {
//发送一个延时为0的消息
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);
}
//此刻才是真正的在queue中添加到了队列
return queue.enqueueMessage(msg, uptimeMillis);
}
而在此处也正好看到了,在enqueueMessage时,会将消息的target指向发送消息的Handler,而此时Message被加入到MessageQueue中。这就解释了为什么MessageQueue在持有Message的同时,会持有发送消息的Handler。恩,这些都懂,但为什么Message需要持有发送他的Handler?这个一会再说,我们接着看post方法。
//发送一个runnable,在下面仍然会返回sendMessageDelayed,与上面相同,所以不再罗列
//重点看看getPostMessage(r)
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
//为message的callBack设置为传递过来的runnable
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
也就是到最后,都会进入到enqueueMessage方法,添加到MessageQueue中。
MessageQueue的添加队列
在添加到MessageQueue时,MessageQueue都做了什么?
boolean enqueueMessage(Message msg, long when) {
//如果target为空,抛出异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
......
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//如果当前消息队列为空,或者现在发送的非延时消息,又或者当前的时间节点小于第一个的时间节点(也就是如果第一个是需要2秒后处理,而现在要插入的是及时处理)
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
//那么把当前发送的消息的next指向消息队列的头部
msg.next = p;
//并将当前的消息队列重新设置为发送的
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
//prev 可以理解为左半的消息队列
Message prev;
for (;;) {
prev = p;
p = p.next;
//当p==null也就是消息遍历完,或者当前的时间小于下个要处理的时间,退出
if (p == null || when < p.when) {
break;
}
//判断是否要唤醒Looper处理
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.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
从这里可以看出,一直说的消息队列竟然是个链表,还是个单向链表。 这里可能会有很多同学第一个会迷住,这样处理会有啥结果啊?为什么要这样处理?不要急,这就分析一波,首先分析第一个if判断语句,这三个都会将当前消息放在链表的头部,作为下个首要处理的消息。
接下来看插入到对应时间,是怎么做到的,首先假设好原给的消息队列和要插入的消息,
可能会说在执行后Prev已经指向的是第二个消息,为什么到最后还能连起来,因为mMessages仍然指向链表,而计算出Prev和p,只是为了让消息更好的插入进入,毕竟它只是个单向链表。
Looper的处理消息
在Looper.loop方法中可以看到,在这里形成了死循环在处理消息,而其中最重要的就是queue的next()取消息了。
Looper.java
for (;;) {
if (!loopOnce(me, ident, thresholdOverride)) {
return;
}
}
private static boolean loopOnce(final Looper me,
final long ident, final int thresholdOverride) {
Message msg = me.mQueue.next(); // might block
......
//消息分发
msg.target.dispatchMessage(msg);
}
在取到消息后,就正式开始处理,并在一系列后,进入到msg.target.dispatchMessage中处理消息,这也就意味着为什么message需要持有一个target,因为在具体处理时,可以进行回调到发送message的Handler的handleMessage进行消息的处理。
来看看dispatchMessage都做了什么吧
public void dispatchMessage(@NonNull Message msg) {
//当回调不为空时,这个指的就是 runnable事件
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
//调用handler的hanldMessage
handleMessage(msg);
}
}
自此Handler消息发送到处理的流程就走完了。 别急,还有更深入的内容。
为什么Looper是死循环不会ANR
首先,我们要知道MessageQueue的next取消息是一个阻塞方法,当首个消息需要延时,或无消息时, nativePollOnce(ptr, nextPollTimeoutMillis)进行阻塞,尤其是无消息时,会传入-1,表示无限期阻塞,这就相当于 Scanner(System.in),但一直阻塞怎么办?新增加的消息不执行?
并不是的,什么时候消息会发生改变,只有在enqueueMessage时,添加消息到消息队列时,所以在这个方法中可以看到nativeWake来唤醒Looper的阻塞。
并且屏幕的绘制、页面刷新等都依靠Looper,loop的停止运行才会导致整个app的崩溃。
throw new RuntimeException("Main thread loop unexpectedly exited");
为什么匿名内部类的方式会内存泄漏
学过java的都知道,只要是非静态的内部类,都会默认持有外部类的引用,比如可以直接获取外部类的属性等。那message中持有了Handler,当Handler为非静态时,同样也会持有Activity的引用。
那么一定会内存泄漏吗?
不一定,如果只是简单的消息任务,那么在执行后,并无大碍。但是在handler发送了一个延时的消息,并且在activity销毁时,延时消息还没有执行,就可能会引起内存泄漏,activity被持有,无法释放。
怎么解决?
最简单的方式就是,创建一个静态内部类,并在内部声明一个弱引用的activity对象,弱引用可以在GC时,无论内存是否充足,都会回收弱引用。
private val handler2 = MainActivity.TestHandler(this)
class TestHandler(activity: Activity) : Handler(Looper.getMainLooper()) {
private val weakActivity: WeakReference<Activity> = WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
}
}
Looper怎么就做到了切换线程?
其实Looper什么都没做,他只是在它自己的线程中不断的循环取出消息,处理消息。因为在同一进程中,不同线程之间的内存是共享的,无论在哪个线程中发送消息,Looper都能够收到唤醒且处理。
Handler异步消息(同步屏障)
有人可能有疑问,Handler不是从子线程调回到主线程处理消息?怎么还有异步消息了?
首先我们需要知道,异步消息是为了更好的处理UI绘制和更新的。哦?这怎么说。
首先如果当前已有很多的消息都在MessageQueue中,虽然都没有延时消息,但是胜在多,而屏幕为60HZ的,将会在16ms刷新一次,消息过多,而没有及时处理绘制UI的消息,那结果会怎么样,屏幕卡顿掉帧。
熟悉的scheduleTraversals就是如此,
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//发送异步消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
@UnsupportedAppUsage
@TestApi
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
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;
}
}
//将target为空的msg放在链表头部
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
//在处理异步消息时,取消同步屏障
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
在开启同步屏障后,如果不手动关闭,将无法再处理同步消息,用户态好像并不可以开启同步屏障。
而在上面的处理中发现,开启同步屏障,就是生成了一个Message,并将其target不设置值也就是null,这样就等于开启了同步屏障。在取数据时,就有了以下的判断.
if (msg != null && msg.target == null) {
// 当有同步屏障后,取出第一个异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
那既然target == null就是同步屏障,那我们直接设置null不行嘛?不行的,首先会在Handler的enqueueMessage中设置消息的target,其实在MessageQueue中的enqueueMessage中,判断target == null就会直接抛出异常。
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
那我们如何发布异步消息呢?
在之前API并没有公开,而在现在用户可以使用Handler.createAsync()就可以返回一个,异步消息专属的Handler了。
这也并看不出什么啊,怎么就特殊了?异步专属了?来看源码
@NonNull
public static Handler createAsync(@NonNull Looper looper) {
if (looper == null) throw new NullPointerException("looper must not be null");
return new Handler(looper, null, true);
}
@UnsupportedAppUsage
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
//重要参数
mAsynchronous = async;
}
在实例化后,我们发布消息,再来看看enqueMessage方法。
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
//在判断mAsynchronous为true时,设置msg的Asynchronous为true,这样就和上面的对应
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
而在next取消息时,也能看到,如果开启了同步屏障,就会找第一个msg的Asynchronous为true的消息。
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());
}
结尾
以上呢,就是看Handler源码的结果,可能还有并没有总结的,希望各位多多批评指正,谢谢。哪里不清楚的,也可以在评论区问我。