相信大家对Android6.0以上的动态权限已经有所了解,很多童鞋也已经跃跃欲试地将自己项目的targetSDK升级到了23及其以上,很不幸的是我也成为了其中一员,然而我还是图样图森破了,升级之后的问题并没有想象的那么简单。在简单的改掉项目的targetSDK之后,由于华为手机的权限是可以动态修改的,即便是低于Android6.0以下的机器上,而这种情况在禁止权限之后,使用代码类似PermissionChecker.checkSelfPermission(),ContextCompat.checkSelfPermission(),甚至是从网上找的checkOp()等方法都是获取到的同意权限,这样在实际运行时就会有空值返回或者抛出IllegalstateException等异常,很是让人头痛。拿使用MediaPlayer的prepare()和start()方法时需要申请android.Manifest.permission.RECORD_AUDIO权限来讲,主要从下面三个维度考察,项目中的targetSDK是否低于23,测试机型版本号是否低于Android6.0,测试机型属于原生Android系统还是类似华为小米的定制系统。

  在版本号Android6.0以下的正常测试机(以OPPO R7_Android4.4.4为例)中,我们暂且称之为老旧版,在项目还未升级(即项目的targetSDK<23)之前,在代码的清单文件中声明需要的权限配置正常,只会在APP安装时,提示这些安全权限且用户无法拒绝使用某一权限,用户只能选择同意安装或者拒绝安装,而在APP运行时是没有任何提示框提醒的,这也是旧版本的Android对权限管理处理方法。而在升级项目的targetSDK时,对这种老旧版是几乎没有影响的。

  在版本号Android6.0以下的定制测试机(以HUAWEI MT7_Android4.4.2为例)中,我们称之为老版升级版,在项目未升级之前,各种效果和老旧版相同,不同的是升级之后,由于定制系统中用户可以选择对APP的某一权限进行授权、禁止、提示等功能,这样虽然代码中调用的是API低于23的方法,但应该走API23以后的执行流程(即对权限同意或拒绝的处理),也就是说在调用权限检查的类似代码时,无论用户选择同意或禁止,返回的都是禁止。升级后的代码处理,主要就是针对这种披着羊皮的狼的,这种狼是防不胜防。

  针对Android6.0及其以上的机型(以HUAWEI MT9_Android7.0为例),被称作新版,在项目未升级之前,新版的效果和老版升级版中安装的升级之后的项目效果是一致的,也就是在APP运行过程中对使用到的权限会有单独的提示框提醒,同样在项目升级之后,由于在代码中调用的是API高于23的方法,所以对权限的检测和申请都是正常的,可以说这是实实在在的狼,因此只要做好正常的防狼手段就可以防住了。

  下面以项目中使用android.Manifest.permission.RECORD_AUDIO权限对MediaRecorder进行操作时在上述三种版本中运行出现的问题处理为例进行剖析。

看一下在未升级之前的原始代码,在点击聊天的发送语音消息按钮时,直接切换语音消息和文字消息模式,没有对任何权限的检查:

 public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_chatting_sound: // 点击发送语音模式的按钮
clickSoundImage();
break;
default:
break;
}
}

按照正常的升级之后的处理逻辑,在点击发送语音模式按钮的时候,要先检查当前Activity是否已经有android.Manifest.permission.RECORD_AUDIO的授权,如果有授权通过,再调用clickSoundImage()方法切换语音消息模式,否则在未授权通过时,仍然停留在文字消息模式界面,并给用户提示。升级后的修改代码如下:

 public void onClick(View v) {
switch (v.getId()) {
case R.id.iv_chatting_sound: // 发送语音模式的按钮
PermissionUtil.needPermission(this, PermissionUtil.PER_RECORD_AUDIO, android.Manifest.permission.RECORD_AUDIO);
break;
default:
break;
}
}

PermissionUtil类是封装的权限检测类,相关的使用方法就是在需要检测权限的地方调用needPermission(Context context, int requestCode, String permission),使用注解的方式分别在权限同意和权限拒绝后执行两个不同注解的方法,类似下面这种情况:

 @PermissionFail(requestCode=PermissionUtil.PER_RECORD_AUDIO)
private void clickSoundImageFail() {
//提示用户权限被拒
}
 @PermissionSuccess(requestCode = PermissionUtil.PER_RECORD_AUDIO)
private void clickSoundImage() {
//权限授权成功,执行发送文字消息模式和语音消息模式的切换代码
}

代码修改截止到目前为止,在老旧版和新版的测试机上升级成功,都是没有问题的,可是运行到老版升级版上之后,即便用户拒绝了该权限,仍然是走权限授权成功之后的方法,接下来就是从羊群中找狼的节奏了。

遇到上面描述的问题,第一反应就是PermissionUtil类中检查权限的那段代码出现了问题,没有正确返回获取权限的处理结果,从网上另外找了几个方法,具体使用PermissionChecker.checkSelfPermission(),ContextCompat.checkSelfPermission(),checkOp()等都是一致返回授权成功的结果,这让我感到很惊讶啊。由于测试机是Android4.4版本的,所以调用权限检查返回的授权结果肯定是根据Android源码中的处理结果走的,API19的权限检查代码,是默认返回授权成功的,按照之前的版本逻辑,如果用户拒绝了某一权限,当前APP就已经安装失败了,更如何运行呢?所以我想到,在老版升级版中想让检查权限的返回值改变是不太现实的了,那就只能让代码过去这里,在系统自带的授权对话框中对处理结果进行监听了。那么就需要修改后续代码,也就是在实际调用MediaRecorder的地方。

  /**
* 开始录制音频
*/
public void startRecording(OnSoundCallBack onSoundCallBack) throws IOException, IllegalStateException {
//OnSoundCallBack对象,音频录制过程中的回调监听
this.mOnSoundCallBack = onSoundCallBack;
//先停止上一次的录音
stopRecording();
//初始化MediaRecorder准备新一轮录制
initMediaRecorder();
//重新创建录音文件
mp3File=initFile().createNewFile();
mMediaRecorder.setOutputFile(mp3File.getAbsolutePath());
startRecorderTime = System.currentTimeMillis();
setPrepare(true);
//准备录制,用户拒绝权限时会抛出IllegalStateException异常
mMediaRecorder.prepare();
//根据是否授权prepare,开启或关闭回调
getRecordCall(getPrepare());
if(getPrepare()){
//在准备充分的时候启动录制
mMediaRecorder.start();
}
} /**
* 停止录制音频
*/
public String stopRecording() throws IOException, IllegalStateException {
//关闭回调
getRecordCall(false);
if(isRecordering()||!getPrepare()){
//在录制过程中或者没有准备充分(即用户拒绝权限)时,释放MediaRecorder对象
releaseMediaRecorder();
}
return mp3File.getAbsolutePath();
} private void initMediaRecorder(){
if (mMediaRecorder == null) {
// 实例化MediaRecorder类对象:
mMediaRecorder = new MediaRecorder();
// 设置录音来源 :
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
// 设置输出格式:
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
// 设置编码方式
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mMediaRecorder.setAudioSamplingRate(SAMPLE_RATE_IN_HZ);
}
} private void releaseMediaRecorder(){
if (mMediaRecorder != null) {
try {
mMediaRecorder.stop();
mMediaRecorder.release();
}catch (Exception e){
// 在释放时,即便有异常也释放
}
mMediaRecorder = null;
}
} /**
* 音频录制过程中的回调
*/
private void getRecordCall(boolean isTrue) {
this.isRecording = isTrue;
if (isRecording) {
newGetSoundMaxThread();
}else {
mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_STOP);
}
} /**
* 开启子线程发送Handler调用OnSoundCallBack回调
*/
private void newGetSoundMaxThread() {
  new Thread(new Runnable() {
    @Override
    public void run() {
      while (isRecording) {
        mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_ING);
        if (startRecorderTime+ RECORD_TIME_MAX<=System.currentTimeMillis() ) {
          mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_OUTTIME);
        }
        SystemClock.sleep(100);
      }
    }
  }).start();
}

  在使用老版升级版跑上面那段修改后的代码时,是没有问题的,修改之前的代码主要少了两部分内容。

  第一是抛出IllegalStateException异常。这是针对老版升级版在拒绝相关权限之后的处理方式。一般代码在startRecording()stopRecording()两个方法只抛出了编译时的IOException,没有抛出IllegalStateException这个运行时异常,修改后的代码不仅多抛出了这个异常,同时需要增加对这个异常的处理。那么在什么状态下会抛出这个异常呢?如果当前APP被用户拒绝使用android.Manifest.permission.RECORD_AUDIO权限,在调用prepare()start()两个底层方法时,会抛出IllegalStateException异常。所以如果捕获到这个异常,就说明在代码运行到这里时,并没有获取到相对应的权限,这时只要停止接下来的操作,并提示用户去打开相关权限就可以了。

  第二是增加setPrepare()getPrepare()两个方法做准备处理。这是针对老版升级版在弹出提示框时的处理方式。修改之前是没有调用上述代码15行的setPrepare(true),以及后边没有对getPrepare()的调用,如果只是从这些代码流程中看,似乎加上那几句话没有任何作用。那么问题来了,为什么要多加这几行代码呢?然而这正是MediaRecorder类提供prepare()这个看似没有作用的方法的一个重要原因。如果用户安装APP时选择权限提示,在运行到prepare()这句代码时,就会在当前代码所在线程创建并弹出权限提示框,此时如果用户进行权限处理操作,可以在长按按钮的地方调用setPrepare(false),使音频录制操作停止,待用户处理完权限操作之后,再重新开始录制。

  增加上边两个处理,就可以解决当前遇到的各种问题,以此记录备案!

												

Android项目的targetSDK>=23,在低于Android6.0的部分测试机(类似华为)上运行时出现的系统权限问题的更多相关文章

  1. Xamarin如何生成Android项目的APK

    Xamarin如何生成Android项目的APK 首先需要选择Release模式生成项目.然后从“生成”菜单中选择Export Android Package命令,就可以导出APK包.APK保存在An ...

  2. Unity中加入Android项目的Build步骤

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 简介: 有的项目需要在Android中加入Unity功能,例如ANDROID应用中嵌入Un ...

  3. eclipse修改android项目的apk包名类名

    在Google提供的Eclipse集成开发环境adt-bundle下修改名称的总结: 1.      修改工程名(apk名称) 在弹出的对话框中输入新名称 该操作实际上是修改<project&g ...

  4. 【转】 Android项目的mvc模式

    MVC (Model-View-Controller):M是指逻辑模型,V是指视图模型,C则是控制器.一个逻辑模型M可以对于多种视图模型V,比如一批统计数据你可以分别用柱状图.饼图V来表示.一种视图模 ...

  5. Android项目的settings.gradle和build.gradle

    gradle构建的项目中的build.gradle和settings.gradle文件 build.gradle 浅析(一) 史上最全的Android build.gradle配置教程 Android ...

  6. Android webview 运行时不调用系统自带浏览器

    WebView mobView = new WebView(this); mobView.loadUrl("http://www.csdn.net"); WebSettings w ...

  7. Android教程 -05 Android6.0权限的管理

    视频为本篇博客知识的讲解,建议采用超清模式观看, 欢迎点击订阅我的优酷 上篇文章我们讲解了通过隐式意图拨打电话,在AndroidManifest.xml文件中添加了权限 <uses-permis ...

  8. spring项目的 context root 修改之后,导致 WebApplicationContext 初始化两次的解决方法

    修改了 spring web 项目的 context root 为 / 之后,在启动项目时,会导致 WebApplicationContext  初始化两次,下面是其初始化日志: 第一次初始化: 四月 ...

  9. 【Android】打电话Demo及Android6.0的运行时权限

    新手开局,查看一些旧资料,从打电话.发短信的小应用开始.代码很简单,主要是学习了: 用StartActivity()激活一个Activity组件.这里是激活了系统原生的打电话和发短信Activity. ...

随机推荐

  1. 【NET CORE微服务一条龙应用】第三章 认证授权与动态权限配置

    介绍 系列目录:[NET CORE微服务一条龙应用]开始篇与目录 在微服务的应用中,统一的认证授权是必不可少的组件,本文将介绍微服务中网关和子服务如何使用统一的权限认证 主要介绍内容为: 1.子服务如 ...

  2. JavaScript之深拷贝和浅拷贝

    前言 工作中会经常遇到操作数组.对象的情况,你肯定会将原数组.对象进行‘备份’当真正对其操作时发现备份的也发生改变,此时你一脸懵逼,到时是为啥,不是已经备份了么,怎么备份的数组.对象也会发生变化.如果 ...

  3. 翻译:DECLARE HANDLER语句(已提交到MariaDB官方手册)

    本文为mariadb官方手册:DECLARE HANDLER的译文. 原文:https://mariadb.com/kb/en/library/declare-handler/我提交到MariaDB官 ...

  4. 使用meterpreter让没有安装python解释器的肉鸡设备执行任意python程序

    目标设备不需安装python解释器就能让其执行python程序 # 需要在与目标meterpreter的session中加载python模块 meterpreter > load python ...

  5. 多选穿梭框总结 (vue + element)

    博客地址:https://ainyi.com/23 示例 介绍 实现省市区三级多选联动,可任选一个省级.市级.区级,加入已选框,也可以在已选框中删除对应的区域. 选择对应仓库,自动勾选仓库对应的省,取 ...

  6. 【转载】阿里云轻量应用型服务器和ECS服务器比较

    在采购阿里云服务器的时候,我们会发现阿里云服务器分好多种,如GPU服务器.ECS服务器.轻量应用型服务器等.ECS服务器和轻量应用型服务器很多人无法搞明白其中的差别,个人的观点是轻量应用型服务器适合入 ...

  7. 水晶报表Crystal 无效索引

    这几天项目用到水晶报表做报表打印,没有前辈指导,都自己摸着石头过河,真是痛并快乐着.其实水晶报表还是挺好用的,对初学者也并不难(我就是初学者).昨天遇到一个问题:无效索引 ……开始以为是报表设置的问题 ...

  8. Centos7.6安装Oracle数据库

    一.安装Oracle前准备 1.创建运行oracle数据库的系统用户和用户组 [humf@localhost ~]$ su root #切换到root Password: [root@localhos ...

  9. 【开源程序(C++)】获取bing图片并自动设置为电脑桌面背景

    众所周知,bing搜索网站首页每日会更新一张图片,张张漂亮(额,也有一些不合我口味的),特别适合用来做电脑壁纸. 我们想要将bing网站背景图片设置为电脑桌面背景的通常做法是: 上网,搜索bing 找 ...

  10. python面向对象学习(六)类属性、类方法、静态方法

    目录 1. 类的结构 1.1 术语 -- 实例 1.2 类是一个特殊的对象 2. 类属性和实例属性 2.1 概念和使用 2.2 属性的获取机制 3. 类方法和静态方法 3.1 类方法 3.2 静态方法 ...