Looper解读 Tue, Mar 2, 2021 类图
prepare 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 ));
}
/**
* 构造方法中创建MessageQueue,并引用当前线程。
*/
private Looper ( boolean quitAllowed ) {
mQueue = new MessageQueue ( quitAllowed );
mThread = Thread . currentThread ();
}
Looper.prepare()方法只是为了创建MessageQueue和初始化ThreadLocal。
之前了解过,ThreadLocal可以保证多线程访问共享变量的线程安全问题。他不像synchronized靠阻塞实现线程安全,而是通过对变量拷贝的方式,使每一个线程都操作自己的拷贝,实现线程安全,所以效率要优于synchronized。详情看这里 和这里
ThreadLocal在此处的使用是为了保证每个线程都只能分配到一个Looper对象,多次调用Looper.prepary()方法会抛出异常。而且在其他线程去操作这个Looper对象可以保证线程安全,比如在主线程中调用mHandler.getLooper().quitSafely()来终止子线程的Looper对象的loop()循环。
loop 只看核心:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void loop () {
final Looper me = myLooper (); //其实就是从threadLocal中取出looper
//省略判空
me . mInLoop = true ; //改变状态
final MessageQueue queue = me . mQueue ; //拿到MessageQueue
...
//接下来就是启动个死循环,为什么不用while而用for呢?
for (;;) {
Message msg = queue . next (); // 从MessageQueue中取出Message
if ( msg == null ) {
//如果池子里面没有msg了则终止循环,所以这里并不是一个真正的死循环
return ;
}
try {
msg . target . dispatchMessage ( msg ); //调用Handler的dispatchMessage
} catch ( Exception exception ) {
//...
} finally {
//...
}
//回收消息
msg . recycleUnchecked ();
}
这里死循环的写法比较特殊,为什么不用while(true)或者do-while(true)呢?一开始总是想不明白。后来发现,用while系列循环,无论如何都要传递个表达式进去,那么就涉及到了内存占用和CPU运算。而用空for循环,则不必创建任何变量或结构体,能够最大限度的降低内存和CPU占用,真是学到了。
今天又突然想到,既然涉及到表达式,那么每次循环的时候都需要判断表达式是否成立,无可避免浪费了cpu,同时带来了cpu计算单元做无效计算带来的性能损失。
上面的代码中可以看到,当没有msg时,loop就会退出,那么问题来了,什么时候会没有msg呢?并且一旦loop退出,那么主线程不也就跟着退出了吗?目前我的猜想是,当没有消息时,Android通过epool机制让cpu休眠,当有了新的msg时又会唤醒cpu,从而保证一直存在msg。而休眠的时机是在messageQueue的next()方法中进行的:
1
2
3
4
5
6
if ( msg != null ) {
...
} else {
//如果当前没有消息,把唤醒时钟设为-1,那么意味着线程会沉睡Integer.MaxValue
nextPollTimeoutMillis = - 1 ;
}
主线程中为何不ANR也不会内存泄露 我们都知道线程一旦运行完毕就会回收,那么主线程中没有执行任何动作时为何不会回收呢?原因就是因为looper在死循环,阻塞了主线程的回收,那么相应的一旦不再死循环,程序也就退出了。Android同时利用Looper的死循环,发送消息,比如通知View重绘等等。
会不会阻塞子线程呢? 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class MyRunnable implements Runnable {
@Override
public void run () {
Looper . prepare ();
Handler handler = new Handler ();
Message msg = Message . obtain ( handler , new Runnable () {
@Override
public void run () {
Log . d ( "" , "run: " );
}
});
msg . sendToTarget ();
Looper . loop ();
Log . d ( "这里永远都不会执行" , "run:" );
int i = 0 ;
}
}
我用Profile 持续观察之后发现:答案是会!CPU会一直被占用,线程池也无法回收该线程!
终止loop 所以为了尽可能减少CPU浪费,应该调用handler.getLooper().quitSafely();
1
2
3
public void quitSafely () {
mQueue . quit ( true );
}
再来看mQueue.quit(true):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void quit ( boolean safe ) {
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 );
}
}
主要是修改了mQuitting = true;,这就是个标记,用来判断是否终止死循环。造成的效果呢当然是在loop方法的死循环中调用mqueue.next()时,会受到下面代码的影响:
1
2
3
4
5
6
7
8
9
10
11
12
13
if ( mQuitting ) {
dispose ();
return null ;
}
private void dispose () {
if ( mPtr != 0 ) {
nativeDestroy ( mPtr );
mPtr = 0 ;
}
}
private native static void nativeDestroy ( long ptr );
直接返回null,而在Looper的loop方法的死循环中有这样的判断:
1
2
3
4
5
6
7
8
...
Message msg = queue . next (); // might block
if ( msg == null ) {
// No message indicates that the message queue is quitting.
me . mLogging . println ( "注意啦,真的停啦~~~" );
return ;
}
...
如果queue.next返回null那么就终止循环。
总结 Looper其实比较简单,核心就两个方法:
prepary()方法用来创建MessageQueue和ThreadLocal;loop()方法就是永动机,不断从message中拿出msg来消费。只有调用quitSafely()或者quit()这两个方法修改mQuitting这个标记位,使MessageQueue的next()方法返回null,才能退出loop()方法。 一个线程只有一个Looper,是通过ThreadLocal实现的。