Android 8.0 learning (18) --- Android8.0 runtime permission policy changes and adaptation schemes

Android8.0 runtime permission policy changes and adaptation scheme

   Prior to Android O, if an app requested a permission at runtime and was granted that permission, the system would incorrectly grant the app along with other permissions that belonged to the same permission group and were registered in the manifest. For apps targeting Android O, this behavior has been corrected. The system will only grant permissions that the app explicitly requests. However, once a user grants a permission to an app, all subsequent requests for permissions in that permission group will be automatically approved.

For example, suppose an app lists READ_EXTERNAL_STORAGEand in its manifest WRITE_EXTERNAL_STORAGE. The app requests READ_EXTERNAL_STORAGE, and the user grants the permission, and if the app targets API level 24 or lower, the system also grants WRITE_EXTERNAL_STORAGEit, since the permission is also part of the STORAGEpermission group and registered in the manifest. If the app is targeting Android O, it will only be granted at this time READ_EXTERNAL_STORAGE, but when the app requests the WRITE_EXTERNAL_STORAGEpermission later, the permission will be granted immediately without prompting the user.

Let's take READ_EXTERNAL_STORAGEand WRITE_EXTERNAL_STORAGEas an example to analyze in detail, what impact this has on our existing code.

Before the official start, we first agree on two methods:

/**
 * 拿到没有被授权的权限。
 */
getDeinedPermission(String... permissions);
/**
 * 请求几个权限。
 */
requestPermission(String... deinedPermissions);
  •      The constant of the permission is in the Manifest.permissionclass, and the READ_EXTERNAL_STORAGEpermission is added after API 16, so after Android M comes out, in order to adapt to the lower version of the system, we generally apply for the permission (pseudo code) like this:
// 需要申请的权限。
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);
}
  • The logic is very simple and clear. The callback is the callback for applying for permission. Here we apply for WRITE_EXTERNAL_STORAGEpermission. Before Android O, we will get READ_EXTERNAL_STORAGEpermission at the same time. When we read the memory card in other places, we only need to judge that WRITE_EXTERNAL_STORAGEwe have permission. to read.

Bad , if the application is installed in the system at this time, we will find that the application crashes when we read the content after Android Ojudging that we have permission, because we did not apply for permission.WRITE_EXTERNAL_STORAGE存储卡READ_EXTERNAL_STORAGE

Response to Android O runtime permission policy changes

According to the characteristics of the runtime permission policy of Android O, in order to adapt to various versions of the system, our code will become as follows (pseudo code):

// 需要申请的权限。
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);
}
  • However, there are two problems in this way. First, some permission groups have more permissions, and it is difficult for developers to remember them all. Second, READ_EXTERNAL_STORAGEthis permission constant was added to the SDK when API 16 was released. Similar permission constants are also available. Several, some were even added to the SDK with Android M. If we force it to be written, the app will still crash when running on a lower version of the system. Some people said that we should judge the system version before applying. Of course, if you don't mind the trouble, that's perfectly fine.

Upgrade plan

Therefore, we have concluded a better solution . In the final analysis, it is necessary to apply for a permission group when applying for permission, rather than a single permission. So we classify according to the system permission group, put the constants of a group into an array, and assign values ​​to this array according to the system version, so we have produced such a class:

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

}
  •   Before Android M, user authorization is not required to use a certain permission. You only need to register in the Manifest. After Android M, you need to register and apply for user authorization. Therefore, we use an empty array as the permission group before Android M according to the system version. Use real array permissions after 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的运行时权限策略变化和应对方案的介绍到这里就结束了,如果还不理解的可以在博客下方留言。

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326008896&siteId=291194637