测试那边反馈了一个bug:把软件切换到后台,把已经授予的存储权限关掉,再切回到demo,demo就像崩溃了一样,重新初始化,并且清除了之前的登录状态,详情可参考录屏:
根据录屏可以看出,在设置中重置了权限后再返回app,app会再次进行初始化,根据logcat也可以看出对应的进程号也变了。根据该现象,可以确定与权限相关。
根据反馈,出现问题的手机版本是Android11。为了确定该问题是否在部分手机或系统上才会出现,我用华为(鸿蒙系统)以及三星(Android10)尝试复现,均复现成功。由此可以得出,该问题并不是因为Android11带来的适配问题。
最初,通过logcat并没有发现相关的报错信息,所以,我们可以初步排除是代码问题。
为了验证这个问题是否是我们应用特有情况,我用手机测试了微信以及另外一款轻量级的app,发现都出现了重启情况。
至此,问题似乎有点明了。我们可以大胆猜测,这个问题应该是Android权限的一种内部机制。
我们可以先猜测一下,Android为什么要这么设计?
首先,如果我们把权限由禁止改成允许,app不会重启,这个其实符合预期。
怎么理解?
默认情况下我们肯定是希望manifest文件中的所有权限都是授予的,毕竟开发的功能如果跟权限紧密相关,那也就意味着如果有了权限,对应的功能就可以使用,这个其实是符合开发者的意图。但是为了安全起见,谷歌在6.0加入了动态权限的设计。
其次,如果我们把权限由允许改成禁止,app会重启。这个怎么理解?首先,根据动态权限设计的初衷来看,当我们需要使用的时候再申请权限,所以权限是跟功能强相关的,如果不进行重启,那么在使用到某个功能的时候,但是我们并没有权限(已经被撤回了,系统内部会把该信息同步到app内部)
那么肯定会报permission denied....这个肯定不是最优解。如果不进行重启,那么动态权限的逻辑就会滞后,并不同步(如果app正在使用某个授予了权限的功能)这个也不符合逻辑。
通过上面的初步分析,大概可以了解,权限重置为什么会重启app。
Android权限分类
首先,我们需要对权限分类有个了解,在接下来的代码分析中会用到。
Android 将权限分为不同的类型,包括安装时权限、运行时权限和特殊权限。每种权限类型都指明了当系统授予应用该权限后,应用可以访问的受限数据范围以及应用可以执行的受限操作范围。
安装时权限
安装时权限授予应用对受限数据的受限访问权限,并允许应用执行对系统或其他应用只有最低影响的受限操作。如果您在应用中声明了安装时权限,系统会在用户安装您的应用时自动授予应用相应权限。应用商店会在用户查看应用详情页面时向其显示安装时权限通知,如下图所示。
图中展示的是某应用商店中显示的某个应用的安装时权限列表。
Android 提供多个安装时权限子类型,包括普通权限和签名权限。
普通权限
此类权限允许访问超出应用沙盒的数据和执行超出应用沙盒的操作。但是,这些数据和操作对用户隐私及对其他应用的操作带来的风险非常小。
系统会为普通权限分配“normal”保护级别,例如网络权限:"android.permission.INTERNET"。
签名权限
当应用声明了其他应用已定义的签名权限时,如果两个应用使用同一证书进行签名,系统会在安装时向前者授予该权限。否则,系统无法向前者授予该权限。
系统会为签名权限分配“signature”保护级别,例如获取电量统计数据:"android.permission.BATTERY_STATS"。
运行时权限
运行时权限也称为危险权限,此类权限授予应用对受限数据的额外访问权限,并允许应用执行对系统和其他应用具有更严重影响的受限操作。因此,您需要先在应用中请求运行时权限,然后才能访问受限数据或执行受限操作。当应用请求运行时权限时,系统会显示运行时权限提示,如下图所示:
许多运行时权限会访问用户私有数据,这是一种特殊的受限数据,其中包含可能比较敏感的信息。例如,位置信息和联系信息就属于用户私人数据。
特殊权限
特殊权限与特定的应用操作相对应。只有平台和原始设备制造商 (OEM) 可以定义特殊权限。此外,如果平台和 OEM 想要防止有人执行功能特别强大的操作(例如通过其他应用绘图),通常会定义特殊权限。
系统设置中的特殊应用访问权限页面包含一组用户可切换的操作。其中的许多操作都以特殊权限的形式实现。例如查询正在进行的通话详情和管理正在进行的通话:"android.permission.MANAGE_ONGOING_CALLS"
以上就是Android官方对权限的分类,详细的权限列表可参考网址: https://developer.android.com/reference/android/Manifest.permission
源码分析
刚刚已经讲过,导致app重启的问题是权限重置的问题,那我们只要找到相关的源码即可,首先我们可以定位和permission相关的类,然后再通过回调的逻辑找到具体的代码(设置中把权限重置,app重启,这个逻辑中肯定会存在回调的逻辑)。
首先我在 https://cs.android.com/ 中搜索了关键字:permission,根据结果找到了一个权限管理类:PermissionManagerService。
经过查找,我们发现了PermissionCallback,根据名字就可以看出是处理权限回调的。
private PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
@Override
public void onGidsChanged(int appId, int userId) {
mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
}
@Override
public void onPermissionGranted(int uid, int userId) {
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Not critical; if this is lost, the application has to request again.
mPackageManagerInt.writeSettings(true);
}
@Override
public void onInstallPermissionGranted() {
mPackageManagerInt.writeSettings(true);
}
@Override
public void onPermissionRevoked(int uid, int userId, String reason) {
mOnPermissionChangeListeners.onPermissionsChanged(uid);
// Critical; after this call the application should never have the permission
mPackageManagerInt.writeSettings(false);
final int appId = UserHandle.getAppId(uid);
if (reason == null) {
mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
} else {
mHandler.post(() -> killUid(appId, userId, reason));
}
}
@Override
public void onInstallPermissionRevoked() {
mPackageManagerInt.writeSettings(true);
}
@Override
public void onPermissionUpdated(int[] userIds, boolean sync) {
mPackageManagerInt.writePermissionSettings(userIds, !sync);
}
@Override
public void onInstallPermissionUpdated() {
mPackageManagerInt.writeSettings(true);
}
@Override
public void onPermissionRemoved() {
mPackageManagerInt.writeSettings(false);
}
public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
int uid) {
onPermissionUpdated(updatedUserIds, sync);
for (int i = 0; i < updatedUserIds.length; i++) {
int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid)); mOnPermissionChangeListeners.onPermissionsChanged(userUid);
}
}
public void onInstallPermissionUpdatedNotifyListener(int uid) {
onInstallPermissionUpdated();
mOnPermissionChangeListeners.onPermissionsChanged(uid);
}
};
我们主要看onPermissionRevoked()这个回调:
该方法会调用killUid()方法:
根据注释可以看出,当权限被授予或被重置时,app会立即重启。
通过对问题的定位与分析,以及结合源码的分析,我们可以得出:当我们对app的权限进行重置操作时,希望会对app进行重新初始化操作,该逻辑为Android权限机制。
最后,我们还测试了一下iOS系统,发现在iOS上如果在设置里对某个应用的权限做重置操作,也会导致该应用重启。
从Android近几个大版本的迭代中,我们可以明显的注意到,Android对权限这一块的把控越来越严。一方面是为了将Android打造成最安全的操作系统,另外一方面也是对用户隐私的重视。