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

news/2024/10/22 21:45:24/

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 的具体实现需要查看源码,这里多了几个新类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;
}

上诉工具类中DisplayAddress.Physical和SurfaceControl.DisplayCaptureArgs为hide类,SurfaceControl.getPhysicalDisplayTokenSurfaceControl.captureDisplayhide方法,所以需要使用反射方法来实现,下述代码为反射的实现:

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

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

http://www.ppmy.cn/news/1275795.html

相关文章

条款5:了解c++默默编写并调用了哪些函数

如果你不自己声明&#xff0c;编译器会替你声明&#xff08;编译器版本的&#xff09;拷贝构造函数、拷贝赋值运算符和析构函数。此外&#xff0c;如果你没有声明任何构造函数&#xff0c;编译器会为你声明一个默认构造函数。 class Empty{};本质上和写成下面这样是一样的: c…

蜘点云原生之 KubeSphere 落地实践过程

作者&#xff1a;池晓东&#xff0c;蜘点商业网络服务有限公司技术总监&#xff0c;从事软件开发设计 10 多年&#xff0c;喜欢研究各类新技术&#xff0c;分享技术。 来源&#xff1a;本文由 11 月 25 日广州站 meetup 中讲师池晓东整理&#xff0c;整理于该活动中池老师所分享…

测试工具Jmeter:界面介绍、核心选项说明、核心选项用途

本文章主要介绍Jmeter的界面布局&#xff0c;以及各个选项的功能和它们的用途。 JMeter基本原理是建立一个线程池&#xff0c;多线程运行取样器产生大量负载&#xff0c;在运行过程中通过断言来验证结果的正确性&#xff0c;通过监听器来记录测试结果。 1. Jmeter主界面 当我…

SpringBoot 源码解析2:启动流程1

SpringBoot 源码解析2&#xff1a;启动流程1 1.启动方式2.SpringBootApplication3.SpringApplication3.1 构造器SpringApplication3.2 SpringApplication#run 3.3 SpringApplication#run 中关键方法3.1 SpringApplication#prepareEnvironment3.2 SpringApplication#prepareCont…

知识竞赛的组织程序

近年来&#xff0c;知识竞赛已由笔试发展到现场竞赛&#xff0c;由个人对赛发展到集体对赛&#xff0c;比赛的组织形式也日趋复杂。下面以公开赛为典型形式&#xff0c;来介绍知识竞赛的一般组织程序。 知识竞赛一般可分为赛前准备和正式比赛两个阶段。 1、赛前准备阶段是指…

C# WPF上位机开发(知识产权ip保护)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 上位机软件如果是和硬件模块搭配开发&#xff0c;这个时候大部分上位机基本上都是白送的&#xff0c;不会收取相关的费用。但是&#xff0c;如果上…

xcode无线真机调试详细图文步骤

步骤一、 步骤二&#xff1a; 步骤三&#xff1a; 配置完到这里&#xff0c;点击真机右键&#xff0c;菜单栏并未出现connect via ip address 选项&#xff0c;也没出现无线连接的小地球图标&#xff0c;别慌&#xff0c;接着进行下一步操作即可。 步骤四&#xff1a; 1.打开…

pytorch中nn.Sequential详解

1 nn.Sequential概述 1.1 nn.Sequential介绍 nn.Sequential是一个序列容器&#xff0c;用于搭建神经网络的模块被按照被传入构造器的顺序添加到容器中。除此之外&#xff0c;一个包含神经网络模块的OrderedDict也可以被传入nn.Sequential()容器中。利用nn.Sequential()搭建好…