Не могу найти причину моего сбоя с этой трассировкой стека
То, что делает мое приложение, - это отображение системного наложения, прикрепленного через windowManager.addView()
и WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY
. Это делается с помощью службы, которая управляет видимостью зрения и несколькими другими вещами.
Однако я получаю отчеты о сбоях и не могу их воспроизвести. Кроме того, трассировка стека ошибок не имеет ничего общего с пакетом моего приложения, поэтому я действительно не могу получить корень этой проблемы. Ниже приведены две таблицы стека, которые поступают из разных источников, но, по-видимому, связаны между собой:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.measure(int, int)' on a null object reference
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2388)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2101)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1297)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7011)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:777)
at android.view.Choreographer.doCallbacks(Choreographer.java:590)
at android.view.Choreographer.doFrame(Choreographer.java:560)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:763)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:6938)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1404)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1199)
.
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getMeasuredWidth()' on a null object reference
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2484)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2181)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1293)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6599)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:800)
at android.view.Choreographer.doCallbacks(Choreographer.java:603)
at android.view.Choreographer.doFrame(Choreographer.java:572)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:786)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:194)
at android.app.ActivityThread.main(ActivityThread.java:5616)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:959)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:754)
Кажется, что ОС (ViewRootImpl) вызывает эту проблему, потому что ей принадлежит нулевая ссылка на мое представление. Поэтому я не могу найти обходное решение для этого.
Кажется, что это происходит во всех версиях Android с 4.4, а мое приложение - Proguarded. Эти трассировки стека получаются из отчетов о сбоях в Google Play Store
Вот как я прикрепляю представление к системному окну в качестве наложения:
private void attachToSystemWindows(boolean overlayNavigationBar) {
final WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
final boolean isNavBarInBottom = isNavBarInBottom();
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
calculateWindowWidth(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
calculateWindowHeight(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
0,
0,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
0x50728,
-3
);
params.gravity = Gravity.TOP;
windowManager.addView(this, params);
}
private boolean isNavBarInBottom() {
final boolean isLargeDevice = getResources().getConfiguration().smallestScreenWidthDp >= 600;
final int orientation = getResources().getConfiguration().orientation;
if (BuildConfig.DEBUG)
Log.d("MeshView", "Is NavBar in bottom: " + (isLargeDevice || orientation == Configuration.ORIENTATION_PORTRAIT));
return isLargeDevice || orientation == Configuration.ORIENTATION_PORTRAIT;
}
И мой метод onMeasure:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (BuildConfig.DEBUG) Log.d("MeshView", "OnMeasure");
setMeasuredDimension(
Math.max(getSuggestedMinimumWidth(), resolveSize(SIZE_MIN_WIDTH, widthMeasureSpec)),
Math.max(getSuggestedMinimumHeight(), resolveSize(SIZE_MIN_HEIGHT, heightMeasureSpec))
);
}
Ответы
Ответ 1
Удивительная. Я, наконец, нашел корень своей проблемы, но не причина... Я делал что-то с моим видом: я хотел изменить параметры макета в зависимости от его ориентации, поэтому я переделал onConfigurationChanged()
.
Затем я использовал неприятный способ обновить параметры макета: отделить, а затем снова прикрепить его к WindowManager
, как это:
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (!(layoutParams instanceof WindowManager.LayoutParams)) return; //Fix for some ClassCastException in Samsung devices
final WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
final boolean isNavBarInBottom = isNavBarInBottom();
layoutParams.width = calculateWindowWidth(mOverlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight);
layoutParams.height = calculateWindowHeight(mOverlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight);
windowManager.removeView(this);
windowManager.addView(this, layoutParams);
}
Я думаю, что что-то плохое происходит в классе ViewRootImpl
, в результате чего он неправильно обрабатывает повторную привязку, вызывая ошибку.
Чтобы исправить это, просто выполните надлежащий путь: используйте метод WindowManager.updateViewLayout();
:
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (BuildConfig.DEBUG) Log.d("MeshView", "OnConfigurationChanged");
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (!(layoutParams instanceof WindowManager.LayoutParams)) return; //Fix for some ClassCastException in Samsung devices
final WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
final DisplayMetrics metrics = new DisplayMetrics();
windowManager.getDefaultDisplay().getMetrics(metrics);
final boolean isNavBarInBottom = isNavBarInBottom();
layoutParams.width = calculateWindowWidth(mOverlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight);
layoutParams.height = calculateWindowHeight(mOverlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight);
//windowManager.removeView(this); DON'T DO THIS
//windowManager.addView(this, layoutParams); DON'T DO THIS
windowManager.updateViewLayout(this, layoutParams); //DO THIS INSTEAD
}
Кажется, это исправляет мою проблему. Я должен поблагодарить автора этого сообщения, что позволило мне найти это исправление): Android: измените LayoutParams View, добавленный WindowManager
Ответ 2
Я думаю, что вы не передаете правильный флаг при создании params
.
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
calculateWindowWidth(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
calculateWindowHeight(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
0,
0,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
0x50728, // problem might be here.
-3
);
Он должен быть одним из этих значений.
Так что используйте его что-то вроде этого (избегайте напрямую передавать шестнадцатеричные значения):
final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
calculateWindowWidth(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
calculateWindowHeight(overlayNavigationBar, isNavBarInBottom, metrics, mNavigationBarHeight),
0,
0,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON, //any flag
PixelFormat.TRANSLUCENT
);
У меня также есть ссылка PixelFormat.
Попробуйте изменить это и посмотрите, работает ли это.
Ответ 3
является коротким, когда вы удаляете представление по (windowManager.removeView(this)
) после цепочки вызовов ViewRoot.dispatchDetachedFromWindow(), которая по существу устанавливает вид viewroot равным null, поэтому у вас есть NPE во время обхода.
В Подробности:
делая
windowManager.removeView(this);
вы по существу вызываете die на ViewRoot представления, который затем отправляет MSG_DIE
своему обработчику, чтобы он убивал позже, когда он безопасен, и добавляет представление в список mDyingViews
в WindowManagerGlobal. все в порядке,
однако, вызывая:
windowManager.addView(this, layoutParams);
вы вынуждаете смерть ViewRoot и вызываете doDie()
и последовательно ViewRoot.dispatchDetachedFromWindow()
, который устанавливает представление в значение null во время обхода и у вас есть NPE.
для более подробной информации WindowManagerGlobal и ViewRootImpl
Ответ 4
Proguard может вызвать проблему с трассировкой стека, хотя должен был быть намек на ваш собственный код, хотя дальнейшее чтение этого требует расшифровки, поэтому это пеньет меня, выключите Proguard через Gradle, затем снова запустите и посмотрите.
Ответ 5
У меня была такая же проблема в моем приложении. Я решил проблему, используя
public static final int TYPE_SYSTEM_ALERT;
вместо TYPE_SYSTEM_OVERLAY. Я предполагаю, что представление уже уничтожено до вызова функции onMeasure(). Такой тип наложения каким-то образом создает проблему для особо не названных устройств pre kit kat.
Примечание. Если вы хотите, чтобы ваше оверлейное окно отображалось за строкой состояния, и все еще выше всех других приложений, используйте
public static final int TYPE_PHONE;
Надеюсь, это поможет.