利用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的指引来运行。若是有什么问题欢迎留言或者邮件联系!

相关文章
相关标签/搜索