Android MediaProjection截屏功能的前台服务实现

11 次阅读

Android系统提供的MediaProjection API是实现屏幕捕获功能的官方方案,从Android 5.0(API 21)开始,开发者可以通过该接口获取设备屏幕内容,并进行截图、录屏、直播推流等操作。由于屏幕采集属于持续运行任务,Android系统要求这类功能必须结合前台服务(Foreground Service)实现,以保证应用在后台运行时仍然具备稳定的截屏能力。

本文将详细介绍如何基于MediaProjection结合前台服务实现Android截屏功能,包括权限申请、服务创建、屏幕数据获取、Bitmap生成以及常见问题处理。

一、MediaProjection截屏原理解析

MediaProjection的核心作用是允许应用通过系统授权获取屏幕内容。由于屏幕数据涉及用户隐私,Android不会直接开放接口,而是要求应用主动调用系统授权页面,由用户确认后才能开始采集。

整体流程如下:

  1. 应用调用MediaProjectionManager请求屏幕捕获权限。

  2. 系统弹出授权确认窗口。

  3. 用户允许后返回授权结果。

  4. 应用通过授权数据创建MediaProjection对象。

  5. 使用VirtualDisplay创建虚拟屏幕。

  6. 通过ImageReader获取屏幕帧数据。

  7. 将图片数据转换为Bitmap完成截图。

数据流程:

用户授权
   ↓
MediaProjection
   ↓
VirtualDisplay
   ↓
ImageReader
   ↓
Image
   ↓
Bitmap
   ↓
保存或处理图片

MediaProjection本身不会直接返回图片,而是通过虚拟显示设备输出屏幕内容,因此需要结合ImageReader完成图像接收。


二、为什么需要前台服务实现截屏

Android系统对于后台运行限制越来越严格,尤其是在Android 8.0之后,普通Service无法长时间在后台执行任务。

如果直接在Activity中启动MediaProjection:

  • Activity关闭后任务可能停止;

  • 应用进入后台后可能被系统回收;

  • 截屏过程无法持续运行;

  • 用户体验不稳定。

因此推荐架构:

Activity
   |
   | 获取MediaProjection授权
   ↓
Foreground Service
   |
   | 创建VirtualDisplay
   ↓
ImageReader
   |
   ↓
截图处理

Activity只负责权限申请,而真正的屏幕采集逻辑放入前台服务中。


三、配置前台服务权限

首先需要在AndroidManifest.xml中声明相关权限。

XML
 android:name="android.permission.FOREGROUND_SERVICE"/>

Android 14及以上版本,如果使用媒体投影功能,需要额外声明前台服务类型:

XML
 android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>

同时注册服务:

XML

    android:name=".ScreenCaptureService"
    android:foregroundServiceType="mediaProjection"/>

其中:

  • foregroundServiceType="mediaProjection"表示该服务用于屏幕投影;

  • 系统会根据类型进行权限校验。


四、Activity中申请MediaProjection权限

首先获取MediaProjectionManager:

Java
MediaProjectionManager manager =
        (MediaProjectionManager)getSystemService(
        MEDIA_PROJECTION_SERVICE);

创建授权Intent:

Java
Intent intent =
        manager.createScreenCaptureIntent();

startActivityForResult(intent, 1001);

用户确认后,在回调中获取结果:

Java
@Override
protected void onActivityResult(
        int requestCode,
        int resultCode,
        Intent data) {

    super.onActivityResult(
        requestCode,
        resultCode,
        data);

    if(requestCode == 1001 
        && resultCode == RESULT_OK){

        Intent serviceIntent =
                new Intent(this,
                ScreenCaptureService.class);

        serviceIntent.putExtra(
                "resultCode",
                resultCode);

        serviceIntent.putExtra(
                "data",
                data);

        startForegroundService(serviceIntent);
    }
}

这里需要注意:

MediaProjection授权结果必须传递给Service,因为Service需要使用该授权信息创建MediaProjection实例。


五、创建截屏前台服务

创建一个继承Service的类:

Java
public class ScreenCaptureService extends Service {

    private MediaProjection mediaProjection;

    @Override
    public void onCreate() {
        super.onCreate();

        createNotificationChannel();

        startForeground(
            1,
            createNotification());
    }
}

前台服务必须显示通知,否则系统会认为服务违规运行。


六、创建前台服务通知

Android 8.0以上必须创建NotificationChannel:

Java
private void createNotificationChannel(){

    NotificationManager manager =
        getSystemService(NotificationManager.class);

    NotificationChannel channel =
        new NotificationChannel(
        "screen_capture",
        "屏幕采集服务",
        NotificationManager.IMPORTANCE_LOW);

    manager.createNotificationChannel(channel);
}

创建通知:

Java
private Notification createNotification(){

    return new NotificationCompat.Builder(
            this,
            "screen_capture")
            .setContentTitle("正在进行屏幕截取")
            .setContentText("MediaProjection运行中")
            .setSmallIcon(
            R.drawable.ic_launcher)
            .build();
}

七、Service中初始化MediaProjection

接收Activity传递的数据:

Java
int resultCode =
        intent.getIntExtra(
        "resultCode",
        -1);

Intent data =
        intent.getParcelableExtra(
        "data");

创建MediaProjection:

Java
MediaProjectionManager manager =
(MediaProjectionManager)
getSystemService(
MEDIA_PROJECTION_SERVICE);


mediaProjection =
manager.getMediaProjection(
resultCode,
data);

此时服务已经具备屏幕采集能力。


八、使用ImageReader获取屏幕数据

创建ImageReader:

Java
DisplayMetrics metrics =
getResources()
.getDisplayMetrics();


int width = metrics.widthPixels;
int height = metrics.heightPixels;
int density = metrics.densityDpi;


ImageReader imageReader =
ImageReader.newInstance(
width,
height,
PixelFormat.RGBA_8888,
2);

参数说明:

  • width:截图宽度;

  • height:截图高度;

  • RGBA_8888:图片格式;

  • 2:缓存数量。


九、创建VirtualDisplay

VirtualDisplay负责将屏幕内容输出到ImageReader:

Java
VirtualDisplay virtualDisplay =
mediaProjection.createVirtualDisplay(
"ScreenCapture",
width,
height,
density,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
imageReader.getSurface(),
null,
null);

创建完成后,系统会不断向ImageReader写入屏幕数据。


十、获取Image并转换Bitmap

监听图片变化:

Java
imageReader.setOnImageAvailableListener(
reader -> {

    Image image =
    reader.acquireLatestImage();

    if(image != null){

        Bitmap bitmap =
        convertImageToBitmap(image);

        image.close();
    }

}, null);

Image转换Bitmap示例:

Java
private Bitmap convertImageToBitmap(
        Image image){

    Image.Plane plane =
        image.getPlanes()[0];

    ByteBuffer buffer =
        plane.getBuffer();

    int pixelStride =
        plane.getPixelStride();

    int rowStride =
        plane.getRowStride();

    Bitmap bitmap =
        Bitmap.createBitmap(
        image.getWidth(),
        image.getHeight(),
        Bitmap.Config.ARGB_8888);

    bitmap.copyPixelsFromBuffer(buffer);

    return bitmap;
}

实际项目中还需要考虑rowStride带来的图片偏移问题,通常需要根据stride创建临时Bitmap进行裁剪。


十一、保存截图到本地

Bitmap生成后,可以保存为PNG:

Java
File file =
new File(
getExternalFilesDir(null),
"screen.png");


FileOutputStream output =
new FileOutputStream(file);


bitmap.compress(
Bitmap.CompressFormat.PNG,
100,
output);


output.close();

Android 10以后推荐使用MediaStore保存公共目录文件:

Java
ContentValues values =
new ContentValues();

values.put(
MediaStore.Images.Media.DISPLAY_NAME,
"capture.png");

values.put(
MediaStore.Images.Media.MIME_TYPE,
"image/png");

这样可以避免传统存储权限问题。


十二、Android版本兼容注意事项

1. Android 8.0后台限制

必须使用:

Java
startForegroundService()

启动服务。

否则可能出现:

Background start not allowed

异常。


2. Android 12限制

Android 12增加了前台服务启动限制:

  • 应用后台不能随意启动Foreground Service;

  • 建议由用户操作触发启动。

例如:

点击“开始截图”按钮后启动服务。


3. Android 14 MediaProjection变化

Android 14加强了MediaProjection安全控制:

  • 每次录屏需要用户授权;

  • 一个授权Intent只能创建一次MediaProjection;

  • 需要正确声明mediaProjection类型。

否则可能出现:

SecurityException:
MediaProjection already used

十三、常见问题解决

1. 截图黑屏

原因:

  • VirtualDisplay创建参数错误;

  • ImageReader尺寸不匹配;

  • 没有正确处理Buffer。

解决:

检查:

Java
width
height
densityDpi

是否与真实屏幕一致。


2. Service启动后立即关闭

原因:

  • 没有调用startForeground;

  • 通知创建失败;

  • Android版本权限不足。

解决:

确保:

Java
startForeground(
notificationId,
notification);

在服务启动后立即执行。


3. 用户关闭授权后崩溃

需要判断:

Java
if(resultCode != RESULT_OK){
    stopSelf();
    return;
}

不要直接创建MediaProjection。


4. 截图出现绿色或花屏

通常是PixelFormat和Buffer处理错误导致。

建议:

Java
PixelFormat.RGBA_8888

并正确处理:

  • rowStride;

  • pixelStride;

  • padding区域。


十四、MediaProjection截屏架构优化建议

实际开发中建议采用模块化设计:

ScreenCaptureManager

    |
    |-- PermissionManager
    |
    |-- ForegroundService
    |
    |-- MediaProjectionController
    |
    |-- ImageProcessor
    |
    |-- FileStorageManager

优势:

  • 权限逻辑独立;

  • 截屏服务可复用;

  • 方便扩展录屏功能;

  • 便于Android版本适配。


十五、应用场景

基于MediaProjection和前台服务实现的截屏能力,可以应用于:

  • 远程协助软件;

  • 屏幕共享应用;

  • 游戏直播工具;

  • 自动化测试工具;

  • 企业设备管理系统;

  • 教学演示软件。

需要注意的是,屏幕采集属于敏感权限,应用必须明确告知用户采集目的,并遵守Android平台隐私规范。


总结

Android MediaProjection结合前台服务是实现稳定截屏功能的标准方案。核心流程包括用户授权、创建MediaProjection、启动Foreground Service、建立VirtualDisplay、通过ImageReader获取屏幕数据以及生成Bitmap。

随着Android版本不断升级,系统对后台运行和隐私权限控制越来越严格,开发者需要重点关注前台服务声明、MediaProjection生命周期管理以及版本兼容处理。采用合理的架构设计,可以让截屏功能更加稳定、安全,并支持进一步扩展为录屏、直播推流等高级功能。