Android 窗口镜像 - 投屏

1,287 阅读3分钟

Android 窗口镜像(mirror)常规的应用场景有投影、录屏等,本文介绍窗口镜像的使用方法和实现机制

mirror相关的方法有些是hide的,所以使用前需要评估当前app是否有权限

1. 投屏使用方法

官方文档:developer.android.google.cn/media/grow/…

投影接口:MediaProjection

标准用法:

  1. 通过用户授权获取一个一次性的MediaProjection
val mediaProjectionManager = getSystemService(MediaProjectionManager::class.java)
var mediaProjection : MediaProjection

val startMediaProjection = registerForActivityResult(
    StartActivityForResult()
) { result ->
    if (result.resultCode == RESULT_OK) {
        mediaProjection = mediaProjectionManager
            .getMediaProjection(result.resultCode, result.data!!)
    }
}

startMediaProjection.launch(mediaProjectionManager.createScreenCaptureIntent())
  1. 启动一个前台服务
<manifest ...>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <application ...>
        <service
            android:name=".MyMediaProjectionService"
            android:foregroundServiceType="mediaProjection"
            android:exported="false">
        </service>
    </application>
</manifest>
  1. 创建一个VirtualDisplay接收投影数据
virtualDisplay = mediaProjection.createVirtualDisplay(
                     "ScreenCapture",
                     width,
                     height,
                     screenDensity,
                     DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                     surface,
                     null, null)

其中的Surface可以来自SurfaceView(实时显示)、ImageReader(截图)、MediaRecorder(录屏)或SurfaceTexture(实时显示、编辑),根据具体使用场景决定

2. 核心实现

核心实现涉及到一个类SurfaceControlSurfaceControl 代表一个绘制图层,是 SurfaceFlingerLayer 在Java层的一种封装,其中也包含了 Layer 的各种属性和操作接口。

一般的窗口,如ActivityDialog以及通过WindowManager.addView()添加的窗口至少包含一个SurfaceControl,这样窗口才能被绘制出来。

核心方法

// android.view.SurfaceControl
public static SurfaceControl mirrorSurface(SurfaceControl mirrorOf)

投屏或者镜像的核心实现是:通过SurfaceControl中的hide方法mirrorSurface(),把数据源SurfaceControl“复制”(mirror)一份,放(reparent)到目标SurfaceControl中。

两个操作对应到SurfaceFlinger中:

  • mirror操作是建一个新的Layer,并记录mirror源 Layer 的信息,到合成时直接使用源Layer的内容
  • reparent操作是将两个Layer设置从属关系,比如位置、透明度等属性从属Layer会受到主Layer的影响和限制

3. 扩展应用

使用mirror功能,需要源/从属SurfaceControl,也需要目标/主SurfaceControl,所以需要知道怎么获取SurfaceControl

3.1 获取SurfaceControl

对于Window来说,可以通过View中的方法AttachedSurfaceControl getRootSurfaceControl()获取到接口AttachedSurfaceControl,通过其中方法buildReparentTransaction(SurfaceControl child)绑定mirror内容。这个接口中使用的是Window的根SurfaceControl

另外也可以通过在布局中添加SurfaceView,然后使用SurfaceControl getSurfaceControl()获取SurfaceView对应的SurfaceControl。这也侧面说明SurfaceView与窗口中其他部分使用了不同的Layer进行合成。

获取到SurfaceControl就可以进行后续的操作。比如可以把一个SurfaceView的内容投影到另一个SurfaceView里,投影时可以添加各种常规的转换,比如缩放、旋转、位移和透明度等。

这些转换和reparent都属于SurfaceControl.Transaction的方法,对SurfaceControl的操作都需要通过SurfaceControl.Transaction进行,系统的窗口动画很多都是通过这个类实现的。

3.2 壁纸预览

AOSP里mirror的一个使用场景是壁纸预览,有两个方式可以获取壁纸:

  • 任意壁纸:SurfaceControl IWallpaperEngine.mirrorSurfaceControl(),使用示例可以参考packages/apps/WallpaperPicker2/src/com/android/wallpaper/util/WallpaperConnection.java,简单来说是需要绑定一个WallpaperService,然后通过回调中的IWallpaperEngine获取壁纸内容的SurfaceControl
  • 当前壁纸: 通过SurfaceControl WindowManagerGlobal.getInstance().mirrorWallpaperSurface(0 /*display id*/)获取某个显示屏上的系统壁纸SurfaceControl

获取到壁纸内容SurfaceControl后,可以通过3.1中的方式把壁纸经过转换显示到窗口或者SurfaceView中。

如果只是希望用壁纸来做窗口背景,可以给窗口加上FLAG_SHOW_WALLPAPER,或者主题里添加showWallpaper。上面列出的方式适合更复杂的使用场景。

3.3 放大镜

AOSP里另一个使用场景是无障碍中的放大镜功能,其中涉及到对SurfaceControl进行转换

image.png