相信大家对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进行操作时在上述三种版本中运行出现的问题处理为例进行剖析。

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

1 public void onClick(View v) {
2     switch (v.getId()) {
3         case R.id.iv_chatting_sound: // 点击发送语音模式的按钮
4             clickSoundImage();
5             break;
6         default:
7             break;
8     }
9 }

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

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

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

1 @PermissionFail(requestCode=PermissionUtil.PER_RECORD_AUDIO)
2 private void clickSoundImageFail() {
3    //提示用户权限被拒
4 }

1 @PermissionSuccess(requestCode = PermissionUtil.PER_RECORD_AUDIO)
2 private void clickSoundImage() {
3    //权限授权成功,执行发送文字消息模式和语音消息模式的切换代码
4 }

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

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

 1  /**
 2  * 开始录制音频
 3  */
 4  public void startRecording(OnSoundCallBack onSoundCallBack) throws IOException, IllegalStateException {
 5       //OnSoundCallBack对象,音频录制过程中的回调监听
 6      this.mOnSoundCallBack = onSoundCallBack;
 7      //先停止上一次的录音
 8      stopRecording();
 9      //初始化MediaRecorder准备新一轮录制
10      initMediaRecorder();
11      //重新创建录音文件
12      mp3File=initFile().createNewFile();
13      mMediaRecorder.setOutputFile(mp3File.getAbsolutePath());
14      startRecorderTime = System.currentTimeMillis();
15      setPrepare(true);
16      //准备录制,用户拒绝权限时会抛出IllegalStateException异常
17      mMediaRecorder.prepare();
18      //根据是否授权prepare,开启或关闭回调
19      getRecordCall(getPrepare());
20      if(getPrepare()){
21           //在准备充分的时候启动录制
22          mMediaRecorder.start();
23      }
24  }
25
26  /**
27  * 停止录制音频
28  */
29  public String stopRecording() throws IOException, IllegalStateException {
30       //关闭回调
31      getRecordCall(false);
32      if(isRecordering()||!getPrepare()){
33           //在录制过程中或者没有准备充分(即用户拒绝权限)时,释放MediaRecorder对象
34          releaseMediaRecorder();
35      }
36      return mp3File.getAbsolutePath();
37  }
38
39  private void initMediaRecorder(){
40      if (mMediaRecorder == null) {
41          // 实例化MediaRecorder类对象:
42          mMediaRecorder = new MediaRecorder();
43          // 设置录音来源 :
44          mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
45          // 设置输出格式:
46          mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.RAW_AMR);
47          // 设置编码方式
48          mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
49          mMediaRecorder.setAudioSamplingRate(SAMPLE_RATE_IN_HZ);
50      }
51  }
52
53  private void releaseMediaRecorder(){
54      if (mMediaRecorder != null) {
55          try {
56              mMediaRecorder.stop();
57              mMediaRecorder.release();
58          }catch (Exception e){
59              // 在释放时,即便有异常也释放
60          }
61          mMediaRecorder = null;
62      }
63  }
64
65  /**
66  * 音频录制过程中的回调
67  */
68  private void getRecordCall(boolean isTrue) {
69      this.isRecording = isTrue;
70      if (isRecording) {
71          newGetSoundMaxThread();
72      }else {
73      mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_STOP);
74      }
75  }
76
77  /**
78  * 开启子线程发送Handler调用OnSoundCallBack回调
79  */
80  private void newGetSoundMaxThread() {
81    new Thread(new Runnable() {
82      @Override
83      public void run() {
84        while (isRecording) {
85          mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_ING);
86          if (startRecorderTime+ RECORD_TIME_MAX<=System.currentTimeMillis() ) {
87            mHandler.sendEmptyMessage(HANDLER_WHAT_RECORD_OUTTIME);
88          }
89          SystemClock.sleep(100);
90        }
91      }
92    }).start();
93  }

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

  第一是抛出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),使音频录制操作停止,待用户处理完权限操作之后,再重新开始录制。

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

转载于:https://www.cnblogs.com/BobGo/p/6729139.html

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

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

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

  2. android拍照所需的权限,eclipse --- Android拍照,相册选择图片以及Android6.0权限管理...

    [实例简介] eclipse --- Android拍照,相册选择图片以及Android6.0权限管理 [实例截图] [核心代码] camreainandroidm └── camreainandro ...

  3. 如何查看Android项目的gradle版本和路径

    1.查看项目的gradle版本:打开gradle-wrapper文件可以看到这一行,其实就是所需gradle的下载网址,如果本地没有找到Android会自动到这个网址进行下载.以下面为示例,版本就是5 ...

  4. Android源码定制(1)——Android6.0源码编译

    一.前言 最近在研究Xposed框架定制,恰好又看到看雪上两个大佬关于源码定制和Xposed源码定制的帖子,所以尝试基于Android6.0版本,详细记录一下从源码下载到Xposed框架定制的全过程. ...

  5. bliss android x86,Bliss OS现在可让您基于Android-x86和AOSP在PC上运行Android 10

    Bliss OS是一个基于Android-x86项目的开源操作系统,有望让您在任何Linux,Windows或Chromebook PC或平板电脑设备上运行最新的Android 10移动操作系统. B ...

  6. 华为谷歌安装器 Android6.0,GO谷歌安装器华为

    GO谷歌安装器华为是一款为安卓手机打造的谷歌安装器.软件能够帮助用户一键下载安装谷歌框架.谷歌服务.让用户没有root也可以轻松安装谷歌.谷歌商店等等.对这款软件感兴趣的小伙伴可千万不要错过哦. GO ...

  7. android个别手机问题排查,【原创】高德地图在个别机型手机上运行崩溃的问题...

    报错信息:java.lang.UnsatisfiedLinkError: Native method not found: com.autonavi.amap.mapcore.MapCore.nati ...

  8. Android6.0运行时权限解决方案

    今天在测试APP的时候发现部分手机APP定位权限使用不了.看了一下机型都是Android6.0以上版本的Android手机. 之前就听说Android6.0以上版本权限管理更严格了,没想到今天踩坑了. ...

  9. Android不使用支持库请求运行时权限

    Android 6.0后,用户开始在应用运行时向其授予权限,而不是在应用安装时授予.Android官网上也有相关教程:在运行时请求权限.但教程是使用支持库来举例的,用支持库的话,APK的大小也会变得很 ...

最新文章

  1. 【转载】WinCE OAL架构分析
  2. 京东数据驱动下的个性化推荐
  3. python二维数据读取对齐_从投影的二维直方图绘制对齐的x,y一维直方图
  4. 分水岭算法(Watershed Algorithm)
  5. RxJava在Android中的简单应用
  6. 十五、CI框架之自动加载数据库
  7. Excel度分秒转度--公式大解析
  8. 百度 图像识别Api logo识别 基于java的Demo
  9. python如何提取奇数_Python 获取奇数和偶数
  10. 基于Springboot开发实现买卖三方二手商品交易网站
  11. JavaWeb学习总结(五十一)——邮件的发送与接收原理
  12. 飞凌嵌入式-基于国产A40i核心板-医用呼吸机解决方案
  13. centos7installan安装postgresql9.6,解决重音不敏感”排序规则,以及扩展pgcrypto函数
  14. 乱弹琴的系统设计套路(适合新手)
  15. 我们要注意的问题.net
  16. Chromium命令行开关列表1
  17. [SMOJ2095]Bug2
  18. DSP与FPGA的技术特点和区别是什么?
  19. 可变长度操作码(扩展操作码)
  20. Packet Tracer 思科模拟器入门教程 之十二 路由器RIP动态路由配置

热门文章

  1. asp.net动态网页制作视频教程
  2. 撸一撸腾讯的微信支付(C#)
  3. 解决浏览器被好123劫持主页的问题。
  4. 互联网自动化赚钱的方法
  5. 10g recyclebin与用户表空间限额
  6. 仿微信朋友圈图片点击放大效果
  7. 2020第二十四届软博会数据治理专场演讲嘉宾及日程安排
  8. Linux技术社区—蜗窝科技
  9. Word文档怎么翻译?翻译word文档简单步骤讲解
  10. 话说ipsec之倚天屠龙记版