[点心]Android权限判断中的版本兼容

声明:博文未经授权一律不允转载

在Android中,我们会经常遇到一些需要检查权限允许后才可以进行的一些操作,尤其是Android 6.0以及以后版本最为明显,我们知道Android6.0后引入了运行时权限检查,如果没有权限那么某些隐私的功能就无法使用。
那么在针对公司的某些服务,制作一款Android SDK的时候,也是一样的,需要使用到权限检查。
庆幸的是,Support Library V4包中已帮我们封装好了一个静态权限检查方法,ContextCompat#checkSelfPermission:


ContextCompat.checkSelfPermission(@NonNull Context context, @NonNull String permission)

我当然可以直接调用这个方法,但是,在打包SDK为Jar的时候就不得不带上这个V4包的,这样会增大SDK整体包的大小,不建议使用,当然我们可以打包的时候不加入这个V4包,然后乞求用户在使用我们SDK的时候加入V4包,但是这显然不是一个专业的操作,用户根据自己的业务需求是不一定会引入V4包的,所以凭什么要使用你SDK的用户去买单,引入一个其他功能都不一定使用到的包。另外,引入这个V4包的SDK集成到其他应用中时候还有可能造成V4包冲突的风险,因为其他应用可能也有用到V4包。

作为一个有节操的SDK,必须解决这个问题,又因为V4是Android屏蔽版本区别的一个支持库,因此其中的方法一定是版本兼容的,于是在技术本能的驱使下,我深入这个checkSelfPermission方法一探究竟:


public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) {
    if (permission == null) {
        throw new IllegalArgumentException("permission is null");
    }
    return context.checkPermission(permission, android.os.Process.myPid(), Process.myUid());
}

核心方法就是一句,调用Context#checkPermission方法,返回的int类型不同的值代表不同的权限。见下图。

demo1

燃鹅,我觉得这个方法需要太多参数,因为在一个应用,后面两个参数其实都是固定的,pid和uid(具体这两个变量是什么含义可以自行查阅),Google应该没有傻到让我做多余的事情,因此我决定进一步寻找,于是找到了这个方法Context#checkSelfPermission,果然只需要传递一个String权限参数,哈哈,和Google想到一块儿去了。见下图。

demo2

燃鹅的再燃鹅,看看左上角,这个方法特么的是“added in API level 23”,也就是说我在Android 6.0 以下的SDK中特么的无法使用啊,法使用啊,使用啊,用啊,啊。
如果有跟上我思路的童鞋,在这里可能就会有小疑问了:为什么既然这个方法在API 23才有,那我就增加如下的版本判断就好了呀:


if (Build.VERSION.SDK_INT >= 23) {
    //call Context#checkSelfPermission
}

但是,其实您并不能保证其他人在使用你的代码编译的时候的Androi SDK版本一定是≥23的,如果是不≥23的Android SDK版本去编译,编译就挂了。因此为了兼容编译的版本,我们使用反射来调用这个方法,取消方法名的显式调用,这样就不会有找不到这个方法的编译问题。所以现在我们的代码就变成了这样:


public static boolean checkSelfPermission(Context context, String permission) {
    boolean permissionGranted = false;
    if (context == null) {
        return false;
    } else {
        if (Build.VERSION.SDK_INT >= 23) {
            try {
                Class clz = Class.forName("android.content.Context");
                Method checkSelfPermission = clz.getMethod("checkSelfPermission", new
                        Class[]{String.class});
                int i = ((Integer) checkSelfPermission.invoke(context, new
                        Object[]{permission})).intValue();
                if (i == PackageManager.PERMISSION_GRANTED) {
                    permissionGranted = true;
                } else {
                    permissionGranted = false;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }
    return permissionGranted;
}

但是的但是,现在显然这个方法是只能提供给API 23的权限检车使用的,因为进行了版本判断。我们再增加如下的代码片将API 22以及以下的版本给兜住喽:


PackageManager packageManager = context.getPackageManager();
if (packageManager.checkPermission(permission, context.getPackageName()) ==
                        PackageManager.PERMISSION_GRANTED) {
    permissionGranted = true;
}

可能到这里又会有同学问了,既然只是判断是否授权,一个布尔值,那干脆全部使用上述的代码片段就好了哇。确实可以这样的,但是其实根据Support Library中的思路,有新的API,都会面向使用更(gèng)新的API,比如,V4包中的DrawableCompat类,在不同的版本会new不同版本的实例,这里也是一样的,不同版本走不同的逻辑,因此我们最终的权限判断方法就变成了如下的样子(针对if增加了一个else):


public static boolean checkSelfPermission(Context context, String permission) {
    boolean permissionGranted = false;
    if (context == null) {
        return false;
    } else {
        if (Build.VERSION.SDK_INT >= 23) {
            try {
                Class clz = Class.forName("android.content.Context");
                Method checkSelfPermission = clz.getMethod("checkSelfPermission", new
                        Class[]{String.class});
                int i = ((Integer) checkSelfPermission.invoke(context, new
                        Object[]{permission})).intValue();
                if (i == PackageManager.PERMISSION_GRANTED) {
                    permissionGranted = true;
                } else {
                    permissionGranted = false;
                }
            } catch (Throwable e) {
                e.printStackTrace();
            }
        } else {//新增
            PackageManager packageManager = context.getPackageManager();
            if (packageManager.checkPermission(permission, context.getPackageName()) ==
                    PackageManager.PERMISSION_GRANTED) {
                permissionGranted = true;
            }
        }
    }
    return permissionGranted;
}

另外,还有最后一点,我们的反射的API在Android中并不是@Hide标记的,因此,按理应不会在以后的Android版本中(比如Android 9.0 中),被禁用。

文章作者: Halohoop
文章链接: http://halohoop.com/2018/07/13/snacks_android_permission_check/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 卖牙膏的芖口钉