react-native-yamap
react-native-yamap copied to clipboard
Краш - Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException
Рендерю компоненты Marker детьми внутри компонента YaMap, при изменении позиции на карте изменяю набор детей, вплоть до полного изменения без пересечений
После очередного изменения позиции карты маркеры "прилипают" к краю карты, затем происходит краш карты
Пример текста ошибки (могут меняться числа children и indicesToRemove)
Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException: Trying to remove a view index above child count 0 view tag: 3523
detail: View tag:3523 View Type:class ru.vvdev.yamap.view.YamapView
children(0): [
],
indicesToRemove(1): [
0,
],
tagsToDelete(1): [
68847,
]
Окружение "react-native": "0.70.14", "react-native-yamap": "4.1.18", Android 13
StackTrace:
Fatal Exception: com.facebook.react.uimanager.IllegalViewOperationException: Trying to remove a view index above child count 0 view tag: 3523
detail: View tag:3523 View Type:class ru.vvdev.yamap.view.YamapView
children(0): [
],
indicesToRemove(1): [
0,
],
tagsToDelete(1): [
68847,
]
at com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:437)
at com.swmansion.reanimated.layoutReanimation.ReanimatedNativeHierarchyManager.manageChildren(ReanimatedNativeHierarchyManager.java:300)
at com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:217)
at com.facebook.react.uimanager.UIViewOperationQueue$1.run(UIViewOperationQueue.java:915)
at com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:1026)
at com.facebook.react.uimanager.UIViewOperationQueue.-$$Nest$mflushPendingBatches()
at com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:1086)
at com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:29)
at com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:175)
at com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:85)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1648)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1659)
at android.view.Choreographer.doCallbacks(Choreographer.java:1129)
at android.view.Choreographer.doFrame(Choreographer.java:1045)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1622)
at android.os.Handler.handleCallback(Handler.java:958)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:230)
at android.os.Looper.loop(Looper.java:319)
at android.app.ActivityThread.main(ActivityThread.java:8893)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:608)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1103)
Видео воспроизведения:
https://github.com/volga-volga/react-native-yamap/assets/15292833/c8926343-26bb-4466-8f84-6f901db0b46c
Мои попытки фиксов:
Обновление либы
Попробовал перейти на либу react-native-yamap-plus, в которой используется нативный map-kit версии 4.6.1. Проблема не пропала
Фикс в removeChild в YamapView
Стектрейс указывает на remove a view в ru.vvdev.yamap.view.YamapView
внутри YamapView видим метод removeChild, внутри которого уже реализованы проверки на наличие mapObject, те тут проблем возникать не должно
if (getChildAt(index) instanceof ReactMapObject) {
final ReactMapObject child = (ReactMapObject) getChildAt(index);
if (child == null) return;
final MapObject mapObject = child.getMapObject();
if (mapObject == null || !mapObject.isValid()) return;
getMap().getMapObjects().remove(mapObject);
}
Однако на всякий случай пробуем отловить краш в try catch
public void removeChild(int index) {
try {
if (getChildAt(index) instanceof ReactMapObject) {
final ReactMapObject child = (ReactMapObject) getChildAt(index);
if (child == null) return;
final MapObject mapObject = child.getMapObject();
if (mapObject == null || !mapObject.isValid()) return;
Log.d("!!!removeChild success", String.valueOf(index));
getMap().getMapObjects().remove(mapObject);
}
} catch (Exception e) {
Log.d("!!!removeChild error", e.getMessage());
}
}
Но это не помогает, краши все равно воспроизводятся, лог removeChild error никогда не выводится
Фикс в removeViewAt в YamapViewManager
Тк падение не отловилось через try catch в YamapView, смотрю на removeViewAt, который последовательно вызывает
parent.removeChild(index);
super.removeViewAt(parent, index);
пробую проверить гипотезу, что parent.removeChild отрабатывает корректно, а super.removeViewAt падает
Изменение порядка вызовов
Обычно super методы вызываются в самом начале, пробуем изменить порядок вызовов
public void removeViewAt(YamapView parent, int index) {
super.removeViewAt(parent, index);
parent.removeChild(index);
}
Это не помогает
Опциональный вызов super.removeViewAt
попробовал возвращать булеан из removeChild, чтобы понимать снаружи, что удаление прошло корректно
public boolean removeChild(int index) {
try {
if (getChildAt(index) instanceof ReactMapObject) {
final ReactMapObject child = (ReactMapObject) getChildAt(index);
if (child == null) return false;
final MapObject mapObject = child.getMapObject();
if (mapObject == null || !mapObject.isValid()) return false;
getMap().getMapObjects().remove(mapObject);
return true;
}
} catch (Exception e) {
Log.d("!!!removeChild error", e.getMessage());
return false;
}
return false;
}
и далее вызывать super.removeViewAt только если removeChild вернул true, те удаление прошло успешно
public void removeViewAt(YamapView parent, int index) {
if (parent.removeChild(index)) {
super.removeViewAt(parent, index);
}
}
Это тоже не помогло, краши остались
Дополнительная проверка на наличие чайлда через super
Перед вызовом super.removeViewAt делаю дополнительную проверку на наличие чайлда по индексу, по которому предполагается удаление
public void removeViewAt(YamapView parent, int index) {
if (parent.removeChild(index)) {
if (super.getChildAt(parent, index) != null) {
super.removeViewAt(parent, index);
}
}
}
это тоже не помогает Аналогично не помогает проверка на размер списка детей
public void removeViewAt(YamapView parent, int index) {
if (parent.removeChild(index)) {
if (super.getChildCount(parent) > index) {
super.removeViewAt(parent, index);
}
}
}
Гипотезы и вопросы:
- Нативный код падает, но приложение не крашится. Падает в каком-то потоке, который не критичен для работы JS?
- Карта практически не падает на сильных устройствах, но зато стабильно падает, когда воспроизвожу на загруженном тормозящем эмуляторе. Проблема в каком-то race condition, который усугубляется на медленных девайсах?
Мои попытки фиксов V2:
removeViewAt в YamapViewManager в try catch
Решил попробовать полностью обернуть removeViewAt в try catch, чтобы понять, правильно ли локализовал место краша
public void removeViewAt(YamapView parent, int index) {
try {
parent.removeChild(index);
super.removeViewAt(parent, index);
Log.d("!!!removeViewAt", "success");
} catch (Exception e) {
Log.d("!!!removeViewAt", e.getMessage());
}
}
Это не принесло результатов, лог в catch ни разу не отработал, хотя краш проявился
По стектрейсу падает в этом месте react-native-reanimated.
:green_circle: Пробовали вариант с включением enableLayoutAnimations(true) из react-native-reanimated, проблема пропадает, видим динамику снижения крашей на пользователях.
:yellow_circle: Но при этом крайне редко бывает что не отрисовываются новые пины до тех пор пока снова не подвигаешь карту
:red_circle: Что еще пробовали, но не помогло
[email protected]→ 4.6.0[email protected]→ 3.11.0,[email protected][email protected]→ 0.73.4[email protected],[email protected],[email protected]