摘要:是权限被拒绝,但是没有勾选不再提醒。这样被拒绝后再次申请权限是不会弹框提醒的。用户点击拒绝,并勾选不再提示,下次请求权限时,系统弹窗不会再出现,而且为,此时你的权限申请被用户彻底拒绝,需要跳转到系统设置页手动允许权限。
版权声明:本文已授权微信公众号:Android必修课,转载请申明出处官方权限申请示例:Android6.0以上的系统中,引入了运行时权限检查,运行时权限分为正常权限和危险权限,当我们的App调用了需要危险权限的api时,需要向系统申请权限,系统会弹出一个对话框让用户感知,只有当用户授权以后,App才能正常调用api。
关于危险权限的说明,请参阅官方文档:https://developer.android.goo...
这里采用googleSamples中的权限申请框架EasyPermissions作为例子:
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,EasyPermissions.RationaleCallbacks{ private static final int RC_CAMERA_PERM = 123; private static final int RC_LOCATION_CONTACTS_PERM = 124; @AfterPermissionGranted(RC_CAMERA_PERM) public void cameraTask() { EasyPermissions.requestPermissions( this, getString(R.string.rationale_camera), RC_CAMERA_PERM, Manifest.permission.CAMERA); } @AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM) public void locationAndContactsTask() { EasyPermissions.requestPermissions( this, getString(R.string.rationale_location_contacts), RC_LOCATION_CONTACTS_PERM, LOCATION_AND_CONTACTS); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } @Override public void onPermissionsGranted(int requestCode, @NonNull Listperms) { Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size()); } @Override public void onPermissionsDenied(int requestCode, @NonNull List perms) { if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { new AppSettingsDialog.Builder(this).build().show(); } } }
官方权限申请的例子,代码量相当多,每个涉及危险权限的地方都得写这么一堆代码。
*
改造既然官方例子无法满足我们,那只能自己改造了,首先看看我们最后要实现的效果:
GPermisson.with(this) .permisson(new String[] {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA}) .callback(new PermissionCallback() { @Override public void onPermissionGranted() {} @Override public void shouldShowRational(String permisson) {} @Override public void onPermissonReject(String permisson) {} }).request();
onPermissionGranted是权限申请通过回调。
shouldShowRational是权限被拒绝,但是没有勾选“不再提醒"。
onPermissonReject是权限被拒绝,并且勾选了"不再提醒",即彻底被拒绝
可以看到,相对于官方例子,我们的api简洁了很多,并且流式调用可以让逻辑更容易接受。
怎么实现呢?慢慢看
1.编写权限申请Activity首先,我们封装一个透明的Activity,在该Activity中进行权限申请
/* * 权限申请回调 */ public interface PermissionCallback { void onPermissionGranted(); void shouldShowRational(String permisson); void onPermissonReject(String permisson); } public class PermissionActivity extends Activity { public static final String KEY_PERMISSIONS = "permissions"; private static final int RC_REQUEST_PERMISSION = 100; private static PermissionCallback CALLBACK; /* * 添加一个静态方法方便使用 */ public static void request(Context context, String[] permissions, PermissionCallback callback) { CALLBACK = callback; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_PERMISSIONS, permissions); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); if (!intent.hasExtra(KEY_PERMISSIONS)) { return; } // 当api大于23时,才进行权限申请 String[] permissions = getIntent().getStringArrayExtra(KEY_PERMISSIONS); if (Build.VERSION.SDK_INT >= 23) { requestPermissions(permissions, RC_REQUEST_PERMISSION); } } @TargetApi(23) @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode != RC_REQUEST_PERMISSION) { return; } // 处理申请结果 boolean[] shouldShowRequestPermissionRationale = new boolean[permissions.length]; for (int i = 0; i < permissions.length; ++i) { shouldShowRequestPermissionRationale[i] = shouldShowRequestPermissionRationale(permissions[i]); } this.onRequestPermissionsResult(permissions, grantResults, shouldShowRequestPermissionRationale); } @TargetApi(23) void onRequestPermissionsResult(String[] permissions, int[] grantResults, boolean[] shouldShowRequestPermissionRationale) { int length = permissions.length; int granted = 0; for (int i = 0; i < length; i++) { if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { if (shouldShowRequestPermissionRationale[i] == true){ CALLBACK.shouldShowRational(permissions[i]); } else { CALLBACK.onPermissonReject(permissions[i]); } } else { granted++; } } if (granted == length) { CALLBACK.onPermissionGranted(); } finish(); } }
添加一个透明的主题:
2.封装一个门面类,提供api调用public class GPermisson { // 权限申请回调 private PermissionCallback callback; // 需要申请的权限 private String[] permissions; private Context context; public GPermisson(Context context) { this.context = context; } public static GPermisson with(Context context) { GPermisson permisson = new GPermisson(context); return permisson; } public GPermisson permisson(String[] permissons) { this.permissions = permissons; return this; } public GPermisson callback(PermissionCallback callback) { this.callback = callback; return this; } public void request() { if (permissions == null || permissions.length <= 0) { return; } PermissionActivity.request(context, permissions, callback); } }
至此,我们就简单封装好了一个权限请求库,达到上述效果。
等等,这种方式足够优雅了吗?
想想,每个涉及权限的地方,我们还是需要写一段权限请求代码,还能简化吗?
上一篇我们通过AOP封装了按钮点击的优雅实现,这里一样可以用AOP来简化我们的权限请求。
我们希望一个注解完成权限申请,例如:
@Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS}) private void initView() {}
这样比上面的方法又简化了很多,但是,有个问题:
大家知道,权限申请是会被拒绝的,甚至是会被勾选上“不再提示”,然后再拒绝。这样被拒绝后再次申请权限是不会弹框提醒的。因此,我们需要处理:
用户点击拒绝,但不勾选“不再提示”,下次请求权限时,系统弹窗依然会出现,而且shouldShowRequestPermissionRationale(permission)为true,意思是,用户拒绝了你,你应该显示一段文字或者其他信息,来说服用户允许你的权限申请。
用户点击拒绝,并勾选“不再提示”,下次请求权限时,系统弹窗不会再出现,而且shouldShowRequestPermissionRationale(permission)为false,此时你的权限申请被用户彻底拒绝,需要跳转到系统设置页手动允许权限。
ok,我们知道了@Permission注解里,只有一个权限数组是不够的,我们还需要有一个rationale信息和被彻底拒绝后让用户跳转到设置页的信息。
升级 1.定义注解/** 注意,@Retention需要为RUNTIME,否则运行时时没有这个注解的 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Permission { /* Permissions */ String[] permissions(); /* Rationales */ int[] rationales() default {}; /* Rejects */ int[] rejects() default {}; }
使用int[]而不使用String[],是因为String[]传入的字串无法适配多语言。
2.改写GPermissionpublic class GPermisson { private static PermissionGlobalConfigCallback globalConfigCallback; private PermissionCallback callback; private String[] permissions; private Context context; public GPermisson(Context context) { this.context = context; } public static void init(PermissionGlobalConfigCallback callback) { globalConfigCallback = callback; } static PermissionGlobalConfigCallback getGlobalConfigCallback() { return globalConfigCallback; } public static GPermisson with(Context context) { GPermisson permisson = new GPermisson(context); return permisson; } public GPermisson permisson(String[] permissons) { this.permissions = permissons; return this; } public GPermisson callback(PermissionCallback callback) { this.callback = callback; return this; } public void request() { if (permissions == null || permissions.length <= 0) { return; } PermissionActivity.request(context, permissions, callback); } /** * 写一个接口,将申请被拒绝的上述两种情况交给调用者自行处理,框架内不处理 */ public abstract class PermissionGlobalConfigCallback { abstract public void shouldShowRational(String permission, int ration); abstract public void onPermissonReject(String permission, int reject); } }3.Aspect切面处理类
@Aspect public class PermissionAspect { @Around("execution(@me.baron.gpermission.Permission * *(..))") public void aroundJoinPoint(final ProceedingJoinPoint joinPoint) { try { // 获取方法注解 MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Method method = methodSignature.getMethod(); Permission annotation = method.getAnnotation(Permission.class); // 获取注解参数,这里我们有3个参数需要获取 final String[] permissions = annotation.permissions(); final int[] rationales = annotation.rationales(); final int[] rejects = annotation.rejects(); final List使用 1.引入Aspectj依赖,依赖方式见上一篇:permissionList = Arrays.asList(permissions); // 获取上下文 Object object = joinPoint.getThis(); Context context = null; if (object instanceof FragmentActivity) { context = (FragmentActivity) object; } else if (object instanceof Fragment) { context = ((Fragment) object).getContext(); } else if (object instanceof Service) { context = (Service) object; } // 申请权限 GPermisson.with(context) .permisson(permissions) .callback(new PermissionCallback() { @Override public void onPermissionGranted() { try { // 权限申请通过,执行原方法 joinPoint.proceed(); } catch (Throwable throwable) { throwable.printStackTrace(); } } @Override public void shouldShowRational(String permisson) { // 申请被拒绝,但没有勾选“不再提醒”,这里我们让外部自行处理 int index = permissionList.indexOf(permisson); int rationale = -1; if (rationales.length > index) { rationale = rationales[index]; } GPermisson.getGlobalConfigCallback().shouldShowRational(permisson, rationale); } @Override public void onPermissonReject(String permisson) { // 申请被拒绝,且勾选“不再提醒”,这里我们让外部自行处理 int index = permissionList.indexOf(permisson); int reject = -1; if (rejects.length > index) { reject = rejects[index]; } GPermisson.getGlobalConfigCallback().onPermissonReject(permisson, reject); } }).request(); } catch (Exception e) { e.printStackTrace(); } } }
Android优雅地处理按钮重复点击
2.设置全局权限请求结果监听GPermisson.init(new PermissionGlobalConfigCallback() { @Override public void shouldShowRational(String permission, int ration) { showRationaleDialog(ration); } @Override public void onPermissonReject(String permission, int reject) { showRejectDialog(reject); } }); private void showRationaleDialog(int ration) { new AlertDialog.Builder(MainActivity.this) .setTitle("权限申请") .setMessage(getString(ration)) .show(); } private void showRejectDialog(int reject) { new AlertDialog.Builder(MainActivity.this) .setTitle("权限申请") .setMessage(getString(reject)) .setPositiveButton("跳转到设置页", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 本人魅族手机,其他品牌的设置页跳转逻辑不同,请百度解决 Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.putExtra("packageName", BuildConfig.APPLICATION_ID); startActivity(intent); dialog.dismiss(); } }) .setNegativeButton("取消", null) .show(); }3.在需要权限的地方添加注解:
@Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS}, rationales = {R.string.location_rationale, R.string.contact_rationale}, rejects = {R.string.location_reject, R.string.contact_reject}) private void initView() {}
一旦权限申请被拒绝,将会回调到全局监听中,这里我们只弹窗提醒,若需要其他形式的提醒,自行实现ui即可。运行效果:
如果你们有过组件化开发,就应该马上了解到,我们在上面使用@Permission注解传入的rationale和reject的字符串id,在Module中是会报错的,原因是Module中的R.string.xxx不是final常量,而注解值需要final常量值。
@Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS}, rationales = {R.string.location_rationale, R.string.contact_rationale}, rejects = {R.string.location_reject, R.string.contact_reject}) private void initView() {}
那么,如何处理在Module中的情况呢,这里我想到了一个思路:
既然R.string.xxx不是常量,我们就给注解值传入我们自定义的常量:
public class Permissions { public static final int LOCATION_RATIONALE = 100; public static final int LOCATION_REJECT= 101; public static final int CONTACT_RATIONALE= 102; public static final int CONTACT_REJECT= 103; }
@Permission(permissions = {Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_CONTACTS}, rationales = {Permissions.LOCATION_RATIONALE, Permissions.CONTACT_RATIONALE}, rejects = {Permissions.LOCATION_REJECT, Permissions.CONTACT_REJECT}) private void initView() {}
然后在全局的监听中修改:
GPermisson.init(new PermissionGlobalConfigCallback() { @Override public void shouldShowRational(String permission, int ration) { if (ration == Permissions.LOCATION_RATIONALE) { showRationaleDialog(R.string.location_rationale); } else if (ration == Permissions.CONTACT_RATIONALE) { showRationaleDialog(R.string.contact_rationale); } else { showRationaleDialog(ration); } } @Override public void onPermissonReject(String permission, int reject) { if (reject == Permissions.LOCATION_RATIONALE) { showRejectDialog(R.string.location_reject); } else if (reject == Permissions.CONTACT_RATIONALE) { showRejectDialog(R.string.contact_reject); } else { showRejectDialog(reject); } } });
可能不是那么优雅,如果有好的方式,请留言告知,让大家学习学习……感谢。
源码地址:(https://github.com/DevBraon/G...)
想解锁更多姿势,请关注微信公众号:Android必修课
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77166.html
摘要:和以前在安装的是就申请了权限不同,在,也就之后加入了动态权限。对于一些敏感的权限,决定权交还给了用户,不再是强制申请了。还有办法可以补救一下,就是跳转到设置页面,让用户手动开启权限。网上有个一个流行的开源库,采用注解来快速实现以上的逻辑。 和以前在安装 APP 的是就申请了权限不同,Google 在 API 23,也就 6.0 之后加入了动态权限。对于一些敏感的权限,决定权交还给了用户...
阅读 2958·2021-11-16 11:42
阅读 3619·2021-09-08 09:36
阅读 904·2019-08-30 12:52
阅读 2467·2019-08-29 14:12
阅读 723·2019-08-29 13:53
阅读 3562·2019-08-29 12:16
阅读 627·2019-08-29 12:12
阅读 2452·2019-08-29 11:16