概述
Android 6.0开始,为了更好的保护用户隐私,引入了新的权限机制:
- 普通权限与Android 6.0之前一样,直接在Manifest中声明即可
- 危险权限在使用之前告知用户.获得用户授权之后才可以获取相应的权限
- 用户可以选择不再提醒永久拒绝某个权限
危险权限列表
粗体字为权限组,权限组下面为权限组内的具体权限</br>
申请权限组中的任何一个权限即获得了整个权限组中的所有权限CONTACTS
- READ_CONTACTS
- WIRTE_CONTACTS
- GET_CONTACTS
PHONE
- READ_CALL_LOG
- READ_PHONE_STATE
- CALL_PHONE
- WRITE_CALL_LOG
- USE_SIP
- PROCESS_OUTGOING_CALLS
- ADD_VOICEMAIL
CALENDAR
- READ_CALENDAR
- WRITE_CALENDAR
CAMERA
CAMERA
SENSORS
BODY_SENSORS
LOCATION
- ACCESS_FINE_LOCATION
- ACCESS_COARSE_LOCATION
STORAGE
- READ_EXTERNAL_STORAGE
- WRITE_EXTERNAL_STORAGE
MICROPHONE
- RECORD_AUDIO
SMS
- READ_SMS
- RECEIVE_WAP_PUSH
- RECEIVE_MMS
- RECEIVE_SMS
- SEND_SMS
- READ_CELL_BROADCASTS
使用安卓自带Api申请运行时权限
以获取用户通讯录为例
- 在Manifest中声明我们需要的权限
//in Manifest
<uses-permission android:name="android.permission.READ_CONTACTS"/>
- 在Accitity中申请权限
大致的逻辑:</br>
-> 检查用户是否授权过该权限
-> 已经授权过 -> 执行业务逻辑
-> 没有授权 -> 申请权限 -> 成功 -> 执行业务逻辑
API:
-
检查用户是否授权过该权限
int checkSelfPermission()
返回值:</br>
PackageManager.PERMISSION_GRANTED 表示有权限
PackageManager.PERMISSION_DENIED 表示无权限 -
获取权限
void requestPermissions()
三个参数:
Context
new String[]{} 需要申请权限的字符串数组
RequestCode 自定义的请求码,结果回调的方法中使用,判断是哪个权限的申请 -
权限请求的结果回调
void onRequestPermissionsResult()
Example:
// 申请一个读取通讯录的权限
private final static int PERMISSION_CONTACT = 1;
private void getPermission() {
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
// 无权限时去请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
}else{
// 有权限直接进行业务操作
doSomeWork();
}
}
/**
* 直接在Activity重写 onRequestPermissionsResult()方法即可
* requestCode requetsPermissions()方法中传入的RequestCode
* String[] permissons requetsPermissions()方法中传入的permission数组
* grantResults 返回的具体结果
**/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,@NonNull int[] grantResults) {
switch(requestCode) {
case REQUEST_CONTACT:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 授予权限,拨打电话
doSomeWork();
} else {
Toast.makeText(this, "请求权限被拒绝", Toast.LENGTH_SHORT).show();
}
break;
}
}
用户拒绝权限的处理
拒绝的逻辑:
申请权限 -> 用户拒绝 -> END
再次申请权限 -> 权限弹框会多一个不再提醒的选项 -> 用户勾选不再提醒选项并拒绝权限 -> END Forever
为了我们业务的正常进行,我们需要避免 END FOREVER的结果,所以在第二次申请权限的时候我们要提醒用户不要拒绝我们的权限,安卓也为我们提供了这样的API
- boolean shouldShowRequestPermissionRationale() 是否给用户提醒权限的必要性
在代码中区分是第一次还是被拒绝后再次申请权限
// 重写一下getPermission()方法
private void getPermission() {
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED) {
// 无权限时去请求权限
if(ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
// 通常是弹框告诉用户权限的必要性,然后继续申请权限
new AlertDialog.Builder(this)
.setCancelable(false)
.setMessage("求求你别拒绝~")
.setPositiveButton("好的", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
}
})
.setNegativeButton("不给", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
} else {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.READ_CONTACTS},PERMISSION_CONTACT);
}
}else{
// 有权限直接进行业务操作
doSomeWork();
}
}
使用第三方框架申请权限
由于国内的各家手机厂商都对安卓进行了魔改,安卓所提供的原生的API可能会在一些手机上报错,或者无法正常申请权限。对种类繁多的手机Rom和安卓版本都进行适配会非常繁琐,推荐大家使用一些成熟的第三方框架去获取手机权限。
框架并不能解决所有的问题
- 写博客之前碰到的问题:
我的App需要申请相机权限,本人用的是小米/华为的手机,当时选择了使用AndPermission申请权限。测试了几次之后都挺正常。然后使用同事的魅族手机测试,在用户第一次拒绝权限之后,第二次请求权限 gradResults 返回的是Granted.所以代码会在没有权限的情况下继续向后执行,相机界面变成了一片黑色.
然后我又用原生Api尝试了一下,结果是一样的.所以原因应该是魅族手机的权限逻辑问题.
接下来去找解决问题的方案. 在AndPermission的Issus里面作者是这样解释的
在有些国产机上,申请权限时系统返回的总是有权限,对于通讯录、读写SD卡等权限,AndPermission的策略是拿到有权限的结果后,再执行一下读取一条通讯录或者向SD卡写入一个文件来验证是否真的有权限。但是对于拨打电话此类权限,AndPermission不能真的去拨打一个电话,因此直接回调了onGranted(),开发者可以在onGranted()中直接拨打电话,来配合AndPermission申请权限,如果有就自然拨打出去了,如果没有权限,上述手机会弹出授权对话框或者抛出异常,如果抛出异常,AndPermission会重新回调到onDenied()方法,开发者即可认为是没有权限。
简单分析下作者所说的内容:
通常的逻辑: getPermission -> granted() -> success
严谨的逻辑: getPermission -> granted() -> do() -> 捕获由于权限造成的异常 -> onDenied() -> getPermission()
针对我上面的相机权限问题: 在申请权限成功进入相机界面之后,在相机启动得时候相机打开失败/获取不到相机参数时,重新弹框去告诉用户.所以还是得具体情况具体分析.
最后总结几个申请权限的注意事项:
- 在需要的时候申请权限,不要在App开屏的时候请求所有的权限。体验非常不友好,而且容易被拒绝
- 对于一些必要权限(用户拒绝概率较高),在申请权限之前弹框提示告知用户.
- 有些手机可能会返回错误的权限信息,获取到权限提供的数据才算结束
- 参考AndPermission的做法,我们可以在代码中抛出由于没有权限引起的异常,再次申请权限。或者显示ErrorView,让用户点击授权。
网友评论