Android应用权限简要介绍
- 一个Android应用默认情况下是不拥有任何权限的, 一个应用是没有权利去进行一些可能会造成不好影响的操作的. 这些不好的影响可能是对其它应用,操作系统,或者是用户.如果应用需要一些额外的能力,则它需要在AndroidManifest.xml中静态地声明相应的权限.
如果应用没有在manifest中声明权限, 却使用了相应的功能, 在调用到相应功能的时候, 将会抛出异常.
比如程序要发送一个请求,却忘记加Internet权限, 那么在发送这个请求的时候程序就会抛出异常,一般不会catch这个异常,所以程序直接就崩溃了:
Caused by: java.lang.SecurityException: Permission denied (missing INTERNET permission?)
Android 6.0开始, 一部分比较危险的权限需要在程序运行时显式弹框,请求用户授权.
至于什么时候弹这个框,由应用程序自己决定.
对于其他权限,认为不是很危险,所以仍然保持原来的做法,在用户安装应用程序时就予以授权.
- 一个Android应用默认情况下是不拥有任何权限的, 一个应用是没有权利去进行一些可能会造成不好影响的操作的. 这些不好的影响可能是对其它应用,操作系统,或者是用户.如果应用需要一些额外的能力,则它需要在AndroidManifest.xml中静态地声明相应的权限.
Permission的保护等级
permission的保护等级通过protectionLevel属性设置, 共有4种: normal,dangerous,signature,signatureOrSystem.
签名相关的比较不常用, 剩下的两种是normal和dangerous
总结下来就是: 所有的权限仍然在manifest中静态声明, normal权限的在安装的时候自动授权, 而dangerous的权限需要应用明确地请求用户授权.
Dangerous Permissions:
Table 1. Dangerous permissions and permission groups.
Permission Group | Permissions |
---|---|
CALENDAR` | READ_CALENDAR * WRITE_CALENDAR` |
CAMERA` | CAMERA` |
CONTACTS` | * READ_CONTACTS* WRITE_CONTACTS* GET_ACCOUNTS` |
LOCATION` | * ACCESS_FINE_LOCATION * ACCESS_COARSE_LOCATION` |
MICROPHONE | * RECORD_AUDIO |
PHONE | * READ_PHONE_STATE* CALL_PHONE* READ_CALL_LOG* WRITE_CALL_LOG* ADD_VOICEMAIL* USE_SIP* PROCESS_OUTGOING_CALLS |
SENSORS | * BODY_SENSORS |
SMS | * SEND_SMS* RECEIVE_SMS* READ_SMS* RECEIVE_WAP_PUSH* RECEIVE_MMS |
STORAGE | * READ_EXTERNAL_STORAGE * WRITE_EXTERNAL_STORAGE |
可以总结为:
1.所有的权限都在manifest中声明.
2.如果(1)你的app的targetSdkVersion是23及以上,并且(2)app运行在Android 6.0及以上的设备,危险权限必须动态请求.
当权限被拒绝,app理应还是能够使用的,只不过权限相关的部分功能不能用.
3.上一条中的两个条件(1)(2)没有同时满足,即属于其他情况, 所有权限在安装时请求,如果用户不接受,则不安装.
特别注意这种情况: 旧应用新系统.
如果targetSdkVersion小于23,即被认为是Android 6.0发布之前开发的应用, 还没有兼容6.0.
这种应用即便是被装在Android 6.0的机器上,也是采用原来的安装即授予权限逻辑, 所有权限在应用安装时全部被授权.
在Android 6.0的设备上安装targetSdkVersion小于23的应用之后, 可以在应用的设置中查看,发现所有的dangerous权限状态都是打开.
Permission group
所有的权限都有自己的permission group.
系统弹框请求某一个permission时也是只说明了它的类别,当用户同意,系统会给予它该条permission.(只有这一条).
但是如果app已经有了该group下的另一条permission,系统将会自动授予权限(也即请求权限的callback直接返回),这过程中不与用户交互.
动态权限请求的实现
原文: https://developer.android.com/training/permissions/requesting.html
因为权限动态检查相关的API是Android 6.0才加入的, 所以minSdkVersion不是23时,推荐使用SupportLibrary来实现,好处是: 程序里不必加if来判断当前设备的版本.
1.检查权限状态
如果执行的操作需要一个dangerous permission, 那么每次在执行操作的地方都必须check你是否有这个permission, 因为用户可以在应用设置里随意地更改授权情况, 所以必须每次在使用前都检查是否有权限.
检查权限的方法: ContextCompat.checkSelfPermission()两个参数分别是Context和权限名.
返回值是:PERMISSION_GRANTED if you have the permission, or PERMISSION_DENIED if not.
比如:
if (PackageManager.PERMISSION_GRANTED== ContextCompat.checkSelfPermission(
MainActivity.this, Manifest.permission.READ_CONTACTS)) {
//has permission, do operation directly
ContactsUtils.readPhoneContacts(this);
Log.i(DEBUG_TAG, "user has the permission already!");
} else {
//do not have permission
}
2.动态请求权限
如果上面权限检查的结果是DENIED, 那么就需要显式地向用户请求这个权限了.
Android提供了几个方法来动态请求权限, 调用这些方法会显示出一个标准的Dialog, 这个Dialog目前是不能被定制的.
2.1有时候可能需要解释为什么需要这个权限
有时候你可能会需要跟用户解释一下权限的用途.
注意不是每条权限都需要解释,显而易见的那种可以不解释,太多的解释会降低用户体验.
一种方式是,当用户拒绝过这个权限,但是又用到了这个功能, 那么很可能用户不是很明白为什么app需要这个权限, 这时候就可以先向用户解释一下.
为了发现这种用户可能需要解释的情形, Android提供了一个工具类方法: shouldShowRequestPermissionRationale()
如果app之前请求过该权限,被用户拒绝, 这个方法就会返回true.
如果用户之前拒绝权限的时候勾选了对话框中”Don’t ask again”的选项,那么这个方法会返回false.
如果设备策略禁止应用拥有这条权限, 这个方法也返回false.
注意具体解释原因的这个dialog需要自己实现, 系统没有提供.
2.2请求权限
请求权限的方法是: requestPermissions() 传入一个Activity, 一个permission名字的数组, 和一个整型的request code.
这个方法是异步的,它会立即返回, 当用户和dialog交互完成之后,系统会调用回调方法,传回用户的选择结果和对应的request code.
代码:
if (PackageManager.PERMISSION_GRANTED == ContextCompat.checkSelfP
ermission(MainActivity.this, Manifest.permission.READ_CONTACTS)) {
//has permission, do operation directly
ContactsUtils.readPhoneContacts(this);
Log.i(DEBUG_TAG, "user has the permission already!");
} else {
//do not have permission
Log.i(DEBUG_TAG, "user do not have this permission!");
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivit
y.this, 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.
Log.i(DEBUG_TAG, "we should explain why we need this permission!");
} else {
// No explanation needed, we can request the permission.
Log.i(DEBUG_TAG, "==request the permission==");
ActivityCompat.requestPermissions(MainActivity.this, 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.
}
}
这个对话框是系统的,不能自定义.
经验证, 请求权限对话框中的”Don’t ask again”的选项, 只有该条权限之前的状态是Denied的时候,才会出现.
以前从未授权(即第一次弹框), 或者之前的状态是Granted(当然这种情况一般不会弹框询问), 出现的弹框都是不带该不再询问的选项的.
2.3处理请求权限的响应
当用户对请求权限的dialog做出响应之后,系统会调用onRequestPermissionsResult() 方法,传回用户的响应.
这个回调中request code即为调用requestPermissions()时传入的参数,是app自定义的一个整型值.
如果请求取消,返回的数组将会为空.
代码:
@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.
ContactsUtils.readPhoneContacts(this);
Log.i(DEBUG_TAG, "user granted the permission!");
} else { // permission denied, boo! Disable the
// functionality that depends on this permission.
Log.i(DEBUG_TAG, "user denied the permission!");
} return;
} // other 'case' lines to check for other
// permissions this app might request
}
}
系统自动回调的情况:
有一些情形下,调用
1.自动授权: 如果用户已经允许了permission group中的一条A权限,那么当下次调用requestPermissions()方法请求同一个group中的B权限时, 系统会直接调用onRequestPermissionsResult()` 回调方法, 并传回PERMISSION_GRANTED的结果.
2.自动拒绝: 如果用户选择了不再询问此条权限,那么app再次调用requestPermissions()方法来请求同一条权限的时候,系统会直接调用onRequestPermissionsResult()回调,返回PERMISSION_DENIED.