Android Service 跨进程启动权限

Android Service 跨进程启动权限

要实现在不同 App 之间通信和交互的功能,最推荐的方式是使用 Service。其实 BroadcastReceiver 也可以实现跨进程通信,但从安全性,携带数据量,生命周期等各方面对比来讲,Service 更优。

如果对于启动方式比价熟悉,文章的 “实践对比” 可以跳过。


ServiceBroadcastReceiver 对比

对比点ServiceBroadcastReceiver
✅ 通信方向双向(Client 与 Server 可互调)单向(广播发送后不可响应)
✅ 支持数据量大量数据(对象、Parcel)数据量有限(Intent Extras)
✅ 生命周期控制可绑定、可持续运行极短(onReceive 后立即销毁)
✅ 响应性强可实时交互延迟响应或系统延后处理
✅ 安全机制可通过权限、签名校验严格控制广播容易被滥用或劫持
✅ 适合复杂操作✔️(如文件 I/O、加密、网络)❌(系统强制 onReceive 内不可耗时)

从表格中的信息可以看出,在各方面 ServiceBroadcastReceiver 更具作为 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.

  1. [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

      系统无阻止或失败日志,启动失败了。

    打开 “关联启动” 项,上述方式都可以启动成功。

  2. [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> 标签中的定义一致。

client 请求授权

点击 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 权限:在客户端和服务端签名一致情况下,不需要权限申请,就可以进行通信,且安全性较高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚妄皆空

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值