前言:
在Android系统不断的升级过程中,Android应用的开发也有不同的变化,对于权限,Android6.0以上的系统中,引入了运行时权限检查,运行时权限分为正常权限和危险权限,当我们的App调用了需要危险权限的api时,需要向系统申请权限,系统会弹出一个对话框让用户感知,只有当用户授权以后,App才能正常调用api。 目前所有的权限类别可参考https://developer.android.google.cn/reference/android/Manifest.permission?hl=zh-cn 如果您确定您的应用必须访问受限数据或执行受限操作才能实现某个用例,请声明相应的权限。有些权限是用户安装应用时自动授予的权限,称为安装时权限。其他权限则需要应用在运行时进一步请求权限,此类权限称为运行时权限。
图 1. 在 Android 中使用权限的概要工作流示意图。
权限分类
- 安装时权限
图 2. 某应用商店中显示的某个应用的安装时权限列表。 图2所示为某个应用的安装时权限列表。右图显示了一个弹出式对话框,其中包含 2 个选项:允许和拒绝。
安装时权限授予应用对受限数据的受限访问权限,并允许应用执行对系统或其他应用只有最低影响的受限操作。如果您在应用中声明了安装时权限,系统会在用户安装您的应用时自动授予应用相应权限。应用商店会在用户查看应用详情页面时向其显示安装时权限通知,如图 2 所示。
Android 提供多个安装时权限子类型,包括普通权限和签名权限。
此类权限允许访问超出应用沙盒的数据和执行超出应用沙盒的操作。但是,这些数据和操作对用户隐私及对其他应用的操作带来的风险非常小。
系统会为普通权限分配“normal”保护级别,如权限 API 参考文档页面中所示。
当应用声明了其他应用已定义的签名权限时,如果两个应用使用同一证书进行签名,系统会在安装时向前者授予该权限。否则,系统无法向前者授予该权限。 版本兼容
一个弹出式对话框,其中包含 2 个选项:允许和拒绝。 运行时权限也称为危险权限,此类权限授予应用对受限数据的额外访问权限,并允许应用执行对系统和其他应用具有更严重影响的受限操作。因此,您需要先在应用中请求运行时权限,然后才能访问受限数据或执行受限操作。当应用请求运行时权限时,系统会显示运行时权限提示,如图 3 所示。 图 3. 当应用请求运行时权限时显示的系统权限提示。
许多运行时权限会访问用户私有数据,这是一种特殊的受限数据,其中包含可能比较敏感的信息。例如,位置信息和联系信息就属于用户私有数据。
系统会为运行时权限分配“dangerous”保护级别,如权限 API 参考文档页面中所示。 本篇即主要说明的是运行时权限的申请。
特殊权限与特定的应用操作相对应。只有平台和原始设备制造商 (OEM) 可以定义特殊权限。此外,如果平台和 OEM 想要防止有人执行功能特别强大的操作(例如通过其他应用绘图),通常会定义特殊权限。
系统设置中的特殊应用访问权限页面包含一组用户可切换的操作。其中的许多操作都以特殊权限的形式实现。
每项特殊权限都有自己的实现细节。如需查看使用每项特殊权限的说明,请访问权限 API 参考文档页面。系统会为特殊权限分配“appop”保护级别。
申请权限
在运行时请求权限的基本原则如下:
当用户开始与需要相关权限的功能互动时,在具体使用情境下请求权限。 不要阻止用户使用应用。始终提供选项供用户取消与权限相关的指导界面流程。 如果用户拒绝或撤消某项功能所需的权限,请适当降级您的应用以便让用户可以继续使用您的应用(可能通过停用需要权限的功能来实现)。 不要对系统行为做任何假设。例如,假设某些权限会出现在同一个权限组中。权限组的作用只是在应用请求密切相关的多个权限时,帮助系统尽可能减少向用户显示的系统对话框数量。
应用权限基于系统安全功能,最佳做法是将运行时权限与特定操作相关联,尽可能往后推迟到在应用的用例流程中请求权限。例如,如果应用允许用户向他人发送语音消息,请等到用户已导航到消息屏幕并已按下发送语音消息按钮后再请求权限。待用户按下该按钮后,应用再请求麦克风使用权限。 注册权限 如需声明应用可能请求的权限,请在应用的清单文件中添加相应的 元素。例如,需要访问相机的应用应在清单中添加以下代码行:
<manifest ...>
<uses-permission android:name="android.permission.CAMERA"/>
<application ...>
...
</application>
</manifest>
申请流程
-
在应用的清单文件中,声明应用可能需要请求的权限。 -
设计应用的用户体验,使应用中的特定操作与特定运行时权限相关联。应当让用户知道哪些操作可能会要求他们向您的应用授予访问其私人数据的权限。 -
等待用户调用应用中需要访问特定用户私人数据的任务或操作。届时,您的应用可以请求访问相应数据所需的运行时权限。 -
检查用户是否已授予应用所需的运行时权限。如果已授权,那么您的应用可以访问用户私人数据。如果没有,请继续执行下一步。 每次执行需要该权限的操作时,您都必须检查自己是否具有该权限。 -
检查您的应用是否应向用户显示理由,说明您的应用需要用户授予特定运行时权限的原因。如果系统确定您的应用不应显示理由,请继续直接执行下一步,无需显示界面元素。 不过,如果系统确定您的应用应该显示一个理由,请在界面元素中向用户显示理由,明确说明您的应用试图访问哪些数据,以及应用获得运行时权限后可为用户提供哪些好处。用户确认理由后,请继续执行下一步。 -
请求您的应用访问用户私人数据所需的运行时权限。系统会显示运行时权限提示,例如权限概览页面上显示的提示。 -
检查用户的响应,他们可能会选择同意或拒绝授予运行时权限。 -
如果用户向您的应用授予权限,您就可以访问用户私人数据。如果用户拒绝授予该权限,请适当降低应用体验,使应用在未获得受该权限保护的信息时也能向用户提供功能。 图 1 说明了与此过程相关的工作流和决策组
原生方法
Android系统为我们Android开发提供了基础的权限申请方法,包括
使用系统方法 ContextCompat.checkSelfPermission() 并传入摇检查的权限。根据您的应用是否具有相应权限,此方法会返回 PERMISSION_GRANTED 或 PERMISSION_DENIED。
如果 ContextCompat.checkSelfPermission() 方法返回 PERMISSION_DENIED,可调用shouldShowRequestPermissionRationale()。此方法在activity和fragment中直接调用。如果此方法返回 true,请向用户显示指导界面,在此界面中说明用户希望启用的功能为何需要特定权限。
用户查看指导界面后或者 shouldShowRequestPermissionRationale() 的返回值表明您这次不需要显示指导界面后,您可以请求权限。用户会看到系统权限对话框,并可在其中选择是否向您的应用授予特定权限。 ActivityCompat.requestPermissions(activity, permissions, requestCode); 按照历来的做法,您可以在权限请求过程中自行管理请求代码,并将此请求代码包含在您的权限回调逻辑中。
开源框架
由于Android动态权限申请复杂,代码较多,所以出现了很多开源框架来帮助我们开发,我们可以学习开源框架的使用。
- EasyPermission
为什么首先提这个库呢,其github地址为:https://github.com/googlesamples/easypermissions。 官方的,最为致命。其代码示例demo如下所示。其实还是很多代码的,但是由于官方考虑的比较多,流程上肯定是没问题的。而且充分考虑了android support 和androidx的兼容,但可定制性没那么高了。由于原生方法较多,依赖onActivityResult和onRequestPermissionsResult,导致代码也多。
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks,EasyPermissions.RationaleCallbacks{
private static final int RC_CAMERA_PERM = 123;
private static final int RC_LOCATION_CONTACTS_PERM = 124;
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_camera),
RC_CAMERA_PERM,
Manifest.permission.CAMERA);
}
@AfterPermissionGranted(RC_LOCATION_CONTACTS_PERM)
public void locationAndContactsTask() {
EasyPermissions.requestPermissions(
this,
getString(R.string.rationale_location_contacts),
RC_LOCATION_CONTACTS_PERM,
LOCATION_AND_CONTACTS);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, @NonNull List<String> perms) {
Log.d(TAG, "onPermissionsGranted:" + requestCode + ":" + perms.size());
}
@Override
public void onPermissionsDenied(int requestCode, @NonNull List<String> perms) {
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
new AppSettingsDialog.Builder(this).build().show();
}
}
}
- XXPermissions
链式调用方式,且去除了原生依赖onActivityResult和onRequestPermissionsResult,减少了Activity和fragment的代码数量。github地址:https://github.com/getActivity/XXPermissions 且其适配了各个Android版本的兼容和与版本相关权限的申请。其自称的亮点有:
- 首款也是唯一一款适配 Android 11 的权限请求框架
- 首款也是唯一一款适配所有 Android 版本的权限请求框架
- 简洁易用:采用链式调用的方式,使用只需一句代码
- 体积感人:功能在同类框架中最全的,但是体积是最小的
- 适配极端情况:无论在多么极端恶劣的环境下申请权限,框架依然坚挺
- 向下兼容属性:新权限在旧系统可以正常申请,框架会做自动适配,无需调用者适配
- 自动检测错误:如果出现低级错误框架会主动抛出异常给调用者(仅在 Debug 下判断,把 Bug 扼杀在摇篮中)
且做了表格对很多其他权限库进行了对比,以证明其优势。是否选用,各位自己判断。 使用的代码示例如下:
XXPermissions.with(this)
.permission(Permission.RECORD_AUDIO)
.permission(Permission.Group.CALENDAR)
.request(new OnPermissionCallback() {
@Override
public void onGranted(List<String> permissions, boolean all) {
if (all) {
toast("获取录音和日历权限成功");
} else {
toast("获取部分权限成功,但部分权限未正常授予");
}
}
@Override
public void onDenied(List<String> permissions, boolean never) {
if (never) {
toast("被永久拒绝授权,请手动授予录音和日历权限");
XXPermissions.startPermissionActivity(MainActivity.this, permissions);
} else {
toast("获取录音和日历权限失败");
}
}
});
- PermissionX
github地址:https://github.com/guolindev/PermissionX 这个库呢,首先一个让你铭记的,应该是作者,郭霖(第一行代码),是否看过他的书么。 作为大神,其开发的开源库肯定也是值得我们使用的。 此库也使用了链式调用,一步到位,且也去除了easypermission中对原生接口的依赖,用户只需要使用如下代码就好了, 且封装了提示用户和打开设置的接口,UI可定制,对部分新的系统权限也做了兼容,设置了直接的申请接口,可直接调用,减少了用户的对特殊危险权限的定制。
PermissionX.init(activity)
.permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.CAMERA, Manifest.permission.CALL_PHONE)
.onExplainRequestReason { scope, deniedList ->
scope.showRequestReasonDialog(deniedList, "Core fundamental are based on these permissions", "OK", "Cancel")
}
.onForwardToSettings { scope, deniedList ->
scope.showForwardToSettingsDialog(deniedList, "You need to allow necessary permissions in Settings manually", "OK", "Cancel")
}
.request { allGranted, grantedList, deniedList ->
if (allGranted) {
Toast.makeText(this, "All permissions are granted", Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this, "These permissions are denied: $deniedList", Toast.LENGTH_LONG).show()
}
}
- RxPermissions
很多人用惯了RXjava和RXAndroid,对这种响应式编程情有独钟,有需求就有人提供,如果没有,那就是你成名的机会。 github地址:https://github.com/tbruyelle/RxPermissions 这里也附上其使用示例,对RX有需求的可以选用:
rxPermissions
.requestEachCombined(Manifest.permission.CAMERA,
Manifest.permission.READ_PHONE_STATE)
.subscribe(permission -> {
if (permission.granted) {
} else if (permission.shouldShowRequestPermissionRationale)
} else {
}
});
- PermissionsDispatcher
不得不说,哪都有注解处理的方式,在当今各种注解框架风行的今天,动态权限库也少不了注解方式的开源库,这种对代码的优点是更简洁了。 github地址:https://github.com/permissions-dispatcher/PermissionsDispatcher 但是这种注解有个缺点就是每次使用都要注解相应的弹框提示或打开系统设置的方法? 这个由于没去使用,就不多评论了,也行没我想的那样。但是不得不说是个很有特色的框架,是我们开发使用习惯中不可或缺的一类。 代码示例如下:
@RuntimePermissions
class MainActivity : AppCompatActivity(), View.OnClickListener {
@NeedsPermission(Manifest.permission.CAMERA)
fun showCamera() {
supportFragmentManager.beginTransaction()
.replace(R.id.sample_content_fragment, CameraPreviewFragment.newInstance())
.addToBackStack("camera")
.commitAllowingStateLoss()
}
@OnShowRationale(Manifest.permission.CAMERA)
fun showRationaleForCamera(request: PermissionRequest) {
showRationaleDialog(R.string.permission_camera_rationale, request)
}
@OnPermissionDenied(Manifest.permission.CAMERA)
fun onCameraDenied() {
Toast.makeText(this, R.string.permission_camera_denied, Toast.LENGTH_SHORT).show()
}
@OnNeverAskAgain(Manifest.permission.CAMERA)
fun onCameraNeverAskAgain() {
Toast.makeText(this, R.string.permission_camera_never_askagain, Toast.LENGTH_SHORT).show()
}
}
其他框架就不一一列举了,很多还是很有特点的。
Jetpack
jetpack一统江湖。 以前的support包,现在的androidx, jetpack包,其诞生的主要目的就是为了统一开发方式,避免市场上越来越多的各种框架。你服不服? 优势么也是显著的,但是官方只可能给你最核心的改进,对于一些附送的福利官方可能不会去干,这也是其他开源框架的优势,比如Android 11的后台权限等等需要各种条件检测。 其实这里的不是专门的权限库,是随着应用的扩展,onActivityResult回调方法各种嵌套、耦合严重、难以维护,Android废弃了startActivityForResult和onActivityResult方法。 如果使用新的activity和fragment的jetpack库,Activity Results API 是 Google官方推荐的Activity、Fragment获取数据的方式。
implementation 'androidx.activity:activity:1.2.0-beta01'
implementation 'androidx.fragment:fragment:1.3.0-beta01'
这里虽然不是专门为申请权限开放的,但是也专门为申请权限提供了新的方法。 新建一个Contract类,继承自ActivityResultContract<I,O>,其中,I是输入的类型,O是输出的类型。需要实现2个方法,createIntent和parseResult,输入类型I作为createIntent的参数,输出类型O作为parseResult方法的返回值,在下面的例子中,输入输出类型都是String: Google 预定义了很多Contract,把你们能想到的使用场景基本上都想到了,它们都定义在类ActivityResultContracts中,有以下这些Contract:
request_permission.setOnClickListener {
requestPermission.launch(permission.CAMERA)
}
request_multiple_permission.setOnClickListener {
requestMultiplePermissions.launch(
arrayOf(
permission.BLUETOOTH,
permission.READ_CONTACTS,
permission.ACCESS_FINE_LOCATION
)
)
}
private val requestPermission =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) toast("Permission is granted")
else toast("Permission is denied")
}
private val requestMultiplePermissions =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
permissions.entries.forEach {
}
}
同样的,没有了对onActivityResult和onRequestPermissionsResult的依赖。
总结
Android权限是在不断的变化的,每个Android新版本都会有新的权限出现,新的使用方式出现,总之是为了用户隐私安全考虑,故动态权限库还会不断的发展更新,后续肯定有更优秀的开源库出来,各位也可以学习现有的框架库根据自己公司的需求开发出更适合自己的动态库。 水平有限,一起学习。
|