美图欣赏 | 设为首页 | 加入收藏 | 网站地图

当前位置:电脑中国 > 编程 > 移动开发 >

Android8.0运行时权限策略变化和适配方案

2018-03-02 14:57|来源:未知 |作者:dnzg |点击:

Android8.0也就是Android O即将要发布了,有很多新特性,目前我们可以通过AndroidStudio3.0 Canary版本下载Android O最新的系统映像的Developer Preview 4版本,Developer Preview 4是Android O正式版推出前的最后一个预览版本,所以它是Android O的候选版本,我们可以使用它来完成开发和测试,让我们的应用平稳过度到Android O。

后期会计划出一篇Android O行为变化和兼容方案的文章,本篇文章主要讲Android O行为变化的其中一点——系统运行时权限的策略变化和适配方案。

Android系统的运行时权限是从Android 6.0(Android M)开始加入的,如果你还不知道Android运行时权限,你可以看Android 6.0 运行时权限管理最佳实践: 
http://blog.csdn.net/yanzhenjie1003/article/details/52503533

针对运行时权限管理,有很多开源的管理库,16年9月份时本人也开源了一个运行权限管理方案,它最大程度上兼容了国产机,当然也兼容了Android 8.0: 
https://github.com/yanzhenjie/AndPermission

在正式开始之前,先纠正一个问题,在网上看到有项目可以做到自定义申请授权的系统Dialog,首先要纠正就目前来看是绝对不行的,最多在调用申请的代码之前弹一个自己的Dialog提示用户要申请授权了。我快速拜读了下那个项目源码,果然如我想象的一样,在绕了一个圈子后最终还是调用了系统申请授权的代码。


Android O的运行时权限策略变化

如果你喜欢看Google官网的文章,你可以看这里: 
https://developer.android.com/preview/behavior-changes.html#rmp

在 Android O 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。

对于针对Android O的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。

例如,假设某个应用在其清单中列出READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。应用请求READ_EXTERNAL_STORAGE,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O,则系统此时仅会授予READ_EXTERNAL_STORAGE,不过在该应用以后申请WRITE_EXTERNAL_STORAGE权限时,系统会立即授予该权限,而不会提示用户。(有人私下跟我讲这跟原来没区别,明眼人应该看得出来,本组没申请的权限的状态应该是拒绝的,那么直接使用拒绝状态的权限会怎么样呢。)

下面我们还是以READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE为例来具体分析一下,这对我们现有的代码有什么影响。

正式开始之前,我们先约定两个方法:

/**
 * 拿到没有被授权的权限。
 */
getDeinedPermission(String... permissions);
/**
 * 请求几个权限。
 */
requestPermission(String... deinedPermissions);

权限的常量在Manifest.permission类中,而READ_EXTERNAL_STORAGE权限是在API 16之后才添加的,所以在在Android M出来后为了适配更低版本的系统,我们一般是这样申请权限的(伪代码):

// 需要申请的权限。
String[] permissions = {
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_SMS,
    ...
};

String[] deniedPermissions = getDeinedPermission(permissions);

if(deniedPermissions.length <= 0) {
    // TODO do something...
} else {
    requestPermission(deniedPermissions, callback);
}

逻辑非常简单清晰,其中的callback是申请权限的回调,这里我们申请了WRITE_EXTERNAL_STORAGE权限,在Android O之前,我们同时会得到READ_EXTERNAL_STORAGE权限,我们在其它地方涉及到读取存储卡的操作时只需要判断有WRITE_EXTERNAL_STORAGE权限就去读取了。

霸特,此时应用如果安装在Android O的系统中我们会发现,判断了有WRITE_EXTERNAL_STORAGE权限后去读取存储卡内容时应用崩溃了,原因就是我们没有申请READ_EXTERNAL_STORAGE权限。

对Android O运行时权限策略变化的应对方案

针对Android O的运行时权限策略的特点,为了适配各个版本的系统,我们的代码会变成如下方式(伪代码):

// 需要申请的权限。
String[] permissions = {
    Manifest.permission.WRITE_EXTERNAL_STORAGE,
    Manifest.permission.READ_EXTERNAL_STORAGE,
    Manifest.permission.READ_SMS,
    ...
};

String[] deniedPermissions = getDeinedPermission(permissions);

if(deniedPermissions.length <= 0) {
    // TODO do something...
} else {
    requestPermission(deniedPermissions, callback);
}

但是这样会存在两个问题,一是有的权限组权限比较多,开发者难易全部记住;二是READ_EXTERNAL_STORAGE这个权限常量是在API 16时才被添加到SDK中,类似这样的权限常量还有好几个,有的甚至在Android M时才被添加到SDK中。如果我们强制写了,当APP运行在低版本的系统中时,还是会崩溃。有人就说了,我们在申请之前判断系统版本不就好啦?当然,如果你不嫌麻烦,这是完全可以的。

升级方案

因此我们总结出一个更优的方案,归根结底就是申请权限时要申请权限组,而不是单一的某个权限。所以我们按照系统权限组分类,把一个组的常量放到一个数组中,并根据系统版本为这个数组赋值,于是乎产生了这样一个类:

public final class Permission {

    public static final String[] CALENDAR;   // 读写日历。
    public static final String[] CAMERA;     // 相机。
    public static final String[] CONTACTS;   // 读写联系人。
    public static final String[] LOCATION;   // 读位置信息。
    public static final String[] MICROPHONE; // 使用麦克风。
    public static final String[] PHONE;      // 读电话状态、打电话、读写电话记录。
    public static final String[] SENSORS;    // 传感器。
    public static final String[] SMS;        // 读写短信、收发短信。
    public static final String[] STORAGE;    // 读写存储卡。

    static {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            CALENDAR = new String[]{};
            CAMERA = new String[]{};
            CONTACTS = new String[]{};
            LOCATION = new String[]{};
            MICROPHONE = new String[]{};
            PHONE = new String[]{};
            SENSORS = new String[]{};
            SMS = new String[]{};
            STORAGE = new String[]{};
        } else {
            CALENDAR = new String[]{
                    Manifest.permission.READ_CALENDAR,
                    Manifest.permission.WRITE_CALENDAR};

            CAMERA = new String[]{
                    Manifest.permission.CAMERA};

            CONTACTS = new String[]{
                    Manifest.permission.READ_CONTACTS,
                    Manifest.permission.WRITE_CONTACTS,
                    Manifest.permission.GET_ACCOUNTS};

            LOCATION = new String[]{
                    Manifest.permission.ACCESS_FINE_LOCATION,
                    Manifest.permission.ACCESS_COARSE_LOCATION};

            MICROPHONE = new String[]{
                    Manifest.permission.RECORD_AUDIO};

            PHONE = new String[]{
                    Manifest.permission.READ_PHONE_STATE,
                    Manifest.permission.CALL_PHONE,
                    Manifest.permission.READ_CALL_LOG,
                    Manifest.permission.WRITE_CALL_LOG,
                    Manifest.permission.USE_SIP,
                    Manifest.permission.PROCESS_OUTGOING_CALLS};

            SENSORS = new String[]{
                    Manifest.permission.BODY_SENSORS};

            SMS = new String[]{
                    Manifest.permission.SEND_SMS,
                    Manifest.permission.RECEIVE_SMS,
                    Manifest.permission.READ_SMS,
                    Manifest.permission.RECEIVE_WAP_PUSH,
                    Manifest.permission.RECEIVE_MMS};

            STORAGE = new String[]{
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE};
        }
    }

}
					
(责任编辑:dnzg)