资讯专栏INFORMATION COLUMN

Android优雅地申请动态权限

hedzr / 3217人阅读

摘要:是权限被拒绝,但是没有勾选不再提醒。这样被拒绝后再次申请权限是不会弹框提醒的。用户点击拒绝,并勾选不再提示,下次请求权限时,系统弹窗不会再出现,而且为,此时你的权限申请被用户彻底拒绝,需要跳转到系统设置页手动允许权限。

</>复制代码

  1. 版权声明:本文已授权微信公众号:Android必修课,转载请申明出处

    Android6.0以上的系统中,引入了运行时权限检查,运行时权限分为正常权限和危险权限,当我们的App调用了需要危险权限的api时,需要向系统申请权限,系统会弹出一个对话框让用户感知,只有当用户授权以后,App才能正常调用api

  2. 关于危险权限的说明,请参阅官方文档:https://developer.android.goo...

官方权限申请示例:

这里采用googleSamples中的权限申请框架EasyPermissions作为例子:

</>复制代码

  1. public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,EasyPermissions.RationaleCallbacks{
  2. private static final int RC_CAMERA_PERM = 123;
  3. private static final int RC_LOCATION_CONTACTS_PERM = 124;
  4. @AfterPermissionGranted(RC_CAMERA_PERM)
  5. public void cameraTask() {
  6. EasyPermissions.requestPermissions(
  7. this,
  8. getString(R.string.rationale_camera),
  9. RC_CAMERA_PERM,
  10. Manifest.permission.CAMERA);
  11. }
  12. @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
  13. public void locationAndContactsTask() {
  14. EasyPermissions.requestPermissions(
  15. this,
  16. getString(R.string.rationale_location_contacts),
  17. RC_LOCATION_CONTACTS_PERM,
  18. LOCATION_AND_CONTACTS);
  19. }
  20. @Override
  21. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  22. super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  23. EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
  24. }
  25. @Override
  26. public void onPermissionsGranted(int requestCode, @NonNull List perms) {
  27. Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
  28. }
  29. @Override
  30. public void onPermissionsDenied(int requestCode, @NonNull List perms) {
  31. if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
  32. new AppSettingsDialog.Builder(this).build().show();
  33. }
  34. }
  35. }

官方权限申请的例子,代码量相当多,每个涉及危险权限的地方都得写这么一堆代码。

*

改造

既然官方例子无法满足我们,那只能自己改造了,首先看看我们最后要实现的效果:

</>复制代码

  1. GPermisson.with(this)
  2. .permisson(new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA})
  3. .callback(new PermissionCallback() {
  4. @Override
  5. public void onPermissionGranted() {}
  6. @Override
  7. public void shouldShowRational(String permisson) {}
  8. @Override
  9. public void onPermissonReject(String permisson) {}
  10. }).request();

onPermissionGranted是权限申请通过回调。

shouldShowRational是权限被拒绝,但是没有勾选“不再提醒"。

onPermissonReject是权限被拒绝,并且勾选了"不再提醒",即彻底被拒绝

可以看到,相对于官方例子,我们的api简洁了很多,并且流式调用可以让逻辑更容易接受。

怎么实现呢?慢慢看

1.编写权限申请Activity

首先,我们封装一个透明的Activity,在该Activity中进行权限申请

</>复制代码

  1. /*
  2. * 权限申请回调
  3. */
  4. public interface PermissionCallback {
  5. void onPermissionGranted();
  6. void shouldShowRational(String permisson);
  7. void onPermissonReject(String permisson);
  8. }
  9. public class PermissionActivity extends Activity {
  10. public static final String KEY_PERMISSIONS = "permissions";
  11. private static final int RC_REQUEST_PERMISSION = 100;
  12. private static PermissionCallback CALLBACK;
  13. /*
  14. * 添加一个静态方法方便使用
  15. */
  16. public static void request(Context context, String[] permissions, PermissionCallback callback) {
  17. CALLBACK = callback;
  18. Intent intent = new Intent(context, PermissionActivity.class);
  19. intent.putExtra(KEY_PERMISSIONS, permissions);
  20. intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  21. context.startActivity(intent);
  22. }
  23. @Override
  24. protected void onCreate(@Nullable Bundle savedInstanceState) {
  25. super.onCreate(savedInstanceState);
  26. Intent intent = getIntent();
  27. if (!intent.hasExtra(KEY_PERMISSIONS)) {
  28. return;
  29. }
  30. // 当api大于23时,才进行权限申请
  31. String[] permissions = getIntent().getStringArrayExtra(KEY_PERMISSIONS);
  32. if (Build.VERSION.SDK_INT >= 23) {
  33. requestPermissions(permissions, RC_REQUEST_PERMISSION);
  34. }
  35. }
  36. @TargetApi(23)
  37. @Override
  38. public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
  39. if (requestCode != RC_REQUEST_PERMISSION) {
  40. return;
  41. }
  42. // 处理申请结果
  43. boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length];
  44. for (int i = 0; i < permissions.length; ++i) {
  45. shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]);
  46. }
  47. this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale);
  48. }
  49. @TargetApi(23)
  50. void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) {
  51. int length = permissions.length;
  52. int granted = 0;
  53. for (int i = 0; i < length; i++) {
  54. if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
  55. if (shouldShowRequestPermissionRationale[i] == true){
  56. CALLBACK.shouldShowRational(permissions[i]);
  57. } else {
  58. CALLBACK.onPermissonReject(permissions[i]);
  59. }
  60. } else {
  61. granted++;
  62. }
  63. }
  64. if (granted == length) {
  65. CALLBACK.onPermissionGranted();
  66. }
  67. finish();
  68. }
  69. }

添加一个透明的主题:

</>复制代码

2.封装一个门面类,提供api调用

</>复制代码

  1. public class GPermisson {
  2. // 权限申请回调
  3. private PermissionCallback callback;
  4. // 需要申请的权限
  5. private String[] permissions;
  6. private Context context;
  7. public GPermisson(Context context) {
  8. this.context = context;
  9. }
  10. public static GPermisson with(Context context) {
  11. GPermisson permisson = new GPermisson(context);
  12. return permisson;
  13. }
  14. public GPermisson permisson(String[] permissons) {
  15. this.permissions = permissons;
  16. return this;
  17. }
  18. public GPermisson callback(PermissionCallback callback) {
  19. this.callback = callback;
  20. return this;
  21. }
  22. public void request() {
  23. if (permissions == null || permissions.length <= 0) {
  24. return;
  25. }
  26. PermissionActivity.request(context, permissions, callback);
  27. }
  28. }

至此,我们就简单封装好了一个权限请求库,达到上述效果。

等等,这种方式足够优雅了吗?

想想,每个涉及权限的地方,我们还是需要写一段权限请求代码,还能简化吗?

上一篇我们通过AOP封装了按钮点击的优雅实现,这里一样可以用AOP来简化我们的权限请求。

我们希望一个注解完成权限申请,例如:

</>复制代码

  1. @Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS})
  2. private void initView() {}

这样比上面的方法又简化了很多,但是,有个问题:

大家知道,权限申请是会被拒绝的,甚至是会被勾选上“不再提示”,然后再拒绝。这样被拒绝后再次申请权限是不会弹框提醒的。因此,我们需要处理:

用户点击拒绝,但不勾选“不再提示”,下次请求权限时,系统弹窗依然会出现,而且shouldShowRequestPermissionRationale(permission)为true,意思是,用户拒绝了你,你应该显示一段文字或者其他信息,来说服用户允许你的权限申请。

用户点击拒绝,并勾选“不再提示”,下次请求权限时,系统弹窗不会再出现,而且shouldShowRequestPermissionRationale(permission)为false,此时你的权限申请被用户彻底拒绝,需要跳转到系统设置页手动允许权限。

ok,我们知道了@Permission注解里,只有一个权限数组是不够的,我们还需要有一个rationale信息和被彻底拒绝后让用户跳转到设置页的信息。

升级 1.定义注解

</>复制代码

  1. /** 注意,@Retention需要为RUNTIME,否则运行时时没有这个注解的 */
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target(ElementType.METHOD)
  4. public @interface Permission {
  5. /* Permissions */
  6. String[] permissions();
  7. /* Rationales */
  8. int[] rationales() default {};
  9. /* Rejects */
  10. int[] rejects() default {};
  11. }

使用int[]而不使用String[],是因为String[]传入的字串无法适配多语言。

2.改写GPermission

</>复制代码

  1. public class GPermisson {
  2. private static PermissionGlobalConfigCallback globalConfigCallback;
  3. private PermissionCallback callback;
  4. private String[] permissions;
  5. private Context context;
  6. public GPermisson(Context context) {
  7. this.context = context;
  8. }
  9. public static void init(PermissionGlobalConfigCallback callback) {
  10. globalConfigCallback = callback;
  11. }
  12. static PermissionGlobalConfigCallback getGlobalConfigCallback() {
  13. return globalConfigCallback;
  14. }
  15. public static GPermisson with(Context context) {
  16. GPermisson permisson = new GPermisson(context);
  17. return permisson;
  18. }
  19. public GPermisson permisson(String[] permissons) {
  20. this.permissions = permissons;
  21. return this;
  22. }
  23. public GPermisson callback(PermissionCallback callback) {
  24. this.callback = callback;
  25. return this;
  26. }
  27. public void request() {
  28. if (permissions == null || permissions.length <= 0) {
  29. return;
  30. }
  31. PermissionActivity.request(context, permissions, callback);
  32. }
  33. /**
  34. * 写一个接口,将申请被拒绝的上述两种情况交给调用者自行处理,框架内不处理
  35. */
  36. public abstract class PermissionGlobalConfigCallback {
  37. abstract public void shouldShowRational(String permission, int ration);
  38. abstract public void onPermissonReject(String permission, int reject);
  39. }
  40. }
3.Aspect切面处理类

</>复制代码

  1. @Aspect
  2. public class PermissionAspect {
  3. @Around("execution(@me.baron.gpermission.Permission * *(..))")
  4. public void aroundJoinPoint(final ProceedingJoinPoint joinPoint) {
  5. try {
  6. // 获取方法注解
  7. MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  8. Method method = methodSignature.getMethod();
  9. Permission annotation = method.getAnnotation(Permission.class);
  10. // 获取注解参数,这里我们有3个参数需要获取
  11. final String[] permissions = annotation.permissions();
  12. final int[] rationales = annotation.rationales();
  13. final int[] rejects = annotation.rejects();
  14. final List permissionList = Arrays.asList(permissions);
  15. // 获取上下文
  16. Object object = joinPoint.getThis();
  17. Context context = null;
  18. if (object instanceof FragmentActivity) {
  19. context = (FragmentActivity) object;
  20. } else if (object instanceof Fragment) {
  21. context = ((Fragment) object).getContext();
  22. } else if (object instanceof Service) {
  23. context = (Service) object;
  24. }
  25. // 申请权限
  26. GPermisson.with(context)
  27. .permisson(permissions)
  28. .callback(new PermissionCallback() {
  29. @Override
  30. public void onPermissionGranted() {
  31. try {
  32. // 权限申请通过,执行原方法
  33. joinPoint.proceed();
  34. } catch (Throwable throwable) {
  35. throwable.printStackTrace();
  36. }
  37. }
  38. @Override
  39. public void shouldShowRational(String permisson) {
  40. // 申请被拒绝,但没有勾选“不再提醒”,这里我们让外部自行处理
  41. int index = permissionList.indexOf(permisson);
  42. int rationale = -1;
  43. if (rationales.length > index) {
  44. rationale = rationales[index];
  45. }
  46. GPermisson.getGlobalConfigCallback().shouldShowRational(permisson, rationale);
  47. }
  48. @Override
  49. public void onPermissonReject(String permisson) {
  50. // 申请被拒绝,且勾选“不再提醒”,这里我们让外部自行处理
  51. int index = permissionList.indexOf(permisson);
  52. int reject = -1;
  53. if (rejects.length > index) {
  54. reject = rejects[index];
  55. }
  56. GPermisson.getGlobalConfigCallback().onPermissonReject(permisson, reject);
  57. }
  58. }).request();
  59. } catch (Exception e) {
  60. e.printStackTrace();
  61. }
  62. }
  63. }
使用 1.引入Aspectj依赖,依赖方式见上一篇:

Android优雅地处理按钮重复点击

2.设置全局权限请求结果监听

</>复制代码

  1. GPermisson.init(new PermissionGlobalConfigCallback() {
  2. @Override
  3. public void shouldShowRational(String permission, int ration) {
  4. showRationaleDialog(ration);
  5. }
  6. @Override
  7. public void onPermissonReject(String permission, int reject) {
  8. showRejectDialog(reject);
  9. }
  10. });
  11. private void showRationaleDialog(int ration) {
  12. new AlertDialog.Builder(MainActivity.this)
  13. .setTitle("权限申请")
  14. .setMessage(getString(ration))
  15. .show();
  16. }
  17. private void showRejectDialog(int reject) {
  18. new AlertDialog.Builder(MainActivity.this)
  19. .setTitle("权限申请")
  20. .setMessage(getString(reject))
  21. .setPositiveButton("跳转到设置页", new DialogInterface.OnClickListener() {
  22. @Override
  23. public void onClick(DialogInterface dialog, int which) {
  24. // 本人魅族手机,其他品牌的设置页跳转逻辑不同,请百度解决
  25. Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC");
  26. intent.addCategory(Intent.CATEGORY_DEFAULT);
  27. intent.putExtra("packageName", BuildConfig.APPLICATION_ID);
  28. startActivity(intent);
  29. dialog.dismiss();
  30. }
  31. })
  32. .setNegativeButton("取消", null)
  33. .show();
  34. }
3.在需要权限的地方添加注解:

</>复制代码

  1. @Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS},
  2. rationales = {R.string.location_rationale, R.string.contact_rationale},
  3. rejects = {R.string.location_reject, R.string.contact_reject})
  4. private void initView() {}

一旦权限申请被拒绝,将会回调到全局监听中,这里我们只弹窗提醒,若需要其他形式的提醒,自行实现ui即可。运行效果:

注意

如果你们有过组件化开发,就应该马上了解到,我们在上面使用@Permission注解传入的rationale和reject的字符串id,在Module中是会报错的,原因是Module中的R.string.xxx不是final常量,而注解值需要final常量值。

</>复制代码

  1. @Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS},
  2. rationales = {R.string.location_rationale, R.string.contact_rationale},
  3. rejects = {R.string.location_reject, R.string.contact_reject})
  4. private void initView() {}

那么,如何处理在Module中的情况呢,这里我想到了一个思路:

既然R.string.xxx不是常量,我们就给注解值传入我们自定义的常量:

</>复制代码

  1. public class Permissions {
  2. public static final int LOCATION_RATIONALE = 100;
  3. public static final int LOCATION_REJECT= 101;
  4. public static final int CONTACT_RATIONALE= 102;
  5. public static final int CONTACT_REJECT= 103;
  6. }

</>复制代码

  1. @Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS},
  2. rationales = {Permissions.LOCATION_RATIONALE, Permissions.CONTACT_RATIONALE},
  3. rejects = {Permissions.LOCATION_REJECT, Permissions.CONTACT_REJECT})
  4. private void initView() {}

然后在全局的监听中修改:

</>复制代码

  1. GPermisson.init(new PermissionGlobalConfigCallback() {
  2. @Override
  3. public void shouldShowRational(String permission, int ration) {
  4. if (ration == Permissions.LOCATION_RATIONALE) {
  5. showRationaleDialog(R.string.location_rationale);
  6. } else if (ration == Permissions.CONTACT_RATIONALE) {
  7. showRationaleDialog(R.string.contact_rationale);
  8. } else {
  9. showRationaleDialog(ration);
  10. }
  11. }
  12. @Override
  13. public void onPermissonReject(String permission, int reject) {
  14. if (reject == Permissions.LOCATION_RATIONALE) {
  15. showRejectDialog(R.string.location_reject);
  16. } else if (reject == Permissions.CONTACT_RATIONALE) {
  17. showRejectDialog(R.string.contact_reject);
  18. } else {
  19. showRejectDialog(reject);
  20. }
  21. }
  22. });

可能不是那么优雅,如果有好的方式,请留言告知,让大家学习学习……感谢。
源码地址:(https://github.com/DevBraon/G...)

想解锁更多姿势,请关注微信公众号:Android必修课

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/77166.html

相关文章

  • Android动态权限

    摘要:和以前在安装的是就申请了权限不同,在,也就之后加入了动态权限。对于一些敏感的权限,决定权交还给了用户,不再是强制申请了。还有办法可以补救一下,就是跳转到设置页面,让用户手动开启权限。网上有个一个流行的开源库,采用注解来快速实现以上的逻辑。 和以前在安装 APP 的是就申请了权限不同,Google 在 API 23,也就 6.0 之后加入了动态权限。对于一些敏感的权限,决定权交还给了用户...

    firim 评论0 收藏0

发表评论

0条评论

hedzr

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<