Android6.0运行时权限的理解和封装

前言

 谷歌在2015年8月份时候,发布了Android 6.0版本,代号叫做“棉花糖”(Marshmallow ),其中的很大的一部分变化,是在用户权限授权上,或许是感觉之前默认授权的不合理,现在6.0出来,使得用户权限授权变得合理。这可能也是参考IOS系统的,只有在用户需要使用权限的时候,才去授权请求,这样做的目的是提高用户体验,当然,用户感觉好了,受苦的是我们开发人员,原来的规则不适用了,现在我们去适应新的规则,毕竟是靠谷歌这颗大树吃饭的嘛。

原来权限模型

 在Android 6.0版本之前,权限都是一条龙服务的,只要用户安装完AndroidManifest清单上申请的权限都会被系统默认授权,并且授权后也撤销不了。这样的弊端在哪里呢?有些权限可能用户觉得不需要,比如他不想有通知的权限,不想受到通知的干扰,那么他就不能屏蔽通知,就是不需要的权限,他去不掉,自主权不在他那边。还有一些情况是,一些恶意程序,会利用这个权限默认授权,进行恶意获取用户数据和攻击。所以Android 6.0版本,一方面让用户更加容易的控制自己的隐私,一方面需要重新适配应用权限。

Android 6.0权限模型

  采用新的权限模型,只有在需要权限的时候,才告知用户是否授权,是在runtime时候授权,而不是在原来安装的时候 ,同时默认情况下每次在运行时打开页面时候,需要先检查是否有所需要的权限申请。这样的用户的自主性提高很多,比如用户可以给APP赋予摄像的权限,但是可以拒绝记录设备位置的权限,就是怕位置信息上传等等。

权限流程

  在API 23中,权限满足的标准流程:

但这里有个问题,那就是在系统授权弹窗环节,提醒框会有个不再提示的复选框,如果用户点击不太提示,并拒绝授权,那么再下次授权的时候,系统授权弹窗的提示框就不会在提示,所以我们很有必要需要自定义权限弹窗提示框,那么流程图就变成如下了。

 

权限类型

 在图中,我们可以看到整个权限里,可以分为系统权限和特殊权限授权。系统权限中,又分为normal和dangerous类型。

 normal:这个权限类型并不直接威胁到用户的隐私,可以直接在manifest清单里注册,系统会帮我们默认授权的。

 dangerous:这个可以直接给app访问用户一些敏感的数据,不仅需要在manifest清单里注册,同时在使用的时候,需要向系统请求授权。

 值得注意一点,这里有特殊权限授权的区别,分别是SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS,虽然这两个权限也是属于dangerous权限类型,但是这两个授权请求方式和其他dangerous权限是不一样的,需要特殊处理 。

   normal列表:

 

dangerous列表:

 

Permission GroupPermissions
CALENDAR
CAMERA
CONTACTS
LOCATION
MICROPHONE
PHONE
SENSORS
SMS
STORAGE

 

版本兼容

 当然,谷歌也是考虑到如果是以API 23之前的版本编译的APP,在6.0的系统上运行,还是能兼容的。如果是23之前的版本编译的APP,则权限模型走的是老的模式,也就是在一条龙的模式,在manifest清单注册,系统会帮我们默认授权,并且也能运行。

   但如果是以API 23的版本编译的APP,那走的的必须是新的权限模型,在遇到dangerous权限的时候,必须进行授权的,如果没有进行授权话,系统则会提示崩溃信息。所以,基于6.0系统的开发的程序, 在打开页面的时候,必须要考虑这个页面是否用到需要授权的权限,页面检测是否已经授权过了没。

实现思路:

  static PermissionListener mPermissionListener;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ExitAppUtils.getInstance().addActivity(this);
        this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);//强制竖屏
    }
  

/**
     * 运行时权限请求方法(建议该方法放在BaseActivity里面,方便重复调用)
     *
     * @param permissions 权限名称
     * @param permissionListener 回调接口
     */
    public static void requestPermission(String[] permissions, PermissionListener permissionListener) {
        mPermissionListener = permissionListener;
        if (Build.VERSION.SDK_INT >= 23) {
            List<String> permissionList = new ArrayList<>();
            Activity topActivity = ExitAppUtils.getInstance().getTopActivity();
            if (topActivity == null) {
                return;
            }
            for (String permission : permissions) {
                if (ContextCompat.checkSelfPermission(topActivity, permission) != PackageManager.PERMISSION_GRANTED) {
                    permissionList.add(permission);
                }
            }
            if (!permissionList.isEmpty()) {
                ActivityCompat.requestPermissions(topActivity, permissionList.toArray(new String[permissionList.size()]), 1);
            } else {
                mPermissionListener.permissionAllowed();
            }
        } else {
            mPermissionListener.permissionAllowed();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0) {
                    List<String> permissionList = new ArrayList<>();
                    for (int i = 0; i < grantResults.length; i++) {
                        int grantResult = grantResults[i];
                        String permission = permissions[i];
                        boolean isTip = ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i]);
                        if (grantResult != PackageManager.PERMISSION_GRANTED) {
                            if (!isTip) {
                                new AlertDialog.Builder(this)
                                        .setTitle("获取相关权限失败")
                                        .setMessage("您已选择了不再提示按钮,或者系统默认不在提示(如MIUI)" +
                                                "获取相关权限失败:" + permission + ",将导致部分功能无法正常使用,如需重新申请,需要到设置页面手动授权")
                                        .setPositiveButton("去授权", new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                //引导用户至设置页手动授权
                                                Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                                Uri uri = Uri.fromParts("package", getApplicationContext().getPackageName(), null);
                                                intent.setData(uri);
                                                startActivity(intent);
                                            }
                                        })
                                        .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                                            @Override
                                            public void onClick(DialogInterface dialog, int which) {
                                                //引导用户手动授权,权限请求失败
                                            }
                                        }).setOnCancelListener(new DialogInterface.OnCancelListener() {
                                    @Override
                                    public void onCancel(DialogInterface dialog) {
                                        //引导用户手动授权,权限请求失败
                                    }
                                }).show();
                            }
                            permissionList.add(permission);
                        }
                    }
                    if (!permissionList.isEmpty()) {
                        mPermissionListener.permissionDisallowed(permissionList);
                    } else {
                        mPermissionListener.permissionAllowed();
                    }
                }
                break;
            default:
                break;
        }
    }

    /**
     * 判断用户是否打开了相机权限,返回true 表示可以使用  返回false表示不可以使用
     */
    public static boolean cameraIsCanUse() {
        boolean isCanUse = true;
        Camera mCamera = null;
        try {
            mCamera = Camera.open();
            Camera.Parameters mParameters = mCamera.getParameters();
            mCamera.setParameters(mParameters);
        } catch (Exception e) {
            isCanUse = false;
        }

        if (mCamera != null) {
            try {
                mCamera.release();
            } catch (Exception e) {
                e.printStackTrace();
                return isCanUse;
            }
        }
        return isCanUse;
    }

    @Override
    protected void onDestroy() {
        ExitAppUtils.getInstance().delActivity(this);
        super.onDestroy();
    }
/**
 * @author xc
 * @version 1.0
 * @date 2016/6/2 13:17
 * @describe android退出程序的工具类,使用单例模式
 * 1.在Activity的onCreate()的方法中调用addActivity()方法添加到mActivityList
 * 2.你可以在Activity的onDestroy()的方法中调用delActivity()来删除已经销毁的Activity实例
 * 这样避免了mActivityList容器中有多余的实例而影响程序退出速度
 */
public class ExitAppUtils {
    /**
     * 转载Activity的容器
     */
    private List<Activity> mActivityList = new LinkedList<>();
    private static ExitAppUtils instance = new ExitAppUtils();

    /**
     * 将构造函数私有化
     */
    private ExitAppUtils() {
    }

    /**
     * 获取ExitAppUtils的实例,保证只有一个ExitAppUtils实例存在
     *
     * @return
     */
    public static ExitAppUtils getInstance() {
        return instance;
    }


    /**
     * 添加Activity实例到mActivityList中,在onCreate()中调用
     *
     * @param activity
     */
    public void addActivity(Activity activity) {
        mActivityList.add(activity);
    }

    /**
     * 从容器中删除多余的Activity实例,在onDestroy()中调用
     *
     * @param activity
     */
    public void delActivity(Activity activity) {
        mActivityList.remove(activity);
    }

    /**
     * 得到栈顶的activity
     *
     * @return activity
     */
    public Activity getTopActivity() {
        if (mActivityList.isEmpty()) {
            return null;
        } else {
            return mActivityList.get(mActivityList.size() - 1);
        }
    }


    /**
     * 退出程序的方法
     */
    public void exit() {
        for (Activity activity : mActivityList) {
            activity.finish();
        }
        System.exit(0);
    }
}

 

其中要特别注意的是,如果是针对

“android.permission.SYSTEM_ALERT_WINDOW”
“android.permission.WRITE_SETTINGS”

这两个权限,实现方式跟上之前在工作中,碰到一种情况,如果是运行在6.0的版本上是需要走新的权限模型,如果是运行在老的版本上,则需要进行一个判断,此时碰到一个问题是,在谷歌官方推荐中,在判断app运行的系统是否在Android M上时,它的判断是如下:  Build.VERSION.CODENAME.equals("MNC"); 可是在我实际适配中,发现这句却无效的,就改用Build.VERSION.SDK_INT >= 23。的,需要另外特殊处理。

针对SYSTEM_ALERT_WINDOW权限,需要向系统发送一个ACTION_MANAGE_OVERLAY_PERMISSION.这样一个动作,同时可以用Settings.canDrawOverlays() 方法进行判断之前是否已经授权过了。

针对WRITE_SETTINGS权限,需要向系统发送一个ACTION_MANAGE_WRITE_SETTINGS 这样一个动作,同时可以用Settings.System.canWrite().方法进行判断之前是否已经授权过了。

具体代码:

Intent intent = new Intent(android.provider.Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

注意点

 之前在工作中,碰到一种情况,如果是运行在6.0的版本上是需要走新的权限模型,如果是运行在老的版本上,则需要进行一个判断,此时碰到一个问题是,在谷歌官方推荐中,在判断app运行的系统是否在Android M上时,它的判断是如下:  Build.VERSION.CODENAME.equals("MNC"); 可是在我实际适配中,发现这句却无效的,就改用Build.VERSION.SDK_INT >= 23。

 

 

 总结

  1. 首先要知道6.0版本权限模型跟原来版本是不同的,不再是统一在manifest中默认系统授权,而是有需要的时候,向系统请求授权,提高用户体验。
  2. 了解权限检测流程,一点注意点是如果系统权限弹窗提示框被不再提醒了,需要我们自定义提示弹窗,引导用户去授权。
  3. 明白权限类型,分为normal和dangerous类型,同时,在dangerous中还需要注意一点,SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS这两个特殊授权请求方式,跟一般授权请求方式不同。
  4. 在判断APP是否运行在Android M上,可以用版本号来判断,可以准确点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值