[TOC]

问题描述与分析:

为了获取Crash日志,项目中实现了UncaughtExceptionHandler接口对未知异常进行捕获并上传到服务器中,同时停止App运行。在Android5.0以下系统一直未出现过问题,但是突然发现在Android 5.0以上的机器中这段代码出现了java.lang.InternalError: Thread starting during runtime shutdown(更多异常信息见最后部分的附录),在stackoverflow中得到了一些帮助:这个问题是因为线程开启的太晚了!

什么时候开启线程才算得上“太晚了”呢?

在我的项目中UncaughtExceptionHandler实现类捕获到异常时(也就是UncaughtExceptionHandler接口的uncaughtException方法执行时),开启了一个线程用于上传崩溃日志,而在线程中上传崩溃日志时创建了HttpClient发送网络请求。HttpClient创建时设置了ThreadSafeClientConnManager来管理连接,而在ThreadSafeClientConnManager的源码中又一次开启了线程!也就是说,uncaughtException方法执行时在开启的线程中又开启了新的线程,这样可能会导致uncaughtException方法在执行完成时,“线程中的线程”才start(),而据说uncaughtException方法执行完成后ART环境就shutdown了(uncaughtException方法结束时ART是否会真的shutdown,这一点有待考证,笔者也仅是从其他非官方资料中得知),这种情况下就会抛出java.lang.InternalError: Thread starting during runtime shutdown。

  为了验证这个问题,编写了一段简单的代码:实现UncaughtExceptionHandler的uncaughtException方法,在uncaughtException方法中开启一个线程,在这个线程中再开一线程,同时在MainActivity中故意制造一个异常发生,此时果然会出现java.lang.InternalError: Thread starting during runtime shutdown(并不是每次都出现,因为如果uncaughtException方法执行结束之前,两个线程都完成start(),此问题就不会出现)。

解决方法:

  避免在uncaughtException方法中出现线程嵌套:针对我的项目,不再去在线程中创建HttpClient(因为HttpClient创建时还会开启线程,这就造成了线程中再次开启线程),而是先创建HttpClient对象,再在线程中使用HttpClient对象。这样就可以确保uncaughtException方法结束之前HttpClient已经被创建,HttpClient中开启线程也就不会有问题。

附异常信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2019-09-05 19:44:52.912 ? E/AndroidRuntime: FATAL EXCEPTION: OfflineDownloader
Process: com.kuainiugroup.wcloud, PID: 6689
java.lang.InternalError: Thread starting during runtime shutdown
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:733)
at android.app.SharedPreferencesImpl.startLoadFromDisk(SharedPreferencesImpl.java:119)
at android.app.SharedPreferencesImpl.<init>(SharedPreferencesImpl.java:112)
at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:402)
at android.app.ContextImpl.getSharedPreferences(ContextImpl.java:383)
at android.content.ContextWrapper.getSharedPreferences(ContextWrapper.java:177)
at com.amap.co.ge.g(SharedPrefUtil.java:42)
at com.amap.co.ge.b(SharedPrefUtil.java:160)
at com.amap.co.fr.a(OfflineDownloader.java:251)
at com.amap.co.fr.f(OfflineDownloader.java:32)
at com.amap.co.fr$a.handleMessage(OfflineDownloader.java:183)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:176)
at android.os.HandlerThread.run(HandlerThread.java:65)