* 本篇文章已受权微信公众号 guolin_blog (郭霖)独家发布java
ActivityCompat.shouldShowRequestPermissionRationale(Activity, String)
没法弹出权限申请对话框
Boss: "mmp,为何展现联系人这块在小米手机显示不出来?"
Programmer: "boss,其余手机都没问题,我这块作了权限申请的,可是小米就是不弹出权限申请对话框,与此同时小米默认受权失败,因此不能读取通信录。"
Boss: "mmp,那这块呢,明明说了读取日历权限成功了,为何仍是没读取到?"
Programmer: "boss,其余手机都没问题,我这块作了权限申请的,可是小米就是不弹出权限申请对话框,与此同时小米默认受权成功,可是其实是受权失败的。"
Boss: "mmp,那这块呢,明明我拒绝授予权限,为何你提示我受权成功?"
Programmer: "boss,其余手机都没问题,我这块作了权限申请的,小米弹出权限申请对话框,与此同时你点了拒绝,可是小米作了手脚,实际上调用了受权成功的方法。"
Boss: "你有个毛用?测试机我都给你买好了,还这么菜,收拾收拾滚蛋吧。"
Programmer: "f**k 小米!"android
原生 Android 请求方式在小米等国内机型上适配的情形,相信有部分读者已经有过经历,这里就不作原生测试了,拿出国内一个比较有名的权限申请框架咱们来看看:git
小米申请地理位置:
github
小米申请联系人:
数组
小米申请手机状态:
bash
其实不只仅是小米,国内其它手机也会有同样的问题,笔者再作了一份 oppo a57 的截图:微信
能够看到,在申请过程当中并无任何弹窗弹出,而且提示受权成功,而实际上咱们到权限管理界面能够看到并未获得权限。app
下图是使用 permissions4m 的效果:框架
permissions4m 申请地理位置(小米):
异步
permissions4m 申请联系人(小米):
permissions4m 申请手机状态(小米):
permissions4m 申请短信、日历(OPPO A57)
咱们能够看到弹出了权限申请对话框,并且授予权限的状况下确实得到了权限。
生活中,不管是做为开发者仍是普通用户,应该都有接触到过 5.0+ 的小米/魅族手机,使用过这些手机的读者们应该还有些许印象——部分国产手机早在 android 6.0 以前,也就是在 google 推出动态权限以前就有了权限申请,而国产的 5.0 权限申请使用 6.0 的权限申请代码是行不通的,理由很简单——在5.0的系统源码里没有6.0权限申请的源码,这个问题在 permissions4m 2.0.0
版本中已经迎刃而解了,这意味着从 2.0.0
版本开始, permissions4m 开始支持国产手机 5.0 权限申请了。
简介中只是节选了部份内容,更详细完整的请移至项目:github.com/jokermonn/p…。
注:截止笔者发布博客为止,permissions4m 最新版本为 2.0.0
project
中的 build.gradle
:
buildscript {
// ...
}
allprojects {
repositories {
// 请添加以下一行
maven { url 'https://jitpack.io' }
}
}复制代码
app
中的 build.gradle
:
dependencies {
compile 'com.github.jokermonn:permissions4m:2.0.0-lib'
annotationProcessor 'com.github.jokermonn:permissions4m:2.0.0-processor'
}复制代码
在须要权限申请的地方调用
Permissions4M.get(MainActivity.this)
// 是否强制弹出权限申请对话框,建议为 true
.requestForce(true)
// 权限
.requestPermission(Manifest.permission.RECORD_AUDIO)
// 权限码
.requestCode(AUDIO_CODE)
// 若是须要使用 @PermissionNonRationale 注解的话,建议添加以下一行
// 返回的 intent 是跳转至**系统设置页面**
// .requestPageType(Permissions4M.PageType.MANAGER_PAGE)
// 返回的 intent 是跳转至**手机管家页面**
// .requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
.request();复制代码
如:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Permissions4M.get(MainActivity.this)
.requestForce(true)
.requestPageType(Permissions4M.PageType.MANAGER_PAGE)
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();
}
});复制代码
而后将会回调相应的 @PermissionsGranted
、@PermissionsDenied
、@PermissionsRationale
/PermissionsCustomRationale
、@PermissionsNonRationale
所修饰的方法
受权成功时回调,注解中须要传入参数,分为两种状况:
单参数:@PermissionsGranted(LOCATION_CODE)
,被修饰函数无需传入参数,例:
@PermissionsGranted(LOCATION_CODE)
public void granted() {
ToastUtil.show("地理位置受权成功");
}复制代码
多参数:@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修饰函数须要传入一个 int 参数,例:
@PermissionsGranted({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void granted(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("地理位置权限受权成功");
break;
case SENSORS_CODE:
ToastUtil.show("传感器权限受权成功");
break;
case CALENDAR_CODE:
ToastUtil.show("读取日历权限受权成功");
break;
default:
break;
}
}复制代码
受权失败时回调,注解中须要传入参数,分为两种状况:
单参数:@PermissionsDenied(LOCATION_CODE)
,被修饰函数无需传入参数,例:
@PermissionsDenied(LOCATION_CODE)
public void denied() {
}复制代码
多参数:@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修饰函数须要传入一个 int 参数,例:
@PermissionsDenied({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void denied(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("地理位置权限受权失败");
break;
case SENSORS_CODE:
ToastUtil.show("传感器权限受权失败");
break;
case CALENDAR_CODE:
ToastUtil.show("读取日历权限受权失败");
break;
default:
break;
}
}复制代码
二次受权时回调,用于解释为什么须要此权限,注解中须要传入参数,分为两种状况:
单参数:@PermissionsRationale(LOCATION_CODE)
,被修饰函数无需传入参数,例:
@PermissionsRationale(LOCATION_CODE)
public void rationale() {
ToastUtil.show("请开启读取地理位置权限");
}复制代码
多参数:@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修饰函数须要传入一个 int 参数,例:
@PermissionsRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
public void rationale(int code) {
switch (code) {
case LOCATION_CODE:
ToastUtil.show("请开启地理位置权限受权");
break;
case SENSORS_CODE:
ToastUtil.show("请开启传感器权限受权");
break;
case CALENDAR_CODE:
ToastUtil.show("请开启读取日历权限受权");
break;
default:
break;
}复制代码
注:系统弹出权限申请 dialog 与 toast 提示是异步操做,因此若是存在但愿自行弹出一个 dialog 后(或其余同步需求)再弹出系统对话框,那么请使用 @PermissionsCustomRationale
二次受权时回调,用于解释为什么须要此权限,注解中须要传入参数,分为两种状况:
@PermissionsCustomRationale(LOCATION_CODE)
,被修饰函数无需传入参数,例:@PermissionsCustomRationale(LOCATION_CODE)
public void cusRationale() {
new AlertDialog.Builder(this)
.setMessage("读取地理位置权限申请:\n咱们须要您开启读取地理位置权限(in activity with annotation)")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意添加 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.READ_SMS)
.requestCode(SMS_CODE)
.request();
}
})
.show();
}复制代码
@PermissionsCustomRationale({LOCATION_CODE, SENSORS_CODE, CALENDAR_CODE})
,被修饰函数须要传入一个 int 参数,例:@PermissionsCustomRationale({SMS_CODE, AUDIO_CODE})
public void cusRationale(int code) {
switch (code) {
case SMS_CODE:
new AlertDialog.Builder(this)
.setMessage("短信权限申请:\n咱们须要您开启短信权限(in activity with annotation)")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意添加 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.READ_SMS)
.requestCode(SMS_CODE)
.request();
}
})
.show();
break;
case AUDIO_CODE:
new AlertDialog.Builder(this)
.setMessage("录音权限申请:\n咱们须要您开启录音权限(in activity with annotation)")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Permissions4M.get(MainActivity.this)
// 注意添加 .requestOnRationale()
.requestOnRationale()
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();
}
})
.show();
break;
default:
break;
}复制代码
注:除上述之外的 dialog,开发者能够自定义其余展现效果,调用权限申请时请使用,不然会陷入无限调用自定义 Rationale 循环中:
Permissions4M.get(MainActivity.this)
// 务必添加下列一行
.requestOnRationale()
.requestPermission(Manifest.permission.RECORD_AUDIO)
.requestCode(AUDIO_CODE)
.request();复制代码
当用户点击拒绝权限且再也不提示(国产畸形权限适配扩展)状况下调用,此时意味着不管是 @PermissionsCustomRationale 或者 @PermissionsRationale 都不会被调用,没法给予用户提示,此时该注解修饰的函数被调用,注解中须要传入参数,分为两种状况:
单参数:@PermissionsNonRationale(LOCATION_CODE)
,被修饰函数只需传入 Intent 参数,例:
@PermissionsNonRationale({LOCATION_CODE})
public void nonRationale(Intent intent) {
startActivity(intent);
}复制代码
多参数:@PermissionsNonRationale(AUDIO_CODE, CALL_LOG_CODE)
,被修饰函数需传入 int 参数和 Intent 参数,例:
@PermissionsNonRationale({AUDIO_CODE, CALL_LOG_CODE})
public void nonRationale(int code, final Intent intent) {
switch (code) {
case AUDIO_CODE:
new AlertDialog.Builder(MainActivity.this)
.setMessage("读取录音权限申请:\n咱们须要您开启读取录音权限")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(intent);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.show();
break;
case CALL_LOG_CODE:
new AlertDialog.Builder(MainActivity.this)
.setMessage("读取通话记录权限申请:\n咱们须要您开启读取通话记录权限")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(intent);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.show();
break;
default:
break;
}
}复制代码
Intent 类型为两种,一种是跳转至系统设置页面,另外一种是跳至手机管家页面,而具体的设置方法请参考 注解回调 中 .requestPageType(int)
设置方法。
例:
Permissions4M.get(MainActivity.this)
// 是否强制弹出权限申请对话框
.requestForce(true)
// 权限
.requestPermission(Manifest.permission.READ_CONTACTS)
// 权限码
.requestCode(READ_CONTACTS_CODE)
// 权限请求结果
.requestCallback(new Wrapper.PermissionRequestListener() {
@Override
public void permissionGranted() {
ToastUtil.show("读取通信录权限成功 in activity with listener");
}
@Override
public void permissionDenied() {
ToastUtil.show("读取通信录权失败 in activity with listener");
}
@Override
public void permissionRationale() {
ToastUtil.show("请打开读取通信录权限 in activity with listener");
}
})
// 权限彻底被禁时回调函数中返回 intent 类型(手机管家界面)
.requestPageType(Permissions4M.PageType.MANAGER_PAGE)
// 权限彻底被禁时回调函数中返回 intent 类型(系统设置界面)
//.requestPageType(Permissions4M.PageType.ANDROID_SETTING_PAGE)
// 权限彻底被禁时回调,接口函数中的参数 Intent 是由上一行决定的
.requestPage(new Wrapper.PermissionPageListener() {
@Override
public void pageIntent(final Intent intent) {
new AlertDialog.Builder(MainActivity.this)
.setMessage("读取通信录权限申请:\n咱们须要您开启读取通信录权限(in activity with listener)")
.setPositiveButton("肯定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
startActivity(intent);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
})
.show();
}
})
.request();复制代码
使用 @PermissionsRequestSync
修饰 Activity 或 Fragment
传入两组参数
使用 Permissions4M.get(MainActivity.this).requestSync();
进行同步权限申请
例:参考 sample 中 MainActivity 上的设置 ——
@PermissionsRequestSync(
permission = {Manifest.permission.BODY_SENSORS,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.READ_CALENDAR},
value = {SENSORS_CODE,
LOCATION_CODE,
CALENDAR_CODE})
public class MainActivity extends AppCompatActivity复制代码
jokerzoc.cn@gmail.com
,谢谢。