Android 开发中的权限申请
一、权限动态申请
我们都知道,从 Android 6.0 开始,部分危险权限在 xml 注册的同时,还需要动态申请。
1、需要动态申请的权限
Manifest.permission.CONTACTS //联系人
Manifest.permission.PHONE //电话
Manifest.permission.CALENDAR //日历
Manifest.permission.CAMERA //相机
Manifest.permission.SENSORS //传感器
Manifest.permission.LOCATION //位置
Manifest.permission.STORAGE //存储
Manifest.permission.MICROPHONE //麦克风
Manifest.permission.CONTACTS //短信
需要动态申请的权限主要分为这 9 类,当然每类中可能不止一个权限,但只要动态申请一个就默认获取整个类别的权限。
2、申请单个权限
//常量,用于回调
int MY_PERMISSION_APPLY = 1;
//要使用的相机权限
int permission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA);
//判断是否有相机权限
if (permission == PackageManager.PERMISSION_GRANTED) {
//有权限直接执行
} else {
//没有权限,提示获取权限
String[] perms = {"android.permission.CAMERA"};
ActivityCompat.requestPermissions(this, perms, MY_PERMISSION_APPLY);
}
在弹出权限申请后,需要对用户的选择结果进行处理,在回调方法中执行:
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
switch(permsRequestCode){
case MY_PERMISSION_APPLY:
boolean albumAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
if(!albumAccepted){
//用户拒绝了权限
}else{
//用户同意了权限
}
break;
}
}
3、同时申请多个权限
//常量,用于回调
int MY_PERMISSION_APPLY = 1;
//要使用的相机和存储权限
String[] permissions = new String[]{
Manifest.permission.CAMERA,
Manifest.permission.STORAGE
};
//用于存放未同意的权限
List<String> mPermissionList = new ArrayList<>();
//清空未同意权限
mPermissionList.clear();
//循环得到未同意权限
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(mContext, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
mPermissionList.add(permissions[i]);
}
}
//判断是否有未同意权限
if (mPermissionList.isEmpty()) {
//都有权限直接执行
} else {
//将List转为数组
String[] permissions = mPermissionList.toArray(new String[mPermissionList.size()]);
ActivityCompat.requestPermissions(this, permissions, MY_PERMISSION_APPLY);
}
然后,同样添加上面的回调方法处理用户的选择结果。
二、本地文件读写权限变更
需求:Android 11.0 版本,sdk 32,使用本地 Download 下文件,在申请动态权限后,仍然提示缺少权限问题。
1、Manifest.xml
我们都知道 Android 开发某些功能(文件读写)时需要在 Manifest.xml 中添加对应权限。才能对 Android 手机中的图片、文件等内容进行处理。Manifest.xml 中添加读写权限如下:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
2、动态权限
Android 6.0 时在 Manifest.xml 添加权限的同时,还需要在使用时动态申请权限,这个大家应该也很熟悉,这里仅展示权限申请代码,至于用户拒绝、不再提示等操作及回调不再展示。
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M
&& context.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED) {//请求权限
((Activity)context).requestPermissions(new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}
3、作用域存储
Android 10.0 开启了作用域存储,即使完成了上面的 Manifest.xml 中添加权限和代码中动态申请权限,在访问包名以外的文件夹时,依旧提示权限不足。这时我们在 Manifest.xml 添加属性关闭作用域存储,这时谷歌官方提供的一个暂时解决方案。
<application
android:requestLegacyExternalStorage="true">
</application>
4、外部存储权限
Android 11.0 开始,作用域存储开始强制启用,上面的关闭方法不在起作用,这时如果还需要访问外部文件夹,就需要用户手动开启一个危险权限——外部存储权限。
Manifest.xml 新增外部存储权限
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
手动设置打开
该权限属于特殊权限需要用户手动去设置中打开,通过 Environment.isExternalStorageManager() 判断是否开启,未开启跳转设置开启界面。
public static boolean checkStorageManagerPermission(Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
context.startActivity(intent);
return false;
}
return true;
}
打开后就可以正常访问 Download 等共享目录了。
三、危险权限弹窗处理
1、需求分析
1)系统应用直接授予权限
2)使用指定签名第三方应用直接授予权限
3)使用普通签名第三方应用正常提示
2、实现方案分析
系统APP
系统 APP 可以直接修改 DefaultPermissionGrantPolicy.java 中的 grantDefaultSystemHandlerPermissions() 方法直接为系统应用配置默认权限。
限制条件:需要修改系统源码,所以第三方应用无法实现
文件位置:frameworks/base/services/core/java/com/android/server/pm/permission/
修改权限属性
1)直接修改权限级别在 frameworks/base/core/res/AndroidManifest.xml 中将 dangerous(危险)权限修改为 normal(正常)权限,所有应用均直接获得权限,不适用上面需求。
2)将 dangerous(危险)权限修改为 Signature(签名)权限,这样指定签名APP可以获得权限,但第三方其他签名 APP 无法获取权限。
ps:是否可以设置同名权限:例如设置摄像头同名权限,级别 Signature,同签名自动拥有权限,其他第三方APP申请默认dangerous 摄像头权限弹出提示。
需要验证,感觉可行性不大。
指定APP通过权限
PackageManagerService.java 的grantPermissionsLpw 中添加指定包名通过权限,网络上很多资料都可以找到该方法,但现在的源码中找到了该文件,未找到该方法。
ps:是否可以将上面的判断包名变成判断签名,待验证。
权限白名单(最优)
1、/frameworks/base/core/res/res/values/config.xml中增加包名白名单
2、/frameworks/base/core/res/res/values/symbols.xml中声明白名单变量
3、frameworks/base/services/core/java/com/android/server/pm/ permission/PermissionManagerService.java中的grantPermissions方法中判断权限白名单
参考:Android 9.0 设置白名单赋予应用权限 - 二的次方 - 博客园
该方法添加白名单也需要修改配置文件,所以第三方指定签名文件也无法直接得到权限。所有权限通过也不适用。
但通过源码逻辑发现,下面代码应该是APP系统签名判断方法:
if(bp.isSignature()){
allowedSig = grantSignaturePermission(perm, pkge, bp, origPermissions);
}
所以上面的判断白名单可以改成判断签名通过权限,即:
if(bp.isRuntime()){
else{
allowedSig = grantSignaturePermission(perm, pkge, bp, origPermissions);
if(allowedSig){
grant = GRANT_INSTALL;
}
}
}
这样使用指定签名APP和系统APP都能跳过权限验证。
直接处理提示框
直接在 GrantPermissionsActivity 中弹框时判断处理,但是根据需求也需要进行签名验证。
可行方案预测(待验证)
1、在 AndroidManifest.xml 中自定义一个新权限,级别为Signature;
2、在DefaultPermissionGrantPolicy.java将新定义的权限加入权限组;
这样指定签名的应用直接获得了新权限,又因为同组权限只要有一个权限是通过状态,其他权限不在验证直接通过。
需要直接通过的APP需要使用指定签名,同时还需要申请我们自定义的新权限。