【问题标题】:Android Kotlin coroutine crashing on strict modeAndroid Kotlin 协程在严格模式下崩溃
【发布时间】:2019-04-09 02:20:28
【问题描述】:

我在下面创建了一个非常简化的问题版本。
严格模式设置了以下策略:

   StrictMode.setThreadPolicy(
            StrictMode.ThreadPolicy.Builder()
                .detectDiskReads()
                .detectDiskWrites()
                .detectNetwork()   // or .detectAll() for all detectable problems
                .penaltyLog()
                .penaltyDeath()
                .build()
        )

视图模型只有一个函数在调用时会导致应用程序崩溃。该函数什么都不做(它有一个空的主体)

class MyViewModel : ViewModel() {
    fun foo() {
        viewModelScope.launch(Dispatchers.IO){  }
    }
}

活动在onCreate 中调用viewModel.foo(),这会导致应用程序崩溃并出现以下跟踪。

   --------- beginning of crash
2019-04-08 22:07:49.579 1471-1471/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 1471
    java.lang.RuntimeException: StrictMode ThreadPolicy violation
        at android.os.StrictMode$AndroidBlockGuardPolicy.onThreadPolicyViolation(StrictMode.java:1705)
        at android.os.StrictMode$AndroidBlockGuardPolicy.lambda$handleViolationWithTimingAttempt$0(StrictMode.java:1619)
        at android.os.-$$Lambda$StrictMode$AndroidBlockGuardPolicy$9nBulCQKaMajrWr41SB7f7YRT1I.run(Unknown Source:6)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
     Caused by: android.os.strictmode.DiskReadViolation
        at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
        at java.io.UnixFileSystem.getBooleanAttributes(UnixFileSystem.java:241)
        at java.io.File.isDirectory(File.java:845)
        at dalvik.system.DexPathList$Element.maybeInit(DexPathList.java:696)
        at dalvik.system.DexPathList$Element.findResource(DexPathList.java:729)
        at dalvik.system.DexPathList.findResources(DexPathList.java:526)
        at dalvik.system.BaseDexClassLoader.findResources(BaseDexClassLoader.java:174)
        at java.lang.ClassLoader.getResources(ClassLoader.java:839)
        at java.util.ServiceLoader$LazyIterator.hasNextService(ServiceLoader.java:349)
        at java.util.ServiceLoader$LazyIterator.hasNext(ServiceLoader.java:402)
        at java.util.ServiceLoader$1.hasNext(ServiceLoader.java:488)
        at kotlin.collections.CollectionsKt___CollectionsKt.toCollection(_Collections.kt:1145)
        at kotlin.collections.CollectionsKt___CollectionsKt.toMutableList(_Collections.kt:1178)
        at kotlin.collections.CollectionsKt___CollectionsKt.toList(_Collections.kt:1169)
        at kotlinx.coroutines.internal.MainDispatcherLoader.loadMainDispatcher(MainDispatchers.kt:15)
        at kotlinx.coroutines.internal.MainDispatcherLoader.<clinit>(MainDispatchers.kt:10)
        at kotlinx.coroutines.Dispatchers.getMain(Dispatchers.kt:55)
        at androidx.lifecycle.ViewModelKt.getViewModelScope(ViewModel.kt:41)
        at com.example.myapplication.MyViewModel.foo(MainActivity.kt:35)
        at com.example.myapplication.MainActivity.onCreate(MainActivity.kt:28)
        at android.app.Activity.performCreate(Activity.java:7136)
        at android.app.Activity.performCreate(Activity.java:7127)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193) 
        at android.app.ActivityThread.main(ActivityThread.java:6669) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858) 

根据堆栈跟踪,存在磁盘读取冲突,但该代码中的任何内容都不应访问磁盘。
感兴趣的行是:

   at com.example.myapplication.MyViewModel.foo(MainActivity.kt:35)
        at com.example.myapplication.MainActivity.onCreate(MainActivity.kt:28)

第 35 行:viewModelScope.launch(Dispatchers.IO){ }

第 28 行:viewModel.foo()

如果我删除penaltyLog(),那么应用程序不会崩溃。

所以我的问题:

如何使用上述严格模式配置防止崩溃?

是协程还是严格模式本身的问题?

更新: 这似乎是协程的一个已知问题。仍未解决 - 请参阅对话here

【问题讨论】:

  • 如果你尝试使用Dispatchers.MAIN,它仍然会崩溃吗?
  • 是的,它也与Main Dispatcher 一起崩溃。相同的堆栈跟踪。

标签: android kotlin-coroutines android-strictmode


【解决方案1】:

解决方案是使用您自己初始化的调度程序,而不在主线程上执行 I/O。

实现起来有点棘手,因为要避免由于 Handler 默认启用的 vsync 导致应用程序变慢(可能延迟高达 16ms 的代码,根本不需要 vsync),您必须使用 API 28+ 构造函数,并为旧版本的 Android 使用反射。之后,您可以使用HandlerasCoroutineDispatcher() 扩展函数,并使用生成的调度程序。

为了让我和其他人更简单,我制作了一个(小)库,它提供了一个 Dispatchers.MainAndroid 扩展,它在没有任何 I/O 的情况下延迟初始化,可以用来代替 Dispatchers.Main。它还将Lifecycle 与协程范围集成。

这是您可以查看如何获取依赖项(可在 jcenter 上获得)及其实现方式的链接:https://github.com/LouisCAD/Splitties/tree/master/modules/lifecycle-coroutines

【讨论】:

  • 结束循环并选择这个答案。由于协程实现超出了应用程序开发人员的范围,因此其他建议(例如删除惩罚性死亡)仍然有效。
【解决方案2】:

问题在于,为 Kotlin Coroutines 初始化 Dispatchers.Main 会占用大量磁盘时间来读取和校验 JAR。这不应该发生。

This issue in Kotlin Coroutines 已通过更快的 ServiceLoader 解决方法解决。您应该使用 a newer version 的 Kotlin Coroutines,它提供了一种解决方法 ServiceLoader,它不对磁盘上的 JAR 进行校验和。

致力于 R8 优化器的 Google Android 团队也在创建一个更好的解决方案,如果您使用足够新的 R8 完全启用 ProGuard 优化,它将在 ProGuard 步骤完全优化 ServiceLoader 读取。当与 R8 一起使用时,该修复将在 Android Gradle Plugin 3.5.0 中。

【讨论】:

    【解决方案3】:

    您的堆栈跟踪清楚地表明您的代码正在访问磁盘,因为它是第一次运行并且它正在触发一些类加载。这会转到DexClassLoader 并触及磁盘。

    在执行完所有代码路径后尝试启用严格模式。

    【讨论】:

    • 我在应用程序级别初始化严格模式。如果我之后初始化它,问题不会传播到不同的活动。
    • 没错,除非您可以在所有初始化后启动它,否则您将不得不适应误报。在类加载期间触摸 UI 线程上的文件系统是您无法避免的。
    • 经过进一步研究,似乎不是误报。这是当前协程实现的一个已知问题。 github.com/Kotlin/kotlinx.coroutines/issues/878 但他们似乎没有任何好的计划来解决它。
    • @Naveed 严格模式,记录所有违规行为,没有任何排除方式。这通常意味着协同程序在性能方面略微次优,设计...这很可能甚至无法避免。一个人可以过度设计一切,但这并不一定会让它变得更好。
    • @MartinZeitler getResources() 调用是使用服务发现 API 解析实现 Dispatchers.Main 的类的结果。它必须检查META-INF/services 目录,该目录未在 Android 上预加载,因此它会转到磁盘并触发 JAR 验证机制,这反过来会导致 整个 JAR 被加载并运行计算昂贵的加密哈希算法。解决方案是“过度设计”它并针对 Android 的特殊情况进行优化。
    【解决方案4】:

    我会删除 .penaltyDeath() 以防止它崩溃 - 并忽略性能损失 - 因为它基本上是“不负责任的”,除非是自己造成的。

    【讨论】:

      【解决方案5】:

      不是真正解决问题,而是忽略 StrictMode 块的解决方法,以便您可以继续在应用的其余部分启用 StrictMode:

      fun <T> permitDiskReads(func: () -> T): T {
          return if (BuildConfig.DEBUG) {
              val oldThreadPolicy = StrictMode.getThreadPolicy()
              StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder(oldThreadPolicy).permitDiskReads().build())
      
              val value = func()
      
              StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder(oldThreadPolicy).build())
      
              value
          } else {
              func()
          }
      }
      

      这样你就可以了

      class MyViewModel : ViewModel() {
          fun foo() {
              permitDiskReads { viewModelScope.launch(Dispatchers.IO) { } }
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多