Android 大版本升级变更截图方法总结
- 一、Android R (11) 平台
- 二、Android S (12) 平台
- 三、Android U (14) 平台
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 screenshotBitmap 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 parameterfinal 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
的具体实现需要查看源码,这里多了几个新类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();
}
在 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;
}
上诉工具类中DisplayAddress.Physical
和SurfaceControl.DisplayCaptureArgs为hide
类,SurfaceControl.getPhysicalDisplayToken
和SurfaceControl.captureDisplay
为hide
方法,所以需要使用反射方法来实现,下述代码为反射的实现:
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;
}
三、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();
}
核心代码:windowManager.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();}
}
IWindowManager
和WindowManager
是 Android 系统中的两个不同的类,它们有以下区别:
接口 vs 类:
IWindowManager
是一个接口,定义了窗口管理器的方法和功能,而WindowManager
是一个具体的实现类,用于实际管理窗口的显示和操作。系统服务 vs 上下文获取:
IWindowManager
通常是通过系统服务机制获取的,可以通过ServiceManager.getService("window")
来获取IWindowManager
的实例。而WindowManager
是通过上下文(Context)的getSystemService()
方法获取的,例如context.getSystemService(Context.WINDOW_SERVICE)
。系统级权限 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;
}