安卓实现屏幕共享

1,419 阅读2分钟

以下是基于Android实现设备间屏幕共享与远程控制的核心架构设计及关键技术实现方案,结合多个技术文档和开源实践总结而成:


🧱 一、系统架构设计

1. ​主控端(Controller)​

graph LR
A[用户界面] --> B[请求屏幕捕获授权]
B --> C[MediaProjection初始化]
C --> D[创建VirtualDisplay]
D --> E[ImageReader获取帧数据]
E --> F[图像编码JPEG/H.264]
F --> G[Socket发送数据]
A --> H[捕获触摸事件]
H --> I[封装事件协议]
I --> G

2. ​受控端(Receiver)​

graph LR
K[Socket监听端口] --> L[接收数据流]
L --> M{解析帧头}
M --> |图像帧| N[JPEG解码]
N --> O[SurfaceView渲染]
M --> |触摸事件| P[构造GestureDescription]
P --> Q[无障碍服务注入事件]

双端通过TCP协议保持长连接,12字节帧头定义见下文。


⚙️ 二、关键技术实现

1. ​屏幕捕获(主控端)​

  • 核心类​:MediaProjectionManager + VirtualDisplay + ImageReader

  • 关键代码​:

    // 请求授权
    MediaProjectionManager manager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    startActivityForResult(manager.createScreenCaptureIntent(), REQUEST_CODE);
    
    // 初始化VirtualDisplay
    DisplayMetrics metrics = getResources().getDisplayMetrics();
    ImageReader imageReader = ImageReader.newInstance(metrics.widthPixels, metrics.heightPixels, PixelFormat.RGBA_8888, 2);
    mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCast", 
        metrics.widthPixels, metrics.heightPixels, metrics.densityDpi,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, 
        imageReader.getSurface(), null, null);
    
    // 获取并编码图像帧
    Image image = imageReader.acquireLatestImage();
    Bitmap bitmap = Bitmap.createBitmap(image.getWidth(), image.getHeight(), Bitmap.Config.ARGB_8888);
    bitmap.copyPixelsFromBuffer(image.getPlanes()[0].getBuffer());
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, bos);  // JPEG压缩50%质量
    

2. ​网络传输协议

帧头字段字节数说明
帧类型4字节0x01=图像, 0x02=触摸事件
数据长度4字节网络字节序(大端)
时间戳4字节毫秒精度
  • 触摸事件数据结构​:

    struct TouchEvent {
        byte type;     // 0:DOWN, 1:MOVE, 2:UP
        float x;       // 坐标归一化值(0.0~1.0)
        float y;
        long timestamp;
    }
    

3. ​远程控制注入(受控端)​

  • 依赖无障碍服务​(需用户手动开启权限):

    <service android:name=".RemoteControlService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data android:name="android.accessibilityservice" 
            android:resource="@xml/accessibility_service_config"/>
    </service>
    
  • 事件注入代码​:

    public void injectTouch(float x, float y) {
        Path path = new Path();
        path.moveTo(x, y);
        StrokeDescription stroke = new StrokeDescription(path, 0, 1); // 持续1ms的点击
        GestureDescription.Builder builder = new GestureDescription.Builder();
        builder.addStroke(stroke);
        dispatchGesture(builder.build(), null, null);
    }
    

4. ​备选方案:WebSocket传输

  • 适用场景​:需网页端查看屏幕(如远程教育)

  • 实现流程​:

    1. 主控端用MediaProjection捕获屏幕 → 编码为Base64 JPEG
    2. 通过Socket.io库发送至Node.js服务端
    3. 浏览器通过<img src="data:image/jpeg;base64, ...">实时渲染
    // Kotlin示例(ScreenCaptureService)
    socket.emit("frame", base64Jpeg)  // 发送Base64数据
    

⚠️ 三、性能优化与难点

  1. 延迟优化

    • 采用H.264硬编码替代JPEG(减少50%带宽)
    • 使用BufferedOutputStream批量发送,避免TCP小包问题
  2. 内存管理

    • 限制ImageReader队列深度(如newInstance(..., 2)表示缓冲2帧)
    • 及时调用image.close()释放底层缓冲区
  3. 权限陷阱

    • Android 10+要求屏幕捕获必须在前台服务中执行:

      startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
      

🔍 四、方案选型建议

场景推荐方案优势
局域网设备控制Socket + MediaProjection低延迟(<100ms)
跨平台屏幕共享WebSocket + 前端渲染支持浏览器观看
高安全要求环境端到端加密(如AES-GCM)防数据嗅探