出品 | 滴滴技術
做者 | 江義旺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...