Android 权限调研

前言

截止当前时间,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,权限被拒绝之后尝试再次申请逻辑;
为了更好的解决这些问题,我们简单画一下流程图:
image

为了完成这个过程初步书写了相关的代码:
首先我们把所有的权限申请放到工具类,并简单对权限分类:

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);
                    }
                });
            }

        }
    }

运行效果:
image

运行时权限结论

通过测试发现:
1.国内手机Rom权限申请不尽相同,以nexus为标准,以微信权限处理为参考,处理权限问题,应对部分rom手动申请权限均成功,但是真正要使用相关功能的时候系统才会真正的去获取权限,这个过程开发者不需要书写代码,所以以标准的处理模式来处理这个问题就行。

2.对于上诉表中没列出来的权限,只需要去在AndroidManifest申请权限就可以,只要申请app就拥有了该权限。

3.对于拒绝权限之后再次申请,可以通过回调得到权限被拒绝,在申请之前可以获取到是否之前被拒绝过,可以考虑文案说明,防止再次被拒。

参考

谷歌安卓

安卓权限列表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值