在移动应用开发中,视频通话或播放器类应用常面临一个特殊需求:当手机锁屏时,用户仍能通过Activity显示视频画面,甚至保持交互。这一场景常见于沉浸式直播、安全监控或远程协作应用。对于Flutter开发者而言,如何让一个FlutterActivity在锁屏状态下保持可见并流畅播放视频,是一项涉及系统权限、Window管理和渲染管线的技术挑战。本文将解析主流实现路径与关键注意事项。

问题核心:锁屏安全与Activity生命周期

Android系统出于安全考虑,默认会阻止锁屏状态下任何Activity覆盖锁屏界面。当设备进入锁屏,前台Activity通常会被暂停或停止,视频渲染也会中断。若要突破这一限制,需要启用系统级窗口标志(Window Flags),包括:

  • FLAG_SHOW_WHEN_LOCKED:允许Activity显示在锁屏之上。
  • FLAG_TURN_SCREEN_ON:点亮屏幕,确保用户看到画面。
  • FLAG_KEEP_SCREEN_ON:防止休眠,维持视频播放。

但仅设置这些标志并不够,还需解决Flutter引擎在锁屏环境下的渲染挂起问题。Flutter的渲染引擎依赖于Android的SurfaceView或TextureView,锁屏时GPU资源可能被回收,导致画面黑屏。

解决方案:两步走实现锁屏播放

第一步:修改Android原生层

在Flutter工程中的MainActivity.kt(或MainActivity.java)中重写onCreate方法,添加以下代码:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
        setShowWhenLocked(true)
        setTurnScreenOn(true)
    } else {
        window.addFlags(
            WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
            WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
            WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
        )
    }
}

对于Android 8.1(API 27)以上版本,官方推荐使用setShowWhenLocked()方法,更简洁且兼容性更好。同时,需要在AndroidManifest.xml中为Activity添加权限声明(SYSTEM_ALERT_WINDOW)吗?实际上,FLAG_SHOW_WHEN_LOCKED不需要此权限,但若需覆盖其他应用(如系统UI),则需额外注册。

第二步:保持视频渲染活锁

即使Activity可见,Flutter的视频渲染组件(如video_playerfijkplayer)仍可能因Surface被销毁而黑屏。解决方案是使用两个独立的SurfaceTexture实例,并在锁屏时重新绑定。推荐采用以下策略:

  1. 使用TextureRegistry:在Flutter插件层中,当Activity状态变化时,手动触发Texture创建与销毁。例如,在onStart中重新注册Texture,在onStop中释放。
  2. 启用硬件加速锁:在AndroidManifest中添加android:hardwareAccelerated="true",确保GPU上下文持续存在。
  3. 采用SurfaceView替代TextureView:SurfaceView拥有独立的Surface,不易受Activity生命周期影响,但需处理好Surface创建时机。

此外,锁屏状态下视频渲染需注意帧率控制:系统可能降低GPU频率以省电,此时可调用window.addFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)强制高性能模式。

安全与隐私的权衡

在锁屏上显示任意内容存在隐私风险,尤其是视频通话画面可能泄露敏感信息。因此Google Play Store对此类应用有严格审核,要求必须存在明确的用户知情机制。开发者需注意:

  • 在锁屏视频界面添加解锁提示快速退出按钮,避免误操作。
  • 若应用需要锁屏显示,请在应用内弹窗征得用户同意。
  • 针对Android 12+,新增了INTERACTIVE锁屏模式,需动态申请POST_NOTIFICATIONS权限。

替代方案:系统覆盖窗口(Overlay)

部分开发者尝试使用WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY创建悬浮窗来播放视频。这种方式可以在任何界面(包括锁屏)上悬浮显示,但存在两大问题:

  1. 需要SYSTEM_ALERT_WINDOW权限,且从Android 10起该权限已被严格限制,普通应用很难获得。
  2. 悬浮窗无法接收到触摸事件(除非设置FLAG_WATCH_OUTSIDE_TOUCH),交互受限。

因此,对于需要完整FlutterActivity交互的场景(如视频通话接听界面),使用Activity窗口标志仍是唯一可靠方案。

实践总结与最佳实践

经过实测,在Android 9至Android 14设备上,采用上述方案后,FlutterActivity可在锁屏下稳定播放视频。但需注意几个关键点:

  • 测试锁屏过久:长时间锁屏(超过30分钟)后,系统可能进入深度休眠,导致Activity被销毁。此时需在onResume中重新设置窗口标志。
  • 兼容性:部分厂商(如华为、小米)的自定义锁屏可能会拦截FLAG_SHOW_WHEN_LOCKED,建议加入设备白名单或提供降级方案。
  • 性能优化:锁屏视频播放不应消耗过多电量,建议监控CPU使用率,降帧或降低画质。

最后,建议开发者使用flutter_windowmanager插件(已开源)封装原生逻辑,减少跨平台适配成本。随着Flutter生态成熟,锁屏播放场景将更加常见,掌握此技术将有助于构建更具竞争力的多媒体应用。