1. 实战说明
(1) 问题描述:应用原在android5.0的基础上开发,换了android 7.0的系统运行该应用程序时报错:
image
(2) 原因分析:在Android6.0(M)之前权限在AndroidMainfest.xml文件中直接申明即可;而在Android6.0(M)及之后权限授权机制发生了很大的变化:
- 普通权限:不涉及用户隐私的权限普通权限(如 手机震动、网络访问、蓝牙、NFC等)仍在AndroidMainfest.xml文件中直接申明即可;
- 危险权限:涉及用户隐私或影响到其他应用的运行危险权限(如SD卡读写,短信、联系人、相机、电话、位置、语音等),当应用需要调用某些权限的时候,会给予用户一个通知与说明这些权限是干什么的,可以让用户有更加清醒的权限分配意识,也在一定程度上更加人性化的保护了用户的隐私。
- 特殊权限:比如系统级别对话框:SYSTEM_ALERT_WINDOW和修改系统设置:WRITE_SETTINGS这2个特殊权限,我们需要在startActivityForResult里调用即可,这2个权限一般不会用到,会用到的地方要么是黑科技或者是反用户体验的场景,这里就不再做过多描述。
备注:另外有一个权限READ_PHONE_STATE,我们可以通过这个权限来获取机器的唯一标识码,很多第三方统计是基于这个标识码来完成统计的,但是在我们应用一开始运行的时候,这个运行权限我们是没有的,在Application里我们也不能对权限进行获取,所以这点也需要我们去注意。
说明: 对于一些比较特别的权限,比如文件的读写权限,一般在我们第一次开启APP的时候就要去获取了,就是在你引导APP启动的时候,就引导用户去获取权限,当用户拒绝的时候,应该给出弹出框并跳转对应的应用权限管理界面(需要对不同机型进行设置)。
2. 需要用户手动赋予的权限( Dangerous Permissions)
所属权限组 | 权限 |
---|---|
日历 | READ_CALENDAR |
日历 | WRITE_CALENDAR |
相机 | CAMERA |
联系人 | READ_CONTACTS |
联系人 | WRITE_CONTACTS |
联系人 | GET_ACCOUNTS |
位置 | ACCESS_FINE_LOCATION |
位置 | ACCESS_COARSE_LOCATION |
麦克风 | RECORD_AUDIO |
电话 | READ_PHONE_STATE |
电话 | CALL_PHONE |
电话 | READ_CALL_LOG |
电话 | WRITE_CALL_LOG |
电话 | ADD_VOICEMAIL |
电话 | USE_SIP |
电话 | PROCESS_OUTGOING_CALLS |
传感器 | BODY_SENSORS |
短信 | SEND_SMS |
短信 | RECEIVE_SMS |
短信 | READ_SMS |
短信 | RECEIVE_WAP_PUSH |
短信 | RECEIVE_MMS |
存储 | READ_EXTERNAL_STORAGE |
存储 | WRITE_EXTERNAL_STORAGE |
3. 适配Android6.0动态权限管理
由于Android6.0(M)及之后运行权限机制的出现,虽然对用户更友好了,但对开发者来说:我们需要对新开发的应用去做适配,在用这些权限的时候去动态重新请求系统权限并得到用户的允许授权;而之前开发的老应用运行到6.0系统上可能会发生各种崩溃需要我们去适配Android6.0动态权限管理,谷歌做了良好的适配。
3.1 方法一:只要把targetSdkVersion的版本设置为低于23就可以了。
- 当你的应用targetSdkVersion小于23的时候,就算你运行在Android6.0系统上,它也会默认采用以前的权限管理机制。
- 当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它才会采用新的这套权限管理机制。
备注:不过不建议采用这种方案,因为随着国产手机ROM的更新,比如小米,华为等也开始有部分机型进行了系统升级,所以这是种趋势。
3.2 方法二:动态权限申请(如申请内存读写权限)
主要记住下面几个API方法就可以(API23之后提供)
- int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
- void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
- void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 请求权限结果回调
(1) 首先,需要在AndroidManifest.xml静态申请权限,否则无法动态申请权限
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
(2) 在java代码中写动态申请权限的逻辑
- 官网写法示例
public void requestPower() {
//判断是否已经赋予权限
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.权限字符)
!= PackageManager.PERMISSION_GRANTED) {
//如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.权限字符)) {//这里可以写个对话框之类的项向用户解释为什么要申请权限,并在对话框的确认键后续再次申请权限.它在用户选择"不再询问"的情况下返回false
} else {
//申请权限,字符串数组内是一个或多个要申请的权限,1是申请权限结果的返回参数,在onRequestPermissionsResult可以得知申请结果
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.权限字符,}, 1);
}
}
}
- 申请内存读写权限
/*动态申请权限:SD卡读写权限*/
public void requestAllPower() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //当前系统大于等于6.0
//判断是否已经赋予权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PERMISSION_GRANTED) {
//如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
//这里可以写个对话框之类的向用户解释为什么要申请权限,并在对话框的确认键后续再次申请权限.它在用户选择"不再询问"的情况下返回false
} else {
//申请权限,字符串数组内是一个或多个要申请的权限,1是申请权限结果的返回参数(可自定义),在onRequestPermissionsResult可以得知申请结果
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
}
}else {
//具有SD卡读写权限,直接调用代码
}
} else {
//当前系统小于6.0,直接调用代码
}
}
(3) 判断权限申请结果的方法
/*判断权限申请结果的方法(在权限申请对话框消失后执行)
注意:PERMISSION_GRANTED变量,导入的是:import static android.content.pm.PackageManager.PERMISSION_GRANTED)*/
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
for (int i = 0; i < permissions.length; i++) {
if (grantResults[i] == PERMISSION_GRANTED) {
//Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
Log.d("PermissionsResult","权限" + permissions[i] + "申请成功");
} else {
//不具有相关权限,给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限开启
//Toast.makeText(this, "" + "权限" + permissions[i] + "申请失败", Toast.LENGTH_SHORT).show();
Log.d("PermissionsResult","权限" + permissions[i] + "申请失败(不具有相关权限,请到系统设置-应用管理里把相关权限开启)");
}
}
}
}
requestCode
对应申请权限时new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
的最后一个参数,该参数代表申请权限结果的返回参数,可自定义,如:
- public static final int
REQUEST_PERMISSION_EXTERNAL_STORAGE_CODE
= 1;- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE},
REQUEST_PERMISSION_EXTERNAL_STORAGE_CODE
);- if (requestCode ==
REQUEST_PERMISSION_EXTERNAL_STORAGE_CODE
) {}
当系统要求用户授予权限时,用户可以选择指示系统不再要求提供该权限。这种情况下,无论应用在什么时候使用 requestPermissions() 再次要求该权限,系统都会立即拒绝此请求。
备注:android各个版本比较、Android 6.0(API 23)及其以上动态申请的权限与申请权限的方法、Android 6.0 动态权限申请简单简洁优雅的处理方式
网友评论