每个APP都运行在独自的进程中,如果APP需要访问其进程以外的资源或信息,那么它就需要请求对应的权限。APP需要在Manifest文件中列出要请求的权限,并在运行时请求用户授予权限(在Android 6.0及以上的版本中)。本文将讲解怎样用Android Support Library来检查和请求权限。Android 6.0 (API level 23)及以上的Android Framework也支持类似的功能,但是使用support library 可以适配更早的Android版本。
在所有Android版本上,在APP的Manifest文件中用元素来什么APP需要的权限。例如,一个APP需要发送SMS消息,那么就需要在Manifest文件中做如下申明:
"http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
"android.permission.SEND_SMS"/>
...>
...
系统根据所申明权限的等级有不同的表现:对于一般权限,系统会在APP安装时就自动授予给APP;对应危险权限,必须要请求用户显式的授予给APP。关于权限等级的信息,请见Android权限概述中的“权限等级”
从Android 6.0 (API level 23)开始,用户可以在任何时候取消某个APP的某个权限,即使APP的target小于Android 6.0 (API level 23)。例如:即使一个APP昨天还有使用Camera的权限,但是今天用户手动取消了这个权限,那么下次使用APP时就没有这个权限了。所以,如果APP需要一个危险权限,那么APP每次执行需要这个权限的操作时都应该检查APP是否有这个权限。
使用ContextCompat.checkSelfPermission()方法来检查APP是否有某个权限。如下的例子为如何检查activity具有写入calendar的权限:
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
}
如果APP有这个权限,那么返回PERMISSION_GRANTED,APP可以进行相应的操作;如果APP没有这个权限,那么返回 PERMISSION_DENIED,APP应该显式的请求用户授予权限。
如果APP调用 checkSelfPermission()后返回PERMISSION_DENIED的结果,那么应该请求用户授予权限。Android提供了几种请求权限的方法,如requestPermissions()。使用这些请求权限的方法会调起一个标准的、不能自定义的对话框,对话框的展示内容及形式与Android系统的版本和APP的targetSdkVersion相关,如Android权限概述中所讲。
在一些情况下,你需要帮助用户理解为什么APP需要这个权限。例如:用户启动了一个图库APP,用户对不会对APP需要使用Camera权限感到意外,但是用户可能不理解为什么这个APP还需要访问用户的位置信息和联系人信息。所以,APP请求权限之前,应该先向用户解释为什么需要使用这些权限。但是注意不要有太繁杂的解释,否则用户会感觉APP难懂并卸载APP。所以,只有当用户曾经拒绝过授予此权限时才提供解释。可以调用Android提供的一个工具方法shouldShowRequestPermissionRationale(),如果返回true,那说明用户曾经拒绝过此权限。关于请求权限时如何保障好的用户体验,请见App Permissions Best Practices。
APP使用 requestPermissions()函数请求所需的权限,并传入APP需要请求的权限和一个integer类型的request code。此函数是异步的,被调用后会立刻返回。等用户响应请求后,系统会调用回调函数返回request code和操作结果。
下面的代码检查APP是否有读取联系人的权限,如果没有权限,那么检查是否要向用户解释为什么要使用此权限,如果不需要解释,那么就向用户请求授予权限。
// Here, thisActivity is the current activity
if (ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not 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; 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.
}
} else {
// Permission has already been granted
}
APP调用requestPermissions()后,系统会向用户展示一个标准的弹出框。APP不能设置或自定义这个弹出框。如果APP需要向用户解释为什么需要某个权限,那么需要在requestPermissions()之前进行,如“向用户解释为什么APP需要这个权限”中所述。
用户响应APP的权限请求后,系统会调用APP的 onRequestPermissionsResult()回调函数,并返回request code和用户的响应结果。例如APP请求 READ_CONTACTS ,它可能有如下的回调:
@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.
}
}
系统弹出的权限请求对话框只描述APP涉及的权限组,而不会单独描述某个权限。例如:APP请求READ_CONTACTS 权限,权限请求对话框只描述APP需要访问设备的联系人。对于属于同一权限组的权限,用户只需显式授予一次就可以了。如果APP已获得某一权限,当它请求同一权限组中的其它权限时,系统会自动授予APP这些权限。从代码运行上表现为:调用requestPermissions()后,系统不需要弹出权限请求框供用户授权,而是直接调用 onRequestPermissionsResult()回调方法 ,并返回PERMISSION_GRANTED。注意:即使用户已经授权过权限组中的某个权限了,APP使用权限组内的其它权限时仍需要调用requestPermissions()显式请求权限,但是用户不需要显式授权,而是系统自动授权了。例如:APP的Manifest文件中申明了READ_CONTACTS 和WRITE_CONTACTS两个权限,如果APP已经请求过READ_CONTACTS权限并且用户已经显式授权了,当你请求WRITE_CONTACTS权限时,系统会立刻授予此权限,而不需要与用户交互。
如果用户拒绝授予某权限,那么APP应该采取对应的措施,如:APP展示一个弹出框,说明如果用户不同意使用这个权限将导致某些功能无法使用。
系统让用户授予权限时,用户可以选择“下次不再提示”的选项。如果用户选择了这个权限,那么下次APP调用requestPermissions()再次请求这个权限时,系统会立即拒绝此权限。从代码运行上表现为:调用requestPermissions()后,系统不需要弹出权限请求框供用户授权,而是直接调用 onRequestPermissionsResult()回调方法 ,并返回PERMISSION_DENIED。如果设备政策禁止APP获得某些权限,那么requestPermissions()返回false。所以,当APP调用requestPermissions()时,APP不一定会与用户有交互。