安卓适配动态权限说明

本文介绍了从Android6.0开始的动态权限管理,包括权限的分类、申请权限的流程、判断权限的方法以及适配动态权限的三种方式,并提供了注意事项,如适配小米机型、FileProvider、DownloadManager以及处理用户拒绝权限的情况。

一、背景:

      从Android6.0版本开始google将权限分为普通权限和特殊权限,app必须在AndroidManifest.xml添加引用权限的语句。 在安装apk时安卓会将普通权限授予该app,但特殊权限需要运行时申请。

      安卓按照权限类别分为权限组和权限, 每个权限都隶属于一个权限组。 当安卓系统授权一个权限时, 那么该权限所属权限组的权限都会自动被授权。

      目前贝壳找房app的targetSdkVersion等于23,即按照Android5.0版本特性运行。 技术层面与市面上主流app差距较大, 功能层面也有一些功能可能失效(例如在一些机型上无法打电话、读写SD卡)。 根本原因是没适配动态权限。

 

二、如何申请动态权限:

      判断当前手机系统是Android6.0及以上版本, 在Activity/Fragment里申请权限并处理权限结果回调。 这里要说明一下:Fragment是通过Activity申请权限的, 且权限回调onRequestPermissionResult也是Activity调用的Fragment该方法.

                                         

     上图是权限申请流程图, 我们看到的权限弹窗对应/packages/apps/PackageInstaller/src/com/android/packageinstaller/permission/ui/GrantPermisssionsActivity.java, 点击“同意”或“不同意”通过PackageManager、AppOpsManager将权限操作更新到PackageManagerService和AppOpsService中。

     调用Activity的申请权限方法其实是打开一个系统的Activity,操作结果通过setResult返回过来。

     能不能直接调用PackageManager/AppOpsManagerd的方法授权给自己? 显然是不行的, PackageManagerService只允许在AndroidManifest.xml配置coreApp="true"的应用修改权限,而普通app无法设置coreApp属性。

public int getPermissionFlags(String name, String packageName, int userId) {
      if (!sUserManager.exists(userId)) {
          return 0;
      }
      //普通app调用该方法会抛异常
      enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
      enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
              "getPermissionFlags");
   ...
}
 private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
      if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
              != PackageManager.PERMISSION_GRANTED
          && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
              != PackageManager.PERMISSION_GRANTED) {
          throw new SecurityException(message + " requires "
                  + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
                  + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
      }
  } 

三、如何判断权限:

       如上图所示,判断是否有权限最终会执行到PackagerManagerService的checkUidPermission函数中。

 

四、适配动态权限的方式:

1、 基本用法:在Activity、Fragment派生类中添加权限申请和结果回调。 坑:1、在插件中调用View的getContext返回值是PluginContext, 无法通过类型强转调用其附着Activity/Fragment的方法。2、如果界面层级很深,需要逐层添加回调参数。

2、AOP方式,推荐https://github.com/permissions-dispatcher/PermissionsDispatcher, 在需要权限的函数上添加注解并在构建阶段注入代码。缺点是贝壳app插件中有多View控件如BaseCard无法使用。

3、第三方库https://github.com/yanzhenjie/AndPermission, 原理:新启动个透明Activity申请权限并保存回调函数到静态变量里,用户操作权限提示框结束后通过回调执行成功、失败逻辑。

  示例代码: 为了避免适配动态权限逻辑产生风险, 可以新增if代码块做动态权限逻辑, else分支仍然是现有逻辑。 各业务线可能无法在同一个版本上搞定, 可以按照这种写法先后完成动态权限适配工作,待所有业务线都完成后调整宿主targetSdkVersion到26。

if (getBaseContext().getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.M
    && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    AndPermission.with(this)
        .runtime()
        .permission(permissions)
        // .rationale(new RuntimeRationale())
        .onGranted(new Action<List<String>>() {
            @Override
            public void onAction(List<String> permissions) {
                toast(R.string.successfully);
            }
        })
        .onDenied(new Action<List<String>>() {
            @Override
            public void onAction(@NonNull List<String> permissions) {
                toast(R.string.failure);
                if (AndPermission.hasAlwaysDeniedPermission(MainActivity.this,
                    permissions)) {
                    showSettingDialog(MainActivity.this, permissions);
                }
            }
        })
        .start();
} else {
    //默认有权限, 用现在的业务逻辑
}

五、注意事项:

  1、适配小米机型动态权限;

  2、Android7.0版本开始使用FileProvider, 需要适配拍照功能;

  3、适配DownloadManager安装apk;

  4、用户禁用权限且不再提醒, 需要有个提示框提示用户去权限设置里放开权限。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值