call release() не работал после создания виртуального дисплея

я использовал MediaProjection создавать VirtualDisplayделать скриншоты. После этого я попытался выпустить virtualDisplay, но это не сработало:

      // create virtual display...
mVirtualDisplay = sMediaProjection.createVirtualDisplay(DISPLAY, mWidth, mHeight, mDensity,
                VIRTUAL_DISPLAY_FLAGS, mImageReader.getSurface(), null, null);

// release it after taking screenshot successfully
if (mImageReader != null){
    mImageReader.setOnImageAvailableListener(null, null);
    if (mImageReader.getSurface() != null) {
        mImageReader.getSurface().release();
    }
    mImageReader.close();
}
if (mVirtualDisplay != null) mVirtualDisplay.release();
if (sMediaProjection != null) sMediaProjection.unregisterCallback(MediaProjectionStopCallback.this);
mVirtualDisplay = null;
mImageReader = null;

Через несколько минут я вызвал эту функцию displayManager.getDisplays() -> Я видел несколько виртуальных дисплеев, которые не были выпущены.

Как его полностью выпустить? Я что-то пропустил?

P / s: это очень похоже на этот вопрос: Android VirtualDisplay.release() не выпускает дисплей , но я пока не смог найти решение.

1 ответ

Однострочный ответ: virtualdisplay.release() ничего не делает, если вы создаете virtualdisplay, передавая значение null в качестве параметра для обратного вызова.

Я считаю, что это очень неприятная проблема, потому что все образцы, с которыми я столкнулся в Интернете, передают NULL для параметра обратного вызова, и, тем не менее, во всех образцах вызовите release (), не осознавая, что он ничего не делает из-за неясной документации Android, поэтому утечка памяти. Хотя я должен упомянуть, что не обнаружил этой проблемы в старых версиях Android.

Нашел ответ из исходного кода VirtualDisplay. Когда вы создаете VirtualDisplay, вам НЕОБХОДИМО создать VirtualDisplay.Callback и передать его как параметр, а не NULL. потому что функция virtualDisplay.release() проверяет, является ли токен нулевым или нет.

https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/hardware/display/VirtualDisplay.java

      VirtualDisplay.java

   /**
 * Releases the virtual display and destroys its underlying surface.
 * <p>
 * All remaining windows on the virtual display will be forcibly removed
 * as part of releasing the virtual display.
 * </p>
 */
public void release() {
    if (mToken != null) { //mToken is the callback
        mGlobal.releaseVirtualDisplay(mToken);
        mToken = null;
    }
}

Поэтому, прежде чем вызывать createVirtualDisplay, создайте VirtualDisplay.Callback

      VirtualDisplay.Callback mVirtualDisplayCallback = new VirtualDisplay.Callback() {
        @Override
        public void onPaused() {
            super.onPaused();
        }

        @Override
        public void onResumed() {
            super.onResumed();
        }

        @Override
        public void onStopped() {
            super.onStopped();
        }
    };
    mVirtualDisplay = mProjection.createVirtualDisplay("screen-mirror", mWidth, mHeight, mDensity, flags, mImageReader.getSurface(), mVirtualDisplayCallback, handler);

если вы посмотрите класс VirtualDisplay, вы найдете эту переменную и конструктор

      VirtualDisplay.java

private IVirtualDisplayCallback mToken;

VirtualDisplay(DisplayManagerGlobal global, Display display,
        IVirtualDisplayCallback token, Surface surface) {
    mGlobal = global;
    mDisplay = display;
    mToken = token;
    mSurface = surface;
}

обратный вызов mToken / VirtualDisplay в конструкторе - это токен, который функция release () проверяет перед вызовом, является ли он нулевым или нет.

         mGlobal.releaseVirtualDisplay(mToken);

Это очень неприятно, потому что документация по функции release () вообще не упоминает об этом.

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

       DisplayManager disp = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
    Display[] allDisplays = disp.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
    Log.e(TAG ,  text);
    Log.e(TAG , "Display Count  " + allDisplays.length);
    for (Display dl : allDisplays) {
        Log.e(TAG , "Display name: " + dl.getName() + " Display id: " + dl.getDisplayId());
    }

Другие вопросы по тегам