利用ASM實現的輕量級跨Module依賴注入框架

爲何須要依賴注入

一些大型項目每每會有多個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會計算依賴圖,至關於對任務進行拓撲排序,而拓撲排序是不容許圖中有環的,上面的AB顯然就構成了一個圖中的環,構建沒法繼續。github

沒有依賴注入的解決方案

Module-AModule-B中抽出一個Module-C,用來裝class Aclass B。造成了這樣的依賴關係。api

這看起來是一個好的辦法,對於比較簡單的好比工具類的處理是不錯的。可是若是class A自己又依賴於不少Module-A中的文件,是否是也得把以來的文件也一塊兒抽出來呢?另外一個問題是,這樣作顯然違反了分Module的初衷,把本該各自屬於Module-AModule-B的功能合到了同一個Module。bash

依賴注入的解決方案

class Aclass B須要的對外提供的功能抽象出接口interface Ainterface 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();
}

複製代碼

在任意module建立接口的實現

實現使用@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()!";
    }
}
複製代碼

在任意module獲取接口實例

在有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()
複製代碼

框架原理

框架的實現有一些背景知識,這裏就不展開說:

  • APK構建流程
  • 自定義Gradle插件
  • 字節碼(稍微瞭解便可,由於有插件能夠生產字節碼)
  • ASM操做字節碼

瞭解了上面的背景知識後,框架的結構就很簡單了。首先有兩個插樁方法:

// 保存單實現接口的信息
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的指引來運行。若是有什麼問題歡迎留言或者郵件聯繫!

相關文章
相關標籤/搜索