Android 6.0 的权限模型

最近做 app 时遇到了一个问题,原本在 Android 5.1 上运行得很好的应用在 Android 6.0 直接提示权限不足或者直接崩溃。这是由于 Google 从 Android Marshmallow 开始修改了系统的权限模型。为了更大地兼容老版本的应用,Google 在设计上做出了很多妥协。目前看来有些机制并不好,可是估计这也是没办法的办法,只有等应用慢慢跟上了然后逐步改善。

这里要马克一下我总结出的一些改变,以便以后写 app 时照搬。

 

一、编译时目标 SDK 在 22 或更低版本时,新的权限模型不起作用。

如果在开发时,编译时指定的 Target SDK 不是 API 23 (Android 6.0 Marshmallow)或以上,在安装时系统会授予应用所有请求的权限。在运行应用时,系统会自动开启旧版本的兼容模式,保证旧版本的应用不会出现问题。

但是有一个例外,如果用户在设置里面关闭了任意一项权限组里的权限(官方称之为 dangerous permission),那么应用在访问响应 API 时会被系统拒绝(可能直接抛异常、返回空数据集或者常量值)。如果应用没有捕获异常的话,应用就会直接崩溃。因此,用户在收回权限时系统会提示这样操作可能会导致应用无法运行。

 

二、编译时 SDK 在 23 版本(或以上)时,新的权限模型适用。

此时,开发者必须要遵循 Google 的规范来请求权限。如果像以前那样编码,系统会直接拒绝抛出 SecurityException。

有一个特别蛋疼的地方在于:如果你的应用需要集成一些第三方的垃圾 SDK (比如我最近集成的微信、微博等像翔一样的 API),那么你也需要为他们事先请求权限。因为这些辣鸡 SDK 到现在还没有跟进 Android 系统的更新,而他们需要一些相关权限的 API 时会被系统毫不犹豫地拒绝掉,就像下面这样:

QQ截图20151212221246

所以,不论是自己使用,还是集成第三方SDK,务必要遵循以下三部曲:检查→请求→访问。

比如需要把图片保存在外部存储中,需要一个 WRITE_EXTERNAL_STORAGE 的权限,在保存之前,需要调用 ContextCompat 的 checkSelfPermission 方法检查一下应用当前是否有这项权限,如果有,那么说明用户之前做过授权,可以很安全地访问相关的 API。

如果该方法没有返回 GRANTED,那么需要调用 ActivityCompat 的 shouldShowRequestPermissionRationale 方法得知一下原因。没有权限的原因无非是:

  • 还没有请求用户授权,此时直接 request 相关的 permission。
  • 用户曾经拒绝过此权限:此时如果照旧 request,系统会不询问用户直接拒绝掉。Google 的做法是在此时像用户解释为何需要这项权限,以便获得用户的同意。

因此代码就像下面这样:

        int permissionCheck = ContextCompat.checkSelfPermission(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE);

        if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
                new AlertDialog.Builder(this)
                        .setMessage(R.string.no_permission_storage)
                        .setTitle(R.string.app_name)
                        .setPositiveButton(R.string.ok, null)
                        .show();
            } else {
                ActivityCompat.requestPermissions(this,
                        new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE},
                        PERMISSION_SAVE_REQUEST_CODE);
            }
        } else {
            savePictureWithPermission();
        }

requestPermissions 可以一次性请求多项权限,所以可以传一个 String[] 数组。调用 requestPermissions 方法时系统会弹出一个类似 iOS 系统那样的对话框询问用户是否授予权限(用户无法在这个对话框上显示任何自定义的信息)。

这个对话框不像 AlertDialog 那样有回调方法,用户在操作后系统会调用 Activity 的 onRequestPermissionsResult 方法,因此我们需要覆盖掉这个方法。使用之前传入的 requestCode 判断要执行什么样的操作(因为你可能会在一个 Activity 里面)。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == PERMISSION_SAVE_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                savePictureWithPermission();
            } else {
                new AlertDialog.Builder(this)
                        .setMessage(R.string.no_permission_storage)
                        .setTitle(R.string.app_name)
                        .setPositiveButton(R.string.ok, null)
                        .show();
            }

        }
    }

 

至此,我们可以看到申请权限是如此的曲折,本来一个用户确认→执行的简单操作要分这么多步来进行。就好比把同步的请求写成异步那样要拆好几步,如果以后能够有 .NET 4.5 那样的 async-await 模式就好了,不过按照 Java 的惯例来看似乎是不太可能了。

对于新的权限模型有很多弊端,最主要体现在:只有9个权限组中的权限才使用这种授权的方式(包括电话、短信、存储、位置、传感器等隐私相关的信息),而对于其他权限还是必须在安装时一次性授予,且不能收回。

由此可见 Google 这样做的目的还是保护用户的隐私,而不是像我所希望的那样对付 BAT 360 之类的流氓应用。Google 本身可能意识不到国内这些辣鸡 APP 丛生的问题,就好比我一直在 Google Play 上购买游戏不会知道其他玩家玩盗版时遇到的各种各样的问题。所以,Google 今后收紧 app 权限的可能性并不大,有这方面需求的还是得靠国内各种定制的 UI (虽然相当多的一部分都是负优化),当然最靠谱的还是自己动手用 Privacy 之类的神器对付之。

Android 6.0 的权限模型”的一个响应

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

You are commenting using your WordPress.com account. Log Out /  更改 )

Google+ photo

You are commenting using your Google+ account. Log Out /  更改 )

Twitter picture

You are commenting using your Twitter account. Log Out /  更改 )

Facebook photo

You are commenting using your Facebook account. Log Out /  更改 )

Connecting to %s