PermissionX 1.6发布,支持Android 12,可能是今年最大的版本升级

2021-10-13 17:54:06 浏览数 (1)

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

各位小伙伴们大家早上好。

没错,PermissionX又升级了,并且这次版本变化非常大,很有可能是今年最大幅度的一次升级。

在这之后,我就准备将精力放到其他开源库上,PermissionX应该短时间内不会再发布新版本了(修bug除外)。

经过这次升级之后,PermissionX也将会是一个非常稳定的版本,它兼容到最新的Android 12系统,所以在未来的一年内都可以放心稳定地使用。

那么接下来我们就一起探索一些,最新的PermissionX 1.6版本到底有哪些变化吧。

持续Kotlin化

看过《第一行代码 第3版》的朋友应该知道,PermissionX本是从书中最后一章的项目实战演化出来的一个开源库。这个开源库的第一个版本就是书中的源码,是一个纯Kotlin的项目。

但是发布了第一个版本之后,许多还在使用Java的朋友跟我进行了大量的反馈,他们也想使用这个开源库,但由于项目工程还是Java的,而且短时间内也不可能将老项目全面Kotlin化,所以就用不了这个库了。

于是,我在第二个版本将PermissionX使用Java进行了重写,以保证不管是Java还是Kotlin的Android工程都可以使用它。详情可以参考这篇文章 快速迭代,PermissionX现在支持Java了!

不过我们都知道,Kotlin才是Android的未来,所以后面的版本中其实我一直都想将PermissionX的代码实现重新改为Kotlin。

我发现,Java工程和Kotlin工程其实只是在接口调用层面有一些用法上的区别,而在开源库的底层实现上,其实不管是用Java来实现还是用Kotlin来实现,对开源库的使用者来说都并没有任何区别。

所以,我在PermissionX 1.6版本中开始将大量的Java代码重构成Kotlin,只保留了不得不用Java实现的部分,以保证PermissionX仍然可以无缝兼容Java和Kotlin这两种项目工程的使用。

Github上有一个功能可以统计当前开源项目的代码所使用的编程语言占比分别是多少。经过这个版本的重构之后,PermissionX中Kotlin语言的占比已经达到了85%以上。

可以说,从1.6版本开始,PermissionX由一个Java主导的项目变成了一个Kotlin主导的项目,并且以后新增的所有功能也都会优先使用Kotlin来实现。

特殊权限直达

PermissionX 1.5版本中新增了对Android特殊权限申请的支持,详情可以参考这篇文章 PermissionX 1.5发布,支持申请Android特殊权限啦 。

但是,1.5版本对特殊权限申请的支持存在着一个问题,我们看一眼下图的演示:

虽说PermissionX确实可以用于去申请修改设置这种特殊权限,但是一开始我们会跳到一个列表界面,然后需要在这个列表界面中找到当前的应用程序,点击进入,再手动开启权限。

如果列表不长的话还好,如果列表很长的话,从里面找到当前的应用程序简直是灾难版的体验。

于是PermissionX 1.6版本在这方面进行了优化,加入了直达当前应用程序界面的功能。

具体的做法也很简单,就是在请求权限的Intent当中加入如下代码:

代码语言:javascript复制
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
intent.data = Uri.parse("package:${requireActivity().packageName}")

我们给Intent添加了一个data,data中包含了当前应用程序的包名,这样发出Intent的时候就可以直接到达当前应用程序的权限管理界面了。如下图所示:

但是我发现上述代码并不是总可以生效,比如我对MANAGE_EXTERNAL_STORAGE权限也加上同样的包名指定,只要一申请权限程序就会崩溃。对SYSTEM_ALERT_WINDOW权限加上包名指定,只有Android 10及以下的手机才能直达当前应用程序界面,在Android 11及以上的手机还是会跳转到列表界面。

所以,这个功能其实并不算稳定,但仍然还是可以在一定程度上提升用户体验。

支持应用安装权限

Android从8.0系统开始对安装未知来源的应用程序进行了限制。

在8.0系统之前,只要用户在手机设置中开启了“允许安装未知来源的应用”这个选项,那么就可以在这台手机上随意安装任意的APK。

而从8.0系统开始,每个应用程序如果想要去跳转安装一个APK,都需要单独让用户去同意一遍“允许安装未知来源的的应用”这个选项才行。

不过在我看来,这只是让用户的操作变得复杂了一些而已,对于开发者来说,代码是不变的。我们仍然只需要用同样的代码去跳转安装一个APK,系统如果检测到需要用户去同意“允许安装未知来源的的应用”,会自动弹窗提醒用户,如下图所示:

所以之前我也从未想过要对“允许安装未知来源的的应用”这个特殊权限进行支持,因为感觉并没有必要。

但是自从上个版本PermissionX加入了对特殊权限的支持之后,有不少朋友就一直向我反馈,希望能在PermissionX中增加对“允许安装未知来源的的应用”权限的支持。

虽然我表达了,这个权限不需要手动申请,系统会自动判断是否需要用户手动授权。但最终证明还是我考虑得不够周全,比如说下面这种场景:

也就是说,有些手机厂商将系统魔改了之后,只在第一次跳转安装的时候提醒用户需要手动授权,否则就再也不会提醒了,而是直接拒绝此权限。

这种情况下,我们就必须要手动对“允许安装未知来源的的应用”权限进行处理才行。

所以,在PermissionX 1.6版本中,我听取了大家的意见,加入了对这一特殊权限的支持。

至于用法也非常简单,只需使用PermissionX请求REQUEST_INSTALL_PACKAGES权限即可,代码如下所示:

代码语言:javascript复制
PermissionX.init(activity)
    .permissions(Manifest.permission.REQUEST_INSTALL_PACKAGES)
    .onExplainRequestReason { scope, deniedList ->
        val message = "PermissionX需要您同意以下权限才能正常使用"
        scope.showRequestReasonDialog(deniedList, message, "Allow", "Deny")
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

当然,不要忘记在AndroidManifest.xml中注册权限:

代码语言:javascript复制
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.permissionx.app">
    
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

</manifest>

运行一下,效果如下图所示:

支持Android 12蓝牙权限

Android 12在运行时权限方面又有了一些新的变化。

之前的Android系统中有一个很奇怪的现象,当我们在应用中使用蓝牙扫描附件设备的时候,需要申请地理位置权限。

蓝牙权限并不是运行时权限,但地理位置权限却是。这就给很多开发者造成了一些困扰,明明只是想要使用蓝牙的功能,却让用户误以为想要定位设备的地理位置。

这个问题在今年的Google I/O大会上有专门提及。据说从Android 1.0版本就是这样设计的,用户不明白为什么,开发者也不明白为什么,就连Google自己都不明白为什么。

于是在Android 12系统中,Google对蓝牙权限重新进行了设计,从而修复了这个已经存在了十几年的bug。

从Android 12开始,过去的蓝牙权限被拆分成了3个新的权限,并且全都是运行时权限:

  • BLUETOOTH_SCAN 用于使用蓝牙扫描附件其他的蓝牙设备
  • BLUETOOTH_ADVERTISE 用于允许当前的设备被其他的蓝牙设备所发现
  • BLUETOOTH_CONNECT 用于连接之前已经配对过的蓝牙设备

不过这3个权限都是从Android 12系统才开始有的,所以为了能够兼容过去的老版本,建议在AndroidManifest.xml中这样声明:

代码语言:javascript复制
<manifest>
    <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH"
                     android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
                     android:maxSdkVersion="30" />
                     
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
    ...
</manifest>

我们仍然像往常一样申请老的蓝牙权限,但是让它们最大只作用到api 30,也就是Android 11系统上。从Android 12开始则启用新的蓝牙权限。

不过要注意,刚才说过了,新增的3个蓝牙权限都是运行时权限,因此只在AndroidManifest.xml中声明是没有用的,还要在代码中申请权限才行,这也是PermissionX 1.6版本主要适配的地方。

3个权限都属于同一个权限组,因此理论上只要申请一个权限,另外2个也就自动授权了。不过根据Google的最佳编程规范,我们仍然应该是用到哪个权限就去申请哪个权限,不要依赖于权限组去编写任何功能逻辑。

下面是使用PermissionX来申请Android 12新增蓝牙权限的示例写法:

代码语言:javascript复制
val requestList = ArrayList<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    requestList.add(Manifest.permission.BLUETOOTH_SCAN)
    requestList.add(Manifest.permission.BLUETOOTH_ADVERTISE)
    requestList.add(Manifest.permission.BLUETOOTH_CONNECT)
}
if (requestList.isNotEmpty()) {
    PermissionX.init(activity)
        .permissions(requestList)
        .explainReasonBeforeRequest()
        .onExplainRequestReason { scope, deniedList ->
            val message = "PermissionX需要您同意以下权限才能正常使用"
            scope.showRequestReasonDialog(deniedList, message, "Allow", "Deny")
        }
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
            }
        }
}

这里我们首先要判断当前的系统版本,只有当Android 12及以上系统时,才应该去请求新增的蓝牙权限。运行效果如下图所示:

升级Activity Result API

不知道关注源码的小伙伴们还记不记得,PermissionX底层的实现原理是向当前的Activity当中添加了一个不可见的Fragment,权限请求操作都是借助Fragment来完成的。

然而,在新版的appcompat库当中,用于请求权限的requestPermissions()方法已经被废弃了。

另外,PermissionX在处理特殊权限请求时,会调用startActivityForResult()方法,而这个方法现在也被废弃了。

之所以这些我们耳熟能详,过去几乎天天都在使用的方法现在一一被废弃,当然是因为现在我们有了更好的选择,Activity Result API。

在上个版本当中,PermissionX还顶着一大堆的Warning,使用了各种废弃的API在处理权限请求。而在1.6版本中,已经全面替换成Activity Result API的用法。可以说,这是一个零Warning的版本了。

不过,关于Activity Result API的具体用法,这里我并不打算展开讲解,而是准备在后面专门写一篇文章来详细地介绍Activity Result API。

下一篇原创我们就来聊一聊这方面的内容。

如何升级

关于PermissionX新版本的内容变化就介绍到这里,升级的方式非常简单,修改一下dependencies当中的版本号即可:

代码语言:javascript复制
repositories {
  google()
  mavenCentral()
}

dependencies {
    implementation 'com.guolindev.permissionx:permissionx:1.6.0'
}

如果你对PermissionX的源码感兴趣,可以访问PermissionX的项目主页:

https://github.com/guolindev/PermissionX

另外,本篇文章主要介绍的是PermissionX 1.6.0版本的新特性。如果你之前并没有接触过PermissionX,可以通过我编写的《PermissionX权限系列专栏》逐步进行学习,里面有非常详尽的用法讲解。

如果想要学习Kotlin和最新的Android知识,可以参考我的新书 《第一行代码 第3版》,点击此处查看详情。

0 人点赞