[关闭]
@cxm-2016 2016-10-28T13:07:52.000000Z 字数 2828 阅读 2585

Android:程序中的Looper.loop()不会造成ANR异常

android

版本:3
作者:陈小默
声明:禁止商用,禁止转载

仅被发布于 作业部落(原)简书


我们在学习Handler的时候一定都接触过Looper这个东西,也知道其中的loop方法会有阻塞等待的过程。

那么问题来了:既然主线程被阻塞了,为什么不会造成ANR异常呢?

首先这个问题就是错误的,至少有两个概念没有认清:第一,什么是ANR异常?;第二,Android程序阻塞的作用是什么?

这里先回答第一个问题:什么是ANR异常。

最简单的话说就是:当前的事件没有机会得到处理

当我们每次点击屏幕就会产生一个事件,这个事件由Android操作系统接收,之后再传递给我们的应用程序。但是我们的Android应用要求我们只能有一个线程去处理事件,这个线程就是我们的主线程。

耗时操作是引起ANR的原因吗?
大部分Android新人都有一个误区,认为只要运行时间长的操作就是耗时操作!是这样吗?我们做一个实验

  1. class MainActivity : BaseActivity() {
  2. override fun onCreate(savedInstanceState: Bundle?) {
  3. super.onCreate(savedInstanceState)
  4. setContentView(R.layout.activity_main)
  5. findViewById(R.id.sleep)!!.setOnClickListener { view ->
  6. Thread.sleep(10000)
  7. }
  8. }
  9. }

当我们点击按钮的时候,主线程休眠10秒,那么这里会抛出异常吗?答案是

有可能

围观群众:卧槽,啥叫有可能?

当你运行这个程序并点击休眠按钮的时候,如果你不点击屏幕就是保证再没有新的事件输入的时候,这时候是不会抛出异常,并且主线程会安安静静的休眠10秒,但是如果你在点击休眠之后又点击应用界面的任意位置,此时新的事件就会产生并且被输入到你的应用,但是你的主线程正在休眠,而Android又不允许在其他线程中处理UI事件,于是新的事件会被阻塞。当事件超过某一个时间限制(一般Activity是5s)仍未被执行的时候,就会抛出ANR异常。

通过这个例子我们可以明白ANR异常只是单纯指事件长时间未响应。

现在我们回答了第一个问题。接下来看第二个问题:Android程序阻塞的作用是什么?

现在打开你们的记事本,在上面输入一段java或者kotlin代码

  1. fun main(args: Array<String>) {
  2. println("hello world")
  3. }

然后我们去运行这段代码,是不是有了特别了不起的发现,那就是程序运行完之后居然自动退出了。

围观群众:卧槽,程序运行完不退出干啥呀,等过年呀!!!

咳咳,程序运行完成之后退出是程序员的常识问题,可是越是简单的细节就越是容易被忽略。Android程序也是JVM进程呀?它是怎么怎么保证程序不会退出的呢?

围观群众:煞笔呀,只要让程序不结束一直运行就行了呀!!!

对呀,又是常识性问题,想让程序永远不会退出的最好方法就是---循环---还要必须是---死循环---

现在我们看一下一个Android程序是如何被启动的

上源码

  1. public static void main(String[] args) {
  2. Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
  3. SamplingProfilerIntegration.start();
  4. // CloseGuard defaults to true and can be quite spammy. We
  5. // disable it here, but selectively enable it later (via
  6. // StrictMode) on debug builds, but using DropBox, not logs.
  7. CloseGuard.setEnabled(false);
  8. Environment.initForCurrentUser();
  9. // Set the reporter for event logging in libcore
  10. EventLogger.setReporter(new EventLoggingReporter());
  11. AndroidKeyStoreProvider.install();
  12. // Make sure TrustedCertificateStore looks in the right place for CA certificates
  13. final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
  14. TrustedCertificateStore.setDefaultUserDirectory(configDir);
  15. Process.setArgV0("<pre-initialized>");
  16. Looper.prepareMainLooper();
  17. ActivityThread thread = new ActivityThread();
  18. thread.attach(false);
  19. if (sMainThreadHandler == null) {
  20. sMainThreadHandler = thread.getHandler();
  21. }
  22. if (false) {
  23. Looper.myLooper().setMessageLogging(new
  24. LogPrinter(Log.DEBUG, "ActivityThread"));
  25. }
  26. // End of event ActivityThreadMain.
  27. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
  28. Looper.loop();
  29. throw new RuntimeException("Main thread loop unexpectedly exited");
  30. }

这是ActivityThread中整个main方法的代码,就这么多
看这个main方法的第39行代码,此时启动了一个死循环用来保证应用程序不会退出。

围观群众:罗里吧嗦一大堆,我还是没听懂!!!

这么跟你说吧,主线程在没有事件需要处理的时候就是处于阻塞的状态。想让主线程活动起来一般有两种方式:一种是系统唤醒主线程,并且将点击事件传递给主线程;第二种是其他线程使用Handler向MessageQueue中存放了一条消息,导致loop被唤醒继续执行。

围观群众:那也就是说应用的UI线程大部分情况下都是“死的”喽?

嗯,就是这样,我们可以看到的界面炫酷的效果都是子线程与Handler的执行结果,比如播放视频,或者View的动画,里面都用到了Handler。

围观群众:哦,那面试的时候被问道这个问题我应该怎么跟面试官说呢?

直接回答:煞笔,你问的问题有问题知道不

围观群众:滚!!!

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注