一、android6.0以后的危险权限介绍
(注意:Android O 8.0对于权限更加严格,下面会说一下8.0)
android6.0以后有些危险权限需要手动去授权,就有了运行时权限的处理。下面的表格就是危险权限组:
权限组名 | 权限名 |
CALENDAR 日历 | READ_CALENDAR WRITE_CALENDER |
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 短信 | Short Message Service SEND_SMS RECEIVE_SMS READ_SMS RECEIVE_WAP_PUSH RECEIVE_MMS |
STORAGE 数据存储 | READ_EXTRAL_STRORAGE WRITE_EXTERNAL_STORAGE |
二、传统的权限申请过程:
1、在AndroidManifest文件中添加需要的权限。
2、检查权限是否授权
3、申请权限
4、处理授权的结果
以定位权限为例:
1.首先在请单文件中声明
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
2.检查权限是否授权
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
//没有授权,编写申请权限代码
}else{
//已经授权,执行操作代码
}
基本上调用checkSelfPermission()函数传入权限参数,返回的结果又两个:
如果是已授权的权限,该方法返回结果是 PackageManager.PERMISSION_GRANTED 常量为 0,
如果是未授权的权限,该方法返回结果是 PackageManager.PERMISSION_DENIED 常量为 -1。
这样就可以判断是否已经授权,来进行下一步的操作。
3.如果没有授权,需要申请权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
0);
这是一个异步的方法,第一个参数是Context;第二个参数是需要申请的权限的字符串数组(这个是支持同时申请多个权限,系统会逐个询问是否授权);第三个参数为请求码requestCode,主要用于回调的时候检测。
4.处理权限申请的回调结果
重写Activity或者fragment的 onRequestPermissionsResul()方法
//权限回调方法
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 0:
//grantResults数组存储的申请的返回结果,
//PERMISSION_GRANTED 表示申请成功
if (grantResults.length>0&&grantResults[0]== PackageManager.PERMISSION_GRANTED){
//授权成功,
//这里写相应的 操作代码
}else{
//授权失败,可以简单提示用户
Toast.makeText(this, "没有授权继续操作", Toast.LENGTH_SHORT).show();
}
break;
case 1:
//同上
break;
default:
break;
}
}
三、使用EasyPermissions框架申请权限
GitHub地址:https://github.com/googlesamp...
EasyPermissions是谷歌封装的一个运行时权限申请的库,简化了操作的过程。
1.依赖库
dependencies {
implementation 'pub.devrel:easypermissions:3.0.0'
}
2.再在清单文件中声明要申请的危险权限
如果不声明的话,直接在代码中写也能申请成功
3.检查权限以及申请权限
if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
// 已经申请过权限,做想做的事
} else {
// 没有申请过权限,现在去申请
/**
*@param host Context对象
*@param rationale 权限弹窗上的提示语。
*@param requestCode 请求权限的唯一标识码
*@param perms 一系列权限
*/
EasyPermissions.requestPermissions(
MainActivity.this,
"申请权限",
0,
Manifest.permission.ACCESS_FINE_LOCATION);
}
requestPermissions() 一般用这个四个参数的就可以
第一个参数:Context对象
第二个参数:权限弹窗上的文字提示语。告诉用户,这个权限用途。
第三个参数:这次请求权限的唯一标识请求码,code。
第四个参数 : 一些系列的权限。
这里说一下第二个参数,不是第一次申请系统默认弹出的提示语,而是,我们拒绝后,再次点击申请弹出的对话框。
4.重写onRequestPermissionsResult()方法,把执行操作给easyPermissions
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
5.实现EasyPermissions.PermissionCallbacks接口,重写两个方法
6.实现EasyPermissions.PermissionCallbacks接口,重写两个方法
public class MainActivity extends AppCompatActivity implements
EasyPermissions.PermissionCallbacks,
EasyPermissions.RationaleCallbacks {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (EasyPermissions.hasPermissions(this, Manifest.permission.ACCESS_FINE_LOCATION)) {
// 已经申请过权限,做想做的事
} else {
// 没有申请过权限,现在去申请
/**
*@param host Context对象
*@param rationale 权限弹窗上的提示语。
*@param requestCode 请求权限的唯一标识码
*@param perms 一系列权限
*/
EasyPermissions.requestPermissions(
MainActivity.this,
"申请权限",
0,
Manifest.permission.ACCESS_FINE_LOCATION);
}
}
/**
* 重写onRequestPermissionsResult,用于接受请求结果
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//将请求结果传递EasyPermission库处理
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
/**
* 请求权限成功。
* 可以弹窗显示结果,也可执行具体需要的逻辑操作
*
* @param requestCode
* @param perms
*/
@Override
public void onPermissionsGranted(int requestCode, List<String> perms) {
Log.e("Permissions", "用户授权成功");
}
/**
* 请求权限失败
*
* @param requestCode
* @param perms
*/
@Override
public void onPermissionsDenied(int requestCode, List<String> perms) {
Log.e("Permissions", "用户授权失败");
/**
* 若是在权限弹窗中,用户勾选了'NEVER ASK AGAIN.'或者'不在提示',且拒绝权限。
* 这时候,需要跳转到设置界面去,让用户手动开启。
*/
if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) {
new AppSettingsDialog
.Builder(this)
.setTitle("需要定位权限")
.setRationale("没有定位权限,此应用程序可能无法正常工作。请打开“应用程序设置”界面修改应用程序权限。")
.build()
.show();
}
}
@Override
public void onRationaleAccepted(int requestCode) {
Log.e("Permissions", "用户看到我们的提示选择了继续申请权限");
}
@Override
public void onRationaleDenied(int requestCode) {
Log.e("Permissions", "用户看到我们的提示依然选择了残忍拒绝");
}
@AfterPermissionGranted(0)
public void allGranted(){
Log.e("Permissions", "请求的权限全部被允许时调用调用此方法");
}
}
7.补充
- EasyPermissions.somePermissionPermanentlyDenied(this, perms):用于判断用户是否勾选“不再提醒功能”
- AppSettingsDialog:用户勾选“不再提醒功能”后弹出的对话框,告诉用户这个权限的重要性,点击确定后跳转setting页,申请被拒绝时使用。
- @AfterPermissionGranted(code)注解:如果所有的请求的权限都被授予了,被注解的方法将会被执行,这样做是为了简化通常的请求权限成功之后再调用方法的流程。参数code是请求权限的唯一标识请求码。
四、android 8.0运行时权限
Android 8.0 之前,如果应用在运行时请求权限并且被授予该权限,系统会错误地将属于同一权限组并且在清单中注册的其他权限也一起授予应用。
对于针对 Android 8.0 的应用,此行为已被纠正。系统只会授予应用明确请求的权限。然而,一旦用户为应用授予某个权限,则所有后续对该权限组中权限的请求都将被自动批准。
例如,假设某个应用在其清单中列出 READ_EXTERNAL_STORAGE 和WRITE_EXTERNAL_STORAGE。应用请求 READ_EXTERNAL_STORAGE,并且用户授予了该权限。如果该应用针对的是 API 级别 24 或更低级别,系统还会同时授予 WRITE_EXTERNAL_STORAGE,因为该权限也属于同一 STORAGE 权限组并且也在清单中注册过。如果该应用针对的是 Android 8.0,则系统此时仅会授予 READ_EXTERNAL_STORAGE;不过,如果该应用后来又请求 WRITE_EXTERNAL_STORAGE,则系统会立即授予该权限,而不会提示用户。
注意:
在6.0-7.1的系统中,如果之前申请过READ_EXTERNAL_STORAGE并且通过申请,则后续的的java代码可以直接使用同一组内的其它权限,比如WRITE_EXTERNAL_STORAGE权限,而不需要再调用requestPermissions(WRITE_EXTERNAL_STORAGE);如果之前的申请没有通过,则后续使用同一组的其它权限时仍然需要申请
在8.0及后续系统中,如果应用之前申请过READ_EXTERNAL_STORAGE并且通过申请,则后续的的java代码在使用同一组内的其它权限如WRITE_EXTERNAL_STORAGE之前,必须调用requestPermissions(WRITE_EXTERNAL_STORAGE),而此次调用系统将会立即回调onPermissionResult(),而不会弹出权限申请框。但如果不调用的话,将会崩溃。
为了兼容性,我们申请的权限直接申请权限组所有权限,我们可以做一个工具类,将同一个权限组的权限封装在一起。
package com.matt.myutils.utils;
/**
* @author matt.Ljp
* @time 2020/2/27 11:04
* @description 权限组
*/
public class PermissionGoup {
//日历
public static final String[] CALENDAR = {
"android.permission.READ_CALENDAR",
"android.permission.WRITE_CALENDAR"};
//相机
public static final String[] CAMERA = {"android.permission.CAMERA"};
//通讯录
public static final String[] CONTACTS = {
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
"android.permission.GET_ACCOUNTS"};
//定位
public static final String[] LOCATION = {
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION"};
//麦克风
public static final String[] RECORD_AUDIO = {"android.permission.RECORD_AUDIO"};
//电话
public static final String[] PHONE = {
"android.permission.READ_PHONE_STATE",
"android.permission.CALL_PHONE",
"android.permission.READ_CALL_LOG",
"android.permission.WRITE_CALL_LOG",
"com.android.voicemail.permission.ADD_VOICEMAIL",
"android.permission.USE_SIP",
"android.permission.PROCESS_OUTGOING_CALLS"};
//传感器
public static final String[] SENSORS = {"android.permission.BODY_SENSORS"};
//短信
public static final String[] SMS = {
"android.permission.READ_SMS",
"android.permission.SEND_SMS",
"android.permission.RECEIVE_SMS",
"android.permission.RECEIVE_WAP_PUSH",
"android.permission.RECEIVE_MMS"};
//存储
public static final String[] STORAGE = {
"android.permission.WRITE_EXTERNAL_STORAGE",
"android.permission.READ_EXTERNAL_STORAGE"};
/**
* 对应的权限组请求码
*/
public static final int CALENDAR_CODE = 1;
public static final int CAMERA_CODE = 2;
public static final int CONTACTS_CODE = 3;
public static final int LOCATION_CODE = 4;
public static final int RECORD_AUDIO_CODE = 5;
public static final int PHONE_CODE = 6;
public static final int SENSORS_CODE = 7;
public static final int SMS_CODE = 8;
public static final int STORAGE_CODE = 9;
}