Android MediaProjection学习(一)之和ImageReader实现屏幕截图

本文深入探讨MediaProjection在Android中的应用,详细介绍了如何通过MediaProjectionManager获取屏幕内容权限,创建虚拟显示并进行屏幕截图。文章还讲解了关键方法createVirtualDisplay的参数及ImageReader在数据解析中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MediaProjection是什么?

按照惯例,附上Google官方文档链接: 官方文档

A token granting applications the ability to capture screen contents and/or record system audio. The exact capabilities granted depend on the type of MediaProjection.
A screen capture session can be started through MediaProjectionManager.createScreenCaptureIntent(). This grants the ability to capture screen contents, but not system audio

根据文档介绍。MediaProjection是手环用户获取屏幕内容或者记录系统的界面视频。你需要什么权限取决于MediaProjection的类型。通过MediaProjectionManager.createScreenCaptureIntent()去请求系统的屏幕信息权限,但是不会录制声音。

MediaProjection的重要方法

官方文档里提供了四种方法,但是最重要的就是下面这个方法

返回值说明
VirtualDisplaycreateVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, VirtualDisplay.Callback callback, Handler handler)Creates a VirtualDisplay to capture the contents of the screen.
参数说明
nameString: The name of the virtual display, must be non-empty.This value must never be null.
-这个值不能为空,用途还没有搞明白
widthint: The width of the virtual display in pixels. Must be greater than 0.
-用px表示的确切的值,必须大于0,我们截图的话传进去屏幕宽度就好
heightint: The height of the virtual display in pixels. Must be greater than 0.
-同上,传入屏幕高度
dpiint: The density of the virtual display in dpi. Must be greater than 0.
-传入屏幕的dpi值
flagsint: A combination of virtual display flags. See DisplayManager for the full list of flags.
-virtual displays的标识组合
surfaceSurface: The surface to which the content of the virtual display should be rendered, or null if there is none initially.
-这个是特别重要的一个参数,是我们的屏幕绘制的内容,会放在这个参数中回调
callbackVirtualDisplay.Callback: Callback to call when the virtual display’s state changes, or null if none.
-VirtualDisplay.Callback的实例对象作为参数,当展示的状态发生变化时回调
handlerHandler: The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread’s main Looper.
-callback回调时要做的操作放在Handler里

不明白?操作一下

说再多的概念也没有实际写一个Demo来的实在,那就写一个。
开始我们已经了解到 要通过MediaProjectionManager.createScreenCaptureIntent()方法获取一个intent来获取权限。

Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(intent, RESULT_CODE);

其中mMediaProjectionManager是MediaProjectionManager的一个实例,RESULT_CODE是一个int的变量,我设置的是1。经过以上操作之后运行app就会有一个弹窗如图所示:
在这里插入图片描述

点击开始就可以获取获取屏幕内容的权限了。我们使用了startActivityForResult方法来启动这个intent,就是为了获取返回值。

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case RESULT_CODE:
                if (resultCode == Activity.RESULT_OK) {
                    mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
                    mMediaProjection.createVirtualDisplay("shot_image", windowManager.getDefaultDisplay().getWidth(),
                            windowManager.getDefaultDisplay().getHeight(),
                            displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader
                                    .getSurface(), null, null);
                }
                break;
        }
    }

这里在onActivityResult方法里用MediaProjectionManager类的getMediaProjection方法传入回传的数据实例化了MediaProjection的对象。
然后重头戏来了,我们调用了createVirtualDisplay方法,上文中已经对这个方法进行了详细的说明,前面几个不再详细说明,我们看看几个关键参数。

1、DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR

这个参数官方的说明是:Virtual display flag: Allows content to be mirrored on private displays when no content is being shown.大概意思就是屏幕开始展示的时候就会把内容传出来。

2、imageReader.getSurface()

这个参数实际上是通过ImageReader的getSurface()方法来获取一个surface对象。然后把截屏内容传给这个imageReader的实例来处理。

聊聊ImageReader

怎么画风一转开始聊ImageReader呢?其实到上一步,MediaProjection的任务已经完成了。接下来要把数据解析成bitmap然后再使用就好,可以保存到本地或者加载到ImageView控件上,这里我们直接加载到Imageview的控件上。

要转化就一步一步来,这个surface是什么?

A Surface is generally created by or from a consumer of image buffers (such as a SurfaceTexture, MediaRecorder, or Allocation), and is handed to some kind of producer (such as OpenGL, MediaPlayer, or CameraDevice) to draw into.

官方文档里说他就是一个Image的缓冲区,交给一些可以绘制Image的工具去绘制后存在这个缓冲区里。surface我们了解这么多就够了。

接下来看看ImageReader

The ImageReader class allows direct application access to image data rendered into a Surface

这是官方的解释,我们结合代码来看看ImageReader:

private void startCapture() {
        mImageName = System.currentTimeMillis() + ".png";
        Image image = imageReader.acquireLatestImage();
        if (image == null)
            return;
        int width = image.getWidth();
        int height = image.getHeight();
        final Image.Plane[] planes = image.getPlanes();
        final ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;
        Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
        if (bitmap != null)
            iv_screen.setImageBitmap(bitmap);
        image.close();
    }

经过上面几行代码之后我们就把拿到的数据转换成了bitmap,这里面主要说明的有几个方法。

1、Image image = imageReader.acquireLatestImage();

acquireLatestImage()
Acquire the latest Image from the ImageReader’s queue, dropping older images.

获得最新的Image从ImageReader的队列里,丢掉旧的images。

这里斜体字可以不看
之前我通过这个方法判断获取的 Image是否为空,判断完以后再去获取发现还是空,最后看了官方文档才知道会丢掉这张Image所以你判断完的时候就再也获取不到了,所以就要先获取再判断

回归正题,下面就看看另外一句关键的代码

2、后面的Image转换为bitmap的代码,我还不是太懂。求告知。

Final

通过以上的操作就可以实现屏幕截图了,那段转换代码我决定去求助一下别人。主要过程都在博文里说了,大家可以自己多看看,不会也可以问我。

<think>我们正在处理个关于AndroidMediaProjectionAPI的问题,用户想要了解如何消费屏幕流(即捕获屏幕内容)并获取实现方法和示例代码。根据引用[1]和引用[4],我们知道MediaProjection是从Android5.0(API21)引入的,用于捕获屏幕内容。在Android10(API29)及以上版本,由于后台限制,需要使用前台服务来启动屏幕捕获。核心步骤:1.请求屏幕捕获权限:通过MediaProjectionManager创建个屏幕捕获的Intent,然后启动这个Intent以请求用户授权。2.根据用户授权结果(在onActivityResult中处理),如果授权成功,则获取MediaProjection对象。3.在Android10及以上版本,需要启动个前台服务来维持屏幕捕获,否则可以直接使用MediaProjection对象。4.使用MediaProjection对象创建VirtualDisplay,将屏幕内容投射到VirtualDisplay中。5.通过ImageReader或Surface来消费屏幕流(例如获取图像帧或视频流)。示例代码结构:我们将分为几个部分:a.请求权限b.在onActivityResult中处理结果c.创建MediaProjection(注意Android版本差异)d.创建VirtualDisplaye.通过ImageReader获取图像帧注意:我们需要在AndroidManifest.xml中声明必要的权限和服务。根据引用[1]中的代码片段,我们可以参考其处理AndroidQ及以上版本的方式。下面是个简化的实现步骤和示例代码:步骤1:在AndroidManifest.xml中添加权限和服务声明```xml<uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE"/><uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/><!--如果需要保存截图--><application><serviceandroid:name=".MediaProjectionService"android:enabled="true"android:exported="false"android:foregroundServiceType="mediaProjection"/><!--针对Android10及以上--></application>```步骤2:请求屏幕捕获权限```javaprivatestaticfinalintREQUEST_MEDIA_PROJECTION=1;privateMediaProjectionManagermediaProjectionManager;//在Activity中mediaProjectionManager=(MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);IntentcaptureIntent=mediaProjectionManager.createScreenCaptureIntent();startActivityForResult(captureIntent,REQUEST_MEDIA_PROJECTION);```步骤3:在onActivityResult中处理结果```java@OverrideprotectedvoidonActivityResult(intrequestCode,intresultCode,Intentdata){super.onActivityResult(requestCode,resultCode,data);if(requestCode==REQUEST_MEDIA_PROJECTION){if(resultCode==RESULT_OK){//根据系统版本决定启动前台服务还是直接创建MediaProjectioncreateMediaProjection(resultCode,data);}}}@RequiresApi(api=Build.VERSION_CODES.LOLLIPOP)privatevoidcreateMediaProjection(intresultCode,Intentintent){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){//Android10及以上,启动前台服务Intentservice=newIntent(this,MediaProjectionService.class);service.putExtra("code",resultCode);service.putExtra("data",intent);//可以传递通知图标和文本service.putExtra("notificationIcon",R.drawable.ic_notification);service.putExtra("notificationText","屏幕捕获中...");ContextCompat.startForegroundService(this,service);}else{//低版本直接创建MediaProjectionMediaProjectionmediaProjection=mediaProjectionManager.getMediaProjection(resultCode,intent);if(mediaProjection!=null){//创建VirtualDisplay等操作setupVirtualDisplay(mediaProjection);}}}```步骤4:创建前台服务(针对Android10及以上)在MediaProjectionService中,我们需要在onStartCommand中获取MediaProjection对象,并启动前台服务。```javapublicclassMediaProjectionServiceextendsService{privateMediaProjectionmediaProjection;@OverridepublicintonStartCommand(Intentintent,intflags,intstartId){intresultCode=intent.getIntExtra("code",0);Intentdata=intent.getParcelableExtra("data");intnotificationIcon=intent.getIntExtra("notificationIcon",0);StringnotificationText=intent.getStringExtra("notificationText");//创建通知createNotificationChannel();Notificationnotification=newNotificationCompat.Builder(this,"MediaProjectionChannel").setSmallIcon(notificationIcon).setContentText(notificationText).build();startForeground(1,notification);MediaProjectionManagermediaProjectionManager=(MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE);mediaProjection=mediaProjectionManager.getMediaProjection(resultCode,data);if(mediaProjection!=null){//这里可以将mediaProjection传递回Activity,或者直接在此Service中创建VirtualDisplay//假设我们有个回调接口MainActivityactivity=MainActivity.instance;if(activity!=null){activity.onMediaProjectionCreated(mediaProjection);}}returnSTART_STICKY;}privatevoidcreateNotificationChannel(){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){NotificationChannelchannel=newNotificationChannel("MediaProjectionChannel","ScreenCapture",NotificationManager.IMPORTANCE_LOW);getSystemService(NotificationManager.class).createNotificationChannel(channel);}}@OverridepublicvoidonDestroy(){if(mediaProjection!=null){mediaProjection.stop();mediaProjection=null;}super.onDestroy();}@Nullable@OverridepublicIBinderonBind(Intentintent){returnnull;}}```步骤5:在Activity中设置VirtualDisplay在获取到MediaProjection对象后,我们需要设置VirtualDisplay来捕获屏幕内容。```javaprivatevoidsetupVirtualDisplay(MediaProjectionmediaProjection){DisplayMetricsmetrics=getResources().getDisplayMetrics();intscreenDensity=metrics.densityDpi;//创建ImageReader来获取屏幕图像ImageReaderimageReader=ImageReader.newInstance(metrics.widthPixels,metrics.heightPixels,PixelFormat.RGBA_8888,2);//设置监听器,当有新的帧可用时回调imageReader.setOnImageAvailableListener(newImageReader.OnImageAvailableListener(){@OverridepublicvoidonImageAvailable(ImageReaderreader){//从reader中获取最新的图像,并处理Imageimage=reader.acquireLatestImage();if(image!=null){//处理图像,例如保存为文件或传输//注意:处理完后要关闭imageimage.close();}}},null);//创建VirtualDisplayVirtualDisplayvirtualDisplay=mediaProjection.createVirtualDisplay("ScreenCapture",metrics.widthPixels,metrics.heightPixels,screenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,imageReader.getSurface(),null,null);}```注意事项:-需要处理不同Android版本的兼容性。-在不需要时,要释放资源(如停止MediaProjection,关闭VirtualDisplay等)。-处理图像时要注意性能,避免在主线程中处理。以上是个基本的实现框架。用户可以根据具体需求扩展,例如将图像编码为视频流等。根据用户问题,我们提供了实现方法和示例代码。下面生成相关问题。</think>### Android MediaProjection 消费屏幕流实现指南 在 Android 中使用 MediaProjection API 消费屏幕流主要涉及以下核心步骤: #### 1. 请求屏幕捕获权限 ```java private static final int REQUEST_CODE_SCREEN_CAPTURE = 1; private MediaProjectionManager mProjectionManager; // 在Activity中初始化 mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); // 启动权限请求 Intent captureIntent = mProjectionManager.createScreenCaptureIntent(); startActivityForResult(captureIntent, REQUEST_CODE_SCREEN_CAPTURE); ``` #### 2. 处理权限回调 ```java @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_SCREEN_CAPTURE) { if (resultCode == RESULT_OK) { startScreenCapture(resultCode, data); // 启动屏幕捕获 } } } ``` #### 3. 创建 MediaProjection 对象(兼容不同版本) ```java @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) private void startScreenCapture(int resultCode, Intent data) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 需使用前台服务 Intent serviceIntent = new Intent(this, CaptureService.class); serviceIntent.putExtra("code", resultCode); serviceIntent.putExtra("data", data); ContextCompat.startForegroundService(this, serviceIntent); } else { // 低版本直接创建 MediaProjection projection = mProjectionManager.getMediaProjection(resultCode, data); startVirtualDisplay(projection); // 启动虚拟显示 } } ``` #### 4. 创建 VirtualDisplay 消费屏幕流 ```java private VirtualDisplay mVirtualDisplay; private ImageReader mImageReader; private void startVirtualDisplay(MediaProjection projection) { DisplayMetrics metrics = getResources().getDisplayMetrics(); int screenDensity = metrics.densityDpi; // 创建ImageReader接收帧数据 mImageReader = ImageReader.newInstance( metrics.widthPixels, metrics.heightPixels, PixelFormat.RGBA_8888, 2 // 缓冲区数量 ); // 设置帧可用监听器 mImageReader.setOnImageAvailableListener(reader -> { Image image = reader.acquireLatestImage(); if (image != null) { // 在此处理屏幕帧数据 processScreenFrame(image); image.close(); } }, null); // 创建虚拟显示 mVirtualDisplay = projection.createVirtualDisplay( "ScreenCapture", metrics.widthPixels, metrics.heightPixels, screenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), // 输出到ImageReader null, null ); } // 处理屏幕帧示例 private void processScreenFrame(Image image) { ByteBuffer buffer = image.getPlanes()[0].getBuffer(); byte[] bytes = new byte[buffer.remaining()]; buffer.get(bytes); // 可在此实现: // 1. 保存为图片文件 // 2. 视频编码 // 3. 实时分析屏幕内容 // 4. 网络传输等 } ``` #### 5. Android 10+ 的前台服务实现 ```java public class CaptureService extends Service { private MediaProjection mMediaProjection; @Override public int onStartCommand(Intent intent, int flags, int startId) { // 创建通知渠道 createNotificationChannel(); // 启动前台服务 Notification notification = new NotificationCompat.Builder(this, "capture_channel") .setContentTitle("屏幕捕获中") .setSmallIcon(R.drawable.ic_notification) .build(); startForeground(1, notification); // 获取MediaProjection int resultCode = intent.getIntExtra("code", 0); Intent data = intent.getParcelableExtra("data"); MediaProjectionManager manager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE); mMediaProjection = manager.getMediaProjection(resultCode, data); // 启动虚拟显示 startVirtualDisplay(mMediaProjection); return START_STICKY; } // 实现startVirtualDisplay方法同上 } ``` #### 6. 释放资源 ```java private void stopScreenCapture() { if (mVirtualDisplay != null) { mVirtualDisplay.release(); mVirtualDisplay = null; } if (mImageReader != null) { mImageReader.close(); mImageReader = null; } if (mMediaProjection != null) { mMediaProjection.stop(); mMediaProjection = null; } } ``` #### 关键注意事项: 1. **权限声明**:在 AndroidManifest.xml 中添加: ```xml <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> ``` 2. **Android 10+ 限制**:必须使用前台服务并添加权限: ```xml <service android:name=".CaptureService" android:foregroundServiceType="mediaProjection"/> ``` 3. **帧处理优化**: - 使用 `ImageReader` 的 `acquireLatestImage()` 避免帧堆积 - 在工作线程处理帧数据,避免阻塞UI线程 - 及时关闭 `Image` 对象防止内存泄漏 4. **虚拟显示配置**: - 使用 `VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR` 自动处理方向 - 根据需求调整分辨率降低性能消耗 - 选择合适像素格式(`PixelFormat.RGBA_8888` 通用性最好) [^1] [^4]
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值