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

原创 2017年08月05日 15:04:14

版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

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_STORAGEWRITE_EXTERNAL_STORAGE。应用请求READ_EXTERNAL_STORAGE,并且用户授予了该权限,如果该应用针对的是API级别24或更低级别,系统还会同时授予WRITE_EXTERNAL_STORAGE,因为该权限也属于STORAGE权限组并且也在清单中注册过。如果该应用针对的是Android O,则系统此时仅会授予READ_EXTERNAL_STORAGE,不过在该应用以后申请WRITE_EXTERNAL_STORAGE权限时,系统会立即授予该权限,而不会提示用户。

下面我们还是以READ_EXTERNAL_STORAGEWRITE_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};
        }
    }

}

在Android M以前使用某权限是不需要用户授权的,只要在Manifest中注册即可,在Android M之后需要注册并申请用户授权,所以我们根据系统版本在Android M以前用一个空数组作为权限组,在Android M以后用真实数组权限。

因为要传入多个权限组,所以我们约定的两个方法就不够用了,所以我们加两个方法:

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

于是我们申请权限的代码就简化成这样了:

// 这方法里面判断版本,返回空数组或者没有权限的数组。
String[] deniedPermissions = getDeinedPermission(Permission.STORAGE, Permission.SMS);

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

当然这不是最简化的,但是已经足以兼容到Android O的权限策略的变化了。

如果是AndPermission如何做到最简

这里只是介绍下AndPermisison也兼容了Android O的权限变化,如果你觉得这个项目不适合你,你可以自行封装一个,我比较鼓励开发者自己动手,下面是开源地址:
https://github.com/yanzhenjie/AndPermission

它的一些简单的特点:
1. 链式调用,一句话申请权限,省去复杂的逻辑判断。
2. 支持注解回调结果、支持Listener回调结果。
3. 拒绝一次某权限后,再次申请该权限时可使用Rationale向用户说明申请该权限的目的,在用户同意后再继续申请,避免用户勾选不再提示而导致不能再次申请该权限。
4. 就算用户拒绝权限并勾选不再提示,可使用SettingDialog提示用户去设置中授权。
5. RationaleDialog和SettingDialog允许开发者自定义。
6. AndPermission自带默认对话框除可自定义外,也支持国际化。
7. 支持在任何地方申请权限,不仅限于Activity和Fragment等。
8. 支持申请权限组、兼容Android8.0。

申请多个权限组示例:

AndPermission.with(this)
    .permission(Permission.CAMERA, Permission.SMS) // 多个权限组。
    .callback(new PermissionListener() {
        @Override
        public void onSucceed(int i, @NonNull List<String> list) {
            // TODO do something...
        }

        @Override
        public void onFailed(int i, @NonNull List<String> list) {
            // TODO 用户没有同意授权,一般弹出Dialog让用户去Setting中授权。
        }
    })
    .start();

申请单个或者某几个权限示例,因为Android O的出现,现在不鼓励这样使用了,但是在Android O正式发布前没有问题:

AndPermission.with(this)
    .permission(
        // 多个不同权限组权限,现在不鼓励这样使用了,但是在Android O正式发布前没有问题。
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_SMS
    ) 
    .callback(new PermissionListener() {
        @Override
        public void onSucceed(int i, @NonNull List<String> list) {
            // TODO do something...
        }

        @Override
        public void onFailed(int i, @NonNull List<String> list) {
            // TODO 用户没有同意授权,一般弹出Dialog让用户去Setting中授权。
        }
    })
    .start();

关于Android O的运行时权限策略变化和应对方案的介绍到这里就结束了,如果还不理解的可以在博客下方留言。


版权声明:转载必须注明本文转自严振杰的博客:http://blog.yanzhenjie.com

版权声明:转载必须注明本文转自严振杰的博客: http://blog.yanzhenjie.com

相关文章推荐

Receiver里面启动dialog引发的一系列错误解决(SYSTEM_ALERT_WINDOW)

首先,在receiver中show出dialog的做法: @Override public void onReceive(final Context context, Intent intent) ...

如何实现android中监听来电并生成悬浮窗体提示

http://www.iteye.com/topic/1118711 最近因为项目的原因需要实现一个来电监听,且生成一个悬浮窗口提示相关信息(具体什么信息不方便透露哈)。 现把我的思路...

android permission权限与安全机制解析(上)

总结整理了一下android权限相关的知识,由于篇幅过长,分为两篇博客来写,上篇博客主要是详解权限和安全,下篇主要是介绍android6.0权限适配问题:   android permission权...

Android 6.0权限机制及开发流程详解

介绍Android6.0的权限机制变更和权限申请流程
  • ccpat
  • ccpat
  • 2016-04-14 15:20
  • 10765

关于android.permission.GET_TASKS

08-18 19:08:25.403: E/AndroidRuntime(8475): FATAL EXCEPTION: THREAD_GUARD 08-18 19:08:25.403: E/A...

随笔:关于权限android.permission.SYSTEM_ALERT_WINDOW

我查了网上权限的意思:android.permission.SYSTEM_ALERT_WINDOW允许一个程序打开窗口使用TYPE_SYSTEM_ALERT,显示在其他所有程序的顶层。 对这个说法有...

Android 悬浮窗权限各机型各系统适配大全

这篇博客主要介绍的是 Android 主流各种机型和各种版本的悬浮窗权限适配,但是由于碎片化的问题,所以在适配方面也无法做到完全的主流机型适配,这个需要大家的一起努力,这个博客的名字永远都是一个将来时...

android 6.0之后 android.permission.SYSTEM_ALERT_WINDOW使用方法变动

在android6.0之前想要用service弹出对话框还蛮简单的,这是之前的方法但是改版之后因为安全的问题, android 系统觉得开发者要放这种功能需要使用者”认真”的同意,于是以后要使用弹出...

Android在Dialog中显示PopupWindow不全问题解决

解决方法如下: 1、实例化PopupWindow时,设置其width为屏幕宽度; 2、获取PopupWindow的高度: popup.getContentView().measure(0, 0); i...

getDrawable过时的替代方法

我在刚接触android的时候,用getDrawable()方法好像就过时了,只不过当时没在意,没报错就用,不过这样毕竟还是不好的。现象网友推荐谷歌查询结果
  • pengkv
  • pengkv
  • 2015-09-28 16:11
  • 12214
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)