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
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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With