本文共 5960 字,大约阅读时间需要 19 分钟。
在经过上一次尝试剖析源码后,我意识到自己并没有一种比较好的方式去讲解代码,从而无法把自己所知道的知识更好地输出。所以接下来,至少在源码讲解有新想法前,我都不会再去尝试,也尽量减少博客中的非核心代码,而以思路及想法为主。另外,我也将尝试改进技术博客的笔法,段落之间尽量连贯,整体内容尽量有节奏感,目标是做到深入浅出地表达出主题相关内容。
从 Android 6.0(API 23)开始,用户可以在应用运行时向其授予权限(6.0 以下,国内厂商也多数做了类似的权限管理),而不是在应用安装时授予。这一方法既可以简化应用的安装过程,也可以让用户对应用的功能进行更多的限制。
在 Android 系统中,系统权限分为两类:正常权限以及危险权限。其中正常权限是不会直接给用户隐私权带来风险的,如果应用在其清单中声明了该权限,则系统会自动授予该权限。而危险权限,从 6.0 开始,而需要应用动态申请,并且由用户授予。
官方的动态权限 API 是从 Android 6.0 才引入的,那么,自然需要做好6.0 起及 6.0 之前的不同版本的权限适配,从而催生了一些动态权限兼容封装库。比如:
然而,我在项目中准备去使用的时候,却发现这几个库都无法完全满足我的想法,于是就想到自己也去造一个轮子,也就是今天要讲的——hey-permission。
我所设想的权限封装库是这样子的:
那么,再看看 Android 6.0(API 23)里都为此提供了哪些 API ?
其中根据第三条,我们可以在权限被拒绝时来判断是否被永久拒绝。
接下来,我们可以开始构思整个权限申请的流程,它主要有两部分:一是权限申请;二是申请结果的回调。
下面来绘制一下权限申请的流程:对应的核心代码则是:
private static void requestPermissions(@NonNull BasePermissionInvoker invoker, @IntRange(from = 0) int requestCode, @Size(min = 1) @NonNull String[]... permissionSets) { final ListpermissionList = new ArrayList<>(); for (String[] permissionSet : permissionSets) { permissionList.addAll(Arrays.asList(permissionSet)); } final String[] permissions = permissionList.toArray(new String[permissionList.size()]); if (hasPermissions(invoker.getContext(), permissions)) { notifyAlreadyHasPermissions(invoker, requestCode, permissions); return; } if (invoker.shouldShowRequestPermissionRationale(permissions)) { if (invokeShowRationaleMethod(false, invoker, requestCode, permissions)) { return; } } invoker.executeRequestPermissions(requestCode, permissions);}
然后是权限申请结果的回调处理:
对应的核心代码则是:
private static void onRequestPermissionsResult( @NonNull BasePermissionInvoker invoker, @IntRange(from = 0) int requestCode, @Size(min = 1) @NonNull String[] permissions, @NonNull int[] grantResults) { if (!invoker.needHandleThisRequestCode(requestCode)) { return; } final Listgranted = new ArrayList<>(); final List denied = new ArrayList<>(); for (int i = 0; i < permissions.length; i++) { if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { granted.add(permissions[i]); } else { denied.add(permissions[i]); } } if (denied.isEmpty()) { // all permissions were granted invokePermissionsResultMethod(PermissionsGranted.class, invoker, requestCode, granted); invokePermissionsResultMethod(PermissionsResult.class, invoker, requestCode, denied); return; } final String[] deniedPermissions = denied.toArray(new String[denied.size()]); boolean neverAskAgain = true; if (invoker.shouldShowRequestPermissionRationale(deniedPermissions)) { neverAskAgain = false; if (invokeShowRationaleMethod(true, invoker, requestCode, deniedPermissions)) { return; } } if (neverAskAgain) { invokePermissionsResultMethod(PermissionsNeverAskAgain.class, invoker, requestCode, denied); } else { invokePermissionsResultMethod(PermissionsDenied.class, invoker, requestCode, denied); } invokePermissionsResultMethod(PermissionsResult.class, invoker, requestCode, denied);}
以上就是最核心的逻辑。
接下来思考三个问题:
对于第一个问题,我是参考了 google 官方的 easypermissions,对实际发起权限申请的 Activity, Fragment 及 SupportFragment 做了一层包装,抽象为 BasePermissionInvoker
抽象类,并定义了一些依赖它们去实现的行为,如 shouldShowRequestPermissionRationale(@NonNull String... permissions)
,startActivityForResult(Intent intent, int requestCode)
等。
PermissionRequestExecutor
接口,里面只声明了一个方法 void executeRequestPermissions(int code, String... permissions);
,由 BasePermissionInvoker
去实现。 然后继承自 BasePermissionInvoker
,在 Activity, Fragment 及 SupportFragment 的封装中对这些方法做具体的实现。 最后是对第二个问题的思考,回调方式如何选择?
一是开发者向用户显示权限重要性的提示,这个开发者可以显示也可以不显示。并且如上面的流程图所示,如果权限申请被拒绝了,而开发者显示了权限重要性的话,那么即表明权限申请已被拒绝,则不用再回调被拒绝的方法。否则表示开发者未处理,则要回调到权限被拒绝的方法。所以这种情况可以使用回调接口的方法,通过返回值来判断。
第二种情况是权限申请结果的回调。申请结果的回调有通过、拒绝、永久拒绝多种,并且我们希望可以在 Activity 或 Fragment 等的基类能够统一处理被拒绝的情况,另一方面,我们需要不同的权限请求,能够在各自的方法里去处理回调。基于这些想法,因此采用了注解的方式。通过不同的 requestCode
来回调对应的方法;并且被拒绝的注解,它接收的参数是应该是数组类型,也就是我们可以在基本统一处理,也可以在某一个类通过再声明对应的注解来单独处理某一种情况。
以上就是 hey-permission 的整个结构设计。具体实现时会遇到一些细节问题,比如判断权限时考虑 6.0 之前的 Ops,比如 SupportFragment 的权限申请结果是通过 AppCompatActivity 来分发,比如回调的注解方法的参数,等等,这些就不在这里赘述,具体可参考项目源码,地址为: 。
目前已实现如下特性:
@PermissionsGranted
申请权限均被允许@PermissionsDenied
申请权限被拒绝(下次还可询问用户)@PermissionsNeverAskAgain
申请权限被永久拒绝@PermissionsResult
申请权限结果(允许或拒绝都会回调)@PermissionsGranted
)或被拒绝(其他注解)的 permissions转载地址:http://apwnm.baihongyu.com/