Android Service 跨进程启动权限
要实现在不同 App 之间通信和交互的功能,最推荐的方式是使用 Service
。其实 BroadcastReceiver
也可以实现跨进程通信,但从安全性,携带数据量,生命周期等各方面对比来讲,Service
更优。
如果对于启动方式比价熟悉,文章的 “实践对比” 可以跳过。
Service
和 BroadcastReceiver
对比
对比点 | Service | BroadcastReceiver |
---|---|---|
✅ 通信方向 | 双向(Client 与 Server 可互调) | 单向(广播发送后不可响应) |
✅ 支持数据量 | 大量数据(对象、Parcel) | 数据量有限(Intent Extras) |
✅ 生命周期控制 | 可绑定、可持续运行 | 极短(onReceive 后立即销毁) |
✅ 响应性强 | 可实时交互 | 延迟响应或系统延后处理 |
✅ 安全机制 | 可通过权限、签名校验严格控制 | 广播容易被滥用或劫持 |
✅ 适合复杂操作 | ✔️(如文件 I/O、加密、网络) | ❌(系统强制 onReceive 内不可耗时) |
从表格中的信息可以看出,在各方面 Service
比 BroadcastReceiver
更具作为 IPC 通信的优势。
总结:Service
是结构化、稳定、双向、安全的通信机制;BroadcastReceiver
是临时、轻量、单向的通知机制。
跨进程的调用涉及到 Server App 和 Client App 两个应用,且在 Android 系统上,两者的安装顺序也是有影响的。
- 先安装 Server App,后安装 Client App,其他前提条件都正确的情况下,是可以成功启动的。
- 先安装 Client App,后安装 Server App,不论是什么前提条件,都会启动失败。
这种情况的形成与 Android 系统安装 App 过程解析机制有关系。
主要内容
对比完上面两种 IPC 的方式,回到这篇文章想要记录的内容。这篇文章主要描述 Service
的启动权限,即官方文档中 <service>
标签的 android:permission="string"
属性值,结合 <permission>
权限标签定义的值,给 Service
启动带来的影响和表现。
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:foregroundServiceType=["camera" | "connectedDevice" |
"dataSync" | "health" | "location" |
"mediaPlayback" | "mediaProjection" |
"microphone" | "phoneCall" |
"remoteMessaging" | "shortService" |
"specialUse" | "systemExempted"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string"
android:stopWithTask=["true" | "false"]>
...
</service>
<permission android:description="string resource"
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |
"signature" | ...] />
先看下 <permission>
标签中主要的属性 android:protectionLevel
值 | 含义 |
---|---|
"normal" | 默认值。具有较低风险的权限,此类权限允许请求授权的应用访问隔离的应用级功能,对其他应用、系统或用户的风险非常小。系统会自动向在安装时请求授权的应用授予此类权限,无需征得用户的明确许可(但用户始终可以选择在安装之前查看这些权限)。 |
"dangerous" | 具有较高风险的权限,此类权限允许请求授权的应用访问用户私人数据或获取可对用户造成不利影响的设备控制权。由于此类权限会带来潜在风险,因此系统可能不会自动向请求授权的应用授予此类权限。例如,应用请求的任何危险权限都可能会向用户显示并且获得确认才会继续执行操作,或者系统会采取一些其他方法来避免用户自动授予使用此类功能的权限。 |
"signature" | 只有在请求授权的应用使用与声明权限的应用相同的证书进行签名时系统才会授予的权限。如果证书匹配,系统会在不通知用户或征得用户明确许可的情况下自动授予权限。 |
"knownSigner" | 只有在请求授权的应用使用允许使用的证书进行签名时系统才会授予的权限。如果请求者的证书已列出,系统会在不通知用户或征得用户明确许可的情况下自动授予权限。 |
"signatureOrSystem" | *`"signature |
下面先看下失败场景。
实践对比
先看下失败的实践。测试手机是 OPPO Reno7, Android 13.
-
[Server App 关联启动默认关闭] Server App 不定义权限,Client App 不使用启动权限。
<!-- server --> <service android:name=".RemoteService" android:enabled="true" android:exported="true" android:foregroundServiceType="dataSync|connectedDevice|location" > <intent-filter> <action android:name="com.sanren1024.action.OPEN_VAULT" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service>
-
显示调用(
ComponentName(pkgName: String, className: String)
):不论是 start 还是 bind 方式企图启动 Server App,都失败且失败信息一致。1892 3325 W OplusAppStartupManager: isAllowStartFromStartService: prevent start <server_package_name> , intent = Intent { cmp=<server_package_name>/<server_class_name> } by <client_package_name>
这表示系统层面就阻断了 Client App 启动 Server App。
-
隐式调用(
setPackage(pkgName: String) && setAction(action: String)
):startForegroundService(Intent)
返回null
。bindService(Intent, ServiceConnection, Int)
返回false
。
系统无阻止或失败日志,启动失败了。
打开 “关联启动” 项,上述方式都可以启动成功。
-
-
[Server App 关联启动默认关闭] Service App 声明权限,设置
android:permission
值。Client App 中添加<queries>
标签和<uses-permission>
标签。<!-- server --> <!-- 自定义权限 --> <permission android:name="com.sanren1024.permission.ACCESS_REMOTE" android:label="Launch Isolated" android:protectionLevel="normal" /> <service android:name="com.sanren1024.vaultservice.RemoteService" android:enabled="true" android:exported="true" android:process="com.sanren1024.vaultservice_rmt" android:foregroundServiceType="dataSync|connectedDevice|location" android:permission="com.sanren1024.permission.ACCESS_REMOTE"> <intent-filter> <action android:name="com.sanren1024.action.OPEN_REMOTE" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </service> <!-- client --> <!-- Android 12+ 之后,需要添加这个标签 --> <queries> <package android:name="com.sanren1024.vaultservice" /> </queries> <!-- Android 11+ 必须添加包可见性声明 --> <uses-permission android:name="com.sanren1024.permission.ACCESS_REMOTE" />
-
显示调用(
ComponentName(pkgName: String, className: String)
):-
bind 方式 / start 方式:Client App 端没有声明
<uses-permission>
使用 Server App 的自定义权限。1892 9151 W ActivityManager: Permission Denial: Accessing service <server_package_name>/<server_class_name> from pid=9701, uid=10076 requires com.sanren1024.permission.ACCESS_REMOTE 9701 9701 E AndroidRuntime: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=<server_package_name>/<server_class_name> } 1892 3633 D OplusAppStartupMonitor: notifyUnstableAppInfo: Bundle[{unstableTime=1750841074449, reason=crash, userId=0, exceptionMsg=Not allowed to bind to service Intent { cmp=<server_package_name>/<server_class_name> }, exceptionClass=java.lang.SecurityException, app_channel_type=unstable, packageName=<client_package_name>, unstable_restrict_switch=true}] 1892 2527 I ActivityManager: Showing crash dialog for package <client_package_name> u0 1892 6976 I ActivityManager: Process <client_package_name> (pid 9701) has died: fg TOP
Client App 发生 crash 崩溃,启动失败,提示没有权限。
-
bind 方式:Client App 声明
<uses-permission>
,使用 Server App 的自定义权限。系统 log 没有错误信息,启动失败。
-
start 方式:Client App 端有声明
<uses-permission>
使用 Server App 的自定义权限。1892 9521 W OplusAppStartupManager: isAllowStartFromStartService: prevent start <server_package_name> , intent = Intent { cmp=<server_package_name>/<server_class_name> } by <client_package_name>
启动失败。
-
-
隐式调用(
setPackage(pkgName: String) && setAction(action: String)
):-
bind 方式 / start 方式:Client App 端没有声明
<uses-permission>
使用 Server App 的自定义权限。1892 8462 W ActivityManager: Permission Denial: Accessing service <server_package_name>/<server_class_name> from pid=4621, uid=10076 requires com.sanren1024.permission.ACCESS_REMOTE 1892 6976 I ActivityManager: Process <client_package_name> (pid 4621) has died: fg TOP
Client App 发生 crash 崩溃,启动失败,提示没有权限。
-
bind 方式:Client App 端有声明
<uses-permission>
使用 Server App 的自定义权限。bindService(Intent, ServiceConnection, Int)
返回false
,启动失败。系统 log 无阻止或错误信息。 -
start 方式:Client App 端有声明
<uses-permission>
使用 Server App 的自定义权限。startForegroundService(Intent)
返回null
,启动失败。系统 log 无阻止或错误信息。
-
打开 “关联启动” 项,上述方式都可以启动成功。
-
从上面的失败场景,可以分析得到:在 Server App 端,如果不声明 permission,或者声明 normal 级别的 permission,Client App 在正确配置后,可以正常启动 Server App(前提: 打开 “关联启动”)。
但是从安全角度来讲,这个 normal 的权限是不够的。
dangerous 和 signature 权限
按上面的描述,dangerous 权限在 Client App 声明后,还需要主动申请授权,获取授权后才能继续访问或调用 Server App 的功能。
下面的测试过程中,如果是国产机,都会提前打开 “关联启动”,即后续的 Service 启动,不会受到这个开关项影响。
dangerous
将文章前面 manifest 中 <permission>
标签的 android:protectionLevel
修改为 dangerous
<permission
android:name="com.sanren1024.permission.ACCESS_REMOTE"
android:label="Launch Isolated"
android:protectionLevel="dangerous" />
不修改 Client App 的代码。
bindService 会返回 false
,即启动失败。这表明在 Client App 中需要作申请权限的操作,与申请系统 dangerous 权限一样处理,运行时申请 Server App 的权限。实际运行结果如下图,弹窗中的信息与 Server App 的 <permission>
标签中的定义一致。
点击 ALLOW 按钮之后,其他的操作与之前的操作一样。
Client App 和 Server App 签名即使不一致,通过授权,也可以通信。
signature
将 <permission>
标签的 android:protectionLevel
值改为 signature
.
-
Server App 和 Client App 使用不同签名文件。Client App 会 crash,log 提示
java.lang.SecurityException
错误,提示权限拒绝。 -
Server App 和 Client App 使用同一签名文件。Client App 可以静默启动 Server App,无需用户显示申请授权,这与
dangerous
权限形成了对比。
到此,对于 Service 启动权限的最常用的 3 个 protectionLevel
值作了实践测试。
- normal 权限:在客户端不需要申请的情况下,可以任意使用,安全性差。
- dangerous 权限:需要用户运行时申请权限,安全性比 normal 高,但需要多一步权限申请动作,体验稍差。
- signature 权限:在客户端和服务端签名一致情况下,不需要权限申请,就可以进行通信,且安全性较高。