Android 4.4 Случайная авария KitKat
РЕДАКТИРОВАТЬ: Перед тем, как делать голосование и подразумевать вещи, пожалуйста, поймите, я не могу воспроизвести эту ошибку. Это происходит постоянно на некоторых устройствах, к которым у меня нет доступа, но не после прошивки reset!
Недавно я обнаружил случайные сбои в приложении, которое я разрабатываю для клиента.
Теперь через 3 года приложение имеет примерно 100 000 активных пользователей.
Мы видели крах на Nexus 4 и 5, как с Android 4.4 KitKat.
Мы не можем воспроизвести его на наших собственных Nexus 4 и 5 под управлением 4.4.
У нас был клиент через нашу поддержку. Он сказал нам, что авария происходит каждый раз в одном и том же месте при вызове новой активности. Он бежал Далвик, а не АРТ. После перезагрузки прошивки приложение отлично работало и не могло воспроизвести его снова!
Я не могу опубликовать источник или макет по юридическим причинам, но у меня есть этот стек:
java.lang.RuntimeException: Unable to start activity ComponentInfo{xx.xxx.xxxxx.xxx.xxxxxx.prod/xx.xxx.xxxxx.xxx.PaymentsActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2176)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2226)
at android.app.ActivityThread.access$700(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1397)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4998)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:126)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at android.view.View.sendAccessibilityEventUncheckedInternal(View.java:4938)
at android.view.View.sendAccessibilityEventUnchecked(View.java:4919)
at android.view.View$SendViewStateChangedAccessibilityEvent.run(View.java:19433)
at android.view.View$SendViewStateChangedAccessibilityEvent.runOrPost(View.java:19465)
at android.view.View.notifyViewAccessibilityStateChangedIfNeeded(View.java:7265)
at android.view.View.setFlags(View.java:8990)
at android.view.View.setVisibility(View.java:6020)
at android.view.LayoutInflater.parseInclude(LayoutInflater.java:859)
at de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative(Native Method)
at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:547)
at android.view.LayoutInflater.parseInclude(Native Method)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:745)
at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
at de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative(Native Method)
at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:547)
at android.view.LayoutInflater.inflate(Native Method)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290)
at android.app.Activity.setContentView(Activity.java:1928)
at xx.xxx.xxxxx.xxx.StandardActivity.setContentView(StandardActivity.java:289)
at xx.xxx.xxxxx.xxx.PaymentsActivity.onCreate(PaymentsActivity.java:61)
at android.app.Activity.performCreate(Activity.java:5243)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
... 12 more
РЕДАКТИРОВАТЬ: вторая стоп-трасса без привязки
java.lang.RuntimeException: Unable to start activity ComponentInfo{xx.xxx.xxxxx.xxx.xxxxx.prod/xx.xxx.xxxxx.xxx.PaymentsActivity}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2176)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2226)
at android.app.ActivityThread.access$700(ActivityThread.java:135)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1397)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4998)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at android.view.View.sendAccessibilityEventUncheckedInternal(View.java:4938)
at android.view.View.sendAccessibilityEventUnchecked(View.java:4919)
at android.view.View$SendViewStateChangedAccessibilityEvent.run(View.java:19433)
at android.view.View$SendViewStateChangedAccessibilityEvent.runOrPost(View.java:19465)
at android.view.View.notifyViewAccessibilityStateChangedIfNeeded(View.java:7265)
at android.view.View.setFlags(View.java:8990)
at android.view.View.setVisibility(View.java:6020)
at android.view.LayoutInflater.parseInclude(LayoutInflater.java:859)
at android.view.LayoutInflater.rInflate(LayoutInflater.java:745)
at android.view.LayoutInflater.inflate(LayoutInflater.java:492)
at android.view.LayoutInflater.inflate(LayoutInflater.java:397)
at android.view.LayoutInflater.inflate(LayoutInflater.java:353)
at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:290)
at android.app.Activity.setContentView(Activity.java:1928)
at xx.xxx.xxxxx.xxx.StandardActivity.setContentView(StandardActivity.java:289)
at xx.xxx.xxxxx.xxx.PaymentsActivity.onCreate(PaymentsActivity.java:61)
at android.app.Activity.performCreate(Activity.java:5243)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
... 11 more
Макет, устанавливаемый в setContentView(), содержит фреймы, иначе он довольно стандартный и простой.
Приветствуется любой ввод: -)
Ответы
Ответ 1
Я также столкнулся с этой проблемой с некоторым кодом, который я поддерживал. Я смог последовательно реплицировать ошибку, включив TalkBack в параметрах доступности.
Во-первых, здесь используется метод из View.java, где была использована нулевая ссылка, вызвавшая крах, из выпуска KitKat для Android:
void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
if (!isShown()) {
return;
}
onInitializeAccessibilityEvent(event);
// Only a subset of accessibility events populates text content.
if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
dispatchPopulateAccessibilityEvent(event);
}
// In the beginning we called #isShown(), so we know that getParent() is not null.
getParent().requestSendAccessibilityEvent(this, event);
}
Для меня основной причиной оказался пользовательский вид, который переопределил View.isShown() следующим образом:
public boolean isShown(){
return someCondition;
}
Это означало, что sendAccessibilityEventUncheckedInternal пропустит проверку if (! isShown()), которую он делает, прежде чем продолжить, даже если представление имеет нулевой родительский элемент и поэтому вызвало сбой.
Я изначально считал, что это проблема concurrency, потому что я предположил, что проверка isShown() гарантировала, что родительский элемент не был нулевым и что ссылка на родительский элемент View была изменена во время выполнения sendAccessibilityEventUncheckedInternal. Неправильно!
Если вы обнаружите подобную проблему, особенно в коде, который вы не пишете, вы можете легко избежать этого сбоя, включив в него результат суперкласса isShown() (при условии, что вы меняете код в прямом подклассе View):
public boolean isShown(){
return super.isShown() && someCondition;
}
Ответ 2
Мои пользователи столкнулись с одной и той же проблемой и, похоже, вызваны включением одной или нескольких параметров доступности. Некоторые из моих пользователей использовали Pebble smart watch, который устанавливает параметр доступности - так что это не просто TalkBack и т.д.
Диагноз
Взгляните на этот бит метода KitKat View#setFlags()
на https://github.com/android/platform_frameworks_base/blob/kitkat-mr1-release/core/java/android/view/View.java#L9006
if (accessibilityEnabled) {
...
notifyViewAccessibilityStateChangedIfNeeded(
AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED);
}
который отправляет вас вниз по кроличьей дыре, заканчивающейся на NullPointerException
, если она выполняется до того, как представление присоединено к иерархии представления (т.е. не имеет родительского элемента), потому что в View#sendAccessibilityEventUncheckedInternal()
в https://github.com/android/platform_frameworks_base/blob/kitkat-mr1-release/core/java/android/view/View.java#L4952:
getParent().requestSendAccessibilityEvent(this, event);
Мое обходное решение (похоже, оно не сработает для вас)
Для моего приложения я создаю подкласс View
программно и вызывал View#setOnClickListener()
в конструкторе. Вместо этого я теперь вызываю View#setOnClickListener()
из
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
/* Due to a bug in how Android 4.4 handles accessibility options,
* we can't set the onClick listener until this View has a parent or we will
* get an NPE. */
setOnClickListener(this);
}
Это работает, потому что этот View
будет иметь родителя к тому времени, когда будет вызван View#onAttachedToWindow()
.
Трассировка стека более проблематична. Вы попадаете в отверстие кролика через атрибуты на макете XML. У меня нет идеи для вас. Одна мысль состоит в том, что это должно произойти только при запуске приложения - иначе практически все инфляции XML-макетов будут приводить к сбою, потому что существует так много путей, которые проходят через View#setFlags()
. В моем приложении это одно место является единственным крахом, и это происходит при запуске приложения. Это не приятная идея, но одна из возможностей заключается в том, чтобы переупорядочить вещи, чтобы раздуть это мнение позже.