call release() did not work after creating virtual display

1k views Asked by At

I used MediaProjection to create VirtualDisplay to take screenshots. Afterward, I tried to release the virtualDisplay, but it did not work:

// 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;

After a few minutes, I called this function displayManager.getDisplays() --> I saw a few virtual displays that did not get released.

How to release it completely? Is there anything I missed?

P/s: it's pretty much like this question: Android VirtualDisplay.release() not releasing the display, but I could not find the solution yet.

1

There are 1 answers

3
Jack Smother On

1 line answer : virtualdisplay.release() do nothing if you create the virtualdisplay passing a null as the parameter for the callback.

I find this is a very frustating problem because all of the samples I came across on the web pass NULL for the callback parameter and yet in all of the samples, call release() without realizing it does absolutely nothing due to unclear android documentation thus the memory leak. Though I do have to mention I didn't find this problem in older Android versions

Found the answer from VirtualDisplay source code. When you are creating a VirtualDisplay, you NEED to make a VirtualDisplay.Callback and pass is as a parameter, not a NULL. because virtualDisplay.release() function checks if the token is null or not.

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;
    }
}

So before you call createVirtualDisplay, make a 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);

if you go see the VirtualDisplay class, you will find this variable and constructor

VirtualDisplay.java

private IVirtualDisplayCallback mToken;

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

the mToken / VirtualDisplay callback on the constructor is that token that the release() function checks on whether it is null or not before calling

   mGlobal.releaseVirtualDisplay(mToken);

This is very frustating on because release() function documentation doesn't mention this at all.

So to check whether this solution works, check the number of displays and the ids before releasing the virtual display and check again after releasing it. The supposed display id should be released.

 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());
    }

enter image description here