Android 性能优化之黑科技开道(一)

文摘   科技   2024-04-03 14:59   山东  

2024年 第07篇


性能优化引起的 Android 日志开关控制问题及解决


  • 性能优化引起的 Android 日志开关控制问题及解决

  • 需要日志开关方案?

  • 方案调研

  • 实现原理(对 Log 类进行 JniHook)

  • 最终效果



01 缘起


在开发电视版智家 App9.0 项目的时候,发现了一个性能问题。电视系统原本剩余的可用资源就少,而随着 9.0 功能的进一步增多,特别是门铃、门锁、多路视频同屏监控后等功能的增加,开始出现了卡顿情况。

经过调研分析发现有一部分是日志输出导致的,profiler 工具显示有一个日志输出的线程会有频繁占用 CPU 时间片的现象。


02 性能优化引起的 Android 日志开关控制问题及解决


「 2.1 查看日志线程的 CPU 占用率 」

使用 Profiler 查看 CPU 占用率,在打开日志及关闭日志的情况下分别如下:

 (图 1 日志开关打开的情况)

 (图 2 日志开关关闭的情况下)

 由上可以看出,存在两个问题:

  1. 日志开关打开的情况下,占用 CPU 时间比较多

  2. 日志开关关闭的情况下,比打开的情况好一些,但还是会有一些 CPU 占用,通过打日志发现,是有一些日志没有关闭,还在频繁的继续打印

「 2.2 需要日志开关方案?」

  1. 发现关不掉的 TAG 大部分第三方库中的,它们没有对外提供使能接口,导致日志关不掉,为了性能考虑,需要对第三方库中的日志进行控制,在必要时可以关闭它们;

  2. 如果线上的版本不关闭日志,会存在用户隐私或产品信息泄漏的风险,为了 APP 和用户安全考虑,需要关闭线上版本的日志输出;

  3. 另外,为了更加方便的发现错误,我们应该对日志级别进行控制,例如我们可以采用如下方案:无论任何时候都输出 E 级别日志,其它级别日志可以根据情况进行关闭;

 结论:我们需要一个统一关闭和控制所有日志的开关方案。

「 2.3 方案调研 」

方案

方案介绍

优点

缺点

混淆

采用混淆 proguard 配置方式关闭 APP 日志输出,包括:

android.util.Log 类

java.io.PrintStream 类(java.lang.System.out.println( )和 java.lang.System.out.print()系列

实现简单,想关哪个关哪个

  1. 不能动态控制,版本一经编译就不能再更改了

  2. 在没有开启混淆的 App 上,需要先开启混淆,需要仔细调试保证没有问题

  3. 能控制 Java 层的日志输出,不能控制 Native 层

插桩+ASM

通过 AOP 编程,在日志输出的某个类中进行字节码插桩,实现控制日志输出的目的

灵活,稳定性高

  1. 实现复杂度较混淆要高

  2. 需要找到合适的适合插桩的点

Hook

通过反射或其它技术,针对日志输出的某一个实现点,进行 Hook,进而实现控制的目的

灵活,稳定性高

  1. 实现复杂度较混淆要高

  2. 需要找到合适的需要 Hook 的点和 Hook 技术


混淆

  1. 混淆方案有个缺点,就是不能通过开关动态的调节日志开关,先做为备选吧

 插桩

  1. 经过技术预研,发现插桩的方案如果选用 Transform, 只能修改自己写的类或者是第三方库中的类,而影响不到 Android SDK 中的类 android.util.Log

  2. 在暂时未找到其它插桩点的情况下,此方案行不通

 Hook

  1. Hook 方案有很多种,需要根据具体的情况进行分析,选择合适的方案

Hook 技术

适用范围

主要原理

优点

缺点

反射+动态代理

Java 层部分场景

虚拟机提供的能力

稳定性高

技术门槛低

适用范围小

ClassLoader 修改

Java 层全部

修改 ClassLoader 加载类的顺序

稳定性高,Java 层都可以替换

需要提前准备好替换的 Dex

JNI Hook

Java 层与 native 间的函数

native 函数指针存储在表中,重定向表中的函数指针

稳定性高

只能 Hook native 方法

Xposed 类

Java 层全部

虚拟机中的目标函数为 Native,然后利用 JNI hook 重定向

java 层全 hook,hook 接口友好,有大量 Hook 插件可以使用和参考

ART/Dalvik 虚拟机每个版本都有不少改动,需适配。稳定性较差

GOT

native 层动态链接库入口函数

Linux 进程会链接 so 到内存中,并给 so 的入口函数们分配地址。原理是重定向入口地址

稳定性高

只能 Hook so 的入口函数,不能 Hook so 的内部

Inline hook

native 层全部

目标函数执行指令代码中插入 Jump 指令实现重定向

hook native everything

和 cpu 指令集相关,需要针对性汇编实现,稳定性较一般,技术门槛相对高

  1. 经过分析,发现

    1. Java 层的两种方案(反射+动态代理、ClassLoader 修改)不能满足我们的需求

    2. Native 的两种方案(GOT 和 inline hook)确实很强大,但在这里我们还用不到修改这么底层的内容

    3. 而 Xposed 框架类型的方案又太重

    4. 对比后发现,JniHook 正好能够满足我们的需求。Hook 并重定向 android.util.Log 类中的 native 方法(print_native)即可满足要求。

「 2.4 实现原理(对 Log 类进行 JniHook) 」

原理图:

「 2.5 最终效果 」

1. 通过我们的开关控制逻辑关闭了所有的 Java 日志,并保留 Error 级别的日志输出,如下图

2. 收益

  1. 提升了性能,CPU 占用率减少约 17%

  2. 有效防止了敏感信息泄漏,保护了用户隐私

  3. 使用自己的策略灵活管控了日志的输出


其它可以黑科技优化的方向是什么?下期敬请期待~


03 团队介绍


「三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。


    _________________ END__________________

三翼鸟数字化科技
三翼鸟数字化技术团队官方订阅号,提供技术前沿洞察、技术实践分享、最佳实践整合、技术规范发布、团队文化输出。