Arello-Mobile / Moxy

Moxy is MVP library for Android

Home Page:https://github.com/Arello-Mobile/Moxy/wiki

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Проблема с жизненным циклом MvpDelegate

ermac95 opened this issue · comments

Добрый день. На большом количестве устройств - от 9 до 12 версии андроид происходит вылет со следующей ошибкой

Fatal Exception: java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.next(ArrayList.java:860)
at moxy.MvpDelegate.onAttach(MvpDelegate.java:166)
at moxy.MvpAppCompatFragment.onStart(MvpAppCompatFragment.java:38)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2731)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1206)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2637)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2595)
at androidx.fragment.app.Fragment.performStart(Fragment.java:2740)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:365)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1206)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1368)
at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1446)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1509)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2637)
at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2595)
at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:258)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:550)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
at moxy.MvpAppCompatActivity.onStart(MvpAppCompatActivity.java:32)
at modulbank.ru.commonapp.ui.main.MainActivity.onStart(MainActivity.kt:274)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1456)
at android.app.Activity.performStart(Activity.java:8076)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3665)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2215)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loopOnce(Looper.java:346)
at android.os.Looper.loop(Looper.java:475)
at android.app.ActivityThread.main(ActivityThread.java:7889)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1009)

   Экран приложения, где происходит вылет не определен, крашлитика firebase выдает ошибку в вышеприведенном виде. Я так понимаю происходит рассинхронизация методов, в ходе чего происходит одновременная запись и чтение из листа, в связи с чем случается падение. 

Привет! Скорей всего причина в следующем:

  1. Во вью стейт летит какая-то команда
  2. Эта команда сохраняется в очереди команд
  3. Эта команда как-то отрабатывает (на этом этапе не важно как)
  4. Затем происходит пересоздание Activity (или что вы используете)
  5. Мокси начинает итерацию команд, последовательно выполняя их
  6. Видимо, в момент вызова какой-то команды на View улетает событие в презентер, чем триггерится новая команда для View
  7. Новая команда добавляется в очередь команд View State
  8. Восстановление стейта продолжается, итератор пытается взять следующую команду, но обнаруживает, что список был изменен
  9. Происходит падение.

Надеюсь, понятно объяснил. Если нет, то спрашивай, что именно не понятно =)

В самой мокси нет никакой многопоточности, и всё вообще работает обычно на одном потоке - на main потоке. Так что проблема именно с модификацией списка команд в процесс итерирования по этому же самому списку.

Все понятно, хорошая мысль, спасибо за наводку. А есть какой нибудь способ отследить, где именно происходит подобная ситуация? чтобы понимать где искать, приложение коммерческое и масштабное, просто так перекапывать все команды всех вью не вариант конечно )

Тут обычно сложность в том, что проблема получается глубинная. Потому что выходит так, что View управляет доменом, а должно быть наоборот =(

Чаще всего это конечно какие-нибудь переключения свитчеров или изменение edit text, от которых отрабатывает какой-то listener, который пушит что-то в presenter, а presenter сразу начинает что-то делать. Например, показывать лоадинг.

Тут нет серебряной пули. Поможет только ручной поиск проблемы.

Как вариант - можно попробовать каким-нибудь regexp поменять код так, что перед каждым вызововм какого-нибудь метода viewState, делать проверку:

if (isInRestoreState()) {
    throw RuntimeException("Wrong moxy usages")
}

А затем запустить приложение, заполнить как-нибудь view state и попересоздавать активити. И можно будет понять, кто ломает flow. Но это костыль.

Как мысль, можно это добавить в кодогенерацию, чтобы в будущем было понятно, какой метод ломает код. Потому что ConcurrentModificationException очень уж не информативен. Но это нужно сперва обмозговать.

Окей, спасибо большое за помощь, буду разбираться )