一个线程有几个Handler?一个线程有几个Looper?如何保证?
Handler的个数与所在线程无关,可以在线程中实例化任意多个Handler。一个线程中只有一个Looper。Looper的构造方法被声明为了private,我们无法通过new关键字来实例化Looper,唯一开放的可以实例化Looper的方法是prepare。prepare方法的源码如下:
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");
}
sThreadLocal.set(new Looper(quitAllowed));
}
我们知道ThreadLocal是一个线程内部的数据存储类,当某个线程调用prepare方法的时候,会首先通过ThreadLocal检查这个线程是否已经创建了Looper,如果还没创建,则实例化Looper并将实例化后的Looper保存到ThreadLocal中,而如果ThreadLocal中已经保存了Looper,则会抛出一个RuntimeException的异常。那么意味着在一个线程中最多只能调用一次prepare方法,这样就保证了Looper的唯一性。
Handler线程是如何切换的?
(1)假设现在有一个线程A,在A线程中通过Looper.prepare和Looper.loop来开启Looper,并且在A线程中实例化出来一个Handler。Looper.prepare()方法被调用时会为会初始化Looper并为ThreadLocal 设置Looper,此时ThreadLocal中就存储了A线程的Looper。另外MessageQueue也会在Looper中被初始化。
(2)接着当调用Loop.loop方法时,loop方法会通过myLooper得到A线程中的Looper,进而拿到Looper中的MessageQueue,接着开启死循环等待执行MessageQueue中的方法。 (3)此时,再开启一个线程B,并在B线程中通过Handler发送出一个Message,这个Message最终会通过sendMessageAtTime方法调用到MessageQueue的equeueMessage方法将消息插入到队列。
(4)由于Looper的loop是一个死循环,当MessageQueue中被插入消息的时候,loop方法就会取出MessageQueue中的消息,并执行callback。而此时,Looper是A线程的Looper,进而调用的Message或者Handler的Callback都是执行在A线成中的。以此达到了线程的切换。
Handler内存泄漏的原因是什么?如何解决?
通常在使用Handler的时候回通过匿名内部类的方式来实例化Handler,而非静态的匿名内部类默认持有外部类的引用,即匿名内部类Handler持有了外部类。而导致内存泄漏的根本原因是是因为Handler的生命周期与宿主的生命周期不一致。比如说在Activity中实例化了一个非静态的匿名内部类Handler,然后通过Handler发送了一个延迟消息,但是在消息还未执行时结束了Activity,此时由于Handler持有Activity,就会导致Activity无法被GC回收,也就是出现了内存泄漏的问题。解决方式是可以把Handler声明为静态的匿名内部类,但这样一来,在Handler内部就没办法调用到Activity中的非静态方法或变量。那么最终的解决方案可以使用静态内部类 + 弱引用来解决。代码如下:
public class MainActivity extends AppCompatActivity {
private MyHandler mMyHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private void handleMessage(Message msg) {
}
static class MyHandler extends Handler {
private WeakReference<Activity> mReference;
MyHandler(Activity reference) {
mReference = new WeakReference<>(reference);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) mReference.get();
if (activity != null) {
activity.handleMessage(msg);
}
}
}
@Override
protected void onDestroy() {
mMyHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}
}
子线程中使用Looper应该注意什么?有什么用?
子线程中开启的Looper,在没有消息时一定要调用Looper的quit或者quitSafely方法。不然子线程会一直处于阻塞状态,无法被回收,进而可能导致内存泄漏的问题。
MessageQueue是如何保证线程安全的?
enqueueMessage方法和next方法中的代码都加了synchronize关键字来保证线程安全。
我们使用Message的时候如何创建它?
这里推荐使用 Message.obtain() 方法来实例化一个 Message,好处在于它会从消息池中取,而避免了重复创建的开销。虽然直接实例化一个 Message 其实并没有多大开销,但是我们知道 Android 是消息驱动的,这也就说明 Message 的使用量是很大的,所以当基数很大时,消息池就显得非常有必要了。
public final class Message implements Parcelable {
//消息的标示
public int what;
//系统自带的两个参数
public int arg1;
public int arg2;
//处理消息的相对时间
long when;
Bundle data;
Handler target;
Runnable callback;
Message next; //消息池是以链表结构存储 Message
private static Message sPool; //消息池中的头节点
//公有的构造方法,所以我们可以通过 new Message() 实例化一个消息了
public Message() {
}
//推荐以这种方式实例化一个 Message,
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
//取出头节点返回
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
//回收消息
public void recycle() {
if (isInUse()) {
if (gCheckRecycle) {
throw new IllegalStateException("This message cannot be recycled because it "
+ "is still in use.");
}
return;
}
recycleUnchecked();
}
void recycleUnchecked() {
//清空消息的所有标志信息
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
//链表头插法
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
}
Message中维护了一个sPools的消息池,obtain方法会从消息池中取出一个消息,避免了实例化。而在Message使用结束后会通过recycle或者recycleUnchedked来回收Message。
Looper死循环为什么不会导致应用卡死?
如果按照 Message.next 方法的注释来解释的话,如果返回的 Message 为空,就说明消息队列已经退出了,这种情况下只能说明应用已经退出了。这也正符合我们开头所说的,Android 本身是消息驱动,所以没有消息几乎是不可能的事;如果按照源码分析,Message.next() 方法可能会阻塞是因为如果消息需要延迟处理(sendMessageDelayed等),那就需要阻塞等待时间到了才会把消息取出然后分发出去。然后这个 ANR 完全是两个概念,ANR 本质上是因为消息未得到及时处理而导致的。同时,从另外一方面来说,对于 CPU 来说,线程无非就是一段可执行的代码,执行完之后就结束了。而对于 Android 主线程来说,不可能运行一段时间之后就自己退出了,那就需要使用死循环,保证应用不会退出。这样一想,其实这样的安排还是很有道理的。
能不能让一个Message被加急处理?
系统内部可以通过开启同步屏障,并发送异步消息来优先处理异步消息。但是由于开启同步屏障的方法postSyncBarrier被隐藏,外部无法调用,并且发送异步消息的方法也是被hide的,所以在APP开发中实际是没有办法去发送一个加急消息的。
Handler的同步屏障是什么?
在 Handler 中还存在了一种特殊的消息,它的 target 为 null,并不会被消费,仅仅是作为一个标识处于 MessageQueue 中。它就是 SyncBarrier (同步屏障)这种特殊的消息。当读取到这个同步屏障后就会开启一个循环取查找msg.isAsynchronous()为true的消息。如果找到则终止循环,优先执行msg.isAsynchronous()为true的这个Message。这个逻辑是在MessageQueue的next方法中实现的,代码如下
// MessageQueue
Message next() {
// ...省略无关代码
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) { // 读取到msg.target为null,即这里被添加了一个同步屏障
// Stalled by a barrier. Find the next asynchronous message in the queue.
do { // 开启循环查找Message
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous()); // 找到isAsynchronous为true的消息,则终止循环,执行后边的代码来处理这个Message
}
// ... 省略无关代码
}
}
同步屏障的原理其实就这么简单,那么这个同步屏障有什么用呢?其实同步屏障仅仅是在系统内部使用,我们无法通过公开的API来开启一个同步屏障。具体的使用案例来看View的绘制流程,在ViewRootImp类中有如下代码:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 调用MessageQueue的postSyncBarrier开启同步屏障
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 此处会发送一个synchronus为true的Message
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
接着来看MessageQueue中的postSyncBarrier方法:
/*
* @hide
*/
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;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
可以看到postSyncBarrier使用了@hide注解隐藏了API,意味着这个方法外部是无法使用的。而在postSyncBarrier(long when)方法中为MessageQueue插入了一个Message,而这个Message并没有给target赋值。至此可以联系到next方法中对target为null情况的处理。
接下来看下mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null)这个方法的源码
public void postCallbackDelayed(int callbackType,
Runnable action, Object token, long delayMillis) {
if (action == null) {
throw new IllegalArgumentException("action must not be null");
}
if (callbackType < 0 || callbackType > CALLBACK_LAST) {
throw new IllegalArgumentException("callbackType is invalid");
}
postCallbackDelayedInternal(callbackType, action, token, delayMillis);
}
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, "PostCallback: type=" + callbackType
+ ", action=" + action + ", token=" + token
+ ", delayMillis=" + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
// 设置异步消息
msg.setAsynchronous(true);
// 发送异步消息到MessageQueue
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
可以看到在postCallbackDelayed方法中最终发送了一个异步消息,因为有了前边的同步屏障,那么这个异步消息就会优先执行。可见这里通过同步屏障和异步消息来保证了View的绘制会优先执行,避免了消息过多的情况下出现掉帧的情况。
Handler的阻塞唤醒机制是什么?
Handler 中其实还存在着一种阻塞唤醒机制,我们都知道不断地进行循环是非常消耗资源的,有时我们 MessageQueue 中的消息都不是当下就需要执行的,而是要过一段时间,此时如果 Looper 仍然不断进行循环肯定是一种对于资源的浪费。
因此 Handler 设计了这样一种阻塞唤醒机制使得在当下没有需要执行的消息时,就将 Looper 的 loop 过程阻塞,直到下一个任务的执行时间到达或者一些特殊情况下再将其唤醒,从而避免了上述的资源浪费。
这个阻塞唤醒机制是基于 Linux 的 I/O 多路复用机制 epoll 实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作。
Handler的阻塞机制的实现是在MessageQueue的next方法中,通过调用nativePollOnce的一个native方法实现的。
遇到以下情况,Java 层会调用 natvieWake 方法进行唤醒。
MessageQueue 类中调用 nativeWake 方法主要有下列几个时机:
调用 MessageQueue 的 quit 方法进行退出时,会进行唤醒
消息入队时,若插入的消息在链表最前端(最早将执行)或者有同步屏障时插入的是最前端的异步消息(最早被执行的异步消息)
移除同步屏障时,若消息列表为空或者同步屏障后面不是异步消息时
可以发现,主要是在可能不再需要阻塞的情况下进行唤醒。(比如加入了一个更早的任务,那继续阻塞显然会影响这个任务的执行)
网友评论