背景問題
我們在元件化的過程,業務被拆分至獨立的Module中,一些公用元件會在各個Module中通過APT生成一些需要被註冊至元件中的資訊類,比如EventBus生成的Index類。我們這邊RN定製的Plugin是跟隨各自module,需要被註冊。還有,各Module對外提供的api介面的話,也需要被註冊。
另外,有些元件為某些Module特有,需要在App啟動的時候就要初始化,有些需要在主執行緒中初始化,有些為不阻塞主執行緒可以在非主執行緒中初始化。
在元件化之前,我們是在Main Module通過硬編碼來進行註冊,在Application中堆積各個元件的初始化邏輯。
有沒有更好的解決方式?
解決思路
首先,將問題分解抽象:
-
把註冊行為進行抽象化,可以把一個類(需要被註冊的資訊)看作方法函式的入參,那方法函式就可以看作是對註冊相關邏輯的實現。那註冊問題可以進一步轉化為各模組如何把相關類(需要被註冊的資訊)轉化為方法函式的入參,元件定義方法函式,獲取入參來實現註冊邏輯。比如:
A a = new A() B b = new B() b.shoot(a.get()) A為Module定義的一個類,通過get方法可以獲得被註冊的資訊,B為元件定義的一個類,在shoot方法中實現註冊邏輯。 複製程式碼
-
接下來的問題是,AB分處不同模組,如何把AB按上述程式碼邏輯組合起來?
AB組合意味著需要程式碼注入,程式碼注入使用的技術方案是Gradle Transform + ASM。AB分別實現約定的介面,再用註解標記。在編譯時,通過Gradle Transform + ASM,通過註解找到AB,生成上述格式的程式碼並注入到合適的位置。
-
剩下的問題就是,在什麼位置注入?
採用的方式是預先定義一個空方法,通過註解標記,並在適當時機呼叫這個空方法。在編譯時通過註解找到AB和空方法,生成上述格式的程式碼並注入到這個空方法中。
以上是有關元件註冊方面的解決思路,而模組中的元件初始化有點不同,因為其不需要入參。 但可以直接在方法函式中實現初始化邏輯,比如:
A a = new A()
a.shoot()
模組定義一個類A,實現約定的介面,在shoot方法中做實現初始化邏輯。
複製程式碼
其餘和元件註冊方式相類似,主要在注入的程式碼邏輯上有所不同。
設計模型
具體的實現方式其實是借鑑了弓箭耙的模式。
弓箭耙模式:
-
箭 Arrow:對應一種型號
提供模組相關類(需要被註冊的資訊)的載體。
-
弓 Bow:適配一種型號的箭,射向唯一的耙。
方法函式,即實現註冊邏輯的載體。
-
耙 Target:位置
將被注入的空方法。
介紹AutoInject
使用方式
在根專案的build.gradle中新增外掛依賴:
buildscript {
...
dependencies {
...
classpath 'com.eastwood.tools.plugins:auto-inject:1.0.0'
}
}
複製程式碼
在模組的build.gradle中新增註解庫依賴:
dependencies {
...
implementation 'com.eastwood.common:auto-inject:1.0.0'
}
複製程式碼
在主模組的build.gradle中引用外掛:
apply plugin: 'auto-inject'
autoInject {
showLog = true
ignorePackages = ['android', 'com/google']
}
複製程式碼
@AutoTarget
預先定義一個空方法並呼叫,在方法上標記@AutoTarget,例如:
public class App extends Application {
public EventBusBuilder eventBusBuilder;
@Override
public void onCreate() {
super.onCreate();
eventBusBuilder = EventBus.builder();
// add config to eventBusBuilder
addIndex2EventBus();
eventBusBuilder.build();
}
@AutoTarget
void addIndex2EventBus() {}
}
複製程式碼
addIndex2EventBus
方法將被注入程式碼。
@AutoArrow
新建一個類,並實現IAutoArrow介面,在get方法中返回需要被註冊的資訊類。例如:
@AutoArrow(model = "eventBusIndex")
public class ModuleBAutoArrow implements IAutoArrow<SubscriberInfoIndex> {
@Override
public SubscriberInfoIndex get() {
return new ModuleBEventBusIndex();
}
}
複製程式碼
@AutoBow
新建一個類,並實現IAutoBow介面,在shoot方法中獲取入參並執行具體的註冊邏輯。例如:
@AutoBow(target = "addIndex2EventBus", model = "eventBusIndex", context = true)
public class EventBusAutoBow implements IAutoBow<SubscriberInfoIndex> {
private App app;
EventBusAutoBow(Application application) {
app = (App) application;
}
@Override
public void shoot(SubscriberInfoIndex index) {
app.eventBusBuilder.addIndex(index);
}
}
複製程式碼
其中 context
用於宣告EventBusAutoBow被例項化時是否需要上下文,這個上下文是被@AutoTarget
標記的方法在執行時的上下文。為 true
時,該類需定義一個以上下文做為唯一入參的建構函式。
@AutoBowArrow
新建一個類,並實現IAutoBowArrow介面,在shoot方法中執行相關邏輯。
@AutoBowArrow(target = "init")
public class InitAutoBowArrow implements IAutoBowArrow {
@Override
public void shoot() {
// ...
}
}
複製程式碼
兩種組合方式
-
@AutoArrow + @AutoBow + @AutoTarget,三者關係為 n:1:1
-
@AutoBowArrow + @AutoTarget ,兩者比例關係為 1:1
編譯後,被注入的程式碼樣式
打包成apk後,@AutoTarget標記的方法將會被注入具有固定結構的程式碼,例如:
// @AutoArrow + @AutoBow + @AutoTarget 組合
@AutoTarget
void addIndex2EventBus() {
ModuleBAutoArrow moduleBAutoArrow = new ModuleBAutoArrow();
EventBusAutoBow eventBusAutoBow = new EventBusAutoBow(this);
eventBusAutoBow.shoot(moduleBAutoArrow.get());
...
eventBusAutoBow.shoot(***.get());
}
// @AutoBowArrow + @AutoTarget 組合
@AutoTarget
void init() {
InitAutoBowArrow initAutoBowArrow = new InitAutoBowArrow();
initAutoBowArrow.shoot();
...
}
複製程式碼
結語
本文並沒有詳細深入解釋Gradle Transform + ASM如何查詢到類和方法並注入,原始碼已上傳至github,方便自行研究。 github地址:github.com/EastWoodYan…