Android 大版本升级变更截图方法总结

2023-12-18 20:36:46

Android 原生的截屏功能是集成在 SystemUI 中,因此我们普通应用想要获取截图方法,就需要研读下 SystemUI 截屏部分的功能实现。

在这里插入图片描述

一、Android R (11) 平台

SystemUI中截屏时获取当前的屏幕截图方法如下

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

核心代码:SurfaceControl.screenshot(sourceCrop, (int) dims[0], (int) dims[1], rot)

关于 SurfaceControl.screenshot 的具体实现查看源码如下

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

看到注解@UnsupportedAppUsage,这个注解的存在旨在提醒开发者,某些 API 或代码元素可能在未来版本中发生变化,可能会有风险或不稳定。

@UnsupportedAppUsage 注解,用于标记不建议应用程序使用的 API。它通常用于标记已被弃用或将在未来版本中删除的 API。

作为普通应用,我们需要兼容多版本,所以在使用高targetSdkVersion时,此方法在SDK中就会找不到,因此我们需要使用反射来完成。在 Android R (11) 上可使用的截图工具方法如下:

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

二、Android S (12) 平台

Android S (12)Android T (13) 平台截图方法无变化,SystemUI 中相比较于 R 平台,代码有变化,梳理下代码找到截屏时获取当前的屏幕截图方法如下

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

核心代码:SurfaceControl.captureDisplay(captureArgs)

关于 SurfaceControl.captureDisplay 的具体实现需要查看源码,这里多了几个新类DisplayCaptureArgsScreenshotHardwareBuffer

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

在 Android S 和 Android T 上可使用的截图工具方法如下:

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

三、Android U (14) 平台

Android 14 平台上的 SystemUI 中的截图方法类是使用Kotlin编写

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

我这里将 Kotlin 转化为 Java 编写的代码

// 导包为隐藏方法,请使用反射重写此代码
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();
}

核心代码:indowManager.captureDisplay(displayId, captureArgs, syncScreenCapture)

之前的方法都封装在了SurfaceControl中,最新的U平台将逻辑挪到了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();
    }
}

IWindowManagerWindowManager 是 Android 系统中的两个不同的类,它们有以下区别:

  1. 接口 vs 类:IWindowManager 是一个接口,定义了窗口管理器的方法和功能,而 WindowManager 是一个具体的实现类,用于实际管理窗口的显示和操作。

  2. 系统服务 vs 上下文获取:IWindowManager 通常是通过系统服务机制获取的,可以通过 ServiceManager.getService("window") 来获取 IWindowManager 的实例。而 WindowManager 是通过上下文(Context)的 getSystemService() 方法获取的,例如 context.getSystemService(Context.WINDOW_SERVICE)

  3. 系统级权限 vs 应用级权限:IWindowManager 通常被用于系统级别的窗口管理,例如修改窗口属性、调整窗口的位置和大小等,因此访问 IWindowManager 需要特定的系统级权限。相比之下,应用程序可以通过 WindowManager 类来管理自己的窗口,但受到应用程序权限的限制。

总的来说,IWindowManager 是用于系统级窗口管理的接口,而 WindowManager 是用于应用程序级窗口管理的类。在大多数情况下,应用程序开发者更常使用 WindowManager 类来管理应用程序的窗口。

在 Android U (14) 上可使用的截图工具方法如下:

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

文章来源:https://blog.csdn.net/weixin_44008788/article/details/135061656
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。