1. 从零开始:理解MediaProjection API与屏幕共享
如果你想让一台Android设备把屏幕内容实时分享给另一台设备,比如老师给学生演示操作,或者远程帮朋友调试手机,那MediaProjection API就是你绕不开的核心技术。我刚开始接触这个功能时,也觉得它挺神秘的,但实际用下来发现,它其实是一套设计得相当巧妙的系统服务。
简单来说,MediaProjection API就像是系统给你开的一个“后门”,允许你的应用在获得用户明确授权后,合法地捕获当前屏幕(或单个应用窗口)上显示的一切内容。这个“捕获”的动作,并不是真的去截一张静态图片,而是创建一个持续的视频流。系统会把屏幕内容“投影”到一个由你的应用提供的虚拟显示区域(我们称之为VirtualDisplay)上,而这个虚拟显示区域连接着一个Surface。你可以把这个Surface理解成一块画布,系统负责在这块画布上实时作画(绘制屏幕内容),而你的应用则作为观众,可以从这块画布上不断地读取最新的画面数据。
这里有一个非常重要的概念升级,尤其是在Android 14及更高版本中:应用屏幕共享。以前,MediaProjection一启动,就是捕获整个设备屏幕,包括状态栏、导航栏和所有通知,隐私性比较差。现在,系统允许用户(以及开发者引导用户)选择只共享某一个特定应用的窗口。比如,你只想共享一个演示文稿,而不想暴露你的微信聊天记录。这个功能对于在线教育、远程协作场景来说,简直是福音,既能展示必要内容,又最大程度保护了用户隐私。你的应用在调用标准API时,会自动获得这个能力,系统会弹出新的选择界面让用户决定是共享整个屏幕还是单个应用。
那么,这套流程能用来做什么呢?远不止简单的“投屏”显示。你可以把捕获到的原始画面数据,通过MediaCodec进行硬件编码,转换成H.264或HEVC视频流,然后通过Socket、WebRTC或者RTMP协议发送出去,实现真正的低延迟屏幕共享。更进一步,结合接收端发回的触控指令,你就能实现远程控制,就像一些远程协助软件那样。所以,它的潜力非常大。
2. 实战第一步:权限申请与MediaProjection初始化
理论说再多,不如动手写代码。我们一步步来构建一个屏幕共享的发送端。首先,你需要一个Android项目,我习惯用Kotlin和最新的Activity Result API来写,这样代码更清晰。
2.1 配置依赖与权限
在app/build.gradle文件中,添加必要的依赖。我们这里为了简单演示,直接处理图像,所以用上了AndroidX的库。如果你后续要做编码,可能还需要引入media3-exoplayer或类似的编码库。
dependencies {
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.appcompat:appcompat:1.6.1")
// 用于生命周期管理
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
// 协程,用于处理网络等异步操作
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
}
权限是重中之重,必须在AndroidManifest.xml里声明清楚。除了网络权限,最关键的是前台服务权限。从Android 10(API 29)开始,特别是targetSdkVersion >= 29后,屏幕捕获必须在一个具有特定类型的前台服务中运行,否则系统会直接抛出SecurityException。
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 前台服务通用权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- 屏幕捕获专用的前台服务类型权限 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<application ...>
<!-- 定义一个前台服务,类型指定为mediaProjection -->
<service
android:name=".ScreenCaptureForegroundService"
android:foregroundServiceType="mediaProjection"
android:exported="false" />
</application>
2.2 申请用户授权并获取MediaProjection实例
接下来,在你的Activity(比如SenderActivity)中,你需要启动系统提供的屏幕捕获权限申请界面。这里我强烈推荐使用registerForActivityResult来替代旧的startActivityForResult,它是类型安全且更易于管理的。
class SenderActivity : AppCompatActivity() {
private lateinit var mediaProjectionManager: MediaProjectionManager
private var mediaProjection: MediaProjection? = null
private val screenCaptureLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
if (result.resultCode == RESULT_OK && result.data != null) {
// 用户已授权,获取MediaProjection令牌
mediaProjection = mediaProjectionManager.getMediaProjection(result.resultCode, result.data!!)
// 重要:在获得MediaProjection后,立即启动前台服务
startForegroundService()
// 然后开始创建虚拟显示和捕获逻辑
setupVirtualDisplayAndStartCapture()
} else {
// 用户拒绝授权
Toast.makeText(this, "屏幕共享需要此权限", Toast.LENGTH_SHORT).show()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.

2434

被折叠的 条评论
为什么被折叠?



