Android 原生的截屏功能是集成在 SystemUI
中,因此我们普通应用想要获取截图方法,就需要研读下 SystemUI
截屏部分的功能实现。
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 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;
}
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();
}
}
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;
}