Summary of screenshot methods for Android major version upgrade changes

Summary of screenshot methods for Android major version upgrade changes

Android's native screenshot function is integrated in SystemUI, so if we want to obtain the screenshot method for ordinary applications, we need to study SystemUIthe function implementation of the screenshot part.

Insert image description here

1. Android R (11) platform

The method to obtain the current screenshot when taking a screenshot in SystemUI is as follows:

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotMoreAction.java

private Bitmap captureScreenshotBitmap() {
    
    
    mDisplay.getRealMetrics(mDisplayMetrics);
    float[] dims = {
    
    mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
    int rot = mDisplay.getRotation();
    Rect sourceCrop = new Rect(0, 0, (int) dims[0], (int) dims[1]);
    // Take the screenshot
    Bitmap bitmap = SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot);
    Log.d(TAG, "capture screenshot bitmap");
    if (bitmap == null) {
    
    
        Log.d(TAG, "capture screenshot bitmap is null!");
    }
    return bitmap;
}

Core code:SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot)

Regarding SurfaceControl.screenshotthe specific implementation, view the source code as follows

frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * @see SurfaceControl#screenshot(Rect, int, int, boolean, int)}
 * @hide
 */
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {
    
    
    return screenshot(sourceCrop, width, height, false, rotation);

/**
 * Copy the current screen contents into a hardware bitmap and return it.
 * Note: If you want to modify the Bitmap in software, you will need to copy the Bitmap into
 * a software Bitmap using {@link Bitmap#copy(Bitmap.Config, boolean)}
 *
 * CAVEAT: Versions of screenshot that return a {@link Bitmap} can be extremely slow; avoid use
 * unless absolutely necessary; prefer the versions that use a {@link Surface} such as
 * {@link SurfaceControl#screenshot(IBinder, Surface)} or {@link GraphicBuffer} such as
 * {@link SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}.
 *
 * @see SurfaceControl#screenshotToBuffer(IBinder, Rect, int, int, boolean, int)}
 * @hide
 */
@UnsupportedAppUsage
public static Bitmap screenshot(Rect sourceCrop, int width, int height,
        boolean useIdentityTransform, int rotation) {
    
    
    // TODO: should take the display as a parameter
    final IBinder displayToken = SurfaceControl.getInternalDisplayToken();
    if (displayToken == null) {
    
    
        Log.w(TAG, "Failed to take screenshot because internal display is disconnected");
        return null;
    }

    if (rotation == ROTATION_90 || rotation == ROTATION_270) {
    
    
        rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
    }

    SurfaceControl.rotateCropForSF(sourceCrop, rotation);
    final ScreenshotGraphicBuffer buffer = screenshotToBuffer(displayToken, sourceCrop, width,
            height, useIdentityTransform, rotation);

    if (buffer == null) {
    
    
        Log.w(TAG, "Failed to take screenshot");
        return null;
    }
    return Bitmap.wrapHardwareBuffer(buffer.getGraphicBuffer(), buffer.getColorSpace());
}

See the annotation @UnsupportedAppUsage. The existence of this annotation is to remind developers that certain API or code elements may change in future versions and may be risky or unstable.

@UnsupportedAppUsage annotation is used to mark APIs that are not recommended for applications. It is often used to mark APIs that have been deprecated or will be removed in future versions.

As a normal application, we need to be compatible with multiple versions, so when using high targetSdkVersion, this method will not be found in the SDK, so we need to use reflection to complete it. The screenshot tool methods available on Android R (11) are as follows:

private static Bitmap screenshotR(int width, int height, Display defaultDisplay) {
    
    
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
    
    
        Class<?> demo = Class.forName("android.view.SurfaceControl");
        Method method = demo.getMethod("screenshot", Rect.class, int.class, int.class, int.class);
        bmp = (Bitmap) method.invoke(null, sourceCrop, (int) width, (int) height,
                defaultDisplay.getRotation());
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return bmp;
}

2. Android S (12) platform

Android S (12)There is no change in Android T (13)the screenshot method of the platform. Compared with the R platform, the code in SystemUI has changed. Comb through the code to find the method of obtaining the current screenshot when taking a screenshot as follows:

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java

private Bitmap captureScreenshot(Rect crop) {
    
    
    int width = crop.width();
    int height = crop.height();
    Bitmap screenshot = null;
    final Display display = getDefaultDisplay();
    final DisplayAddress address = display.getAddress();
    if (!(address instanceof DisplayAddress.Physical)) {
    
    
        Log.e(TAG, "Skipping Screenshot - Default display does not have a physical address: "
                + display);
    } else {
    
    
        final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) address;
        final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
                physicalAddress.getPhysicalDisplayId());
        final SurfaceControl.DisplayCaptureArgs captureArgs =
                new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                        .setSourceCrop(crop)
                        .setSize(width, height)
                        .build();
        final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
                SurfaceControl.captureDisplay(captureArgs);
        screenshot = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
    }
    return screenshot;
}

Core code:SurfaceControl.captureDisplay(captureArgs)

For SurfaceControl.captureDisplaythe specific implementation, you need to check the source code. There are several new classes here DisplayCaptureArgs:ScreenshotHardwareBuffer

frameworks/base/core/java/android/view/SurfaceControl.java

/**
 * @param captureArgs Arguments about how to take the screenshot
 * @param captureListener A listener to receive the screenshot callback
 * @hide
 */
public static int captureDisplay(@NonNull DisplayCaptureArgs captureArgs,
        @NonNull ScreenCaptureListener captureListener) {
    
    
    return nativeCaptureDisplay(captureArgs, captureListener);
}

/**
 * Captures all the surfaces in a display and returns a {@link ScreenshotHardwareBuffer} with
 * the content.
 *
 * @hide
 */
public static ScreenshotHardwareBuffer captureDisplay(DisplayCaptureArgs captureArgs) {
    
    
    SyncScreenCaptureListener screenCaptureListener = new SyncScreenCaptureListener();

    int status = captureDisplay(captureArgs, screenCaptureListener);
    if (status != 0) {
    
    
        return null;
    }

    return screenCaptureListener.waitForScreenshot();
}

The screenshot tool methods available on Android S and Android T are as follows:

private static Bitmap screenshotS(int width, int height, Display defaultDisplay) {
    
    
    Bitmap bmp;
    Rect sourceCrop = new Rect(0, 0, width, height);
    final DisplayAddress.Physical physicalAddress = (DisplayAddress.Physical) defaultDisplay.getAddress();
    final IBinder displayToken = SurfaceControl.getPhysicalDisplayToken(
            physicalAddress.getPhysicalDisplayId());
    final SurfaceControl.DisplayCaptureArgs captureArgs =
            new SurfaceControl.DisplayCaptureArgs.Builder(displayToken)
                    .setSourceCrop(sourceCrop)
                    .setSize(width, height)
                    .build();
    final SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer =
            SurfaceControl.captureDisplay(captureArgs);
    bmp = screenshotBuffer == null ? null : screenshotBuffer.asBitmap();
    return bmp;
}

In the appeal tool class DisplayAddress.Physical, SurfaceControl.DisplayCaptureArgs and SurfaceControl.DisplayCaptureArgs are hideclasses, SurfaceControl.getPhysicalDisplayTokenand SurfaceControl.captureDisplayare hidemethods, so they need to be implemented using reflection methods. The following code is the implementation of reflection:

public static Bitmap screenshotS(int width, int height, Display defaultDisplay) {
    
    
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
    
    
        Class<?> displayAddressClass = Class.forName("android.view.DisplayAddress$Physical");
        Object physicalAddress =  displayAddressClass.getMethod("getPhysicalDisplayId").invoke(defaultDisplay.getAddress());

        Class<?> surfaceControlClass = Class.forName("android.view.SurfaceControl");
        Method getPhysicalDisplayTokenMethod = surfaceControlClass.getMethod("getPhysicalDisplayToken", long.class);
        Object displayToken = getPhysicalDisplayTokenMethod.invoke(null,physicalAddress);

        Class<?> displayCaptureArgsBuilderClass = Class.forName("android.view.SurfaceControl$DisplayCaptureArgs$Builder");
        Constructor<?> displayCaptureArgsBuilderConstructor = displayCaptureArgsBuilderClass.getDeclaredConstructor(IBinder.class);
        Object displayCaptureArgsBuilder = displayCaptureArgsBuilderConstructor.newInstance(displayToken);
        Method setSourceCropMethod = displayCaptureArgsBuilderClass.getMethod("setSourceCrop", Rect.class);
        Method setSizeMethod = displayCaptureArgsBuilderClass.getMethod("setSize", int.class, int.class);
        Method buildMethod = displayCaptureArgsBuilderClass.getMethod("build");
        setSourceCropMethod.invoke(displayCaptureArgsBuilder, sourceCrop);
        setSizeMethod.invoke(displayCaptureArgsBuilder, width, height);
        Object captureArgs = buildMethod.invoke(displayCaptureArgsBuilder);

        Method captureDisplayMethod = surfaceControlClass.getMethod("captureDisplay", captureArgs.getClass());
        Object screenshotBuffer = captureDisplayMethod.invoke(null, captureArgs);

        if (screenshotBuffer != null) {
    
    
            Class<?> screenshotHardwareBufferClass = Class.forName("android.view.SurfaceControl$ScreenshotHardwareBuffer");
            Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");
            bmp = (Bitmap) asBitmapMethod.invoke(screenshotBuffer);
        }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return bmp;
}

3. Android U (14) platform

The screenshot method class in SystemUI on the Android 14 platform is written Kotlinusing

frameworks/base/packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt

override fun captureDisplay(displayId: Int, crop: Rect?): Bitmap? {
    
    
    val captureArgs = CaptureArgs.Builder()
        .setSourceCrop(crop)
        .build()
    val syncScreenCapture = ScreenCapture.createSyncCaptureListener()
    windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)
    val buffer = syncScreenCapture.getBuffer()
    return buffer?.asBitmap()
}

Here I will Kotlinconvert into Javacode written by

// 导包为隐藏方法,请使用反射重写此代码
import android.view.IWindowManager;
import android.window.ScreenCapture;
import android.window.ScreenCapture.CaptureArgs;
import android.window.ScreenCapture.SynchronousScreenCaptureListener;
import android.window.ScreenCapture.ScreenshotHardwareBuffer;

IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));
CaptureArgs captureArgs = new CaptureArgs.Builder()
        .setSourceCrop(sourceCrop)
        .build();
SynchronousScreenCaptureListener syncScreenCapture = ScreenCapture.createSyncCaptureListener();
windowManager.captureDisplay(defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);
ScreenshotHardwareBuffer buffer = syncScreenCapture.getBuffer();
if (buffer != null) {
    
    
    bitmap = buffer.asBitmap();
}

Core code:windowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)

The previous methods are all encapsulated in SurfaceControl. The latest U platform has moved the logic to IWindowManager.

frameworks/base/core/java/android/view/IWindowManager.aidl

/**
  * Captures the entire display specified by the displayId using the args provided. If the args
  * are null or if the sourceCrop is invalid or null, the entire display bounds will be captured.
  */
 oneway void captureDisplay(int displayId, in @nullable ScreenCapture.CaptureArgs captureArgs,
         in ScreenCapture.ScreenCaptureListener listener);

// aidl 是接口,相关实现在 WindowManagerService 中
//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
@Override
public void captureDisplay(int displayId, @Nullable ScreenCapture.CaptureArgs captureArgs,
        ScreenCapture.ScreenCaptureListener listener) {
    
    
    Slog.d(TAG, "captureDisplay");
    if (!checkCallingPermission(READ_FRAME_BUFFER, "captureDisplay()")) {
    
    
        throw new SecurityException("Requires READ_FRAME_BUFFER permission");
    }

    ScreenCapture.LayerCaptureArgs layerCaptureArgs = getCaptureArgs(displayId, captureArgs);
    ScreenCapture.captureLayers(layerCaptureArgs, listener);

    if (Binder.getCallingUid() != SYSTEM_UID) {
    
    
        // Release the SurfaceControl objects only if the caller is not in system server as no
        // parcelling occurs in this case.
        layerCaptureArgs.release();
    }
}

IWindowManagerand WindowManagerare two different classes in the Android system. They have the following differences:

  1. Interface vs class: IWindowManagerIt is an interface that defines the methods and functions of the window manager, but WindowManagerit is a specific implementation class used to actually manage the display and operation of the window.

  2. System service vs context acquisition: IWindowManagerIt is usually obtained through the system service mechanism, and the instance can be ServiceManager.getService("window")obtained through IWindowManager. Instead, it is obtained WindowManagerthrough the method of Context , for example .getSystemService()context.getSystemService(Context.WINDOW_SERVICE)

  3. System-level permissions vs. application-level permissions: IWindowManagerUsually used for system-level window management, such as modifying window properties, adjusting window position and size, etc., so access IWindowManagerrequires specific system-level permissions. In contrast, applications can WindowManagermanage their own windows through classes, but are limited by the application's permissions.

In general, IWindowManagerit is an interface for system-level window management, but WindowManagera class for application-level window management. In most cases, application developers more commonly use WindowManagerclasses to manage the application's windows.

The screenshot tool methods available on Android U (14) are as follows:

private static Bitmap screenshotU(int width, int height, Display defaultDisplay) {
    
    
    Bitmap bmp = null;
    Rect sourceCrop = new Rect(0, 0, width, height);
    try {
    
    
        IWindowManager windowManager = IWindowManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.WINDOW_SERVICE));
        Class<?> screenCaptureClass = Class.forName("android.window.ScreenCapture");
        Class<?> captureArgsClass = Class.forName("android.window.ScreenCapture$CaptureArgs");
        Class<?> captureArgsBuilderClass = Class.forName("android.window.ScreenCapture$CaptureArgs$Builder");
        Class<?> screenCaptureListenerClass = Class.forName("android.window.ScreenCapture$ScreenCaptureListener");
        Class<?> synchronousScreenCaptureListenerClass = Class.forName("android.window.ScreenCapture$SynchronousScreenCaptureListener");
        Class<?> screenshotHardwareBufferClass = Class.forName("android.window.ScreenCapture$ScreenshotHardwareBuffer");
        Method setSourceCropMethod = captureArgsBuilderClass.getDeclaredMethod("setSourceCrop", Rect.class);
        Object captureArgsBuilder = captureArgsBuilderClass.newInstance();
        setSourceCropMethod.invoke(captureArgsBuilder, sourceCrop);
        Method buildMethod = captureArgsBuilderClass.getDeclaredMethod("build");
        Object captureArgs = buildMethod.invoke(captureArgsBuilder);
        Method createSyncCaptureListenerMethod = screenCaptureClass.getMethod("createSyncCaptureListener");
        Object syncScreenCapture = createSyncCaptureListenerMethod.invoke(null);
        Method captureDisplayMethod = windowManager.getClass().getMethod("captureDisplay", int.class, captureArgsClass, screenCaptureListenerClass);
        captureDisplayMethod.invoke(windowManager, defaultDisplay.getDisplayId(), captureArgs, syncScreenCapture);
        Method getBufferMethod = synchronousScreenCaptureListenerClass.getMethod("getBuffer");
        Object buffer = getBufferMethod.invoke(syncScreenCapture);
        if (buffer != null) {
    
    
            Method asBitmapMethod = screenshotHardwareBufferClass.getMethod("asBitmap");
            bmp = (Bitmap) asBitmapMethod.invoke(buffer);
        }
    } catch (Exception e) {
    
    
        e.printStackTrace();
    }
    return bmp;
}

Guess you like

Origin blog.csdn.net/weixin_44008788/article/details/135061656