热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

探讨Android6.0及以上新权限系统的检测与处理

探讨Android6.0及以上新权限系统的检测与处理作者:蒋东国时间:2016年11月24日星期四应用来源:

探讨Android 6.0及以上新权限系统的检测与处理 

 

作者:

蒋东国

时间:

 2016年11月24日 星期四

应用来源:

 lcb APP(测试机型:huawei 4X(6.0)/Samsung Note4(Android 5.0.1))

博客地址:

  http://blog.csdn.net/andrexpert/article/details/53331836

      

       情景再现“最近在开发一个地图导航软件,由于之前的测试都是部署在三星Note4(5.0.1)上,相关功能基本正常。当自己换到荣耀4X(Android 6.0)时,软件虽然没有异常退出,但却无法定位,通过查看Logcat打印出来的日志,发现这是因为APP部署到6.0以上系统后,Android系统没有授予相关位置权限才会报“Could not open database,(OS error -13:Permission denied)”错误。”

 

       从Google官方文档可知,Android系统升级到6.0后,它的权限系统被重新设计。相比原来新安装的APP系统会一次性授予所有权限和用户无法管理APP权限的不足,新的权限系统不再允许新安装的APP一次性获得所有权限,APP必须在运行时一个一个地询问用户授予权限,甚至有时候都不会主动申请用户授权,开发者不得不自己去检测和请求用户授予来获得权限。那么当我们的APP部署到Android 6.0以上系统的终端时,某些功能没有获得相关权限或者用户拒绝授予权限又会出现什么问题呢?这里的异常情况有两种:

      (1)   APP暂时不支持6.0以上系统,即APP的AndroidManifest.xml配置文件

中的android:targetSdkVersion属性值<23时(Android 6.0即SDK版本23),系统会认为新安装的APP还没有支持新的权限系统而不会异常退出,当然APP相关的功能也不会正常运行。

      (2)   APP已经支持6.0以上系统,即APP的 AndroidManifest.xml配置文件

android:targetSdkVersion属性值>=23时,如果APP在运行时没有获得相关的权限,将会异常退出。


1. 让你的APP支持新的运行时权限  


     Android权限系统被重新设计后,系统的安全性自然提高不少,用户的相关数据也得到了很好的保护。但是对于开发者而言,如果其APP还没有引入支持新的权限系统,当APP运行在Android6.0以上系统时,很可能导致很多问题,这对于一款APP来说可能是致命的。因此,谷歌在API 23中引入了ContextWrapper.checkSelfPermission和Activity.requestPermissions两个方法分别用来检查所请求权限的授予情况和请求用户授予该权限。当然,之前很多人都认为使用权限的检查都是用checkPermission、checkCallingOrSelfPermission来实现,其实,这两个方法只是检测你的APP是否声明了该权限,只要你声明了运行时请求的权限,它们都将返回PERMISSION_GRANTED,最后自然也无法在APP运行时真正检查到是否拥有该权限。

       为了更好的理解和使用ContextWrapper.checkSelfPermission、Activity.requestPermissions来进行运行时权限的检测和请求,我根据需求将其进行了封装,即是自己的总结,也方便了他人,具体内容如下:

      (1) APP请求单个权限,封装的方法为isPermissionGranted,它需要传递两个参数:

permissionName参数为请求的权限,比如发送短信为Manifest.permission.SEND_SMS;questCode参数为请求标志,将作为该权限的判断标志。

       protectedboolean isPermissionGranted(String permissionName,int questCode){ 
//6.0以下系统,取消请求权限
if(Build.VERSION.SDK_INT returntrue;
}
//判断是否需要请求允许权限,否则请求用户授权
inthasPermision = checkSelfPermission(permissionName);
if(hasPermision != PackageManager.PERMISSION_GRANTED) {
requestPermissions(newString[] { permissionName}, questCode);
returnfalse;
}
returntrue;
}

     (2) APP同时请求多个权限,即批量请求权限,常见于APP第一次安装时需要用户批量授予多个权限,这样能够提高APP后续的用户体验,封装的方法为isPermissionsAllGranted,它需要传递两个参数:permArray参数为批量请求的权限字符串数组,questCode参数为请求标志,将作为这次请求的判断标志。

protected boolean isPermissionsAllGranted(String[] permArray,intquestCode){
//6.0以下系统,取消请求权限
if(Build.VERSION.SDK_INT returntrue;
}
//获得批量请求但被禁止的权限列表
List deniedPerms = newArrayList();
for(int i=0;permArray!=null&&iif(PackageManager.PERMISSION_GRANTED !=checkSelfPermission(permArray[i])){
deniedPerms.add(permArray[i]);
}
}
int denyPermNum = deniedPerms.size();
//进行批量请求
if(denyPermNum != 0){
requestPermissions(deniedPerms.toArray(newString[denyPermNum]),questCode);
returnfalse;
}
return true;
}

    注意:在进行权限检测、请求时,需要对当前部署的终端进行系统版本检查,判断是否为Android6.0以上系统,否则会报“method can not reserved”异常,因为checkSelfPermission和requestPermissions是API 23才引入的方法。另外,Android 6.0以下的系统无需进行权限检测,因为它仍然使用老的权限系统,APP安装时会默认授予运行时所有的权限。

    (3) 获得权限用户授予情况

    由上述(1)、(2)可知,通过封装的两个方法,我们可以很容易的检测某个权限或多个权限是否被用于授予,以及去请求某个或多个权限需要用户授予。那么,我们又如何知道用户最终的授权情况,和针对不同的授权情况做相应的处理呢?答案是:onRequestPermissionsResult方法。onRequestPermissionsResult是Activity的一个方法,通过覆写该方法并结合权限标识码requestCode来判断相应权限授予结果,代码如下:

       publicvoid onRequestPermissionsResult(int requestCode,
String[]permissions, int[] grantResults) {
if(grantResults.length==0){
return;
}
switch(requestCode) {
caseConstants.QUEST_CODE_SEND_SMS:
if(grantResults[0] != PackageManager.PERMISSION_GRANTED) {
//用户拒绝授予发送短信权限,弹出一个警告对话框
popAlterDialog();
}
break;
caseConstants.QUEST_CODE_ALL:
//用户拒绝授予请求所有或某一项权限,弹出一个经过对话框
doPermissionAll(Constants.permArray,grantResults);
break;
default:
super.onRequestPermissionsResult(requestCode,permissions,
grantResults);
break;
}
}

2.  Demo实战剖析


    为了进一步理解Android 6.0以上系统新权限系统的检查处理,我写了一个小Demo,也算是对封装的两个方法的演示。整个Demo项目以BaseActivity.class、MainActivity.class、AndroidManifest.xml为核心,代码介绍如下:

(1)  BaseActivity.class

   由于checkSelfPermission、requestPermissions、onRequestPermissionsResult方法属于Activity,为了防止在不同的Activity都要写一个套权限检测方法导致代码冗余,这里封装一个BaseActivity,当我们在某个Activity需要请求某个权限时,只需继承该父Activity即可使用相关的权限检查方法和获得处理,代码如下:

/**
*@dscrible Activity基类
*
* Created by jiangdongguo on 2016-11-25 上午9:42:08
*/
public abstract class BaseActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
onCreateView();
init();
}

abstract void onCreateView();

abstract void init();

protected boolean isPermissionGranted(String permissionName,int questCode){
if(Build.VERSION.SDK_INT return true;
}
//判断是否需要请求允许权限
int hasPermision = checkSelfPermission(permissionName);
if (hasPermision != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] { permissionName }, questCode);
return false;
}
return true;
}

protected boolean isPermissionsAllGranted(String[] permArray,int questCode){
if(Build.VERSION.SDK_INT return true;
}
//获得批量请求但被禁止的权限列表
List deniedPerms = new ArrayList();
for(int i=0;permArray!=null&&i if(PackageManager.PERMISSION_GRANTED != checkSelfPermission(permArray[i])){
deniedPerms.add(permArray[i]);
}
}
//进行批量请求
int denyPermNum = deniedPerms.size();
if(denyPermNum != 0){
requestPermissions(deniedPerms.toArray(new String[denyPermNum]),questCode);
return false;
}
return true;
}

@Override
public void onRequestPermissionsResult(int requestCode,
String[] permissions, int[] grantResults) {
if(grantResults.length==0){
return;
}
switch (requestCode) {
case Constants.QUEST_CODE_LOCTION:
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
popAlterDialog("位置","位置信息权限被禁止,将导致定位失败。。是否开启该权限?(步骤:应用信息->权限->'勾选'位置)");
}else{
showShortMsg("恭喜,用户已经授予位置权限");
}
break;
case Constants.QUEST_CODE_CAMERA:
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
popAlterDialog("相机","摄像头使用权限被禁止,手电筒无法正常使用。是否开启该权限?(步骤:应用信息->权限->'勾选'相机)");
}else{
showShortMsg("恭喜,用户已经授予相机权限");
}
break;
case Constants.QUEST_CODE_SEND_SMS:
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
popAlterDialog("短信","发送短信权限被禁止,无法使用反馈/建议功能。是否开启该权限?(步骤:应用信息->权限->'勾选'短信)");
}else{
showShortMsg("恭喜,用户已经授予短信权限");
}
break;
case Constants.QUEST_CODE_ALL:
doPermissionAll(Constants.permArray,grantResults);
break;
case Constants.QUEST_CODE_CALL_PHONE:
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
popAlterDialog("拨打电话","拨打电话权限被禁止,无法使用拨打电话功能。是否开启该权限?(步骤:应用信息->权限->'勾选'电话)");
}else{
showShortMsg("恭喜,用户已经授予所有权限");
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions,
grantResults);
break;
}
}

private void doPermissionAll(String[] permissions, int[] grantResults) {
int grantedPermNum = 0;
int totalPermissOns= permissions.length;
int totalResults = grantResults.length;
if(totalPermissOns== 0 || totalResults == 0){
return;
}
Map permResults = new HashMap();
//初始化Map容器,用于判断哪些权限被授予
for(String perm:Constants.permArray){
permResults.put(perm,PackageManager.PERMISSION_DENIED);
}
//根据授权的数目和请求授权的数目是否相等来判断是否全部授予权限
for(int i=0;i permResults.put(permissions[i],grantResults[i]);
if(permResults.get(permissions[i]) == PackageManager.PERMISSION_GRANTED){
grantedPermNum ++;
}
Log.d("Debug","权限:"+permissions[i]+"-->"+grantResults[i]);
}
if (grantedPermNum == totalPermissons) {
//用于授予全部权限
}else{
showShortMsg( "批量申请权限失败,将会影响正常使用!");
}
}

private void showShortMsg(String msg) {
Toast.makeText(this,msg, Toast.LENGTH_SHORT).show();
}

private void popAlterDialog(final String msgFlg, String msgInfo) {
new AlertDialog.Builder(BaseActivity.this)
.setTitle("使用警告")
.setMessage(msgInfo)
.setNegativeButton("取消", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setPositiveButton("设置",new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
//前往应用详情界面
try {
Uri packUri = Uri.parse("package:"+getPackageName());
Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,packUri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
BaseActivity.this.startActivity(intent);
} catch (Exception e) {
showShortMsg("跳转失败");
}
dialog.dismiss();
}
}).create().show();
}
}

(2) MainActivity.class:继承于BaseActivity,发起权限检测

/**
*@dscrible 分别请求短信、位置等权限
*
* Created by jiangdongguo on 2016-11-25 上午9:45:20
*/
public class MainActivity extends BaseActivity implements OnClickListener{
private Button mCheckContactBtn;
private Button mCheckLocationBtn;
private Button mCheckAllBtn;
@Override
void onCreateView() {
setContentView(R.layout.activity_main);
initLayout();
}

private void initLayout() {
mCheckCOntactBtn= (Button)findViewById(R.id.check_contacts_btn);
mCheckLocatiOnBtn= (Button)findViewById(R.id.check_location_btn);
mCheckAllBtn = (Button)findViewById(R.id.check_all_permission_btn);
mCheckContactBtn.setOnClickListener(this);
mCheckLocationBtn.setOnClickListener(this);
mCheckAllBtn.setOnClickListener(this);
}

@Override
void init() {

}

@Override
public void onClick(View v) {
int vId = v.getId();
switch (vId) {
case R.id.check_contacts_btn:
//检测发送短信权限
isPermissionGranted(Manifest.permission.WRITE_CONTACTS,
Constants.QUEST_CODE_SEND_SMS);
break;
case R.id.check_location_btn:
//检测位置信息权限 isPermissionGranted(Manifest.permission.ACCESS_COARSE_LOCATION,
Constants.QUEST_CODE_LOCTION);
break;
case R.id.check_all_permission_btn:
//批量检测短信、位置、相机、电话使用权限
isPermissionsAllGranted(Constants.permArray,Constants.QUEST_CODE_ALL);
break;
default:
break;
}
}
}

(3) AndroidManifest.xml:声明要请求的权限,设置android:targetSdkVersion支持Android6.0+新权限系统,即android:targetSdkVersion="23"。


package="com.example.mandroidpermissiondemo"
android:versiOnCode="1"
android:versiOnName="1.0" >





android:minSdkVersion="18"
android:targetSdkVersion="23" />

android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:name=".MainActivity "
android:label="@string/app_name" >









(4) Constants.class:常量类

/**
*@dscrible 常量和临时变量类
*
* Created by jiangdongguo on 2016-10-31 上午11:10:27
*/
public class Constants {
/**位置信息权限请求标志*/
public static final int QUEST_CODE_LOCTION = 1;
/**发送短信权限请求标志*/
public static final int QUEST_CODE_SEND_SMS = 2;
/**摄像头权限标志*/
public static final int QUEST_CODE_CAMERA = 3;
/**批量请求权限*/
public static final int QUEST_CODE_ALL = 4;
/**拨打电话权限*/
public static final int QUEST_CODE_CALL_PHOnE= 5;
//要申请的权限
public static final String[] permArray =
{ Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.SEND_SMS, Manifest.permission.CAMERA,
Manifest.permission.CALL_PHONE};
}

效果演示:

      


码字不容易,转载请注明出处:http://blog.csdn.net/andrexpert/article/details/53331836

 

最后的话:“实际上,Android的权限系统中涵盖了两类权限:PROTECTION_NORMAL、非PROTECTION_NORMAL。其中,PROTECTION_NORML类权限在AndroidManifest.xml中声明后,在APP安装时,系统将无需检查权限而直接授予,且用户不可取消,如蓝牙、网络、闪光灯、NFC、WIFI等等;非PROTECTION_NORMAL类权限在AndroidManifest.xml中声明,在安装APP时,系统将会询问APP每次请求的权限,用户可取消此类权限。系统有时候不会弹出权限授权对话框,需要APP主动检查有没有该类权限,并弹出授权提示对话框,我们处理的就是这类权限。另外,Android权限系统是以组的形式管理权限,比如发送短信、接收短信同属一组权限,一旦其他一个获得授予,其他同组组员都得到了授权。”

 

关于资料与Demo:Android6.0新权限系统Demo



推荐阅读
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • 本文讨论了在Windows 8上安装gvim中插件时出现的错误加载问题。作者将EasyMotion插件放在了正确的位置,但加载时却出现了错误。作者提供了下载链接和之前放置插件的位置,并列出了出现的错误信息。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • HDFS2.x新特性
    一、集群间数据拷贝scp实现两个远程主机之间的文件复制scp-rhello.txtroothadoop103:useratguiguhello.txt推pushscp-rr ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
author-avatar
kimjizi
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有