Andriod 6.0 动态权限Permission相关
随着Android 6.0发布以及普及,我们开发者所要应对的主要就是新版本SDK带来的一些变化,首先关注的就是权限机制的变化。对于6.0的几个主要的变化,查看查看官网的这篇文章http://developer.android.com/intl/zh-cn/about/versions/marshmallow/android-6.0-changes.html,其中当然包含Runtime Permissions。
相关知识点
新的权限机制更好的保护了用户的隐私,Google将权限分为两类,一类是Normal Permissions
,这类权限一般不涉及用户隐私,是不需要用户进行授权的,比如手机震动、访问网络等;另一类是Dangerous Permission
,一般是涉及到用户隐私的,需要用户进行授权,比如读取sdcard、访问通讯录等。
targetSdkVersion和minSdkVersion相关的区别
关于taretSdkVersion和minSdkVersion的区分相信很多人都不是太清楚,在这里推荐Android Min SDK Version vs. Target SDK Version,对相关的设置有点说明。这两者相当于一个区间,你可以用到targetSDK中最新的API和最酷的新功能,但你又不得不向下兼容到minSDK,保证这个区间内的设备都可以正常的运行你的app。换句话说,你想使用Android刚刚推出的新特性,但这对于你的app又不是必须的,你就可以将targetSDK设置为你想使用新特性的SDK版本,minSDK设置成低版本保证所有人都可以使用你的app。
minSdkVersion与maxSdkVersion比较容易理解,就是在安装程序的时候,如果目标设备的API版本小于minSdkVersion, 或者大于maxSdkVersion,程序将无法安装。一般来说没有必要设置maxSdkVersion。
targetSdkVersion相对复杂一些,如果设置了此属性,那么在程序执行时,如果目标设备的API版本正好等于此数值, 他会告诉Android平台:此程序在此版本已经经过充分测,没有问题。不必为此程序开启兼容性检查判断的工作了。 也就是说,如果targetSdkVersion与目标设备的API版本相同时,运行效率可能会高一些。 但是,这个设置仅仅是一个声明、一个通知,不会有太实质的作用, 比如说,使用了targetSdkVersion这个SDK版本中的一个特性,但是这个特性在低版本中是不支持的 ,那么在低版本的API设备上运行程序时,可能会报错:Java.lang.VerifyError。也就是说,此属性不会帮你解决兼容性的测试问题。 你至少需要在minSdkVersion这个版本上将程序完整的跑一遍来确定兼容性是没有问题的。(这个问题确实让人头疼)
project.properties中的target是指在编译的时候使用哪个版本的API进行编译。 综上,上面的四个值其实是作用于不同的时期:
target API level是在编译的时候起作用,用于指定使用哪个API版本(SDK版本)进行编译。 minSdkVersion和maxSdkVersion是在程序安装的时候起作用, 用于指定哪些版本的设备可以安装此应用。 targetSdkVersion是在程序运行的时候起作用,用于提高指定版本的设备上程序运行体验。知道注意的是:最好将project.properties中target与AndroidManifest中minSdkVersion的APILevel设置为同一个等级,达到编译兼容最小版本的效果,如果前者比后者等级高的话,可能出现的一个问题就是,在开发过程中,一些高等级的api能够通过编译,但是当实际运行在低系统的真机或者模拟器上时,将会出现一些NoSuchMethodError的错误,导致程序奔溃。例如:
android.view.view.setBackgroundDrawable(Drawable background)方法在APILeven为16时废弃(deprecated)掉了,改用setBackground(Drawable background),开发中如果工程project.properties中的target编译版本大于或等于16,而在AndroidManifest中设置的minSdkVersion为小于16,则会出现能够通过开发工具的编译,但运行在4.1系统一下的机器中时,会出现NoSuchMethodError的错误!
- 当你的应用targetSdkVersion小于23的时候,就算你运行在Android6.0系统上,它也会默认采用以前的权限管理机制,也就是一刀切。当你的targetSdkVersion大于等于23的时候且在Andorid6.0(M)系统上,它才会采用新的这套权限管理机制。
所以如果你想逃开这个“麻烦”,只要把targetSdkVersion的版本设置为低于23就可以了,不过不建议采用这种方案,该来的总是要来的,随着国产手机ROM的更新,比如小米,华为,魅族等也开始有部分机型进行了系统升级,所以这是种趋势。
说了这么多,那么来看下怎么进行Android6.0(M)的权限管理适配吧,其实很简单,只需要记住下面几个API方法就可以:(API23之后提供)
- int checkSelfPermission(String permission) 用来检测应用是否已经具有权限
- void requestPermissions(String[] permissions, int requestCode) 进行请求单个或多个权限
- void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 请求权限结果回调
参考博文链接:http://www.jianshu.com/p/a37f4827079a
下面来段代码示例(为了向下兼容,这里我采用了v4包下的ContextCompat和ActivityCompat):
View.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//判断当前系统是否高于或等于6.0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//当前系统大于等于6.0
if (ContextCompat.checkSelfPermission(MineInforActivity.this,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
//具有拍照权限,直接调用相机
//具体调用代码
} else {
//不具有拍照权限,需要进行权限申请
ActivityCompat.requestPermissions(MineInforActivity.this,new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE);
}
} else {
//当前系统小于6.0,直接调用拍照
}
}
});
如果用户勾选了不再提醒,然后把你拒绝了,那你的应用就GG了,其实这里还有一个API方法:
if(!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)){
//如果用户勾选了不再提醒,则返回false
//给予用户提醒,比如Toast或者对话框,让用户去系统设置-应用管理里把相关权限打开
}
ContextCompat.checkSelfPermission无效的问题
在做项目中发现,我在使用ContextCompat.checkSelfPermission时,无论如何开关权限返回值都是PackageManager.PERMISSION_GRANTED,而使用PackageManager.checkPermission()的时候返回值又始终都是PackageManager.PERMISSION_DENIED;
经过查询相关文档及博客发现:
**If your application is targeting an API level before 23 (Android M) then both:ContextCompat.CheckSelfPermission and Context.checkSelfPermission doesn't work and always returns 0 (PERMISSION_GRANTED). Even if you run the application on Android 6.0 (API 23).
**
在targetSdkVersion小于23(Android M)的时候,ContextCompat.CheckSelfPermission 和Context.checkSelfPermission方法都不能正常工作并且始终返0(PERMISSION_GRANTED),即使你的应用运行在Android6.0(API 23)的设备上。
解决办法:
As I said in the 1st point, if you targeting an API level before 23 on Android 6.0 then ContextCompat.CheckSelfPermission and Context.checkSelfPermission doesn't work. Fortunately you can use PermissionChecker.checkSelfPermission to check run-time permissions.
使用permissionChecker.checkSelfPermission,来检测权限是否被授予。
关于权限android.permission.READ_PHONE_STATE和 WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE问题
参考博文
以上两个权限对应用运行时影响最大,其中READ_PHONE_STATE用来获取deviceID,即IMEI号码。这是很多统计依赖计算设备唯一ID的参考。如果新的权限导致读取不到,避免导致统计的异常。建议在完全支持运行时权限之前,将对应的值写入到App本地数据中,对于新安装的,可以采取其他策略减少对统计的影响。
WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE这两个权限和外置存储(即sdcard)有关,对于下载相关的应用这一点还是比较重要的,我们应该尽可能的说明和引导用户授予该权限。
关于权限android.permission.READ_PHONE_STATE,系统会弹出一个对话框提醒撤销的危害,如果用户执意撤销,会带来如下的反应
如果你的程序正在运行,则会被杀掉。
当你的应用再次运行时,可能出现崩溃
为什么会可能崩溃的,比如下面这段代码
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
String deviceId = telephonyManager.getDeviceId();
if (deviceId.equals(mLastDeviceId)) {//This may cause NPE
//do something
}
如果用户撤消了获取DeviceId的权限,那么再次运行时,deviceId就是null,如果程序后续处理不当,就会出现崩溃。
目前我在项目中做了版本控制,当版本较低是还是用老的方法,做了向下兼容。
service与动态权限管理兼容问题
requestPermission()
can only be called from an Activity and not a Service (unlike checkPermission()
that only requires PackageManager). So you need to do some extra work to get around that; you do need to provide an Activity in your app and, for example, your Service can check for permissions it needs and if they have not been granted yet, it can create a notification and that can inform user with a descriptive short message as to why there is a notification and what needs to happen when they click on the notification, etc.
requestPermission()需要用户提供Activity,在service里使用存在问题,可以在service里面先执行checkPermission方法,判断是否授予权限,鄙人技术有限没能解决这个问题,不过有技术大牛实现相关问题GitHub传送门有兴趣的可以下载先来研究研究源码,这里我先做下相关备注以便后面学习。
部分手机兼容存在禁止权限却始终返回PERMISSION_GRANTED
这种情况部分手机解决方案跟上述ContextCompat.checkSelfPermission无效问题类似,先设置targetSdkVersion>=23,再设置ContextCompat.checkSelfPermission()改为permissionChecker.checkSelfPermission()方法,来检测是否授予权限。
但是针对魅族、小米手机,还是存在无效问题,这就需要对应的处理,还去大佬指教……
BaseActivity部分核心代码
1、先检测权限是否授予
* 检测所有的权限是否都已授权
*
* @param permissions
* @return
*/
private boolean checkPermissions(String[] permissions) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return true;
}
for (String permission : permissions) {
if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
2、获取权限集合中权限列表
* 获取权限集中需要申请权限的列表
*
* @param permissions
* @return
*/
private List<String> getDeniedPermissions(String[] permissions) {
List<String> needRequestPermissionList = new ArrayList<>();
for (String permission : permissions) {
if (PermissionChecker.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED
|| ActivityCompat.shouldShowRequestPermissionRationale(this, permission)) {
needRequestPermissionList.add(permission);
}
}
return needRequestPermissionList;
}
3、请求所需权限
* 请求权限
*
* @param permissions
* 请求的权限
* @param requestCode
* 请求权限的请求码
*/
public void requestPermission(String[] permissions, int requestCode) {
this.REQUEST_CODE_PERMISSION = requestCode;
if (checkPermissions(permissions)) {
permissionSuccess(REQUEST_CODE_PERMISSION);
} else {
List<String> needPermissions = getDeniedPermissions(permissions);
ActivityCompat.requestPermissions(this, needPermissions.toArray(new String[needPermissions.size()]),
REQUEST_CODE_PERMISSION);
}
}
4、确定所有权限是否都已授权
* 确认所有的权限是否都已授权
*
* @param grantResults
* @return
*/
private boolean verifyPermissions(int[] grantResults) {
for (int grantResult : grantResults) {
if (grantResult != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
5、系统请求权限回调执行对应的操作
* 系统请求权限回调
*
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_PERMISSION) {
if (verifyPermissions(grantResults)) {
permissionSuccess(REQUEST_CODE_PERMISSION);
} else {
permissionFail(REQUEST_CODE_PERMISSION);
}
}
}
6、子类继承实现方法执行相应的操作
* 获取权限成功
*
* @param requestCode
*/
public void permissionSuccess(int requestCode) {
Log.e("TAG", "获取权限成功=" + requestCode);
}
/**
* 权限获取失败
*
* @param requestCode
*/
public void permissionFail(int requestCode) {
Log.e("TAG", "获取权限失败=" + requestCode);
}
欢迎各位大佬给予宝贵意见……