原文地址:https://developer.android.google.cn/training/permissions/requesting.html
从Android6.0
(API23)开始,用户在应用运行时向其授予权限,而不再是安装应用时。这一方法简化了应用安装过程,因为用户不需要在安装或者升级应用的时候去授予权限,它还给予用户对应用功能进行更多的控制。例如,用户可以授予一个相机应用拍照权限,而不提供设备的位置信息。用户可以进入应用的设置界面随时取消权限。
系统权限被分为两类,normal
和dangerous
:
- 正常权限不会直接给用户隐私带来风险。如果你的应用在
manifest
中列举了正常权限,系统会自动赋予这些权限。 - 危险权限允许应用访问用户的机密数据。如果你的应用在
manifest
中列举了正常权限,系统会自动赋予这些权限,如果列举了危险权限,必须由用户在应用中明确允许。
危险权限分组:
在Android的所有版本中,你的应用都需要在清单文件中声明它所需要的正常权限和危险权限,如Declaring Permissions中所描述。但是,基于系统版本和你的应用的目标sdk级别的不同,声明带来的影响也是不同的:
- 如果设备运行在Android5.1或者更低,或者你的应用的目标sdk是22或者更低:如果你在清单文件中列举了危险权限,用户将在安装应用的时候授予权限;如果用户不授予权限,系统就不会安装这个应用。
- 如果设备运行在Android6.0或者更高,并且你的应用的目标sdk是23或者更高:对于在清单文件中声明的权限,必须在应用运行时请求它需要的每一个危险权限。用户可以授予或者拒绝每一个权限,如果用户拒绝了一个权限请求,应用将继续受限制运行。
Note:从Android6.0(API23)开始,用户可以随时撤销应用的权限,即使应用的目标sdk较低。你必须测试你的应用在缺失某些需要的权限时能否正常运行,不管你的应用目标等级。
这篇文章告诉你如何使用Android Support Library 来检查,请求权限。从 Android 6.0 (API level 23)开始提供了一些类似的方法,但是使用支持库更加简便,因为你在调用方法前不需要检查运行的安卓版本。
权限检查
如果你需要一个危险权限,你必须在每次进行需要这个权限的操作时去检查是否拥有这个权限。用户可以自由撤销权限,所以即使应用昨天使用了相机,也不能保证今天仍然拥有权限。
使用 [ContextCompat.checkSelfPermission()](https://developer.android.com/reference/android/support/v4/content/ContextCompat.html#checkSelfPermission(android.content.Context, java.lang.String))来检查是否拥有一个权限。例如,下面代码段展示了如何检查是否具有写入日历的权限:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CALENDAR);
如果应用拥有这个权限,方法返回PackageManager.PERMISSION_GRANTED,应用可以继续操作。如果没有这个权限,返回PERMISSION_DENIED,应用需要向用户明确请求权限。
请求权限
如果你的应用需要你在清单文件中注册的一个危险权限,必须要求用户授予权限。Android提供了几个方法用来请求权限,调用这些方法会产生一个标准的对话框,你不可以自定义。
解释为什么应用需要这些权限
在一些情况下,你可能需要帮助用户了解为什么你的应用需要某个权限。例如,如果用户启动了一个拍照应用,那么对于请求使用相机的权限他并不会感到惊讶,但是用户不会理解为什么需要访问他的地理位置和联系人。在请求权限之前,你需要考虑给用户提供一个解释;记住你并不想用解释来击败用户;如果你提供了过多的解释,用户会对应用失望然后卸载它。
有一种方式是,仅仅当用户已经拒绝了请求权限时才去提供解释。如果用户持续尝试使用需要权限的功能,并且持续拒绝了请求权限,可能说明用户不理解这个功能为何需要这个权限。这种情况下,提供解释是一个好主意。
为了帮助发现可能需要提供解释的情况,Android提供了一个有效的方法[shouldShowRequestPermissionRationale()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String))。如果应用已经请求过这个权限并且用户拒绝了,这个方法会返回true
Note:如果用户之前拒绝了权限申请,并且在权限申请的系统弹窗中选择了
Don't ask again
,这个方法会返回false
。如果设备政策禁止应用拥有该权限也会返回false
。
请求你需要的权限
如果你的应用尚未拥有它所需要的权限,就必须调用 [requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))方法来请求合适的权限。这个方法需要传入你所需要的权限,和一个整形的请求码,用来标记权限请求。这个方法是异步的:它立即返回,在用户响应弹窗之后系统携带结果调用应用的回调方法,传回和之前一样的整形请求码。
下面代码检查了应用是否具有读取联系人的权限,并在必要的时候请求权限:
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// Show an explanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
}
}
Note:当你调用[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))时,系统会向用户展示一个标准的对话框,你不能配置或者改变这个对话框。如果你想向用户提供一些信息或者解释,你需要在调用[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))之前处理。
处理权限请求结果
当你的应用请求权限时,系统会向用户展示一个对话框,用户进行响应之后,系统调用[onRequestPermissionsResult()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback.html#onRequestPermissionsResult(int, java.lang.String[], int[]))方法,来传递用户的响应。你需要重写这个方法来判断用户是否授予权限。回调中会返回你在[requestPermissions()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.html#requestPermissions(android.app.Activity, java.lang.String[], int))传入的请求码。例如,请求读取联系人可能会有如下回调:
@Override
public void onRequestPermissionsResult(int requestCode,
String permissions[], int[] grantResults) {
switch (requestCode) {
case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// permission was granted, yay! Do the
// contacts-related task you need to do.
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
}
return;
}
// other 'case' lines to check for other
// permissions this app might request
}
}
系统对话框展示了你的应用需要访问的权限组,它并不列举指定的权限。例如,如果你请求READ_CONTACTS
权限,系统对话框中只会说你需要访问设备的联系人,用户每次需要授予一个权限组的权限。如果你的应用需要请求这个权限组中的其他权限(并在清单文件中注册过的),系统会自动授予。当你请求这个权限的时候,系统会调用 [onRequestPermissionsResult()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback.html#onRequestPermissionsResult(int, java.lang.String[], int[]))方法,并且返回PERMISSION_GRANTED,和用户通过系统对话框明确授予权限一样。
Note:你的应用必须明确请求它需要的每一个权限,即使用户已经授予同一组中的其他权限。实际上,权限分组可能会在未来的Android版本中发生变化,你的代码不能依赖于权限是否在同一组中。
例如,假设你在清单文件中列举了READ_CONTACTS
和WRITE_CONTANCTS
,如果你请求READ_CONTACTS
权限并且用户授予了,然后你再请求WRITE_CONTACTS
权限,系统会立即授予,不再通知用户。
如果用户拒绝了一个权限请求,你的应用应该采取适当的措施。例如,你可以展示一个对话框说明为什么无法执行需要这个权限的操作。
当系统请求用户授予权限时,用户可以选择告诉系统不再询问。这种情况下,用户任意时候再次使用requestPermissions
请求权限,系统会立即拒绝。系统会调用 [onRequestPermissionsResult()](https://developer.android.com/reference/android/support/v4/app/ActivityCompat.OnRequestPermissionsResultCallback.html#onRequestPermissionsResult(int, java.lang.String[], int[]))方法,并且返回PERMISSION_GRANTED,和用户通过系统对话框明确授予权限一样。这意味着当你调用requestPermissions
时,你不能假设已经和用户发生任何的直接交互。