Android6.0动态权限
在6.0以后,Google将权限分为两类,一类是Normal Permissions,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。
普通权限
- Normal Permissions(普通权限):
- | - |
---|---|
ACCESS_LOCATION_EXTRA_COMMANDS | 定位权限 |
ACCESS_NETWORK_STATE | 网络状态权限 |
ACCESS_NOTIFICATION_POLICY | 通知 APP通知显示在状态栏 |
ACCESS_WIFI_STATE | WiFi状态权限 |
BLUETOOTH | 使用蓝牙权限 |
BLUETOOTH_ADMIN | 控制蓝牙开关 |
BROADCAST_STICKY | 粘性广播 |
CHANGE_NETWORK_STATE | 改变网络状态 |
CHANGE_WIFI_MULTICAST_STATE | 改变WiFi多播状态,应该是控制手机热点(猜测) |
CHANGE_WIFI_STATE | 控制WiFi开关,改变WiFi状态 |
DISABLE_KEYGUARD | 改变键盘为不可用 |
EXPAND_STATUS_BAR | 扩展bar的状态 |
GET_PACKAGE_SIZE | 获取应用安装包大小 |
INTERNET | 网络权限 |
KILL_BACKGROUND_PROCESSES | 杀死后台进程 |
MODIFY_AUDIO_SETTINGS | 改变音频输出设置 |
NFC | 支付 |
READ_SYNC_SETTINGS | 获取手机设置信息 |
READ_SYNC_STATS | 数据统计 |
RECEIVE_BOOT_COMPLETED | 监听启动广播 |
REORDER_TASKS | 创建新栈 |
REQUEST_INSTALL_PACKAGES | 安装应用程序 |
SET_TIME_ZONE | 允许应用程序设置系统时间区域 |
SET_WALLPAPER | 设置壁纸 |
SET_WALLPAPER_HINTS | 设置壁纸上的提示信息,个性化语言 |
TRANSMIT_IR | 红外发射 |
USE_FINGERPRINT | 指纹识别 |
VIBRATE | 震动 |
WAKE_LOCK | 锁屏 |
WRITE_SYNC_SETTINGS | 改变设置 |
SET_ALARM | 设置警告提示 |
INSTALL_SHORTCUT | 创建快捷方式 |
UNINSTALL_SHORTCUT | 删除快捷方式 |
危险权限
- Dangerous Permissions(危险权限):
(联系人)
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
(手机状态)
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
(日历)
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
(相机)
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
(传感器)
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
(定位)
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
(存储权限)
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
(麦克相关,比如录音)
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
(短信)
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
可以通过adb shell pm list permissions -d -g进行查看。
这类权限需要在需要的时候,需要我们动态申请,比如:当我们需要打开相机拍摄照片的时候需要我们通过代码的方式在需要的地方去申请权限。Android6.0中权限问题中我们需要注意的是:
1:由于权限API的问题,我们的Actiivty最好是AppCompatActivity类型的,也就是说在你的BaseActivity需要继承AppCompatActivity
2:权限是分组的,同一组的权限申请其中一个,同组的权限就全部都申请了
相关API
使用的API:
6.0的运行时权限,我们最终都是要支持的,通常我们需要使用如下的API
int checkSelfPermission(String permission)
用来检测应用是否已经具有权限,这个方法是在API23中才有的,为了兼容低版本,建议使用v4包中的ContextCompat.checkSelfPermission,在下面的注意事项中有解释,这里就不在赘述了void requestPermissions(String[] permissions, int requestCode)
进行请求单个或多个权限,第一个参数是请求的权限集合,第二个参数是请求码,在回调监听中可以用来判断是哪个权限请求的结果void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
用户对请求作出响应后的回调,请求成功或者失败的监听shouldShowRequestPermissionRationale
这个API可以帮我们判断用户是否 已经拒绝过了授予该权限了。如果用户拒绝过,那么为true。此时你就可以解释为什么一定需要这么权限了,并且同时再次申请授予该权限。如果用户在再次申请该权限时,勾选了Never ask again,那么就一直为false,也就是说不会再展示该权限的任何申请了。参考鸿洋大神的解释:这个API主要用于给用户一个申请权限的解释,该方法只有在用户在上一次已经拒绝过你的这个权限申请。也就是说,用户已经拒绝一次了,你又弹个授权框,你需要给用户一个解释,为什么要授权,则使用该方法。
一个例子
注意:在AndroidManifest文件中添加需要的权限。这个步骤和我们之前的开发并没有什么变化,试图去申请一个没有声明的权限可能会导致程序崩溃。
final String permission_call_phone = Manifest.permission.CALL_PHONE;
final String permission_read_contacts = Manifest.permission.READ_CONTACTS;
final String permission_camear = Manifest.permission.CAMERA;
//版本判断
if (Build.VERSION.SDK_INT >= 23) {
Log.d("pepe", "Main"+"当前版本大于等于23");
//检查是否拥有权限
int checkCallPhonePermission = ContextCompat.checkSelfPermission(getApplicationContext(), permission_call_phone);
int checkReadContactsPermission = ContextCompat.checkSelfPermission(getApplicationContext(), permission_read_contacts);
int checkCamearPermission = ContextCompat.checkSelfPermission(getApplicationContext(), permission_camear);
//如果其中有未授权的
if (checkCallPhonePermission != PackageManager.PERMISSION_GRANTED||checkReadContactsPermission!= PackageManager.PERMISSION_GRANTED||checkCamearPermission!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, permission_call_phone)) {
//TODO:之前申请被拒绝,需要弹出解释对话框,为什么一定需要这个权限
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission_call_phone}, 1);
}
});
} else if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, permission_read_contacts)){
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission_read_contacts}, 2);
}
});
} else if(ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, permission_camear)) {
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission_camear}, 3);
}
});
}else{
//TODO:之前没有申请过,无需解释,直接申请授权
// No explanation needed, we can request the permission.
ActivityCompat.requestPermissions(MainActivity.this, new String[]{permission_call_phone,permission_read_contacts,permission_camear}, 0);
// MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
// app-defined int constant. The callback method gets the
// result of the request.
return;
}
} else if (checkCallPhonePermission == PackageManager.PERMISSION_GRANTED) {
//TODO:已授权,不用申请
Log.d("pepe", "Main"+"已授权,不用申请");
// callPhone();
}
} else {
//TODO:当前版本低于23,直接使用
Log.d("pepe", "Main"+"当前版本低于23,直接使用");
callPhone();
}
再来看回调:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 0: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//TODO:授权成功,做你想做的吧!
callPhone();
} else {
//TODO:用户拒绝授权!
startActivity(new Intent(MainActivity.this,BActivity.class));
Toast.makeText(MainActivity.this,"你已拒绝授予该权限,如需更改请于设置中设置!",Toast.LENGTH_LONG).show();
}
return;
}
}
}
封装
介绍两个库:
- 1、https://github.com/lovedise/PermissionGen
使用注解+反射,来判断授权成功与否的两种回调 - 2 、https://github.com/hongyangAndroid/MPermissions
鸿洋大神根据第一个库改的,将运行时注解改成Annotation Processor的方式,并直接用几个静态方法。
注意事项
API问题
由于checkSelfPermission和requestPermissions从API 23才加入,低于23版本,需要在运行时判断 或者使用Support Library v4中提供的方法
ContextCompat.checkSelfPermission
ActivityCompat.requestPermissions
ActivityCompat.shouldShowRequestPermissionRationale
多系统问题
当我们支持了6.0必须也要支持4.4,5.0这些系统,所以需要在很多情况下,需要有两套处理。比如Camera权限
if (isMarshmallow()) {
requestPermission();//然后在回调中处理
} else {
useCamera();//低于6.0直接使用Camera
}
两个特殊权限
特殊权限,顾名思义,就是一些特别敏感的权限,在Android系统中,主要由两个
SYSTEM_ALERT_WINDOW
,设置悬浮窗,进行一些黑科技WRITE_SETTINGS
修改系统设置
关于上面两个特殊权限的授权,做法是使用startActivityForResult
启动授权界面来完成。
请求SYSTEM_ALERT_WINDOW
private static final int REQUEST_CODE = 1;
private void requestAlertWindowPermission() {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE) {
if (Settings.canDrawOverlays(this)) {
Log.i(LOGTAG, "onActivityResult granted");
}
}
}
上述代码需要注意的是
使用Action Settings.ACTION_MANAGE_OVERLAY_PERMISSION启动隐式Intent
使用”package:” + getPackageName()携带App的包名信息
使用Settings.canDrawOverlays方法判断授权结果
请求WRITE_SETTINGS
private static final int REQUEST_CODE_WRITE_SETTINGS = 2;
private void requestWriteSettings() {
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_CODE_WRITE_SETTINGS) {
if (Settings.System.canWrite(this)) {
Log.i(LOGTAG, "onActivityResult write settings granted" );
}
}
}
上述代码需要注意的是
使用Action Settings.ACTION_MANAGE_WRITE_SETTINGS 启动隐式Intent
使用”package:” + getPackageName()携带App的包名信息
使用Settings.System.canWrite方法检测授权结果
注意:关于这两个特殊权限,一般不建议应用申请。
引用:
Android 6.0 动态权限申请注意事项 - uana_777的博客 - 博客频道 - CSDN.NET
Android 6.0 运行时权限处理完全解析 - Hongyang - 博客频道 - CSDN.NET
Android_动态权限管理的解决方案
GitHub - shiraji/permissions-dispatcher-plugin:一个自动生成权限相关代码的AS插件
谈谈Android 6.0运行时权限理解 – 码农网