1. 缘起
在开发电视版智家 App9.0 项目的时候,发现了一个性能问题。电视系统原本剩余的可用资源就少,而随着 9.0 功能的进一步增多,特别是门铃、门锁、多路视频同屏监控后等功能的增加,开始出现了卡顿情况。
经过调研分析发现有一部分是日志输出导致的,profiler 工具显示有一个日志输出的线程会有频繁占用 CPU 时间片的现象。
2. 性能优化引起的 Android 日志开关控制问题及解决
2.1 查看日志线程的 CPU 占用率
使用 Profiler 查看 CPU 占用率,在打开日志及关闭日志的情况下分别如下:
(图 1 日志开关打开的情况)
(图 2 日志开关关闭的情况下)
由上可以看出,存在两个问题:
-
日志开关打开的情况下,占用 CPU 时间比较多
-
日志开关关闭的情况下,比打开的情况好一些,但还是会有一些 CPU 占用,通过打日志发现,是有一些日志没有关闭,还在频繁的继续打印
2.2 需要日志开关方案?
-
发现关不掉的 TAG 大部分第三方库中的,它们没有对外提供使能接口,导致日志关不掉,为了性能考虑,需要对第三方库中的日志进行控制,在必要时可以关闭它们;
-
如果线上的版本不关闭日志,会存在用户隐私或产品信息泄漏的风险,为了 APP 和用户安全考虑,需要关闭线上版本的日志输出;
-
另外,为了更加方便的发现错误,我们应该对日志级别进行控制,例如我们可以采用如下方案:无论任何时候都输出 E 级别日志,其它级别日志可以根据情况进行关闭;
结论:我们需要一个统一关闭和控制所有日志的开关方案。
2.3 方案调研
方案 | 方案介绍 | 优点 | 缺点 |
混淆 | 采用混淆 proguard 配置方式关闭 APP 日志输出,包括: android.util.Log 类 java.io.PrintStream 类(java.lang.System.out.println( )和 java.lang.System.out.print()系列) | 实现简单,想关哪个关哪个 |
|
插桩+ASM | 通过 AOP 编程,在日志输出的某个类中进行字节码插桩,实现控制日志输出的目的 | 灵活,稳定性高 |
|
Hook | 通过反射或其它技术,针对日志输出的某一个实现点,进行 Hook,进而实现控制的目的 | 灵活,稳定性高 |
|
混淆
-
混淆方案有个缺点,就是不能通过开关动态的调节日志开关,先做为备选吧
插桩
-
经过技术预研,发现插桩的方案如果选用 Transform, 只能修改自己写的类或者是第三方库中的类,而影响不到 Android SDK 中的类 android.util.Log
-
在暂时未找到其它插桩点的情况下,此方案行不通
Hook
-
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 指令集相关,需要针对性汇编实现,稳定性较一般,技术门槛相对高 |
-
经过分析,发现
-
Java 层的两种方案(反射+动态代理、ClassLoader 修改)不能满足我们的需求
-
Native 的两种方案(GOT 和 inline hook)确实很强大,但在这里我们还用不到修改这么底层的内容
-
而 Xposed 框架类型的方案又太重
-
对比后发现,JniHook 正好能够满足我们的需求。Hook 并重定向 android.util.Log 类中的 native 方法(print_native)即可满足要求。
-
2.4 实现原理(对 Log 类进行 JniHook)
原理图:
2.5 最终效果
1. 通过我们的开关控制逻辑关闭了所有的 Java 日志,并保留 Error 级别的日志输出,如下图
2. 收益
-
提升了性能,CPU 占用率减少约 17%
-
有效防止了敏感信息泄漏,保护了用户隐私
-
使用自己的策略灵活管控了日志的输出
其它可以黑科技优化的方向是什么?下期敬请期待~
3. 团队介绍
「三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。