前言
截止当前时间,Android版本已经到了O,Android 6.0引入动态权限机制,到目前随着Android 6.0以及以上版本普及,动态申请权限变得越来越重要,也是大多数APP的通用机制,目前我们的APP涉及到的权限也不少,为了得到更好的用户体验,应该在需要权限时,动态申请,还有需要在第一次被拒绝之后能够保证再次申请。
动态申请权限列表
目前危险等级的权限都需要动态申请,正常等级的权限不需要动态申请。Android官网有正常权限列表:正常权限列表
| 所属权限组 | 权限名 | 权限等级 | 解释 |
|---|---|---|---|
| 日历 | READ_CALENDAR | 危险 | 允许应用程序读取用户的日历数据 |
| 日历 | WRITE_CALENDAR | 危险 | 允许应用程序写入用户的日历数据 |
| 相机 | CAMERA | 危险 | 使用摄像头做相关工作 |
| 联系人 | READ_CONTACTS | 危险 | 读取联系人 |
| 联系人 | WRITE_CONTACTS | 危险 | 写入联系人 |
| 联系人 | GET_ACCOUNTS | 危险 | 允许访问帐户服务中的帐户列表 |
| 位置 | ACCESS_FINE_LOCATION | 危险 | 允许应用访问精确位置 |
| 位置 | ACCESS_COARSE_LOCATION | 危险 | 允许应用访问大致位置 |
| 麦克风 | RECORD_AUDIO | 危险 | 麦克风的使用 |
| 电话 | READ_PHONE_STATE | 危险 | 允许对电话状态进行只读访问,包括设备的电话号码,当前蜂窝网络信息,任何正在进行的呼叫的状态以及设备上注册的任何PhoneAccounts列表 |
| 电话 | CALL_PHONE | 危险 | 允许应用程序在不通过拨号器用户界面的情况下发起电话呼叫,以便用户确认呼叫 |
| 电话 | READ_CALL_LOG | 危险 | 允许应用程序读取用户的通话记录 |
| 电话 | WRITE_CALL_LOG | 危险 | 允许应用程序写入(但不读取)用户的呼叫日志数据 |
| 电话 | ADD_VOICEMAIL | 危险 | 允许应用程序将语音邮件添加到系统中 |
| 电话 | USE_SIP | 危险 | 允许应用程序使用SIP服务 |
| 电话 | PROCESS_OUTGOING_CALLS | 危险 | 允许应用程序查看拨出呼叫期间拨打的号码,并选择将呼叫重定向到其他号码或完全中止呼叫 |
| 传感器 | BODY_SENSORS | 危险 | 允许应用程序访问来自传感器的数据 |
| 短信 | SEND_SMS | 危险 | 允许应用程序发送SMS消息 |
| 短信 | RECEIVE_SMS | 危险 | 允许应用程序接收SMS消息 |
| 短信 | READ_SMS | 危险 | 允许应用程序读取SMS消息 |
| 短信 | RECEIVE_WAP_PUSH | 危险 | 允许应用程序接收WAP推送消息 |
| 短信 | RECEIVE_MMS | 危险 | 允许应用程序监视传入的MMS消息(彩信) |
| 存储 | READ_EXTERNAL_STORAGE | 危险 | 允许应用程序从外部存储读取 |
| 存储 | WRITE_EXTERNAL_STORAGE | 危险 | 允许应用程序写入外部存储 |
tip:如果应用程序请求在AndroidManifest中列出的危险权限,并且应用程序已经在同一权限组中具有另一个危险权限,系统会立即授予权限,而不会弹框确认。即如果一个应用程序已经请求并被授予READ_CONTACTS权限,然后它请求WRITE_CONTACTS,系统会立即授予该权限,不会再弹出权限授予弹框。
如何申请权限
第一步:在AndroidManifest.xml静态申请权限
第二步:代码申请(单一权限申请):
public void requestPermission() {
//判断是否已经赋予权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.权限名)
!= PackageManager.PERMISSION_GRANTED) {
//之前请求已经被拒绝,返回 true。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.权限名)) {
//这里写再次申请逻辑,可以多写点申请理由,防止再次被拒绝
} else {
//申请权限可以写多个权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.权限名}, 1);
}
}
}
第三步:处理请求回执:
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PERMISSION_GRANTED) {
// 申请成功的权限
} else {
// 被拒绝的权限
}
}
}
}
权限处理测试
我们为了兼容6.0权限的时候可能图方便采用不更改代码的方式来解决这个问题,即targetSdkVersion<23,这样做权限申请会在APP一开始的时候弹出,用户一上来很蒙,为啥需要我语音权限,为啥要读我的通讯录,但是如果到达指定场景的时候才申请指定权限的话,用户往往可以接受,所以我们要写一个demo,模拟这种场景,解决两个问题:
1,在指定位置申请权限;
2,权限被拒绝之后尝试再次申请逻辑;
为了更好的解决这些问题,我们简单画一下流程图:
为了完成这个过程初步书写了相关的代码:
首先我们把所有的权限申请放到工具类,并简单对权限分类:
public class PermissionUtils {
public static final int REQUESTPERMISSIONCODE = 0x1234;
/**
* 权限申请公用方法
*/
public static void requestPermissions(final Activity activity, String... permissions) {
final List<String> requestPermissions = new ArrayList<>();
List<String> reRequestPermissions = new ArrayList<>();
// 过滤已有权限
for (int i = 0 ;i < permissions.length;i++){
//判断是否已经赋予权限
if (ContextCompat.checkSelfPermission(activity,
permissions[i]) != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
permissions[i])) {
//这里写再次申请逻辑
reRequestPermissions.add(permissions[i]);
}
requestPermissions.add(permissions[i]);
}
}
if (!reRequestPermissions.isEmpty()){
String dialogInfo = "我们申请";
for (String pesmission:reRequestPermissions){
dialogInfo = dialogInfo + PermissionUtils.getDialogName(pesmission) + ",";
}
dialogInfo = dialogInfo + "是为了正常使用相关功能";
showNormalDialog(activity,dialogInfo,"开启权限", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 申请权限
if (!requestPermissions.isEmpty()){
ActivityCompat.requestPermissions(activity,
requestPermissions.toArray(new String[requestPermissions.size()]), REQUESTPERMISSIONCODE);
}
}
});
}else {
// 申请权限
if (!requestPermissions.isEmpty()){
ActivityCompat.requestPermissions(activity,
requestPermissions.toArray(new String[requestPermissions.size()]), REQUESTPERMISSIONCODE);
}
}
}
/**
* 简单弹框提示
* @param context
* @param dialogInfo
* @param conform
* @param listener
*/
public static void showNormalDialog(Context context,String dialogInfo,
String conform, DialogInterface.OnClickListener listener){
final AlertDialog.Builder normalDialog =
new AlertDialog.Builder(context);
normalDialog.setIcon(R.drawable.ic_launcher_background);
normalDialog.setTitle("提示");
normalDialog.setMessage(dialogInfo);
normalDialog.setPositiveButton(conform, listener);
normalDialog.show();
}
/**
* 跳转到权限设置界面
**/
public static void getAppDetailSettingIntent(Context context){
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT >= 9){
intent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
intent.setData(Uri.fromParts("package", context.getPackageName(), null));
} else if(Build.VERSION.SDK_INT <= 8){
intent.setAction(Intent.ACTION_VIEW);
intent.setClassName("com.android.settings","com.android.settings.InstalledAppDetails");
intent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName());
}
context.startActivity(intent);
}
/**
* 简单权限转文字
* @param permission
* @return
*/
public static String getDialogName(String permission){
switch (permission){
case Manifest.permission.READ_CALENDAR:
case Manifest.permission.WRITE_CALENDAR:
return "日历数据";
case Manifest.permission.CAMERA:
return "相机";
case Manifest.permission.READ_CONTACTS:
case Manifest.permission.WRITE_CONTACTS:
case Manifest.permission.GET_ACCOUNTS:
return "联系人";
case Manifest.permission.ACCESS_FINE_LOCATION:
case Manifest.permission.ACCESS_COARSE_LOCATION:
return "位置";
case Manifest.permission.RECORD_AUDIO:
return "麦克风";
case Manifest.permission.READ_PHONE_STATE:
case Manifest.permission.CALL_PHONE:
case Manifest.permission.READ_CALL_LOG:
case Manifest.permission.WRITE_CALL_LOG:
case Manifest.permission.ADD_VOICEMAIL:
case Manifest.permission.USE_SIP:
case Manifest.permission.PROCESS_OUTGOING_CALLS:
return "电话";
case Manifest.permission.BODY_SENSORS:
return "传感器";
case Manifest.permission.SEND_SMS:
case Manifest.permission.RECEIVE_SMS:
case Manifest.permission.READ_SMS:
case Manifest.permission.RECEIVE_WAP_PUSH:
case Manifest.permission.RECEIVE_MMS:
return "短信";
case Manifest.permission.READ_EXTERNAL_STORAGE:
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
return "存储";
}
return null;
}
}
然后在Activity申请权限,我们列举单个权限,多个权限、和正常等级权限申请(NFC):
@Override
public void onClick(View v) {
switch (v.getId()) {
// 位置
case R.id.btn_location:
PermissionUtils.requestPermissions(MainActivity.this,Manifest.permission.ACCESS_FINE_LOCATION);
break;
// 位置、相机
case R.id.btn_camera:
PermissionUtils.requestPermissions(MainActivity.this,Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.CAMERA);
break;
// nfc
case R.id.btn_nfc:
PermissionUtils.requestPermissions(MainActivity.this,Manifest.permission.NFC);
break;
// 相机、位置、短信、麦克风
case R.id.btn_all:
PermissionUtils.requestPermissions(MainActivity.this,Manifest.permission.CAMERA,Manifest.permission.CALL_PHONE,Manifest.permission.SEND_SMS,Manifest.permission.RECORD_AUDIO);
break;
}
}
/**
* 处理权限申请后回执
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
final List<String> refusePermissions = new ArrayList<>();
if (requestCode == PermissionUtils.REQUESTPERMISSIONCODE) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
// 申请成功的权限
Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
} else {
// 被拒绝的权限
refusePermissions.add(permissions[i]);
}
}
if (!refusePermissions.isEmpty()){
String dialogInfo = "在设置-应用-Permission-权限中开启";
for (String pesmission:refusePermissions){
dialogInfo = dialogInfo + PermissionUtils.getDialogName(pesmission) + ",";
}
dialogInfo = dialogInfo + "权限,以正常使用相关功能";
PermissionUtils.showNormalDialog(MainActivity.this,dialogInfo,"去设置", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// 申请权限
PermissionUtils.getAppDetailSettingIntent(MainActivity.this);
}
});
}
}
}
运行效果:
运行时权限结论
通过测试发现:
1.国内手机Rom权限申请不尽相同,以nexus为标准,以微信权限处理为参考,处理权限问题,应对部分rom手动申请权限均成功,但是真正要使用相关功能的时候系统才会真正的去获取权限,这个过程开发者不需要书写代码,所以以标准的处理模式来处理这个问题就行。
2.对于上诉表中没列出来的权限,只需要去在AndroidManifest申请权限就可以,只要申请app就拥有了该权限。
3.对于拒绝权限之后再次申请,可以通过回调得到权限被拒绝,在申请之前可以获取到是否之前被拒绝过,可以考虑文案说明,防止再次被拒。