1. 这个权限到底是什么?为什么普通App拿不到?

如果你在Android开发文档里翻找权限列表,看到 android.permission.INTERACT_ACROSS_USERS_FULL 这个名字,第一感觉可能就是“好长,好高级”。没错,这个权限确实是Android权限体系里的“顶级玩家”之一,普通应用开发者基本接触不到,但它却是构建多用户Android系统生态的基石。

简单来说,这个权限赋予了一个应用跨越不同用户空间进行交互的能力。你可以把它想象成一栋高级公寓楼,每个住户(用户)都有自己的独立套房(用户空间),门禁森严,互不干扰。而拥有这个权限的应用,就像是拥有万能钥匙的物业管理员,可以进入任何一个住户的房间,帮忙处理事情,甚至启动住户家里的电器(应用组件)。

具体能做什么呢?官方描述是“允许应用程序在所有用户之间进行交互”,这包括但不限于:在其他用户的上下文中启动Activity(界面)、发送和接收广播、绑定其他用户空间里的服务、访问其他用户的数据(在拥有相应数据权限的前提下)。这听起来是不是有点像“超级权限”?确实如此,所以Android系统对它管控极其严格。

为什么普通App申请不到?这是出于最核心的安全和隐私考虑。想象一下,如果一个普通的社交App能随意进入你手机上的“访客”用户空间或者“孩子”的用户空间,读取那里的信息,甚至启动银行App,那将是一场隐私灾难。因此,这个权限被标记为 signature|privileged|development 级别。这是什么意思?

  • signature:只有和系统使用相同签名密钥的应用才能获得。这通常意味着只有设备制造商(OEM)预装的应用,或者深度集成到ROM里的系统应用才行。
  • privileged:应用必须被放置在系统的特权目录(如 /system/priv-app)下。这同样是系统应用的标志。
  • development:在调试和开发阶段,可以通过ADB等特殊方式授予,方便系统开发者测试。

所以,如果你在开发一个要上架应用商店的普通App,基本上可以不用考虑这个权限了。但如果你是系统定制团队的一员,或者在开发需要深度集成到特定设备(如企业级设备、教育平板、定制化终端)的系统级应用,那理解这个权限就至关重要了。它能帮你实现一些非常强大的跨用户功能。

2. 哪些场景下非用这个权限不可?

既然这个权限门槛这么高,那到底在什么情况下才真的需要用它呢?我结合自己过去在系统定制项目里的经验,给大家分享几个最典型的应用场景。你会发现,这些场景都围绕着“系统级管理”和“跨用户协同”这两个核心。

场景一:设备管理器和多用户管理应用 这是最核心的应用场景。比如,你公司为员工定制了一批平板,上面有“公司空间”和“个人空间”两个用户。作为设备管理员,你需要在“公司空间”里安装一个管理应用,这个应用可能需要:

  • 静默安装/卸载应用到“个人空间”。比如统一部署安全软件。
  • 监控和管控“个人空间”的某些行为(符合企业政策的前提下)。
  • 在“公司空间”一键清理所有用户的缓存数据。
  • 实现跨用户的文件投送功能,比如从“公司空间”发送一个文档到“个人空间”的指定文件夹。

这些操作都需要跨越用户边界,INTERACT_ACROSS_USERS_FULL 权限就是实现这些功能的钥匙。

场景二:系统设置和全局配置 真正的“系统设置”应用是需要这个权限的。举个例子,当你在主用户(比如机主)的“设置”里修改了系统语言、日期时间或者网络APN配置时,这个变更应该立即生效于所有用户。设置应用就需要有能力去通知和修改其他用户空间下的系统配置。再比如,在“设置”中查看所有用户的存储空间使用情况,也需要跨用户访问数据统计信息。

场景三:电话、短信等核心系统服务 在支持多用户的平板或手机上,虽然可能只有一个用户能直接插SIM卡,但系统级的电话和短信服务需要处理所有用户的来电、短信通知。例如,当有来电时,系统需要有能力在所有活跃用户的界面上弹出接听界面(或至少通知)。这个跨用户的广播发送和Activity启动,就依赖于相关服务持有此权限。

场景四:跨用户的剪贴板共享或屏幕投射 一些高级功能,比如允许在“工作资料”和“个人资料”之间安全地共享剪贴板内容,或者将某个用户屏幕上的内容投射到所有用户都能看到的公共区域(例如会议室投屏),其底层实现也需要跨用户交互的权限。

场景五:系统调试和日志收集工具 开发阶段,工程师使用的系统调试工具需要能抓取所有用户进程的日志、分析所有用户的应用性能。这类工具通常作为系统应用存在,并申请此权限以完成全面的数据收集。

简单来说,所有需要以“上帝视角”管理和协调设备上多个独立用户空间的应用,都是这个权限的潜在使用者。它的存在,让Android的多用户功能从一个简单的“空间隔离”工具,变成了一个可被系统级应用灵活管理和协同的生态。

3. 代码层面:如何声明和使用它?

理论说了这么多,咱们来点实际的。虽然你可能用不上,但看看“顶级权限”在代码里长什么样、怎么用,对理解Android系统机制也大有好处。

首先,声明权限。和在Manifest里声明其他权限一样,但位置有讲究。由于这是系统级权限,你的应用通常需要是系统应用。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.systemapp">

    <!-- 声明使用跨用户全交互权限 -->
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />

    <!-- 通常还会声明相关的跨用户权限,形成组合拳 -->
    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
    <uses-permission android:name="android.permission.MANAGE_USERS" />
    ...

    <application ...>
        ...
    </application>
</manifest>

光声明还不够,你的APK必须被签名并安装到 /system/priv-app 目录下,系统在启动时才会将权限授予它。

接下来,我们看看几个核心的使用代码片段。跨用户操作的核心是 UserHandle 和 Context。

1. 获取用户列表和切换用户上下文 在进行任何操作前,你通常需要知道有哪些用户。

// 获取UserManager服务
UserManager userManager = (UserManager) getSystemService(Context.USER_SERVICE);
// 获取所有活跃的用户(不包括已删除的)
List<UserHandle> userHandles = userManager.getUserProfiles();

for (UserHandle userHandle : userHandles) {
    int userId = userHandle.getIdentifier();
    Log.d(TAG, "发现用户ID: " + userId);
    // 接下来可以针对这个userId进行操作
}

要为一个操作指定目标用户,你需要为目标用户创建一个 Context。

try {
    // 假设 targetUserId 是你要交互的目标用户ID
    Context targetUserContext = createContextAsUser(UserHandle.of(targetUserId), 0);
    // 现在,通过 targetUserContext 启动的Activity、发送的广播,都会在目标用户空间内执行
    Intent intent = new Intent(targetUserContext, TargetActivity.class);
    targetUserContext.startActivity(intent);
} catch (PackageManager.NameNotFoundException e) {
    // 处理异常,例如目标用户可能不存在
    Log.e(TAG, "无法创建目标用户上下文", e);
}

2. 跨用户启动Activity 这是非常常见的操作。注意,直接使用 startActivity(intent) 只能启动当前用户的应用。跨用户启动需要携带 UserHandle 信息。

// 方法一:使用Context.createContextAsUser(如上例)
// 方法二:为Intent显式设置用户
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.target.app", "com.target.app.MainActivity"));
// 关键:设置Activity启动的目标用户
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 使用ActivityManager的startActivityAsUser方法(需要相应权限)
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
am.startActivityAsUser(intent, UserHandle.of(targetUserId));

3. 跨用户发送广播 广播默认只在当前用户内传播。要让广播穿越用户边界,需要在发送时指定用户。

Intent broadcastIntent = new Intent("com.example.action.CROSS_USER_EVENT");
broadcastIntent.putExtra("data", "来自主用户的消息");
// 关键:使用sendBroadcastAsUser方法
sendBroadcastAsUser(broadcastIntent, UserHandle.ALL); // 发送给所有用户
// 或者发送给特定用户
sendBroadcastAsUser(broadcastIntent, UserHandle.of(targetUserId));

接收方应用如果也需要接收其他用户的广播,可能需要在清单文件里为 receiver 配置 android:directBootAware="true" 或处理多用户场景,但这又是另一个话题了。

4. 跨用户绑定服务 绑定其他用户空间的服务稍微复杂一点,因为涉及到进程间通信(IPC)的隔离。

Intent serviceIntent = new Intent();
serviceIntent.setComponent(new ComponentName("com.target.app", "com.target.app.RemoteService"));
// 尝试绑定到目标用户的服务
boolean bindSuccess = bindServiceAsUser(serviceIntent, serviceConnection,
        Context.BIND_AUTO_CREATE, UserHandle.of(targetUserId));
if (!bindSuccess) {
    Log.w(TAG, "绑定到目标用户服务失败");
}

这里踩过一个坑:你绑定的服务(com.target.app.RemoteService)必须在其 AndroidManifest.xml 中声明 android:exported="true",否则即使有跨用户权限,也会绑定失败,因为服务不允许被外部(包括其他用户)绑定。

4. 深入原理:权限背后的多用户隔离机制

要真正用好 INTERACT_ACROSS_USERS_FULL,不能只停留在API调用层面,还得稍微了解一下Android多用户隔离是怎么实现的。理解了“墙”是怎么砌的,你才知道“门”该开在哪里。

Android的多用户隔离,核心是 Linux内核层面的用户ID(UID)隔离 和 Android框架层的进程/数据沙盒。每一个Android用户(比如用户0,用户10,用户11)在Linux系统层面都有一个独立的UID范围。应用安装到不同用户下时,会被分配属于该用户UID范围的唯一UID。内核通过UID来严格管控文件访问、进程信号等资源。

在Android框架层,ActivityManagerService、PackageManagerService 等核心服务是知道所有用户信息的“总调度”。当一个普通应用调用 startActivity() 时,AMS会检查这个调用者的UID属于哪个用户,然后默认只在该用户的空间内寻找和启动目标Activity。这就是天然的隔离。

而 INTERACT_ACROSS_USERS_FULL 权限,本质上是在AMS进行这些检查时的一个“通行证”。当AMS看到发起调用的进程拥有这个权限(具体是检查调用者进程的 permission 列表中是否有它),它就会允许后续的跨用户UID检查通过,允许操作指向其他用户的组件。

这个过程可以粗略概括为:

  1. 权限检查:AMS收到一个意图(Intent),例如启动Activity。
  2. 用户归属判断:AMS解析Intent,发现目标组件(Activity)属于用户A,而调用者进程属于用户B。
  3. 触发跨用户检查:因为用户B != 用户A,AMS启动跨用户交互的检查流程。
  4. 验证“通行证”:检查调用者进程是否拥有 INTERACT_ACROSS_USERS_FULL 或部分功能的 INTERACT_ACROSS_USERS 权限。
  5. 决策:如果有权限,则允许操作继续,AMS会创建或切换到用户A的上下文去启动Activity;如果没有权限,直接抛出 SecurityException。

所以,这个权限不是一个“主动技能”,而是一个“被动光环”。它不让你直接调用某个特殊方法,而是让你调用常规方法(如 startActivity)时,系统允许你打破默认的用户边界限制。

这里还有一个重要的兄弟权限:android.permission.INTERACT_ACROSS_USERS。它俩名字很像,区别在于 FULL 版本权限更高,允许完全的交互,包括启动Activity、发送广播等。而非 FULL 版本可能只允许部分受限的交互,比如某些系统内部使用的跨用户通信。在开发时,如果你不确定,申请 FULL 版本并确保你的应用是系统特权应用,是最稳妥的。

5. 实战避坑指南与安全考量

就算你拿到了这个“万能钥匙”,在实际项目里乱用,也分分钟掉坑里,甚至引发系统稳定性问题。我总结了几条血泪教训,希望能帮你避开。

第一大坑:忽略用户状态 不是所有用户都是可交互的。用户可能有以下几种状态:STATE_BOOTING(启动中)、STATE_RUNNING(运行中)、STATE_STOPPING(停止中)、STATE_SHUTDOWN(已关闭)。如果你尝试向一个 STATE_STOPPING 或 STATE_SHUTDOWN 的用户发送广播或启动Activity,操作会失败。

UserManager um = ...;
UserInfo userInfo = um.getUserInfo(targetUserId);
if (userInfo != null && userInfo.isEnabled()) {
    // 还需要进一步检查用户状态,可以通过UserManager的其他方法或监听用户状态广播
    // 再进行跨用户操作会更安全
}

最佳实践是,在关键操作前检查目标用户是否处于活跃状态,或者监听 Intent.ACTION_USER_BACKGROUND、Intent.ACTION_USER_FOREGROUND 等广播来感知用户状态变化。

第二大坑:上下文(Context)滥用 通过 createContextAsUser 创建的目标用户Context,其生命周期和资源访问是受限的。不要长期持有这个Context引用,尤其不要在Activity等组件的生命周期回调之外持有。因为目标用户可能被删除,导致Context失效,进而引发内存泄漏或异常。应该遵循“按需创建,用完即弃”的原则。

第三大坑:性能与资源消耗 跨用户操作是相对昂贵的。它涉及系统服务(如AMS)的额外调度,可能涉及进程的唤醒(如果目标用户的应用进程还没启动)。频繁地、无节制地进行跨用户操作(比如每秒轮询所有用户的应用列表),会显著增加系统负载,耗电增加。在设计功能时,要考虑操作的频率,尽量采用事件驱动(如监听广播)而非轮询的方式。

第四大坑,也是最关键的:安全与隐私 这是双刃剑的另一面。你的应用拥有了巨大能力,也意味着巨大的责任。在设计任何跨用户功能时,必须反复问自己:

  • 这个操作真的需要跨用户吗? 有没有更安全、更轻量的实现方式?(比如通过一个所有用户都能访问的公共ContentProvider?)
  • 我访问的数据是最小必要的吗? 不要因为能访问,就把其他用户的所有数据都扫一遍。严格遵守最小权限原则。
  • 用户知情吗? 对于企业设备管理场景,可能通过管理策略告知。但对于可能涉及个人用户数据的操作,必须有清晰透明的隐私政策。
  • 代码是否健壮? 你的代码必须能妥善处理权限被拒绝、目标用户不存在、目标组件不存在等各种异常情况,避免因为你的应用崩溃导致系统服务出现不可预知的问题。

一个具体的建议:在实现跨用户文件传输时,不要直接去读写其他用户的私有数据目录(/data/user/<userId>/),这即使有权限也可能引发问题。更好的方式是,让目标用户的应用暴露一个安全的ContentProvider接口,或者使用系统预定义的共享目录(如公共媒体目录),通过标准的FileProvider机制进行安全共享。

最后,测试至关重要。多用户场景的测试非常复杂,你需要在真机或支持多用户的模拟器上,反复测试以下场景:主用户到副用户、副用户到主用户、用户切换前后、用户删除后、系统重启后等。只有经过充分测试,才能确保你的系统级应用稳定可靠。

更多推荐