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 SystemUI
the function implementation of the screenshot part.
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.screenshot
the 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.captureDisplay
the 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 hide
classes, SurfaceControl.getPhysicalDisplayToken
and SurfaceControl.captureDisplay
are hide
methods, 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 Kotlin
using
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 Kotlin
convert into Java
code 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();
}
}
IWindowManager
andWindowManager
are two different classes in the Android system. They have the following differences:
Interface vs class:
IWindowManager
It is an interface that defines the methods and functions of the window manager, butWindowManager
it is a specific implementation class used to actually manage the display and operation of the window.System service vs context acquisition:
IWindowManager
It is usually obtained through the system service mechanism, and the instance can beServiceManager.getService("window")
obtained throughIWindowManager
. Instead, it is obtainedWindowManager
through the method of Context , for example .getSystemService()
context.getSystemService(Context.WINDOW_SERVICE)
System-level permissions vs. application-level permissions:
IWindowManager
Usually used for system-level window management, such as modifying window properties, adjusting window position and size, etc., so accessIWindowManager
requires specific system-level permissions. In contrast, applications canWindowManager
manage their own windows through classes, but are limited by the application's permissions.In general,
IWindowManager
it is an interface for system-level window management, butWindowManager
a class for application-level window management. In most cases, application developers more commonly useWindowManager
classes 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;
}