#android #kotlin-coroutines #android-strictmode
#Android #kotlin-сопрограммы #android-strictmode
Вопрос:
Я создал очень упрощенную версию моей проблемы ниже.
Строгий режим настроен со следующими политиками:
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){ }
}
}
Вызывается действие viewModel.foo()
, в onCreate
котором происходит сбой приложения со следующей трассировкой.
--------- 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()
, приложение не выйдет из строя.
Итак, мой вопрос (ы):
Как я могу предотвратить сбой с помощью приведенных выше конфигураций строгого режима?
Проблема в сопрограмме или в самом строгом режиме?
Обновление: похоже, это известная проблема с сопрограммами. Все еще не решено — смотрите Обсуждение здесь
Комментарии:
1. Что, если вы попробуете
Dispatchers.MAIN
, все равно произойдет сбой?2. Да, он
Main
также выходит из строя с помощью диспетчера. Та же трассировка стека.
Ответ №1:
Решение состоит в том, чтобы использовать свой собственный диспетчер, который вы инициализируете, не выполняя ввод-вывод в основном потоке.
Это немного сложно реализовать, потому что, чтобы избежать замедления работы вашего приложения из-за vsync, включенного по умолчанию Handler
(может задерживать код до 16 мс, который вообще не нуждается в vsync), вы должны использовать конструктор API 28 и использовать отражение для более старых версий Android. После этого вы можете использовать функцию asCoroutineDispatcher()
расширения для Handler
и использовать результирующий диспетчер.
Чтобы упростить его для меня и других, я создал (небольшую) библиотеку, которая предоставляет Dispatchers.MainAndroid
расширение, которое лениво инициализируется без какого-либо ввода-вывода и может использоваться вместо Dispatchers.Main
. Он также интегрирован Lifecycle
с областями сопрограммы.
Вот ссылка, по которой вы можете увидеть, как получить зависимость (доступную в jcenter) и как она реализована: https://github.com/LouisCAD/Splitties/tree/master/modules/lifecycle-coroutines
Комментарии:
1. Закрываем цикл и выбираем этот ответ. Другие предложения, такие как удаление смертной казни, по-прежнему актуальны, поскольку реализация сопрограммы выходит за рамки с точки зрения разработчиков приложений.
Ответ №2:
Проблема в том, что инициализация диспетчеров.Основным для сопрограмм Kotlin является использование большого количества дискового времени для чтения и проверки контрольной суммы вашего JAR. Этого не должно произойти.
Эта проблема в сопрограммах Kotlin была решена с помощью обходного решения faster ServiceLoader. Вам следует использовать более новую версию сопрограмм Kotlin, которая предлагает обходной путь ServiceLoader, который не проверяет контрольную сумму JAR на диске.
Команда Google Android, работающая над оптимизатором R8, также создает еще лучшее решение, которое полностью оптимизирует чтение ServiceLoader на этапе ProGuard, если у вас полностью включена оптимизация ProGuard с достаточно новым R8. Это исправление будет в плагине Android Gradle 3.5.0 при использовании с R8.
Ответ №3:
Ваш stacktrace делает очевидным, что ваш код обращается к диску, потому что он запускается в первый раз и вызывает некоторую загрузку классов. Это переходит к DexClassLoader
и касается диска.
Попробуйте включить строгий режим после выполнения всех ваших путей кода.
Комментарии:
1. Я инициализирую строгий режим на уровне приложения. Проблема не будет просто распространяться на другое действие, если я инициализирую его впоследствии.
2. Точно, если вы не сможете запустить его после всей инициализации, вам придется приспосабливаться к ложным срабатываниям. Прикосновение к файловой системе в потоке пользовательского интерфейса во время загрузки класса — это то, чего вы не можете избежать.
3. После дальнейших исследований не похоже, что это ложный положительный результат. В настоящее время это известная проблема с реализацией сопрограммы. github.com/Kotlin/kotlinx.coroutines/issues/878 но у них, похоже, нет никаких хороших планов по ее разрешению.
4. @Naveed строгий режим с журналом всех нарушений, без малейшего способа исключения. обычно это означает, что по дизайну совместные подпрограммы немного неоптимальны с точки зрения производительности… чего, скорее всего, даже нельзя избежать. можно перепроектировать все, что не обязательно делает его лучше.
5. @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) { } }
}
}
Ответ №6:
Если кто-нибудь наткнется на это, эта проблема уже исправлена.
Поэтому, если вы получаете нарушения строгого режима, вам, вероятно, просто нужно обновить библиотеку сопрограмм.
Текущую версию можно найти на github:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.2")