相信大家对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. C# 通过java生成的RSA公钥加密和解密

    最近工作需要将对方公司生成的RSA加密公钥进行明文加密和解密,发现了几点贴出来做个笔记. RSA单次加密是有长度限制!微软封装的加密方法如果出现长度超出指定范围的话报错是直接报“该项不适于在指定状态下 ...

  2. HTML语法介绍

    一 基本标签(块级标签和内联标签) <hn>: n的取值范围是1~6; 从大到小. 用来表示标题. <p>: 段落标签. 包裹的内容被换行.并且也上下内容之间有一行空白. &l ...

  3. 使用Hexo搭建个人博客的终极资料

    一.前言 Hexo 是一个基于 NodeJs 博客框架,可以快速的帮我们搭建一个博客系统,Hexo使用的是Markdown(下文简称MD)解析文章的,在几秒内即可利用靓丽的主体生成静态网页. 推荐使用 ...

  4. python变量和变量赋值的几种形式

    动态类型的语言 python是动态类型的语言,不需要声明变量的类型. 实际上,python中的变量仅仅只是用来保存一个数据对象的地址.无论是什么数据对象,在内存中创建好数据对象之后,都只是把它的地址保 ...

  5. vue_drf之多级过滤、排序、分页

    一.前端代码 1,父组件free_course.vue <template> <div id="free_course"> <el-container ...

  6. Docker虚拟机理论

    Docker虚拟机架构     ◆ Docker架构                 Docker创建的所有虚拟实例共用同一个Linux内核,对硬件占用较小,属于轻量级虚拟机   Docker镜像与容 ...

  7. ASP.NET Core中的Startup类

    ASP.NET Core程序要求有一个启动类.按照惯例,启动类的名字是 "Startup" .Startup类负责配置请求管道,处理应用程序的所有请求.你可以指定在Main方法中使 ...

  8. FastReport.Net

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  9. XAML: 在 MVVM 模式中,关于绑定的几处技巧

    以下会提到三个绑定的技巧,分别是 在 ListView 中为 ListViewItem 的 MenuFlyout 绑定 Command: 在 ListView 的 事件中绑定所选择项目,即其 Sele ...

  10. 空间、域名与IP之间的关系?

    空间说白了就是服务器里面你可以使用的一个地方,在这里你可以放置数据和程序.最常用的就是放置您的网站程序和相关的所有文档和图片文件等等.这个放置你的网站文件的空间所在的服务器会有一个电信部门分配的固定编 ...