出品 | 滴滴技术
做者 | 江义旺java
前言:近日,滴滴发布的开源项目 DroidAssist ,提供了一种简单易用、无侵入、配置化、轻量级的 Java 字节码操做方式,只须要在 XML 配置中添加简单的 Java 代码便可实现编译期对 Class 文件的动态修改。android
DroidAssist 和其余 AOP 方案不一样,它提供了一种简单易用、无侵入、配置化、轻量级的 Java 字节码操做方式,你不须要 Java 字节码的相关知识,只须要在 XML 配置中添加简单的 Java 代码便可实现编译期对 class 文件的动态修改,同时不须要引入其余额外的依赖。git
▍起源github
做为大型 APP 的表明,滴滴出行乘客端集成了较多的业务线,包含了大量的依赖库,每一个版本都有多个团队向乘客端集成大量的代码,并且这些代码都是难以直接追溯到源码的,同时乘客端还有用户量大,日活高,迭代快等特色,这些状况对乘客端的开发和维护造成很大的挑战,主要体如今:问题防范难度大、问题规模大、后期维护成本高。app
2018年5月,乘客端团队进行卡顿专项优化, 其中有个问题是:因为安卓系统 SharedPreferences自身机制,当频繁调用 SharedPreferences.apply() 方法时,可能会出现由 QueuedWork.waitToFinish() 形成的卡顿和 ANR。主要缘由是系统在 Activity 的 onPause、onStop,以及 Service 的 start 和 stop 生命周期时会执行阻塞等待 QueuedWork 清空,推测系统是为了保证持久化成功率,从而确保用户离开组件以前完成 SharedPreferences 的文件写入。框架
分析缘由以后,咱们认为,乘客端 APP 相对处于单一的进程环境,去掉这个持久化阻塞也是能够的。为了解决这个问题,咱们决定对系统的 SharedPreferences 进行改造,实现咱们本身的 SharedPreferences。ide
可是随之而来的问题是,咱们自定义的 SharedPreferences 怎么以最小的成本接入到乘客端呢?很容易想到如下两种方案:工具
以上两种方式都具备较大的侵入性,会涉及到大量的源码以及依赖库的代码改动,后期维护和升级成本也比较高,为了寻找更加理想的解决方案,咱们但愿找到一种无侵入的 Mock 工具,能作到不修改代码就能 Mock 全部 getSharedPreferences()方法的调用返回结果,初步有以下两种实现思路:优化
相似 SharedPreferences 替换这样的需求还有不少,因而咱们决定本身开发一个Android 平台 Mock 工具,通过调研以后,咱们肯定了字节码修改的技术方向,经过修改字节码实现这样的需求,由此 DroidAssist 应运而生。ui
项目地址:https://github.com/didi/Droid...
▍示例
下面例子是背景中提到的 SharedPreferences 改造,添加以下 DroidAssist 配置,在项目编译后,全部调用Context.getSharedPreferences() 的代码,将所有会被修改成返回自定义的 SharedPreferences 实例的代码:
1 <Replace> 2 <MethodCall> 3 DroidAssist 4 <Source>android.content.SharedPreferences android.content.Context.getS 5 haredPreferences(java.lang.String,int)</Source> 6 <Target>{$_= com.didi.quicksilver.QuicksilverPreferencesHelper.getShar 7 edPreferences($0,$$);}</Target> 8 </MethodCall> 9 </Replace>
处理前的 class:
1 public class MainActivity extends Activity { 2 @Override 3 protected void onCreate(Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState); 5 SharedPreferences sp = getSharedPreferences("test", MODE_PRIVATE); 6 } }
处理后的 class:
1 public class MainActivity extends Activity { 2 protected void onCreate(Bundle savedInstanceState) { 3 super.onCreate(savedInstanceState); 4 SharedPreferences sp = PreferencesHelper.getSharedPreferences(this 5 , "test", MODE_PRIVATE); // The target method return custom SharedPreferen 6 ces. 7 } }
具体的使用方式及原理可参见 DroidAssist WIKI 。
▍特性
通过不断的打磨完善,DroidAssist 已经从最开始的 Mock 工具扩展成为具备完整 AOP 框架功能的工具,有以下特性。
▍简单易用
采用灵活的配置化方式,使用者只须要依赖一个插件,而后在配置文件中定义字节码处理方式,DroidAssist 就能够根据配置文件处理项目中全部的 class 文件。处理过程以及处理后的代码中都不须要添加额外的依赖,而且不会修改原始代码行号。
▍丰富的字节码处理功能
除了解决咱们最初遇到的代码替换问题外,还扩展了其余的 AOP 功能,目前有 4 类 28 种代码修改方式。
▍简单易用
支持增量构建,处理速度快,只占用不多的构建时间。
▍Q&A
DroidAssist 能够轻易实现诸如代码替换,代码插入等功能,滴滴出行 APP 利用 DroidAssist 实现了日志输出替换,系统 SharedPreferences 替换,SharedPreferences commit 替换为 apply,Dialog 展现保护,getDeviceId 接口替换,getPackageInfo 接口替换,getSystemService 接口替换,startActivity 保护,匿名线程重命名,线程池建立监控,主线程卡顿监控,文件夹建立监控,Activity 生命周期耗时统计,APP启动耗时统计等功能。
DroidAssist 采用配置化方案,编写相关配置就能够实现 AOP 的功能,能够彻底不用修改 Java 代码;DroidAssist 在使用上使用比较简单,不须要复杂的注解配置;DroidAssist 能够比较方便的实现 AspectJ 不容易实现的代码替换功能。通常状况下使用 DroidAssist 能够完成大部分功能,较复杂状况能够和 AspectJ 配合使用。
有关安装、使用过程以及常见问题解答,请查看如下连接:
GitHub:https://github.com/didi/Droid...
Wiki:https://github.com/didi/Droid...