一个很离谱的Android混淆打包下MediaStore查询排序问题

78 阅读4分钟

在我们的项目中其实有一个问题,断断续续困扰着整个项目组,也一直有用户反馈,而当我们排查时也没有什么头绪,最后发现了一个很离奇的情况,在使用 MediaStore 查询本地文件时,设置排序条件的写法直接影响查询结果——不混淆打包时一切正常,但在混淆打包后出现两种问题:

  1. 报错
  2. 查询出来的图片数量不全,仅返回十几二十张

在代码中这样设置排序条件:

// 动态拼接字符串
var sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED
sortOrder += " DESC"

这种写法在开发过程中(不进行混淆打包时)没有问题,但在混淆打包后可能会出现以下问题:

  • 程序直接报错
  • 查询出来的文件不完整(例如,图片数量明显少于实际相册中的数量)

而当我们改为使用字符串模板直接生成排序条件时:

// 使用字符串模板生成编译期常量
val sortOrder = "${MediaStore.Files.FileColumns.DATE_MODIFIED} DESC"

问题便完全消失

原因分析

编译期常量内联 vs. 运行时拼接

  • 编译期内联的优势
    使用字符串模板(如上例所示)可以在编译期间将排序字符串生成一个不可变的常量(例如 "date_modified DESC")。这样生成的常量不仅格式固定,而且会在编译时被内联到调用处,确保在混淆过程中不会被意外修改。
  • 动态拼接的潜在风险
    而使用变量先声明,再通过“+=”进行字符串拼接的方式,是在运行时动态生成最终字符串。这种做法会让排序字符串的生成过程多出一个中间状态,可能会受到隐式类型转换或空格等细微问题的影响。更重要的是,在混淆打包过程中,ProGuard 或 R8 可能不会对运行时生成的字符串做同样的常量折叠和内联优化,从而导致最终传给 MediaStore 查询的排序字符串格式不符合预期。

混淆工具的影响

在混淆打包过程中,混淆工具(如 ProGuard 或 R8)会对代码进行重命名、优化以及内联操作。

  • 常量内联保护
    如果排序字符串在编译期间就被确定为常量(例如使用字符串模板生成),混淆工具会将它内联到代码中,从而保证排序条件不会被修改。
  • 动态拼接的不确定性
    对于运行时拼接生成的字符串,由于它不被视为编译期常量,混淆工具可能会在优化或重构过程中改变其构造方式,导致传入 MediaStore 查询的排序字符串格式出错,从而引发查询错误或返回不完整结果。

事件回顾与解决过程

  1. 问题发现
    开发过程中发现使用动态拼接的排序字符串在非混淆环境下工作正常,但在混淆打包后出现报错或查询结果不全的问题。

  2. 问题排查
    经过仔细调试和对比,确认问题与排序字符串的生成方式有关。

  3. 动态拼接的方式在混淆后可能没有生成符合预期格式的字符串。 使用字符串模板生成的不可变常量能够保证排序字符串的正确性。

  4. 原因定位
    进一步分析发现,混淆工具在处理运行时动态拼接的字符串时,可能未能将其优化成编译期常量,导致排序条件格式出现问题,而编译期常量则不会受到这种影响。

  5. 问题解决
    最终,通过改为使用字符串模板生成排序字符串,问题得以解决。这也提醒我们,在关键字符串的生成上,尽量在编译期内完成生成,从而避免混淆打包过程中的不可预期行为。

在 Android 开发中,混淆打包是一种常见的代码保护措施,但同时也可能引发一些意外问题。
本次案例中,MediaStore 查询排序字符串由于生成方式的不同而在混淆后产生错误。

  • 建议:
    在涉及排序、查询等敏感操作时,尽量使用编译期内联的常量(例如使用字符串模板),以确保在混淆过程中字符串不会被错误处理,从而保证功能正常。

原文地址:mp.weixin.qq.com/s/S3awfotC9…