结论
- 初始化后,所有主线程做的事情都是在looper.loop()中完成的,因为主线程不做其他事,所以不会卡死
- 基于linux的epoll模型,当主线程没有message消费时,会进入睡眠状态(简单理解),等到有新的可消费的Message时,再转为活跃状态处理Message(类似一个事件回调)。主线程在睡眠状态会让出CPU,并不是一直不停在执行循环。
如有兴趣了解下epoll机制可以看看这篇文章:
产生此疑问的前提
怎么执行到looper.loop()
首先得分析下ActivityThread的main()方法,我们可以简单认为此方法为一个APP启动的入口。(代码只展示关键步骤)
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
- 首先Looper.prepareMainLooper()方法生成属于当前线程(主线程)的Looper对象。
- sMainThreadHandler = thread.getHandler();获得主线程内置的Handler对象。此Handler对象为ActivityThread的内部类 H 。可以简单看看 H 的源码,会发现 H 能够处理绝大多数的主线程事件,包括了Activity的启动、Service的绑定等等。。
- 接下来就是Looper.loop();
进入Looper.loop()
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} catch (Exception exception) {
throw exception;
}
msg.recycleUnchecked();
}
}
可以看到第6行 for(;;)是死循环,参考前边的结论2,queue·next()是导致主线程让出CPU的原因,queue.next()利用了epoll机制,闲则睡眠,忙则唤醒。
为什么觉得会卡死
下边看一段典型的死循环 ,结合这个例子来简单探讨下大伙可能觉得会导致程序卡死的原因:
public class A{
public static void main(String[] args){
while(true){
doSomething();
}
doSomethingElse();
}
public static void doSomething(){
// ...
}
public static void doSomethingElse(){
// ...
}
}
- 上边代码会不停的循环执行doSomething(),会导致doSomethingElse()执行不到(编译甚至都无法通过),并且由于没有停顿,死循环会占用大量的CPU资源(结合任务管理器可以看到程序的CPU急剧升高到接近100%),此时再进行其他操作就会卡顿,因为CPU时间都分配给了死循环的进程。
- 另外结合我们的共识:android 的 UI操作都是在主线程的。既然主线程死循环了,那他就只能一直执行死循环内部的逻辑,好像就无暇处理UI更新了(编不下去了。。)。
对于问题1
首先可以考虑如何解决上边的死循环程序一直占用CPU的问题,一种简单的做法是让线程sleep()
while(true){
doSomething();
sleep(50);
}
- sleep()就会使线程休眠,从而会让出CPU,这样CPU就有时间去处理其他事情了。
- 但是sleep(long time)不太智能,只能休眠固定的时间。结合android主线程要负责更新UI的职责,我们想要的效果应该是当不需要更新UI的时候主线程就睡眠,以让出CPU,一旦有UI更新的需求时就停止睡眠执行UI更新操作。于是android就引入了handler-looper机制。
- android将每个UI更新的需求包装成一个消息,放入MessageQueue,Looper.loop()就在不断的尝试从MessageQueue中拿消息,以更新UI。然后我们再结合前边说的queue.next()和epoll机制,正是一种闲则睡眠,忙则唤醒的模型。
对于问题2
问题1算是基本解答了问题2,因为更新UI的操作都在死循环内部,没有其他操作,Looper.loop()本身也就不会出现造成UI卡死。
ANR问题
ANR即Application Not Responding,顾名思义就是应用程序无响应。一般都是由于在主线程执行了耗时操作,导致新到来的UI事件的来不及处理,系统检测到这种情况就会ANR。
这种问题就是应该通过编程来避免。核心要点:不要让主线程干耗时的工作。
扩展问题
- 正常情况下APP播放动画都是在主线程,但是主线程有无法执行耗时任务的限制,动画不就是耗时任务吗,另外一般播放动画时,app仍然能响应触摸事件,该如何解释?
解释:动画虽然不是 Handler-Looper 机制,基于Vsync机制,两者都是类似的会有隔一段时间执行以一次循环的特点。最重要的在于明白动画的播放并不是连续的,将动画的播放与屏幕的刷新联系起来。如果屏幕 1s 刷新60次,则1s有60次播放动画的机会(播放动画的一部分),也就是每16.6ms执行一次。但是每一帧中处理动画播放的逻辑一般都是不会超过16.6ms(不然就掉帧了),所以CPU还是有空闲时间来处理其他输入事件的。 - 在Activity的onCreate()方法中执行耗时操作会导致ANR,我知道了onCreate()是在主线程中执行的,但是前边分析的主线程只执行Looper.loop()死循环,如何联系起来。
解释:需要将Activity的启动理解为向MessageQueue添加一个消息,onCreate()方法是在Activity启动时调用 (ApplicationThread.scheduleLaunchActivity()内部,最后通过sendMessage(H.LAUNCH_ACTIVITY,r)来启动acitivty,但新版本sdk源码不同)。所有onCreate()也可以认为是在Looper.loop()中执行的。