开发者

Android bug最近遇到的几个坑解决分享

开发者 https://www.devze.com 2022-12-13 10:22 出处:网络 作者: leobert-lan
目录前言android R、S中的外部文件管理权限判断与申请完全的控制权限如何快速改造蓝牙居然搜索不到设备一个高star库中的I/O和多线程的综合性问题前言
目录
  • 前言
  • android R、S中的外部文件管理权限
    • 判断与申请完全的控制权限
    • 如何快速改造
  • 蓝牙居然搜索不到设备
    • 一个高star库中的I/O和多线程的综合性问题

      前言

      6-7月的事情着实有点多。没有清风亦没有鲜花,连蝉都热到燥不动了,人自然也变得慵懒了许多。

      月头计划和诸君探讨下Compose(Compose-Desktop)和MVI实际投产后的感受,眼看即将到月底,低头看着才写一半的草稿,沉默良久,大抵这个月是没有希望成稿了。

      终究,只能挑一些最近遇到的问题写一写,欺骗自己还未曾摆烂。

      Android R、S中的外部文件管理权限

      想来是舒适太久了,目前负责的核心项目均是面向特定Android-Pad开发的,在 Android 9 已经躺了一年,已经快忘记了 适配 这一祖传包袱。

      但还有部分即将推出的项目是面向普通用户的,顺便谈一谈这个 老问题

      Android 10 中提出了分区存储,对于外部存储空间的读写,除了需要处理FileProvider外,还需要配置 requestLegacyExternalStorage

      Android R、S 中,进行了更严格的限制,需android要获取完整的外部存储控制权限。

      作者按:笔者负责的项目受技术之外因素的影响,改变现有的文件存储路径的阻力非常大

      判断与申请完全的控制权限

      补充声明权限

      <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
      

      那么,相关权限如下:

      <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
      <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
      

      并且需要运行时动态获取权限,关键代码如下:

      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
          if (!Environment.isExternalStorageManage开发者_JS学习r()) {
              //略去引导弹窗等相关交互逻辑
              startActivityForResult(Intent(Settings.ACTION_MANAGE_ALL_FILES_Access_PERMISSION), REQ_CODE_FILE_MANAGE)
              return
          }
      }
      // do something
      

      可以结合 ActivityResultC编程客栈ontracts 改造 onActivityResult,不再赘述。

      用户对软件授予权限后可读写文件。

      如何快速改造

      随着Compose跨平台技术越来越热,将应用的业务在多个平台上复用也越发有价值,而平台的差异性内容应当在平台内部解决。

      那么 如何在不修改Presenter/ViewModel 和 Model层的前提下,便捷的解决此类适配问题 越发具有价值。

      当然,此类问题的解决不能脱离实际空谈,便不做具体展开。目前在项目中先运用了 Proxy 方式,对业务层追加了前置逻辑,进行了简单处理。

      我已经设想了一个框架,并非PermissionX之类处理Android动态权限的框架,有后续实质进展后再与诸君分享。

      蓝牙居然搜索不到设备

      如果直接适配Android 12,大概率不会出现该问题(未经过大量rom验证),target是低版本,以往的业务代码不到位就有可能受到影响

      未适配Android 12 蓝牙权限的应用,在部分手机上发现未打开 "访问我的位置信息" 时(不是定位权限!!),会导致搜索不编程到蓝牙设备。大抵是ROM厂商的杰作。

      参考地图类SDK的操作,增加以下检测代码:

      LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
      if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
          //引导用户开启定位:Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
      }
      

      在 target Android12 中:

      • 请求精确位置,需同时申请 ACCESS_FINE_LOCATION 和 ACCESS_COARSE_LOCATION 权限
      • (动态)申请蓝牙相关权限时,不再需要申请设备位置信息相关权限

      理论上不需要再加这一检测,但仍需经过大量ROM测试。建议保留该段检测逻辑。

      一个高star库中的I/O和多线程的综合性问题

      前几日和好友闲聊时,好友提到他们的项目中使用了一个开源的下载库 Aria,但时不时会出现下载图片失败的问题,实质是库的bug。

      阅读源码后感到问题出在细节知识上,本篇也需要水一下字数,索性拿来讨论一二。

      库中运用到的技术:

      • 多线程&锁
      • IO
      • Android的Handler

      其中,多线程和锁的知识本篇不谈,我亦写有相关系列;库作者使用Android中的Handler简化多线程间的通信,也不再展开;IO部分系统展开也很长,我已有系列文编程客栈章计划,本篇结合问题简单谈一谈。

      和文www.devze.com件服务器对比,移动端的文件上传下载并发量很小,其速度制约一般在于:服务器单连接流量限制,网络条件。

      对于单个文件采用多线程方式上传、下载,只能突破 单连接流量限制,以充分使用网络资源。多个文件并行上下载的产品意义远大于充分吃掉网络带宽资源。

      注意:上下载的高带宽占用一定程度上会影响其他网络层业务

      一般对于下载而言,使用多线程时用常规的 "碎文件合并" 的思路即可。用 RandoMACcessFile并不是一个好主意。

      多线程写RandomAccessFile并非一件很美好的事情,以后的文章中细聊

      在使用Java经典IO时,使用Buffer减少IO次数可以获得很好的性能提升,需要注意及时 flush,该库中用Buffer设计对 RandomAccessFile 进行了一层封装。想来也是源自三方库.

      将部分代码简化后类似如下代码:

      class Demo {
          @Test
          fun mockDownload() {
              Looper.prepare()
              val appContext = InstrumentationRegistry.getInstrumentation().targetContext
              val file = File(appContext.cacheDir, "testtmp")
              if (file.exists()) {
                  file.delete()
              }
              file.createNewFile()
              assertEquals(0, file.length())
              val data = "hello".encodeToByteArray()
              val bFile = BufferedRandomAccessFile(file, "rwd", 1024)
              val handler = object : Handler(Looper.myLooper()) {
                  override fun handleMessage(msg: Message?) {
                      super.handleMessage(msg)
                      //模拟文件合并线程,仅断言文件大小
                      if (msg?.what == 1) {
                          assertEquals(data.size.toLong(), file.length())
                          Looper.myLooper()?.quit()
                      }
                  }
              }
              //模拟下载线程
              thread(priority = 1) {
                  val ins = ByteArrayInputStream(data)
                  try {
                      val buffer = ByteArray(1024)
                      var len: Int
                      while (ins.read(buffer).also { len = it } != -1) {
                          bFile.write(buffer, 0, len)
                      }
                      //模拟下载完成消息
                      handler.sendEmptyMessage(1)
      //                Thread.yield() -- 模拟一下线程被切换或者因为锁导致的同步
                  } finally {
                      ins.close()
      //                bFile.flush() will be invoked in close
                      bFile.close()
                  }
              }
              Looper.loop()
          }
      }
      

      观察这段代码可以发现,存在不安全因素:最后一个buffer块的内容大小未必是1024,需等到 bFile.close()时方可写入文件。

      虽然 handler.sendEmptyMessage(1) 发送的消息会被异步执行,但并不意味着 bFile.close() 一定会先执行。锁、AQS、系统线程调度等均可能会导致该问题。

      以上就是Android bug最近遇到的几个坑解决分享的详细内容,更多关于Android bug解决的资料请关注我们其它相关文章!

      0

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      关注公众号