Android Kotlin coroutine crashing on strict mode

落花浮王杯 提交于 2021-02-07 06:02:24

问题


I have created a very simplified version of my issue below.
The strict mode is set up with the following policies:

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

The view model has only one function which crashes the application when invoked. The function does nothing (it has an empty body)

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

The activity invokes viewModel.foo() in onCreate which crashes the application with the following trace.

   --------- 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) 

According to the stack trace there is a disk read violation but nothing in that code should be accessing disk.
The lines of interest are:

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

line 35: viewModelScope.launch(Dispatchers.IO){ }

line 28: viewModel.foo()

Further more if I remove penaltyLog() then the application does not crash.

So my question(s):

How can I prevent the crash with the strict mode configurations above?

Is the problem with coroutine or strict mode itself?

Update: This seems to be a known issue with coroutines. Still unresolved - see the conversation here


回答1:


The solution is to use your own dispatcher that you initialize without doing I/O on the main thread.

It's a bit tricky to implement because to avoid slowing down your app because of vsync enabled by default on Handler (could delay by up to 16ms code that doesn't need vsync at all), you have to use an API 28+ constructor, and use reflection for older versions of Android. After doing that, you can use the asCoroutineDispatcher() extension function for Handler, and use the resulting dispatcher.

To make it simpler for me, and others, I made a (small) library that provides a Dispatchers.MainAndroid extension that is lazily initialized without any I/O and can be used in place of Dispatchers.Main. It also integrated Lifecycle with coroutine scopes.

Here's the link where you can see how to get the dependency (available on jcenter) and how it is implemented: https://github.com/LouisCAD/Splitties/tree/master/modules/lifecycle-coroutines




回答2:


The problem is that initializing Dispatchers.Main for Kotlin Coroutines is using a lot of disk time to read and checksum your JAR. This shouldn't happen.

This issue in Kotlin Coroutines was resolved with a workaround faster ServiceLoader. There's a newer version of Kotlin Coroutines you should use which offers a workaround ServiceLoader that does not checksum the JAR on disk.

The Google Android team working on the R8 optimizer is also creating an even better solution that will optimize out ServiceLoader reads entirely at the ProGuard step if you have ProGuard optimizations fully enabled with a new enough R8. That fix will be in Android Gradle Plugin 3.5.0 when used with R8.




回答3:


Your stacktrace makes it obvious that your code is accessing the disk because it's being run for the first time and it's triggering some classloading. This goes to the DexClassLoader and touches the disk.

Try to enable the strict mode after exercising all your code paths.




回答4:


I'd remove .penaltyDeath() in order to prevent it from crashing - and ignore that performance penalty - because it is basically "outside of responsibility", unless one has caused it by oneself.




回答5:


Not really fixing the issue, but a workaround to ignore StrictMode for a block so that you can continue having StrictMode enabled in the rest of your app:

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()
    }
}

so you can do

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


来源:https://stackoverflow.com/questions/55584194/android-kotlin-coroutine-crashing-on-strict-mode

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!