龙空技术网

你真的了解Android权限机制吗?

码仔 367

前言:

如今咱们对“安卓打开权限请求弹出框”大体比较关心,各位老铁们都想要了解一些“安卓打开权限请求弹出框”的相关内容。那么小编同时在网上收集了一些有关“安卓打开权限请求弹出框””的相关资讯,希望小伙伴们能喜欢,姐妹们快快来了解一下吧!

码个蛋(codeegg)第 610 次推文

作者:FeelsChaotic

原文:

前言

Android将安全设计贯穿系统架构的各个层面,覆盖系统内核、虚拟机、应用程序框架层以及应用层各个环节,力求在开放的同时,也最大程度地保护用户的数据、应用程序和设备的安全。Android安全模型主要提供以下几种安全机制:

从技术架构角度来看,Android安全模型基于Linux操作系统内核安全性,有基本的用户和文件访问隔离,在这个基础上辅以内存管理技术和进程间通信机制,来适应移动端处理器性能与内存容量的限制。在应用层面,使用显式定义且经用户授权的权限控制机制,系统化地规范并强制各类应用程序的行为准则与权限许可,可以看到权限机制在安全模型中所处的位置。

那么为什么有权限机制?

我们知道 Android 应用程序是沙箱隔离的,每个应用都有一个只有自己具有读写权限的专用数据目录,应用只能访问自己的文件和一些设备上全局可访问的资源。

那如果我需要访问系统服务呢?这就有了 Android 的权限机制。所以根本原因一是沙箱隔离,二是服务支持的需求。

在本文开始之前,先抛出几个问题思考,什么是权限?权限是怎么进行赋予的?怎么判断一个组件是否拥有特定的权限?权限维护在哪?Android 6.0 运行时权限是什么原理?权限赋予后还能再更改吗?

带着问题和结构图,我们一一解开疑惑。

权限的本质

什么是权限?在 Android 中,一个权限,本质上是一个字符串,一个可以表示执行特定操作的能力的字符串。比如说:访问 SD 卡的能力,访问通讯录的能力,启动或访问一个第三方应用中的组件的能力。

使用 pm list permissions -f 命令可以详细查看 Android 所有预定义的权限。我们挑一个权限出来:

+ permission:android.permission.DELETE_PACKAGES

package:android

label:

description:

protectionLevel:signature|privileged

可以看到,一个权限的信息包括:定义的包名、标签、描述和保护级别,保护级别,嗯,这个我们需要详细讲讲。因为不是应用声明了权限,就一定会全部被自动赋予的,保护级别决定了包管理器是否应该赋予组件所申请的权限。

权限的级别

normal 级别。权限保护级别的默认值,无须用户确认,只要声明了,就自动默默授权。如:ACCESS_NETWORK_STATE。

dangerous 级别。赋予权限前,会弹出对话框,显式请求权限。如:READ_SMS。因为 Android 需要在安装时赋予权限,所以安装的确认对话框,也会显示列出权限清单。

signature 级别。signature 级别的权限是最严格的权限,只会赋予与声明权限使用相同证书的应用程序。

以系统内置 signature 级别权限为例,Android 系统应用的签名由平台密钥签发,默认情况下源码树里有 4 个不同的密钥文件:platform、shared、media 和 testkey。所有核心平台的包(如:设置、电话、蓝牙)均使用 platform 密钥签发;搜索和通讯录相关的包使用 shared 签发;图库和媒体相关的包使用 media 密钥签发;其他的应用使用 testkey 签发。定义系统内置权限的 framework-res.apk 文件是使用平台密钥签发的,因此任何试图请求 signature 级别内置权限的应用程序,需要使用与框架资源包相同的密钥进行签名。

signatureOrSystem 级别。可以看做是一种折中的级别,可被赋予与声明权限具有相同签名证书密钥的应用程序(同 signature 级别)或者系统镜像的部分应用,也就是说这允许厂商无须共享签名密钥。Android 4.3 之前,安装在 system 分区下的应用会被自动赋予该保护级别的权限,而 Android 4.4 之后,只允许安装在 system/priv-app/ 目录下的应用才能被主动赋予。

权限的管理

那么,系统内置权限,自定义权限,是怎么维护和管理的?

在每个应用安装时,权限就已经赋予了,系统使用包管理服务来管理权限。打开我们系统目录下的 /data/system/packages.xml,可以看到文件包含了所有已定义的权限列表和所有 apk 的包信息,这可以看做是包管理服务维护的一个已安装程序的核心数据库,这个数据库,随着每次应用安装、升级或卸载而进行更新。

主要属性如图:

<permissions> 标签内,定义了目前系统中的所有权限,分为系统内置的(package 属性为 android 的)和 apk 自定义的(package 属性为 apk 的包名)。根元素 <package> 含有每个 apk 的核心属性,以一个应用程序的条目为例:

Android 6.0 以下 packages.xml

<package 

name="com.feelschaotic.demo"

codePath="/data/app/com.feelschaotic.demo-1"

nativeLibraryPath="/data/app/com.feelschaotic.demo-1/lib"

primaryCpuAbi="x86"

flags="5783366"

ft="16349bcc4d0"

it="16349bcc752"

ut="16349bcc752"

version="8220"

userId="10097">

<sigs count="1">

<cert index="7" />

</sigs>

<perms>

<item name="android.permission.READ_SMS" />

<item name="android.permission.ACCESS_FINE_LOCATION" />

<item name="android.permission.CHANGE_NETWORK_STATE" />

<item name="android.permission.INTERNET" />

<item name="android.permission.READ_EXTERNAL_STORAGE" />

<item name="android.permission.ACCESS_COARSE_LOCATION" />

<item name="android.permission.READ_PHONE_STATE" />

<item name="android.permission.CALL_PHONE" />

<item name="android.permission.CHANGE_WIFI_STATE" />

<item name="android.permission.ACCESS_NETWORK_STATE" />

<item name="android.permission.CAMERA" />

<item name="android.permission.WRITE_EXTERNAL_STORAGE" />

<item name="android.permission.READ_CONTACTS" />

</perms>

<proper-signing-keyset identifier="1686" />

<signing-keyset identifier="1686" />

</package>

Android 6.0 及以上 packages.xml

<package 

name="com.feelschaotic.demo"

codePath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ=="

nativeLibraryPath="/data/app/com.feelschaotic.demo-Gi5ksdF6mUDLakfOugCcwQ==/lib"

primaryCpuAbi="x86"

publicFlags="945307462"

privateFlags="0"

ft="16348dc3870"

it="16343f1d6aa"

ut="16348dc4c4d"

version="8220"

userId="10102">

<sigs count="1">

<cert index="20" key="..." />

</sigs>

<perms>

<!-- 此处普通权限的 granted 全都默认是 true,且不可改变 granted 值-->

<item name="android.permission.CHANGE_NETWORK_STATE" granted="true" flags="0" />

<item name="android.permission.INTERNET" granted="true" flags="0" />

<item name="android.permission.CHANGE_WIFI_STATE" granted="true" flags="0" />

<item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />

</perms>

<proper-signing-keyset identifier="48" />

</package>

可以发现,其他信息没有太大差异,但是权限列表中,部分在 Android 6.0 被标记为高危的权限都不在 <perms> 里了,如:READ_SMS、READ_EXTERNAL_STORAGE、CALL_PHONE 等。这是怎么一回事呢?

Android 6.0 之前,权限都是在安装时自动赋予的,不卸载应用的情况下,不能更改或撤销(实际上有些厂商打开了appOps,使得 6.0 以下也能更改权限,这种特殊情况我们不讨论)。而 Android 6.0 版本对 permission 的管理做了部分改动,针对 dangerous 级别,不再安装的时候赋予权限,而是在运行时动态申请。

我们大胆地推断下,packages.xml 里保留的是不会再变更的权限,运行时权限一定是另外单独地维护。我们在 data/system 目录下来了个全盘搜索,找到了 /data/system/users/0/runtime-permissions.xml。

<pkg name="com.feelschaotic.demo">

<!-- 该demo我们故意拒绝了定位权限,可以看到:ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 的 granted 为 false -->

<item name="android.permission.ACCESS_FINE_LOCATION" granted="false" flags="1" />

<item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />

<item name="android.permission.ACCESS_COARSE_LOCATION" granted="false" flags="1" />

<item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />

<item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />

...

</pkg>

没错了,就是这个文件。里面就记录着运行时权限的授予和拒绝状态。申请时,申请的结果会动态修改 granted 值。管理权限的仍然是 PMS,只不过 Android 6.0 之后新增了 runtime-permissions.xml 数据库。

权限的赋予

我们知道,Android 应用安装时,会被分配一个唯一的 UID,应用启动时,包管理器会设置新建进程的 UID 和 GID 为应用程序的 UID。如果应用已经被赋予了额外的权限,就把这些权限映射成一组 GID,作为补充 GID 分配给进程。低层就可以依赖于进程的 UID、GID 和补充 GID 来决定是否赋予权限了。

那么,上面流程的重点:

权限是如何映射到 OS 层的 UID、GID 上的呢?

映射完是怎么分配给进程的?

低层是怎么判断是否赋予权限的?

我们一个个来看,首先第一个,权限是如何映射的?内置权限到 GID 的映射是定义在 /etc/permission/platform.xml 中。

<permissions>

···

<permission name="android.permission.READ_EXTERNAL_STORAGE" >

<group gid="sdcard_r" />

</permission>

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" >

<group gid="sdcard_r" />

<group gid="sdcard_rw" />

</permission>

<permission name="android.permission.INTERNET" >

<group gid="inet" />

</permission>

···

</permissions>

值得注意的是:READ_EXTERNAL_STORAGE 这种运行时权限,在 Android 6.0 之后已经不会映射到 gid 了。动态赋予,动态申请,也就不需要映射了。

<!-- These are permissions that were mapped to gids but we need

to keep them here until an upgrade from L to the current

version is to be supported. These permissions are built-in

and in L were not stored in packages.xml as a result if they

are not defined here while parsing packages.xml we would

ignore these permissions being granted to apps and not

propagate the granted state. From N we are storing the

built-in permissions in packages.xml as the saved storage

is negligible (one tag with the permission) compared to

the fragility as one can remove a built-in permission which

no longer needs to be mapped to gids and break grant propagation. -->

<permission name="android.permission.READ_EXTERNAL_STORAGE" />

<permission name="android.permission.WRITE_EXTERNAL_STORAGE" />

包管理器在启动时读取 platform.xml,并维护「权限-GID」对应的列表。当它给安装中的包授权时,会把权限对应的 GID 加入到该应用进程的补充 GID 中。我们举个例子,看下进程的属性:adb shell ps 拿到想要查找的进程的 PID。

USER PID PPID VSZ RSS WCHAN ADDR S NAME

...

u0_a88 2149 1624 1929916 112340 SyS_epoll_wait 0 S com.feelschaotic.demo

接着 adb shell -> cd proc -> cd 2149 (2149 为进程 PID,不固定) -> cat status

我们关注如下两行:

Gid: 10050 10050 10050 10050Groups: 1006 1015 1028 3002 3003 50050

这里我们便看到了系统进程的权限配置信息,这里的数字具体代表意义,可以在Android\system\core\include\private\android_filesystem_config.h 里面看到,其部分内容如下:

#define AID_CAMERA 1006 /* camera devices */ 

#define AID_SDCARD_RW 1015 /* external storage write access */

#define AID_SDCARD_R 1028 /* external storage read access */

···

static const struct android_id_info android_ids = {

{ "camera", AID_CAMERA, },

{ "sdcard_r", AID_SDCARD_R, },

{ "sdcard_rw", AID_SDCARD_RW, },

···

};

有没有发现什么?前文所述 sdcard_r 和 android.permission.READ_EXTERNAL_STORAGE 的映射已经定义在 /etc/permission/platform.xml 中了,此处 sdcard_r 映射 AID_SDCARD_R 对应 gid = 1015,这就是权限映射到 gid 的整个关系。总共分为三层:

那么第二个问题,当我们安装应用完成,启动应用,应用的进程是如何启动并被赋予进程属性的呢?

每个应用都会运行在自己的 Dalvik 虚拟机进程中,但是为了提高启动效率,Android 不会为每个应用都新建一个 Dalvik 进程,而是采用 fork 的形式。每个进程都 fork form zygote 进程。那么 fork 比起 new ,效率提高在哪里?

因为 zygote 进程已经预加载了大部分核心和 Java 应用框架库,fork 的子进程会继承 zygote 的进程空间,也就是说,fork 的子进程,可以共享这些预加载的副本(记住,是副本,不是直接共享。fork 时,会 copy-on-write 预加载的内容),减少了重新加载核心库的时间。

当 zygote 收到启动新进程的请求时,它会 fork 自身出一个子进程,并对该子进程做特殊化处理。其源代码位于 dalvik/vm/native/dalvik_system_Zygote.c 中。forkAndSpecializeCommon 的主要代码如下:

static pid_t forkAndSpecializeCommon(const u4* args, boolisSystemServer) 

{

...

pid = fork; //创建新进程

if (pid == 0) //判断是否是root,有没有权限修改自己的进程属性

{

setgroupsIntarray(gids); //设置进程的所有组

setrlimitsFromArray(rlimits);

setgid(gid); //设置进程的组ID

setuid(uid); //设置进程的用户ID

}

...

}

如上所示:这里设置进程的组 ID 和用户 ID,通过 fork 创建的子进程调用 setgroups Intarray 设置该进程所属的组,这样应用程序就拥有了该组的权限,并且可以通过 setgid 及 setuid 确定应用程序的 GID 及 UID 值。刚刚开始 fork 时,子进程是以 root 执行的,所以它可以更改自己的进程属性,当属性都设置完成,子进程就以分配的 GID 和 UID 执行,此时,子进程无法再更改自己的进程属性了,因为用户 ID 已经不是 root 即 ! = 0 了,没有修改自己进程属性的权限了。

adb shell ps,看下进程列表:

USER PID PPID VSZ RSS WCHAN ADDR S NAME

root 1 0 10456 2352 SyS_epoll_wait 0 S init

...

root 1620 1 1614572 20312 poll_schedule_timeout 0 S zygote

...

u0_a88 3468 1620 1929916 112340 SyS_epoll_wait 0 S com.feelschaotic.demo

u0_a90 3574 1620 1696012 24176 SyS_epoll_wait 0 S com.demo.pushdemo

u0_a90 3607 1620 1708844 26748 SyS_epoll_wait 0 S com.demo.pushdemo:mult

u0_i0 3741 1879 1557800 11720 SyS_epoll_wait 0 S com.android.chrome:sandboxed

u0_a15 3774 1620 1690604 22056 SyS_epoll_wait 0 S com.google.android.ext.services

system 3805 1620 1687840 19688 SyS_epoll_wait 0 S com.android.keychain

u0_a42 3831 1620 1834408 45660 SyS_epoll_wait 0 S com.android.chrome

...

PID 表示应用的进程 ID,PPID 表示父进程 ID,NAME 表示进程名称(一般情况下 NAME 是应用包名)。可以看到 zygote 进程是由 init 进程启动,所有的应用进程的父进程都是 zygote。USER 表示的是进程的专有用户,这个我们下次再详细讲讲 Android 的用户管理机制。

好了,既然如此,每个应用进程都分配好自己的 GID、UID和补充 GID,系统内核和守护进程就可以用这些标识来决定,是否要赋予进程权限。

权限的检查

1. 系统内核层权限检查

思考一下:如果我们的应用没有在 AndroidManifest.xml 中申请 android.permission.INTERNET 权限就进行网络请求,是不是会报 Permission denied 错误。这个权限,是谁来检查?其他进程来检查吗?明显不是,网络访问权限是由低层来进行控制的。

Android 的访问控制,和 Linux 是一样的,但 Android 增加了个特有的网络访问安全控制机制,也就是说,创建网络套接字的进程,必须属于 inet 组。

如上内核代码,current_has_network(void) 方法检查了进程的所在组。如果不在 inet 组,则直接返回错误。所以为了使我们的应用具有访问网络的能力,我们需要在 AndroidManifest.xml 中申请 INTERNET 权限,经过解析,逐步映射到内核层的组 ID 和用户 ID,最终才能通过内核层的检查。

你可能会有疑问,那非内核层的其他 C/C++ 层,要怎么拿到进程的所在组信息呢?在 PMS 初始化所有包信息之后,就会调用 mSettings.writeLPr。

这段代码的任务就是将mPackages 中保存的所有包的信息保存到 /data/system/packages.list。所以,packages.list中保存了所有应用申请的权限,C代码只要读这个文件就能判断某个应用是否申请了我们要求的权限。

2. 框架层

因为 Android 6.0 之前组件不能在运行时改变权限,所以系统的权限检查执行过程是静态的。这个情况下,组件的角色和权限的等安全属性会被放置在元数据中,即 AndroidManifest.xml 文件中,而不是组件的本身。系统包管理器会负责记录组件的权限,所以静态权限检查可以从包管理器拿到权限,由运行环境或容器来执行权限检查,这样子可以把业务逻辑和安全决策分离开来,但是灵活性不足。

那 Android 组件可不可以不预先声明权限在 AndroidManifest.xml 中呢?答案是:可以的。Android 的动态权限执行,可以让组件自身执行权限检查,而不是运行环境。

所以接下来我们将深入了解框架层的动态和静态权限执行的原理。

动态权限执行

动态权限执行,最典型的场景,就是 IPC。Android 的核心系统服务统一会注册到服务管理器,任何应用,只要知道服务的注册名称,就可以拿到对应的 Binder引用,就可使用 Binder IPC 机制调用服务。因为 Binder 没有内置的访问控制机制,所以每个系统服务需要自己实现访问控制机制。

系统服务可以直接检查调用者的 UID,通过限定 UID 来控制访问权限,这种方式简单直接,但是对于非固定UID的应用,就比较棘手了。而且大部分服务,并不关心调用者的 UID,只需要检查调用者是否被赋予特定的权限即可。所以这种方式,比较适合只允许以 root(UID:0) 或 system(UID:1000) 运行的进程访问的服务检查。

那换一种方式,服务怎么拿到调用者的权限列表?我们知道,大部分 UID 都是和包一一对应的,除了共享 UID。(共享 UID 后面再详细解释)

使用 Binder.getCallingUid 和 Binder.getCallingPid 获取调用者的 UID 和 PID,通过 UID 在包管理器中查询到对应应用的权限。android.content.Context 类中就有 checkPermission(String permission, int pid, int uid) 方法。实质上会调用到 PMS 中的 checkUidPermission(String perName, int uid),如下:

Android 6.0 以下 PMS 中的 checkUidPermission(String perName, int uid)

public int checkUidPermission(String permName, int uid) {

synchronized (mPackages) {

Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));

if (obj != ) {

GrantedPermissions gp = (GrantedPermissions)obj;

if (gp.grantedPermissions.contains(permName)) {

return PackageManager.PERMISSION_GRANTED;

}

} else {

HashSet<String> perms = mSystemPermissions.get(uid);

if (perms != && perms.contains(permName)) {

return PackageManager.PERMISSION_GRANTED;

}

}

}

return PackageManager.PERMISSION_DENIED;

}

Android 6.0 以下的 checkUidPermission 方法比较简单,首先,基于入参 uid 获取应用的 appId,拿到权限列表对象(也就是 packages.xml 里的 <package> 映射),如果 GrantedPermissions 类中的 grantedPermissions 集合包含目标权限,则检查通过。

如果没有该 GrantedPermissions 对象,则检查目标权限是否可以被自动授予,实际上 mSystemPermissions 就是 platform.xml 文件中的 <assing-permission> 标签映射缓存,记录了一些系统级应用的 uid 对应的 permission。例:

...

<assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />

<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />

<assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="cameraserver" />

<assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="graphics" />

...

Android 6.0 及以上 PMS 中的 checkUidPermission(String perName, int uid)

@Override

public int checkUidPermission(String permName, int uid) {

final int callingUid = Binder.getCallingUid;

final int callingUserId = UserHandle.getUserId(callingUid);

final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != ;

final boolean isUidInstantApp = getInstantAppPackageName(uid) != ;

final int userId = UserHandle.getUserId(uid);

if (!sUserManager.exists(userId)) {

return PackageManager.PERMISSION_DENIED;

}

synchronized (mPackages) {

Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));

if (obj != ) {

...

final SettingBase settingBase = (SettingBase) obj;

final PermissionsState permissionsState = settingBase.getPermissionsState;

if (permissionsState.hasPermission(permName, userId)) {

if (isUidInstantApp) {

BasePermission bp = mSettings.mPermissions.get(permName);

if (bp != && bp.isInstant) {

return PackageManager.PERMISSION_GRANTED;

}

} else {

return PackageManager.PERMISSION_GRANTED;

}

}

...

} else {

ArraySet<String> perms = mSystemPermissions.get(uid);

if (perms != ) {

if (perms.contains(permName)) {

return PackageManager.PERMISSION_GRANTED;

}

...

}

}

}

return PackageManager.PERMISSION_DENIED;

}

可以注意到,6.0 之后 checkPermission 方法有所改变。多了从 mSettings.mPermissions 去查询权限列表。关键就在于这个 mSettings 里面保存的这个 SettingBase 对象,它记录了 PermissionsState 也就是权限的授予情况。

// PermissionsState.java

public boolean hasPermission(String name, int userId) {

enforceValidUserId(userId);

if (mPermissions == ) {

return false;

}

PermissionData permissionData = mPermissions.get(name);

return permissionData != && permissionData.isGranted(userId);

}

所以检查权限的流程是本来就有的,6.0 之后差异仅在于:危险级别权限可以动态修改授权情况,也就是修改 PermissionState 的 mGranted 值,所以每次权限执行,都会查询下 mGranted 值。

静态权限执行

静态权限执行的典型场景,是跨应用组件交互。我们使用隐式 Intent 来表达意图,搜索匹配的组件,如果有多个,弹出选择框,目标组件被选定后,会由 ActivityManagerService 执行权限检查,检查目标组件是否有相应的权限要求,如果有,则把权限检查的工作交给 PMS,去检查调用者有没有被授权这些权限。

接下来的总体的流程和动态执行流程大致相同:Binder.getCallingUid和Binder.getCallingPid获取调用者的 UID 和 PID,然后利用 UID 映射包名,再获得相关权限集合。如果权限集合中含有所需权限即启动,否则抛出 SecurityException 异常。静态权限执行这里,我们可以详细了解下,每种组件的权限检查时机和具体顺序是怎么样的。

组件权限执行

思考一下,什么时候会执行对调用者的权限检查?那肯定是在目标组件被调用的时候,去解析目标组件声明的权限,如果有,就执行权限检查。

Activity 和 Service。Activity 显而易见,会在 startActivity 和 startActivityForResult 里解析到声明权限的 Activity 时,就执行权限检查。而 Service startService、stopService 和 bindService,这 3 个方法被调用时都会进行权限检查。

广播。我们注意到,发送广播除了常用的 sendBroadcast(Intent intent),还有个 sendBroadcast(Intent intent, String receiverPermission),该方法可以要求广播接受者具备特定的权限,但是,调用 sendBroadcast 是不会进行权限检查的,因为广播是异步的,所以权限检查会在 intent 传递到已注册的广播接受者时进行,如果接收者不具备特定的权限,则不会接收到该广播,也不会收到 SecurityException 异常。

反过来,接收者可以要求广播发送者必须具备的权限,所要求的权限在 manifest 文件中设置 <receiver> 标签的 permission 属性,或者动态注册时指定 registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler),权限检查也是在广播传递时执行。

所以,收发广播可以分开指定权限。值得一提的是,一些系统广播被声明为 protected,并且只能由系统进程发送,比如 PACKAGE_INSTALLED。只能由系统进程发送,这个限制会在内核层进行检查,对调用者的 UID 进行匹配,只能是 SYSTEM_UID、PHONE_UID、SHELL_UID、BLUETOOTH_UID 或 root。如果其他 UID 的进程试图发送系统广播,则会收到 SecurityException 异常。

想了解所有的系统广播,可以打开/system/framework/framework-res.apk 中的 AndroidManifest.xml <protected-broadcast> 标签详细了解。

ContentProvider。ContentProvider 可以为读写分别指定不同的权限,即:调用目标 provider、query 方法 和 insert、update、delete 都会进行权限检查。

总结

综上所述,Android 的权限的检查会在各个层次上实施。

高层的组件,例如应用和系统服务,通过包管理器查询应用程序被赋予的权限,并决定是否准予访问。

低层的组件,通常不访问包管理器,比如本地守护进程,依赖于进程的 UID、GID 和补充 GID 来决定赋予。

访问系统资源时,如设备文件、UNIX 域套接字和网络套接字,则由内核根据所有者、目标资源的访问权限和访问进程的进程属性或者 packages.list 来进行控制。

共享 UID

最后简单说下共享 UID,填一下前面挖的坑。虽说 Android 会为每一个应用分配唯一的 UID,但如果应用使用相同的密钥签发,就可以使用相同 UID 运行,也就是运行在同一个进程中。

这个特性被系统应用和核心框架服务广泛使用,比如:Google Play 和 Google 定位服务,请求同一进程内的 Google 登录服务,从而达到静默自动同步用户数据的体验。

值得注意的是:Android 不支持将一个已安装的应用,从非共享 UID 切换到共享状态,因为改变了已安装应用的 UID,会导致应用失去对自己文件的访问权限(在一些早期 Android 版本中),所以如果使用共享 UID 必须从一开始就设计好。

近期文章:

10段奇葩的代码注释

Android studio 3.4 稳定版来了

Flutter 入门指北之弹窗和提示(干货)

今日问题:

权限超出了你的想象了吧?

在码个蛋(codeegg)后台回复「社群」即可加入学习群

标签: #安卓打开权限请求弹出框