Не могу найти причину моего сбоя с этой трассировкой стека

То, что делает мое приложение, - это отображение системного наложения, прикрепленного через 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;

Надеюсь, это поможет.