一些大型項目每每會有多個module
,隨着module
愈來愈複雜,module
間的依賴關係會變得難以維護,一不當心就可能形成循環依賴
,致使項目編譯不過。java
有一個Module-A
,裏面有一個class A
,在Module B
有一個class B
,若是Module A
須要用到class B
,同時Module-B
又須要用到class A
時,就必須得改變代碼結構了。若是直接在gradle裏寫android
// module A build.gradle
implementation project(":Module-B")
// module B build.gradle
implementation project(":Module-A")
複製代碼
在構建時就會看到循環依賴
的錯誤輸出。git
爲何不能循環依賴? 在構建前
Gradle
會計算依賴圖,至關於對任務進行拓撲排序
,而拓撲排序
是不容許圖中有環的,上面的A
和B
顯然就構成了一個圖中的環,構建沒法繼續。github
從Module-A
和Module-B
中抽出一個Module-C
,用來裝class A
和class B
。造成了這樣的依賴關係。api
這看起來是一個好的辦法,對於比較簡單的好比工具類的處理是不錯的。可是若是class A
自己又依賴於不少Module-A
中的文件,是否是也得把以來的文件也一塊兒抽出來呢?另外一個問題是,這樣作顯然違反了分Module的初衷,把本該各自屬於Module-A
和Module-B
的功能合到了同一個Module。bash
把class A
和class B
須要的對外提供的功能抽象出接口interface A
和interface B
放在Module-C
中,其實現放在原來module中,經過依賴注入建立A和B的實例。任何module要使用A、B的功能,只須要依賴Module-C
便可。造成以下依賴關係:app
其中,API Factory
就集中管理實例的建立和查詢,經過他獲取到接口的實例。框架
項目地址:github.com/SirLYC/AppI…ide
在根目錄的build.gradle
添加工具
buildscript {
repositories {
//...
jcenter()
}
dependencies {
// ...
classpath "com.lyc:app-inject-plugin:latest.release"
}
}
複製代碼
在輸出apk文件的module中添加gradle插件:
apply plugin: 'com.android.application'
// 必須在application插件apply後引入
apply plugin: "com.lyc.app-inject-plugin"
複製代碼
最後在須要獲取接口實例的module添加上這個依賴便可:
dependencies {
// provide Annotations and AppInject API
implementation "com.lyc:app-inject:latest.release"
}
複製代碼
固然,一個一個添加嫌麻煩的話,能夠直接在一個基礎module使用api
引入:
dependencies {
// provide Annotations and AppInject API
api "com.lyc:app-inject:latest.release"
}
複製代碼
使用@InjectApi
標記須要依賴注入的接口。
@InjectApi
public interface ISingleApi {
String logMsg();
}
複製代碼
oneToMany
參數表示這個接口是否能夠有多個實現,oneToMany
不一樣,獲取接口實例的方式不一樣。
@InjectApi(oneToMany = true)
public interface IOneToManyApi {
String logMsg();
}
複製代碼
實現使用@InjectApiImpl
標記,同時須要指出實現的父接口是哪個,默認狀況是調用這個類的空構造方法構造實例(一個接口只會構建一次實例)。
@InjectApiImpl(api = ISingleApi.class)
public class SingleApiImpl implements ISingleApi {
@Override
public String logMsg() {
return "I'm SingleApiImpl!";
}
}
複製代碼
對於oneToMany=true
的接口,容許有多個實現
// 第一個實現
@InjectApiImpl(api = IOneToManyApi.class)
public class OneToManyApiImpl1 implements IOneToManyApi {
@Override
public String logMsg() {
return "I'm OneToManyApiImpl1!";
}
}
複製代碼
// 第二個實現
@InjectApiImpl(api = IOneToManyApi.class)
public class OneToManyApiImpl2 implements IOneToManyApi {
@Override
public String logMsg() {
return "I'm OneToManyApiImpl2!";
}
}
複製代碼
這個是用kotlin
實現的例子,其中createMethod是實例的建立方法,這個改爲了GET_INSTANCE,會調用類的靜態方法getInstance
構建,因此在用kotlin
實現時要添加@JvmStatic
註解,不然會找不到方法沒法建立這個實例。
// 這是用kotlin實現的例子
// class直接傳::classs
@InjectApiImpl(api = IOneToManyApi::class, createMethod = CreateMethod.GET_INSTANCE)
class OneToManyApiImplKt private constructor() : IOneToManyApi {
companion object {
private val instance = OneToManyApiImplKt()
// important!
@JvmStatic
fun getInstance(): IOneToManyApi {
return instance
}
}
override fun logMsg(): String {
return "I'm OneToManyApiImplKt, created by getInstance()!"
}
}
複製代碼
下面這個是使用Java
實現的建立方法爲getInstance
的實例:
@InjectApiImpl(api = IGetInstanceApi.class, createMethod = CreateMethod.GET_INSTANCE)
public class GetInstanceApiImpl implements IGetInstanceApi {
private static GetInstanceApiImpl instance = new GetInstanceApiImpl();
private GetInstanceApiImpl() {
}
` // 會用這個方法去獲取實例
public static IGetInstanceApi getInstance() {
return instance;
}
@Override
public String logMsg() {
return "I'm GetInstanceApiImpl, created by getInstance()!";
}
}
複製代碼
在有com.lyc:app-inject
這個依賴的任意module,經過如下方法就能夠獲取到實例:
// oneToMany = false
ISingleApi singleApi = AppInject.getInstance().getSingleApi(ISingleApi.class);
Log.d(TAG, singleApi.logMsg());
// oneToMany = true
for (IOneToManyApi oneToManyApi : AppInject.getInstance().getOneToManyApiList(IOneToManyApi.class)) {
Log.d(TAG, oneToManyApi.logMsg());
}
複製代碼
能夠看到,調用哪一個方法獲取實例,由前文提到的oneToMany
相關,取決因而否容許接口有多個實現。
kotlin
進一步封裝利用kotlin
的泛型特化,能夠進一步作封裝(在sample
中有):
inline fun <reified T> getSingleApi(): T? {
return AppInject.getInstance().getSingleApi(T::class.java)
}
inline fun <reified T> getOneToManyApiList(): List<T> {
return AppInject.getInstance().getOneToManyApiList(T::class.java)
}
複製代碼
使用就變得更加簡潔:
val testApi = getSingleApi<ITestApi>()
// or
val testApi:ITestApi? = getSingleApi()
複製代碼
框架的實現有一些背景知識,這裏就不展開說:
瞭解了上面的背景知識後,框架的結構就很簡單了。首先有兩個插樁方法:
// 保存單實現接口的信息
private Map<Class<?>, Implementation> singleApiClassMap = new HashMap<>();
// 保存多實現接口的信息
private Map<Class<?>, List<Implementation>> oneToManyApiClassMap = new HashMap<>();
// 插樁方法
private void initSingleApiMap() {
}
// 插樁方法
private void initOneToManyApiMap() {
List<Implementation> list;
}
複製代碼
在構建的transform
流程裏,掃描全部類(必須在輸出apk的module中引入插件才能夠掃描到app中用到的全部類),收集接口和實現的信息,再把把這些信息插入到上述代碼兩個map
對應的字節碼插到兩個對應的插樁方法便可。因此在瞭解了上述背景知識後實現起來並非太難,只是有可能遇到一些坑...
這個框架的靈感實際上是來源於我以前實習的團隊。最近本身在寫畢設的時候才發現模塊多了後,模塊之間的依賴很差處理,因而就想起了實習團隊中用到的相似的模塊解耦的方式,當時還沒怎麼注意這個框架,本身摸索着去實現這樣一個框架才發現須要這麼多的背景知識...還得繼續加油啊!
再次貼一下項目地址:github.com/SirLYC/AppI…
裏面有sample能夠clone下來,按照README的指引來運行。若是有什麼問題歡迎留言或者郵件聯繫!