为什么需要依赖注入
一些大型项目往往会有多个module
,随着module
越来越复杂,module
间的依赖关系会变得难以维护,一不小心就可能造成循环依赖
,导致项目编译不过。
典型的循环依赖
有一个Module-A
,里面有一个class A
,在Module B
有一个class B
,如果Module A
需要用到class B
,同时Module-B
又需要用到class A
时,就必须得改变代码结构了。如果直接在gradle里写
// module A build.gradle
implementation project(":Module-B")
// module B build.gradle
implementation project(":Module-A")
在构建时就会看到循环依赖
的错误输出。
为什么不能循环依赖? 在构建前
Gradle
会计算依赖图,相当于对任务进行拓扑排序
,而拓扑排序
是不允许图中有环的,上面的A
和B
显然就构成了一个图中的环,构建无法继续。
没有依赖注入的解决方案
从Module-A
和Module-B
中抽出一个Module-C
,用来装class A
和class B
。形成了这样的依赖关系。
这看起来是一个好的办法,对于比较简单的比如工具类的处理是不错的。但是如果class A
本身又依赖于很多Module-A
中的文件,是不是也得把以来的文件也一起抽出来呢?另一个问题是,这样做显然违反了分Module的初衷,把本该各自属于Module-A
和Module-B
的功能合到了同一个Module。
依赖注入的解决方案
把class A
和class B
需要的对外提供的功能抽象出接口interface A
和interface B
放在Module-C
中,其实现放在原来module中,通过依赖注入创建A和B的实例。任何module要使用A、B的功能,只需要依赖Module-C
即可。形成如下依赖关系:
其中,API Factory
就集中管理实例的创建和查询,通过他获取到接口的实例。
框架使用
添加依赖
在根目录的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的指引来运行。如果有什么问题欢迎留言或者邮件联系!