App实战:权限管理
今天下午7酱又要上场了,开森。b话不多说,先来一套泰拳警告。
上一篇博客中,其实遗留了一个比较大的问题。那就是我们保存图片到本地,是需要文件存储权限的,而我是直接在manifests文件中注册。这对于6.0之前系统的手机是没有问题,但是在6.0之后手机就不行了。因为Google认为写入文件的权限属于隐私权限,需要向用户申请,而不是开发者决定。
还是那个妹子!
动态申请权限
现在权限管理的库已经有很多了,而我作为葬爱家族的首席Android开发,我选谷歌官方出的EasyPermissions。我就不给各位大兄dei绕弯子了,直接给出几个主要步骤(首先Activity实现EasyPermissions.PermissionCallbacks接口):
第一肯定是要申请权限,项目里面可以这么写:
String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; EasyPermissions.requestPermissions(this, "保存图片需要文件存储", Constants.WRITE_REQUEST_CODE, perms);
请求回调,在Activity里的onRequestPermissionsResult接管:
@Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); switch (requestCode) { case Constants.WRITE_REQUEST_CODE: //说白了就是用EasyPermissions来接管Activity中的此方法 EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); break; default: break; } }
授权成功和失败的回调:
public void onPermissionsGranted(int requestCode, List<String> perms) { switch (requestCode) { case Constants.WRITE_REQUEST_CODE: //这里如果调用saveImgToPhone,writePer中还会执行一次 //如果说此次动态申请的权限全部成功,没有一个拒绝,那么会执行writePer方法 break; default: break; } }
public void onPermissionsDenied(int requestCode, List<String> perms) { switch (requestCode) { case Constants.WRITE_REQUEST_CODE: Toast.makeText(FlatMap1Activity.this, "缺少文件存储,图片保存失败", Toast.LENGTH_SHORT).show(); //在拒绝的这个地方来进行终极处理, 这里防止有人点击了不再提醒的选项 break; default: break; } }
注意到EasyPermissions库给我们提供了一个AfterPermissionGranted注解,它的用法如下:
//注意本方法返回类型必须是void, 而且是无参数 @AfterPermissionGranted(Constants.WRITE_REQUEST_CODE) private void writePer() { String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; if (EasyPermissions.hasPermissions(this, perms)) { saveImgToPhone(); } else { EasyPermissions.requestPermissions(this, "保存图片需要文件存储", Constants.WRITE_REQUEST_CODE, perms); } }
根据注解名字也能明白意思,就是在权限授权成功后,会调用此方法。所以我们可以把权限申请放在里面,判断当已经有了存储权限时,我们就不用进行申请权限的步骤;否则才会进行权限申请。这里面有两个点需要注意下的。
第一是:
/** * Request permissions from an Activity with standard OK/Cancel buttons. * * @see #requestPermissions(Activity, String, int, int, int, String...) */ public static void requestPermissions( @NonNull Activity host, @NonNull String rationale, int requestCode, @NonNull String... perms) { requestPermissions(host, rationale, android.R.string.ok, android.R.string.cancel, requestCode, perms); }
这个方法的第二个参数就是我们给这个权限的解释,即“保存图片需要文件存储权限”。注意到它其实调用了
多两个参数的requestPermissions方法,那两个参数默认是确定和取消。当然你也可以自定义了,这么写:
EasyPermissions.requestPermissions(this, "保存图片需要文件存储", getResources().getString(R.string.refuse), getResources().getString(R.string.gladly_agree), Constants.WRITE_REQUEST_CODE, perms);
第二是:AfterPermissionGranted注解的方法。
public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, @NonNull Object... receivers) { //省略大段代码, 只有当所有的权限都授权成功, 没有权限拒绝时,才会执行 //被AfterPermissionGranted注解的方法 // If 100% successful, call annotated methods if (!granted.isEmpty() && denied.isEmpty()) { runAnnotatedMethods(object, requestCode); } } }
上面看似已经很完美了,但是Google除了一个比较人性化的选项卡,那就是不再询问!!!当有个用户惦记了不再询问而且拒绝了之后,那么就再也不会调出弹框和解释框了,只会走onDenied拒绝的回调。坑死人啊,咋办咧???
终极防护措施
我们先来理一理权限申请的流程,如下图所示:
所以想要解决问题,我们可以在onPermissionsDenied方法里面做文章了。我们可以这样做:
@Override public void onPermissionsDenied(int requestCode, List<String> perms) { switch (requestCode) { case Constants.WRITE_REQUEST_CODE: Toast.makeText(FlatMap1Activity.this, "缺少文件存储,图片保存失败", Toast.LENGTH_SHORT).show(); //在拒绝的这个地方来进行终极处理, 这里防止有人点击了不再提醒的选项 App.getSp().edit().putInt(Constants.REQUEST_CODE_PERMISSION, Constants.WRITE_REQUEST_CODE).commit(); PermissionUtil.showMissingPermissionDialog(this, "存储"); break; default: break; } }
我们会在拒绝后给出一个人为的再给一个弹框,并且详细向用户解释发生了什么,如下图所示:
然后给设置按钮一个点击事件跳转到该应用管理权限的页面。代码如下:
/** * 启动应用的设置 * * @param activity */ public static void startAppSettings(Activity activity) { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse("package:" + activity.getPackageName())); activity.startActivityForResult(intent, App.getSp().getInt(Constants.REQUEST_CODE_PERMISSION, 0)); }
注意到我们用了一个startActivityForResult启动回调,然后在申请权限的页面我们再这么写:
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case Constants.WRITE_REQUEST_CODE: //页面返回后再次执行此方法 writePer(); break; default: break; } }
哦耶,舒服了。恭喜7酱夺下一城!
Github:Demo
以上。
上篇博客:App网络请求实战四:rxjava操作符flatMap使用以及rxjava源码解析
下篇博客:App架构设计实战二:基于MVP模式的相似UI界面复用问题解决方案