对于Dagger 的深入探究
依赖注入DI 是一个比较有趣也比较有用的一个方法去实现IOC。
它提供一种机制,将需要依赖(低层模块)对象的引用传递给被依赖(高层模块)对象
<!--more-->
一. Dagger 原理
首先有一个比喻很形象,Dagger 进行依赖注入相当于*,@Interj 就是 *的部位,@Component 就是 这个注射器 而 @Moudle 则是 注射瓶 用来存放药水,最后@Providers 就是 药水。
@Interj 标识依赖需求方 (也可以写在注入对象的无参构造函数上,但是无法使用在接口,有参构造函数,第三方类库上面)
@Providers 标识依赖提供方。
@Moudle 存放@Providers 的地方,只提供依赖关系,不反应真实的依赖,它会告诉Dagger 可以从哪里找到依赖。
(Moudle里面的Porviders 可能会和 @Interj 标识的 无参构造函数冲突 这时候 会优先扫描Moudle 里面的注入方法,而不是@Interj)
@Component 依赖需求方和依赖提供方进行交互的桥梁接口,
在进行编译的时候会生成该接口的实现类,而通过该实现类的builder方法,再需要被注入的对象的构造函数里进行一个依赖注入。
其余注解 以及 解决的问题
- 相同返回值的 Providers 重命名问题
@Interj 的 对象类型与@Providers 的方法的返回参数的类型一致的情况下,就可以进行注入。
但是,有一些情况下,同一个对象的注入方式有几种,对应着不同的注入对象,注入需求。
如果想要解决的话,只能分散在不同的Moudle 中,进行隔离。但是这样做的话,就可能将同一功能模块下的依赖进行分离,影响代码的可读性。
所以使用到了两个注解对返回同一对象的Providers 进行一个重命名,以便于区分。
@Named 不同返回对象返回不同的id 给不同的提供依赖方不同的id,对应依赖需求方,使用相同的id,就可以进行区分。
@Qualfied 自定义注解 使用@Qulifier来定义自己的注解,然后通过自定义的注解去标注提供依赖的方法和依赖需求方
- 作用域问题
- @Scope 元注解,用来标注自定义注解的。 需要同时 标识Component和Providers 时才会生效。
- @Singleton "全局" 单例,但是其实内部就是一个使用Scope 的注解,并且 生成的java 代码也一模一样
@Scope @Documented @Retention(RUNTIME) public @interface Singleton {} // 生成的java 文件中 Component 里的 部分 this.contextProvider = DoubleCheck.provider(ContextModule_ContextFactory.create(builder.contextModule));
根据官方文档的解释,更能理解Scope 的意义
When a binding uses a scope annotation, that means that the component object holds a reference to the bound object until the component object itself is garbage-collected.
当Component 和 Providers 同时绑定一个scope 同一个作用域的话,其实是说明这个Component 将拿到这个注入对象的引用,直到这个Component 被 rebuild 或者销毁。
所以实际上 @Scope 和 @Singleton 并没有真正的控制生命周期,真正控制生命周期的一直都是@Component 所以 这两个作用域注解实际上 只起一个声明作用。
- @Reusable
@Documented @Beta @Retention(RUNTIME) @Scope public @interface Reusable {}
与@Singleton绑定相比,@ Reusable绑定与未绑定有更多共同点:你告诉Dagger你可以创建一个全新的对象,但如果已经创建了一个方便的对象,那么Dagger可能会使用那个.相比之下,@ Singleton对象保证您将始终接收相同的实例,这可能会更加昂贵.
Reusable 作用域不关心绑定的 Component,Reusable 作用域只需要标记目标类或 provide 方法,不用标记 Component
当你使用 两种不同的 注解的时候,生成的代码也不相同了
//当使用Singleton的时候 this.provideCarProvider = DoubleCheck.provider(CarMoudle_ProvideCarFactory.create()); //当是用Reusable的时候 this.provideCarProvider = SingleCheck.provider(CarMoudle_ProvideCarFactory.create());
这里Reusable 使用的 SingleCheck 而不是DoubleCheck,在多线程的情况下,可能生成多个实例,但是Reusable 只是保证了复用上次的实例,并没有保证实例的唯一。所以这也是一个提升性能的点吧。
- 两种注入方式
- Lazy<T> 延迟注入 在使用的时候再进行注入,加快加载速度
- Provide<T> 与单例恰好相反,每次调用它的get方法的时候,都会调用@Interj构造函数创建新的实例或者是Moudle 中的 Providers 方法返回实例
MembersInjector //Lazy<T> 标注的时候 public static void injectCar(Man instance, Provider<Car> carProvider) { instance.car = DoubleCheck.lazy(carProvider); } //Provider<T> 标注的时候 public static void injectCar(Man instance, Provider<Car> car) { instance.car = car; } //不标注的时候 public static void injectCar(Man instance, Provider<Car> carProvider) { instance.car = carProvider.get(); }
Dagger 注解生成代码
这边 都是沿用同一套依赖注入的注解标准的,即
使用了 包下的 五个基础注解和一个接口
下面是三个特殊 注解 的 AbstractProcessor
首先是Moudle 的 Processor
首先 在 Process 中 获取到所有的Moudle
并在 之后 writeDotFile 写入 Factory文件中
//首先是Proess 函数 对完整的模块进行全图分析,然后找到所有的Moudle 然后生成 Factory @Override public boolean process(Set<? extends TypeElement> types, RoundEnvironment env) { if (!env.processingOver()) { // Storing module names for later retrieval as the element instance is invalidated across // passes. for (Element e : env.getElementsAnnotatedWith(Module.class)) { if (!(e instanceof TypeElement)) { error("@Module applies to a type, " + e.getSimpleName() + " is a " + e.getKind(), e); continue; } delayedModuleNames.add(((TypeElement) e).getQualifiedName().toString()); } return false; } Set<Element> modules = new LinkedHashSet<Element>(); for (String moduleName : delayedModuleNames) { modules.add(elements().getTypeElement(moduleName)); } for (Element element : modules) { Map<String, Object> annotation = null; try { annotation = getAnnotation(Module.class, element); } catch (CodeGenerationIncompleteException e) { continue; // skip this element. An up-stream compiler error is in play. } TypeElement moduleType = (TypeElement) element; if (annotation == null) { error("Missing @Module annotation.", moduleType); continue; } if (annotation.get("complete").equals(Boolean.TRUE)) { Map<String, Binding<?>> bindings; try { bindings = processCompleteModule(moduleType, false); new ProblemDetector().detectCircularDependencies(bindings.values()); } catch (ModuleValidationException e) { error("Graph validation failed: " + e.getMessage(), e.source); continue; } catch (InvalidBindingException e) { error("Graph validation failed: " + e.getMessage(), elements().getTypeElement(e.type)); continue; } catch (RuntimeException e) { if (ERROR_NAMES_TO_PROPAGATE.contains(e.getClass().getName())) { throw e; } error("Unknown error " + e.getClass().getName() + " thrown by javac in graph validation: " + e.getMessage(), moduleType); continue; } try { writeDotFile(moduleType, bindings); } catch (IOException e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); processingEnv.getMessager() .printMessage(Diagnostic.Kind.WARNING, "Graph visualization failed. Please report this as a bug.\n\n" + sw, moduleType); } } if (annotation.get("library").equals(Boolean.FALSE)) { Map<String, Binding<?>> bindings = processCompleteModule(moduleType, true); try { new ProblemDetector().detectUnusedBinding(bindings.values()); } catch (IllegalStateException e) { error("Graph validation failed: " + e.getMessage(), moduleType); } } } return false; } void writeDotFile(TypeElement module, Map<String, Binding<?>> bindings) throws IOException { JavaFileManager.Location location = StandardLocation.SOURCE_OUTPUT; String path = getPackage(module).getQualifiedName().toString(); String file = module.getQualifiedName().toString().substring(path.length() + 1) + ".dot"; FileObject resource = processingEnv.getFiler().createResource(location, path, file, module); Writer writer = resource.openWriter(); GraphVizWriter dotWriter = new GraphVizWriter(writer); new GraphVisualizer().write(bindings, dotWriter); dotWriter.close(); }</details> 接下来是 Interj 的 Processor
主要Process 干的事情就是 确定Interj 的Class 正确并且匹配之后
写入 方法。
``` @Override public boolean process(Set<? extends TypeElement> types, RoundEnvironment env) { remainingTypeNames.addAll(findInjectedClassNames(env)); for (Iterator<String> i = remainingTypeNames.iterator(); i.hasNext();) { InjectedClass injectedClass = createInjectedClass(i.next()); // Verify that we have access to all types to be injected on this pass. boolean missingDependentClasses = !allTypesExist(injectedClass.fields) || (injectedClass.constructor != null && !allTypesExist(injectedClass.constructor .getParameters())) || !allTypesExist(injectedClass.staticFields); if (!missingDependentClasses) { try { generateInjectionsForClass(injectedClass); } catch (IOException e) { error("Code gen failed: " + e, injectedClass.type); } i.remove(); } } if (env.processingOver() && !remainingTypeNames.isEmpty()) { processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Could not find injection type required by " + remainingTypeNames); } return false; } private void generateInjectionsForClass(InjectedClass injectedClass) throws IOException { if (injectedClass.constructor != null || !injectedClass.fields.isEmpty()) { generateInjectAdapter(injectedClass.type, injectedClass.constructor, injectedClass.fields); } if (!injectedClass.staticFields.isEmpty()) { generateStaticInjection(injectedClass.type, injectedClass.staticFields); } } private MethodSpec getMethod(ExecutableElement constructor, boolean disambiguateFields, boolean injectMembers, ClassName injectedClassName) { MethodSpec.Builder result = MethodSpec.methodBuilder("get") .addJavadoc(AdapterJavadocs.GET_METHOD, injectedClassName) .addAnnotation(Override.class) .returns(injectedClassName) .addModifiers(PUBLIC); result.addCode("Tresult=newT(", injectedClassName, injectedClassName); boolean first = true; for (VariableElement parameter : constructor.getParameters()) { if (!first) result.addCode(", "); else first = false; result.addCode("$N.get()", parameterName(disambiguateFields, parameter)); } result.addCode(");\n"); if (injectMembers) { result.addStatement("injectMembers(result)"); } result.addStatement("return result"); return result.build(); } ```</details>
首先 按类索引 寻找所有 Providers 的映射
然后通过 方法 对Moudle 以及 Porviders 进行适配 生成,将对应的Moudle以及Providers 方法 的一些信息,,,写入
最后写入文件DaggerComponent 中。
``` //进行Moudle 和 Providers 的适配 生成 private TypeSpec generateProvidesAdapter(ClassName moduleClassName, ClassName adapterName, ExecutableElement providerMethod, Map<ExecutableElement, ClassName> methodToClassName, Map<String, AtomicInteger> methodNameToNextId, boolean library) { String methodName = providerMethod.getSimpleName().toString(); TypeMirror moduleType = providerMethod.getEnclosingElement().asType(); ClassName className = bindingClassName( adapterName, providerMethod, methodToClassName, methodNameToNextId); TypeName returnType = Util.injectableType(providerMethod.getReturnType()); List<? extends VariableElement> parameters = providerMethod.getParameters(); boolean dependent = !parameters.isEmpty(); TypeSpec.Builder result = TypeSpec.classBuilder(className.simpleName()) .addJavadoc("$L", bindingTypeDocs(returnType, false, false, dependent)) .addModifiers(PUBLIC, STATIC, FINAL) .superclass(ParameterizedTypeName.get(ClassName.get(ProvidesBinding.class), returnType)); result.addField(moduleClassName, "module", PRIVATE, FINAL); for (Element parameter : parameters) { result.addField(bindingOf(parameter.asType()), parameterName(parameter), PRIVATE); } boolean singleton = providerMethod.getAnnotation(Singleton.class) != null; String key = GeneratorKeys.get(providerMethod); result.addMethod(MethodSpec.constructorBuilder() .addModifiers(PUBLIC) .addParameter(moduleClassName, "module") .addStatement("super(S,L, S,S)", key, (singleton ? "IS_SINGLETON" : "NOT_SINGLETON"), typeToString(moduleType), methodName) .addStatement("this.module = module") .addStatement("setLibrary($L)", library) .build()); if (dependent) { MethodSpec.Builder attachBuilder = MethodSpec.methodBuilder("attach") .addJavadoc(AdapterJavadocs.ATTACH_METHOD) .addAnnotation(Override.class) .addAnnotation(Util.UNCHECKED) .addModifiers(PUBLIC) .addParameter(Linker.class, "linker"); for (VariableElement parameter : parameters) { String parameterKey = GeneratorKeys.get(parameter); attachBuilder.addStatement( "N=(T) linker.requestBinding(S,T.class, getClass().getClassLoader())", parameterName(parameter), bindingOf(parameter.asType()), parameterKey, moduleClassName); } result.addMethod(attachBuilder.build()); MethodSpec.Builder getDependenciesBuilder = MethodSpec.methodBuilder("getDependencies") .addJavadoc(AdapterJavadocs.GET_DEPENDENCIES_METHOD) .addAnnotation(Override.class) .addModifiers(PUBLIC) .addParameter(Util.SET_OF_BINDINGS, "getBindings") .addParameter(Util.SET_OF_BINDINGS, "injectMembersBindings"); for (Element parameter : parameters) { getDependenciesBuilder.addStatement("getBindings.add($N)", parameterName(parameter)); } result.addMethod(getDependenciesBuilder.build()); } MethodSpec.Builder getBuilder = MethodSpec.methodBuilder("get") .addJavadoc(AdapterJavadocs.GET_METHOD, returnType) .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(returnType) .addCode("return module.$N(", methodName); boolean first = true; for (Element parameter : parameters) { if (!first) getBuilder.addCode(", "); getBuilder.addCode("$N.get()", parameterName(parameter)); first = false; } getBuilder.addCode(");\n"); result.addMethod(getBuilder.build()); return result.build(); } // 按类索引 寻找所有Providers 的映射 private Map<String, List<ExecutableElement>> providerMethodsByClass(RoundEnvironment env) { Elements elementUtils = processingEnv.getElementUtils(); Types types = processingEnv.getTypeUtils(); Map<String, List<ExecutableElement>> result = new HashMap<String, List<ExecutableElement>>(); provides: for (Element providerMethod : findProvidesMethods(env)) { switch (providerMethod.getEnclosingElement().getKind()) { case CLASS: break; // valid, move along default: // TODO(tbroyer): pass annotation information error("Unexpected @Provides on " + elementToString(providerMethod), providerMethod); continue; } TypeElement type = (TypeElement) providerMethod.getEnclosingElement(); Set<Modifier> typeModifiers = type.getModifiers(); if (typeModifiers.contains(PRIVATE) || typeModifiers.contains(ABSTRACT)) { error("Classes declaring @Provides methods must not be private or abstract: " + type.getQualifiedName(), type); continue; } Set<Modifier> methodModifiers = providerMethod.getModifiers(); if (methodModifiers.contains(PRIVATE) || methodModifiers.contains(ABSTRACT) || methodModifiers.contains(STATIC)) { error("@Provides methods must not be private, abstract or static: " + type.getQualifiedName() + "." + providerMethod, providerMethod); continue; } ExecutableElement providerMethodAsExecutable = (ExecutableElement) providerMethod; if (!providerMethodAsExecutable.getThrownTypes().isEmpty()) { error("@Provides methods must not have a throws clause: " + type.getQualifiedName() + "." + providerMethod, providerMethod); continue; } // Invalidate return types. TypeMirror returnType = types.erasure(providerMethodAsExecutable.getReturnType()); if (!returnType.getKind().equals(TypeKind.ERROR)) { // Validate if we have a type to validate (a type yet to be generated by other // processors is not "invalid" in this way, so ignore). for (String invalidTypeName : INVALID_RETURN_TYPES) { TypeElement invalidTypeElement = elementUtils.getTypeElement(invalidTypeName); if (invalidTypeElement != null && types.isSameType(returnType, types.erasure(invalidTypeElement.asType()))) { error(String.format("@Provides method must not return %s directly: %s.%s", invalidTypeElement, type.getQualifiedName(), providerMethod), providerMethod); continue provides; // Skip to next provides method. } } } List<ExecutableElement> methods = result.get(type.getQualifiedName().toString()); if (methods == null) { methods = new ArrayList<ExecutableElement>(); result.put(type.getQualifiedName().toString(), methods); } methods.add(providerMethodAsExecutable); } TypeMirror objectType = elementUtils.getTypeElement("java.lang.Object").asType(); // Catch any stray modules without @Provides since their injectable types // should still be registered and a ModuleAdapter should still be written. for (Element module : env.getElementsAnnotatedWith(Module.class)) { if (!module.getKind().equals(ElementKind.CLASS)) { error("Modules must be classes: " + elementToString(module), module); continue; } TypeElement moduleType = (TypeElement) module; // Verify that all modules do not extend from non-Object types. if (!types.isSameType(moduleType.getSuperclass(), objectType)) { error("Modules must not extend from other classes: " + elementToString(module), module); } String moduleName = moduleType.getQualifiedName().toString(); if (result.containsKey(moduleName)) continue; result.put(moduleName, new ArrayList<ExecutableElement>()); } return result; } private Set<? extends Element> findProvidesMethods(RoundEnvironment env) { Set<Element> result = new LinkedHashSet<Element>(); result.addAll(env.getElementsAnnotatedWith(Provides.class)); return result; } ```</details>
经过编译以及注解处理器之后,会生成 XXMoudleFactory和Dagger XX Component 的样本代码
DaggerXXComponent
initialize 每次在Component 的构造函数中调用 ,调用的时候,其实就是通过 factory 进行一个 生产注入,而如果标记了Scope 的就使用 双检锁单例模式进行一个赋值,没有标记的则直接使用Factory.create。
builder() new 一个内部类 ,里面包含了所有关联的Moudle 进行一个new Moudle 的 操作
实现Component 接口中自己定义的函数,提供一些依赖资源 的实例,方便其他 dependencies 使用
public final class DaggerRandomUserComponent implements RandomUserComponent { private Provider<Context> contextProvider; private Provider<File> fileProvider; private Provider<Cache> cacheProvider; private Provider<HttpLoggingInterceptor> httpLoggingInterceptorProvider; private Provider<OkHttpClient> okHttpClientProvider; private Provider<Gson> gsonProvider; private Provider<GsonConverterFactory> gsonConverterFactoryProvider; private Provider<Retrofit> retrofitProvider; private RandomUsersModule randomUsersModule; private Provider<OkHttp3Downloader> okHttp3DownloaderProvider; private Provider<Picasso> picassoProvider; private DaggerRandomUserComponent(Builder builder) { initialize(builder); } public static Builder builder() { return new Builder(); } @SuppressWarnings("unchecked") private void initialize(final Builder builder) { this.contextProvider = DoubleCheck.provider(ContextModule_ContextFactory.create(builder.contextModule)); this.fileProvider = DoubleCheck.provider( OkHttpClientModule_FileFactory.create(builder.okHttpClientModule, contextProvider)); this.cacheProvider = OkHttpClientModule_CacheFactory.create(builder.okHttpClientModule, fileProvider); this.httpLoggingInterceptorProvider = OkHttpClientModule_HttpLoggingInterceptorFactory.create(builder.okHttpClientModule); this.okHttpClientProvider = OkHttpClientModule_OkHttpClientFactory.create( builder.okHttpClientModule, cacheProvider, httpLoggingInterceptorProvider); this.gsonProvider = RandomUsersModule_GsonFactory.create(builder.randomUsersModule); this.gsonConverterFactoryProvider = RandomUsersModule_GsonConverterFactoryFactory.create( builder.randomUsersModule, gsonProvider); this.retrofitProvider = DoubleCheck.provider( RandomUsersModule_RetrofitFactory.create( builder.randomUsersModule, okHttpClientProvider, gsonConverterFactoryProvider, gsonProvider)); this.randomUsersModule = builder.randomUsersModule; this.okHttp3DownloaderProvider = PicassoModule_OkHttp3DownloaderFactory.create(builder.picassoModule, okHttpClientProvider); this.picassoProvider = DoubleCheck.provider( PicassoModule_PicassoFactory.create( builder.picassoModule, contextProvider, okHttp3DownloaderProvider)); } @Override public RandomUsersApi getRandomUserService() { return Preconditions.checkNotNull( randomUsersModule.randomUsersApi(retrofitProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } @Override public Picasso getPicasso() { return picassoProvider.get(); } public static final class Builder { private ContextModule contextModule; private OkHttpClientModule okHttpClientModule; private RandomUsersModule randomUsersModule; private PicassoModule picassoModule; private Builder() {} public RandomUserComponent build() { if (contextModule == null) { throw new IllegalStateException(ContextModule.class.getCanonicalName() + " must be set"); } if (okHttpClientModule == null) { this.okHttpClientModule = new OkHttpClientModule(); } if (randomUsersModule == null) { this.randomUsersModule = new RandomUsersModule(); } if (picassoModule == null) { this.picassoModule = new PicassoModule(); } return new DaggerRandomUserComponent(this); } public Builder randomUsersModule(RandomUsersModule randomUsersModule) { this.randomUsersModule = Preconditions.checkNotNull(randomUsersModule); return this; } public Builder okHttpClientModule(OkHttpClientModule okHttpClientModule) { this.okHttpClientModule = Preconditions.checkNotNull(okHttpClientModule); return this; } public Builder contextModule(ContextModule contextModule) { this.contextModule = Preconditions.checkNotNull(contextModule); return this; } public Builder picassoModule(PicassoModule picassoModule) { this.picassoModule = Preconditions.checkNotNull(picassoModule); return this; } } }
Factory
一个构造函数 + get() + create() 作为一个工厂对注入对象进行创造加工
public final class RandomUsersModule_GsonConverterFactoryFactory implements Factory<GsonConverterFactory> { private final RandomUsersModule module; private final Provider<Gson> gsonProvider; public RandomUsersModule_GsonConverterFactoryFactory( RandomUsersModule module, Provider<Gson> gsonProvider) { this.module = module; this.gsonProvider = gsonProvider; } @Override public GsonConverterFactory get() { return Preconditions.checkNotNull( module.gsonConverterFactory(gsonProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<GsonConverterFactory> create( RandomUsersModule module, Provider<Gson> gsonProvider) { return new RandomUsersModule_GsonConverterFactoryFactory(module, gsonProvider); } }
public final class OkHttpClientModule_FileFactory implements Factory<File> { private final OkHttpClientModule module; private final Provider<Context> contextProvider; public OkHttpClientModule_FileFactory( OkHttpClientModule module, Provider<Context> contextProvider) { this.module = module; this.contextProvider = contextProvider; } @Override public File get() { return Preconditions.checkNotNull( module.file(contextProvider.get()), "Cannot return null from a non-@Nullable @Provides method"); } public static Factory<File> create(OkHttpClientModule module, Provider<Context> contextProvider) { return new OkHttpClientModule_FileFactory(module, contextProvider); } } ......
Dagger 的 组合 依赖 继承
这里提到了一点就是 dagger 中 Component 的 依赖关系
对于一个复杂的项目来说,复杂的依赖关系是必不可少的,也是非常难以解决的问题。
如果对于一个Demo或者是一个小的项目来说,完全可以在程序入口处,对所需要的对象进行直接注入,方便快捷。 但是当一个项目开始变的完善,复杂,分工之后,其实如果继续在程序入口处进行一个赋值的话,会导致很大的混乱,也影响程序的可读性,并且不利于多人合作开发与测试以及后期的修改与维护。<br>
所以这也是一个DI 依赖注入框架应该做的,但是上面所说的那些仅仅只是解决了一个依赖注入的情况,对于一个复杂的项目来说,通过依赖关系所构建的DAG(有向无环图)才是一个依赖关系的重中之重.
如何描述一个DAG 也是 DI 框架所要考虑的问题。在Dagger2中
有三种方法可以去描述两个依赖之间的关系
- Moudle.include
实现的是Moudle 之间的组合关系 表示包含有 其他Moudle 的Providers ,也可以提供其他Moudle 的实例
<img src="https://s1.ax1x.com/2020/06/03/tUqgkq.jpg" alt="tUqgkq.jpg" border="0" />
这个就是一个RandomUsers API 的一个实例 使用的就是Moudle.include
将其转换成Dagger 依赖形式则是
<img src="https://s1.ax1x.com/2020/06/03/taKTrF.jpg" alt="taKTrF.jpg" border="0" />
依赖关系分别是
RandomUsersMoudle 需要 OkhttpCilentMoudle
OkHttpClientMoudle 需要 ContextMoudle
PicassoMoudule 需要 OkhttpClientModule 以及 ContextMoudle
(但是OkhttpClientModule 已经 include ContextMoudle 所以 可以简化成 PicassoMoudule 需要 OkhttpClientModule)
所以 关系链接起来就变成了
<img src="https://s1.ax1x.com/2020/06/03/taQXjK.jpg" alt="taQXjK.jpg" border="0" />
@Module public class ContextModule { Context context; public ContextModule(Context context){ this.context = context; } @ApplicationContext @RandomUserApplicationScope @Provides public Context context(){ return context.getApplicationContext(); } } @Module(includes = ContextModule.class) public class OkHttpClientModule { @Provides public OkHttpClient okHttpClient(Cache cache, HttpLoggingInterceptor httpLoggingInterceptor){ return new OkHttpClient() .newBuilder() .cache(cache) .addInterceptor(httpLoggingInterceptor) .build(); } @Provides public Cache cache(File cacheFile){ return new Cache(cacheFile, 10 * 1000 * 1000); //10 MB } @Provides @RandomUserApplicationScope public File file(@ApplicationContext Context context){ File file = new File(context.getCacheDir(), "HttpCache"); file.mkdirs(); return file; } @Provides public HttpLoggingInterceptor httpLoggingInterceptor(){ HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { Timber.d(message); } }); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return httpLoggingInterceptor; } } @Module(includes = OkHttpClientModule.class) public class PicassoModule { @RandomUserApplicationScope @Provides public Picasso picasso(@ApplicationContext Context context, OkHttp3Downloader okHttp3Downloader){ return new Picasso.Builder(context). downloader(okHttp3Downloader). build(); } @Provides public OkHttp3Downloader okHttp3Downloader(OkHttpClient okHttpClient){ return new OkHttp3Downloader(okHttpClient); } } @Module(includes = OkHttpClientModule.class) public class RandomUsersModule { @Provides public RandomUsersApi randomUsersApi(Retrofit retrofit){ return retrofit.create(RandomUsersApi.class); } @RandomUserApplicationScope @Provides public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, Gson gson){ return new Retrofit.Builder() .client(okHttpClient) .baseUrl("https://randomuser.me/") .addConverterFactory(gsonConverterFactory) .build(); } @Provides public Gson gson(){ GsonBuilder gsonBuilder = new GsonBuilder(); return gsonBuilder.create(); } @Provides public GsonConverterFactory gsonConverterFactory(Gson gson){ return GsonConverterFactory.create(gson); } }
- Component.dependencies
其实Component 作为一个桥接器接口,不仅仅是提供一个Moudle 与 Interj 之间的连接作用,作为接口,其实也可以在里面定义方法 返回 创建实例。
UserComponent 和 FriendComponent 都拥有一个CarMoudule,但是 实际逻辑是UserComponent 是 Car 的拥有者,而FriendComponent 是 可以使用UserComponent 的 Car
所以如果没有对两个Component 或者是CarMoudle 进行一个限制的话,其实会有问题的。
比如 UserComponent 进行销毁了,那FriendComponent 能继续拥有CarMoudle 吗?
显然是不能的,那实际意义是 CarMoudle 的生命周期的控制是在 UserComponent 这里的,而跟FriendComponent 没有任何关系,FriendComponent 应该通过UserComponent 对外暴露的接口进行一个调用访问。
所以这里就用到了 Component.dependencies
// 两个Component 通过 依赖关系进行连接 @ManScope @Component(modules = CarModule.class) public interface ManComponent { void inject(Man man); Car car(); //必须向外提供 car 依赖实例的接口,表明 Man 可以借 car 给别人 } @FriendScope @Component(dependencies = ManComponent.class) public interface FriendComponent { void inject(Friend friend); } // 注入方式 ManComponent manComponent = DaggerManComponent.builder() .build(); FriendComponent friendComponent = DaggerFriendComponent.builder() .manComponent(manComponent) .build(); friendComponent.inject(friend);
父Component 需要提供一个获取依赖资源的接口方法,而且对于依赖资源的生命周期Scope 应该设置的不一样 (父Component 生命周期 子Component 生命周期)
而且 子 Component 的依赖资源不能设置为Singleton
因为Dagger2 中的@Singleton 的 Component 不能依赖于其他Component
- SubComponent 与 Moudle.SubComponent
SubComponent 编译时不会生成 DaggerXXComponent,需要通过 parent Component 的获取 SubComponent.Builder 方法获取 SubComponent 实例。
这里和 Dependencies 不一样的就是 Dependencies 需要显示 暴露自己想要暴露的一些 实例的接口,而 SubComponent 不一样 他直接就能继承 Parent Component 的所有依赖。
@ManScope @Component(modules = CarModule.class) public interface ManComponent { void inject(Man man); // 继承关系中不用显式地提供暴露依赖实例的接口 } //SubComponent 子接口 需要去 声明一个 Builder @SonScope @SubComponent(modules = BikeModule.class) public interface SonComponent { void inject(Son son); @Subcomponent.Builder interface Builder { // SubComponent 必须显式地声明 Subcomponent.Builder,parent Component 需要用 Builder 来创建 SubComponent SonComponent build(); } } // Parent Component 所使用的Moudle 中 才去 标记 SubComponent @Module(subcomponents = SonComponent.class) public class CarModule { @Provides @ManScope static Car provideCar() { return new Car(); } } //注入方式 ManComponent manComponent = DaggerManComponent.builder() .build(); SonComponent sonComponent = manComponent.sonComponent() .build(); sonComponent.inject(son);
而当他们 设置为了 SubComponent 之后呢,编译完成后,其实是没有DaggerSubComponent,是会变成一个内部类进行组合,生成两个内部类一个是 SubComponentBuilder 一个是 SubComponentImpl 里面和Component 一样。
当然了 这里和依赖关系一样,就是 父Component 和子Component 的 Scope 不能一样
(附:这里还会遇到一个问题,就是重复Moudle 的覆盖问题,即父Component 和子Component 同时去 使用同一个 Moudle 的时候 ,如果是在使用抽象工厂的时候,参数传入Moudle 的时候 会编译报错,而 如果使用 Moudle.subComponent 的时候 是运行时报错)
另一种方式声明继承关系 及在父 Component 通过抽象工厂的定义 去声明一个继承关系
@ManScope @Component(modules = CarModule.class) public interface ManComponent { void injectMan(Man man); SonComponent sonComponent(); // 这个抽象工厂方法表明 SonComponent 继承 ManComponent }
Interj 的 容器形式
遇到的问题其实就是 在 实际操作的过程,我们不是只是注入单个成员变量,而是需要注入多个对象,而如果每次都要去Interj 一遍就很麻烦。
所以Dagger 也考虑到了这个问题,就设计了两种注解提供给我们容器注入的方式
@IntoSet
Providers 中使用 即可注入一个Set里面
@IntoMap
同样是 Providers 使用 并且使用的时候 需要时候@Intkey @StringKey 设置键值
Binds 家族
@Binds
@BindsOptionalOf
@MultiBinds
@BindsInstance
Dagger 包下的常用注解
dagger // dagger包下大多是核心注解 ├── Binds.class // 注解@Module中的抽象方法,主要场景是@Inject注解构造函数+有继承关系的对象上 ├── BindsInstance.class // 注解@Component.Builder中的方法,用于绑定某个实例以提供数据依赖 ├── BindsOptionalOf.class // 需要结合JDK 1.8中的Optional类使用,用于某个对象可空的情景上,在kotlin中实在鸡肋 ├── Component$Builder.class // 用于自定义Component的Builder对象 ├── Component.class // dagger核心注解之一,用于定义一个桥接类,其中有modules和dependencies两个属性,分别指定依赖的数据仓库 和 依赖的其他桥接类 ├── Component$Factory.class // 与Component$Builder作用一样,dagger2.22引入,目前不常用 ├── internal // internal包中是一些辅助类,常用于dagger生成的代码中(此包中的内容有所省略,挑重点总结) │ ├── DoubleCheck.class // 提供了provider()和lazy()两个静态方法,分别用于实现局部单例和dagger.Lazy懒加载数据上 │ ├── MapBuilder.class // 用于辅助@IntoMap等构建Map容器,最终生成的是一个不可修改的Map容器 │ ├── SetBuilder.class // 用于辅助@IntoSet等构建Set容器,最终生成的是一个不可修改的Set容器 ├── Lazy.class // dagger.Lazy对象,用于dagger注入懒加载对象 ├── MapKey.class // 用于辅助Map注入的自定义Key的注解 ├── MembersInjector.class // 注入器的接口 ├── Module.class // dagger核心注解之一,用于定义一个数据仓库,其中有includes和subcomponents两个属性,分别指定对其他数据仓库的简单组合 和 对@Subcomponent桥接类的依赖 ├── multibindings // multibindings包下是一些有关与dagger的multibing特性的东西 │ ├── ClassKey.class // 用于辅助@IntoMap,指定Map容器的Key类型 │ ├── ElementsIntoSet.class // 用于辅助Set注入,可以将一个已有的Set注入到最终的Set中 │ ├── IntKey.class // 用于辅助@IntoMap,指定Map容器的Key类型 │ ├── IntoMap.class // 用于辅助@IntoMap,指定Map容器的Key类型 │ ├── IntoSet.class // 用于辅助@IntoMap,指定Map容器的Key类型 │ ├── LongKey.class // 用于辅助@IntoMap,指定Map容器的Key类型 │ ├── Multibinds.class // 用于辅助Set和Map注入,适用在编译器不确定是否有注入元素时,使得可以注入空容器 │ └── StringKey.class // 用于辅助@IntoMap,指定Map容器的Key类型 ├── Provides.class // dagger核心注解之一,注解@Module中的方法表示此方法可以提供某种类型的数据 ├── Reusable.class // dagger中对@Scope的一个特殊实现,目前处于测试阶段,相较于普通的@Scope,@Reusable不用在@Component上再次声明作用域 ├── Subcomponent$Builder.class // 用于自定义Subcomponent的Builder对象 ├── Subcomponent.class // 用于自定义一个桥接类,此桥接类不能单独使用也不会被dagger单独生成对应Daggerxxx类,需要配合@Module.subcomponent使用 └── Subcomponent$Factory.class // 与Subcomponent$Builder作用一致 参考Blog :https://blog.csdn.net/u012273376/article/details/90297137
二. Dagger 使用场景 即 优势
1. 更好的单元测试
对于一些复杂的项目,涉及到的复杂的依赖 ,测试的时候往往不太方便 因为在很多时候,对于一些依赖的注入要考虑顺序,考虑注入方式。。。。 所以使用Dagger 可以更好的将接口分离,业务逻辑的 模块更加清晰。 并且 对于测试数据的 注入,也更加方便,即 专门设置一个 测试的Component 以及 Moudle 将 测试数据与上线数据进行分离。
单独的Component 配置(Separate component configurations)
@Component(modules = { OAuthModule.class, // real auth FooServiceModule.class, // real backend OtherApplicationModule.class, /* … */ }) interface ProductionComponent { Server server(); } @Component(modules = { FakeAuthModule.class, // fake auth FakeFooServiceModule.class, // fake backend OtherApplicationModule.class, /* … */}) interface TestComponent extends ProductionComponent { FakeAuthManager fakeAuthManager(); FakeFooService fakeFooService(); } /** * Provides auth bindings that will not change in different auth configurations, * such as the current user. */ @Module class AuthModule { @Provides static User currentUser(AuthManager authManager) { return authManager.currentUser(); } // Other bindings that don’t differ among AuthManager implementations. } /** Provides a {@link AuthManager} that uses OAuth. */ @Module(includes = AuthModule.class) // Include no-alternative bindings. class OAuthModule { @Provides static AuthManager authManager(OAuthManager authManager) { return authManager; } // Other bindings used only by OAuthManager. } /** Provides a fake {@link AuthManager} for testing. */ @Module(includes = AuthModule.class) // Include no-alternative bindings. class FakeAuthModule { @Provides static AuthManager authManager(FakeAuthManager authManager) { return authManager; } // Other bindings used only by FakeAuthManager. }
这里我们使用一个TestComponent 去继承 ProductionComponent
然后我们就可以在注入点使用 去替代 然后传入不同的 就可以进行一个测试模块和生产模块的切换了。
2. MVP 架构 + Dagger 2
首先MVP 架构 本来就将MVC 架构 的功能更加细化 并且使得View 和 Model 层 完全分离。 虽然结构更加清晰,但是 也会导致一些问题 比如 Presenter 层 的负担加重,需要同时联系 View 和 Modeel 的多个类和接口, 所以这里就需要一个的注入框架的帮助,帮助Present 减轻 负担。
3. Dagger 2 For Android
对应 Android 项目来说,其实Dagger 2 框架还是有所缺点的。 比如 框架所需的类和接口繁多,并且需要依靠DaggerComponent的build方法进行一个注入。 每一个Activity 都需要对应一个 ActivityComponent 并且重复一个Builder 的重复过程,枯燥且容易出错。 对于每一个Activity 来说, 他都需要明白,他所对应的Component 是谁, 才能执行该Component的build方法, 其实是违背最少知道原则的。 (即 被注入的对象不应该了解到注入的细节) 所以对于这些问题,Dagger 专门有一个 Android 的扩展库 即 使用AndroidInjector和IntoMap做一个收集,整合派发和注入。 即通过Intjector 将所有ActivityComponent做一个收集并存放到Map 里, 然后当我们再使用Intjector.inject(this)的时候就可以直接派发当前对应的一个注入方式了。 就不需要知道对应的Component并且执行对应的Builder方法了。
三. Dagger 与其他依赖注入框架的对比
Dagger 前身 ------ Guice (Android RoboGuice)
RoboGuice 应该是安卓方面非常早的一个依赖注入框架了,跟spring一样,是使用反射机制进行依赖注入的,可以使用Roboblender 优化注解性能,但是如果注解写的太多的话,还是会或多或少的影响安卓性能
RoboGuice 提供了视图,对象,资源,服务的注入。
- @Interview 视图注入
- @InterResourse 资源注入
- @Inter 系统服务注入(在Activity中注入震动,通知管理等系统服务) POJO对象注入
<details>
<summary>完整的系统服务注入列表</summary>
## Context Class: Context Provider: ContextScope Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject MyComponent component; static class MyComponent { Context context; @Inject MyComponent(Context context) { this.context = context; } } Provider Example public class MyComponent { Context context; @Inject MyComponent(Provider<Context> context) { this.context = context; } } ## Application Class: Application, RoboApplication, MyRoboApplication Provider: Instance binding Scope: Instance Injection Points: Constructors, Fields, Methods Strongly Typed Application Example public class MyRoboApplication extends RoboApplication { } public class MyActivity extends RoboActivity { @Inject MyComponent component; static class MyComponent { MyRoboApplication application; @Inject MyComponent(MyRoboApplication application) { this.application= application; } } Strongly Typed Provider Example public class MyRoboApplication extends RoboApplication { } public class MyComponent { MyRoboApplication application; @Inject MyComponent(Provider<MyRoboApplication> applicationProvider) { this.application= applicationProvider.get(); } } Note: The guice bindings are created in such a way that you can inject any type of application class in the full application hierarchy. So any where that MyRoboApplication is being injected you can also inject RoboApplication, or just the Android Application as well. ## Activity Class: Activity Provider: ActivityProvider Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public static class MyComponent { Activity activity; @Inject MyComponent(Activity activity) { this.activity = activity; } } Provider Example public class MyComponent { Provider<Activity> activityProvider; @Inject MyComponent(Provider<Activity> activity) { this.activityProvider = activity; } } Note: Currently RoboGuice cannot bind an applications individual activities. There is no way to inject MyActivity out of the box like RoboGuice can do with applications. In order to inject strongly typed activities you need to create your own binding in a custom application module and provide that module to guice when the application is configured. public class MyModule extends AbstractAndroidModule { @Override @SuppressWarnings("unchecked") protected void configure() { bind((Class)MyActivity.class) .to(ActivityProvider.class) .in(ContextScoped.class); } } Shared Preferences Class: SharedPreferences Provider: SharedPreferencesProvider Scope: Transient Injection Points: Constructors, Fields, Methods By default roboguice will retrieve an instance of Android SharedPreferences using the filename: "default". This is not the default file name android uses for your shared preferences. If you would like to override the file name then you can set up a binding when RoboGuice is initialized. Android Default Shared Preferences File Name Binding bindConstant() .annotatedWith(SharedPreferencesName.class) .to("com.mypackage.myapp_preferences"); Example public class MyActivity extends RoboActivity { @Inject SharedPreferences sharedPreferences; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<SharedPreferences> sharedPreferencesrProvider; } Content Resolver Class: ContentResolver Provider: ContentResolverProdivder Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject ContentResolver contentResolver; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<ContentResolver> contentResolverProvider; } Asset Manager Class: AssetManager Provider: AssetManagerProvider Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject AssetManager assetManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<AssetManager> assetManagerProvider; } Resources Class: Resources Provider: ResourcesProvider Scope: @Singleton Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject Resources resources; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<Resources> resources; } Location Manager Class: LocationManager Provider: SystemServiceProvider<LocationManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject LocationManager locationManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<LocationManager> locationManagerProvider; } Window Manager Class: WindowManager Provider: SystemServiceProvider<WindowManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject WindowManager windowManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<WindowManager> windowManager; } Layout Inflater Class: LayoutInflater Provider: SystemServiceProvider<LayoutInflater> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject LayoutInflater layoutInflater; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<LayoutInflater> layoutInflaterProvider; } Activity Manager Class: ActivityManager Provider: SystemServiceProvider<ActivityManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject ActivityManager activityManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<ActivityManager> activityManager; } Power Manager Class: PowerManager Provider: SystemServiceProvider<PowerManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject PowerManager powerManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<PowerManager> powerManagerProvider; } Alarm Manager Class: AlarmManager Provider: SystemServiceProvider<AlarmManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject AlarmManager alarmManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<AlarmManager> alarmManagerProvider; } Notification Manager Class: NotificationManager Provider: SystemServiceProvider<NotificationManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject NotificationManager notificationManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<NotificationManager> notificationManagerProvider; } Keyguard Manager Class: KeyguardManager Provider: SystemServiceProvider<KeyguardManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject KeyguardManager keyguardManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<KeyguardManager> keyguardManagerProvider; } Search Manager Class: SearchManager Provider: SystemServiceProvider<SearchManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject SearchManager searchManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<SearchManager> searchManagerProvider; } Vibrator Class: Vibrator Provider: SystemServiceProvider<Vibrator> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject Vibrator vibrator; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<Vibrator> vibrator; } Connectivity Manager Class: ConnectivityManager Provider: SystemServiceProvider<ConnectivityManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject ConnectivityManager connectivityManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<ConnectivityManager> connectivityManagerProvider; } Wifi Manager Class: WifiManager Provider: SystemServiceProvider<WifiManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject WifiManager wifiManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<WifiManager> wifiManagerProvider; } Input Method Manager Class: InputMethodManager Provider: SystemServiceProvider<InputMethodManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject InputMethodManager inputMethodManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<InputMethodManager> inputMethodManagerProvider; } Sensor Manager Class: SensorManager Provider: SystemServiceProvider<SensorManager> Scope: @ContextScoped Injection Points: Constructors, Fields, Methods Example public class MyActivity extends RoboActivity { @Inject SensorManager sensorManager; } Provider Example public class MyActivity extends RoboActivity { @Inject Provider<SensorManager> sensorManagerProvider; }
</details>
Dagger 框架
因为RoboGuide 是一种纯动态 依靠反射 的注入框架,所以性能方面肯定还是备受诟病的 所以Google 公司就深入研发了 Dagger 框架 对比与 RoboGuice 框架 ,Dagger 的 从纯动态 向 半静态半动态的方向进一步发展了。 即 对于 所有的注入 都是用 编译时 注入的方式 通过APT 进行一个代码生成。 但是对于 比较复杂的 循环依赖,依赖嵌套等等,还是使用反射机制进行一个验证。 优势: 1. 半静态半动态的方式 其实是对性能有了一个很大的提升 但是相对于 Dagger框架, Dagger2 对DAG 的验证判断 也变成了静态,即在编译的时候就生成了 一个完整的 DAG 依赖图 ,所以也导致了 Dagger 2 对于一些动态问题的难入手,动态修改 依赖关系。。。 Dagger 2 对于 所有 依赖变更都需要经过 重新编译。
Koin 框架
Kotlin 主推的 依赖注入框架 ,也是Dagger2 的升级版 一种轻量级的 无代理,无反射,无代码生成的 依赖注入框架。 这里使用到的是 Kotlin 语言特性的 拓展函数,即在注入的时候 ,在自身类中加入一个静态方法,对需要注入的对象进行一个依赖注入。
Kodein 框架
与Koin相似,它是一个小型轻量级的库,可以在运行时懒惰地解决依赖关系。它也不会生成任何代码,并在后台广泛使用Kotlin内联函数作为性能优化。
从技术上讲,Dagger使用了依赖注入(DI)模式。Koin和Kodein是服务定位符。在服务定位器模式中,有一个服务定位器类创建并存储依赖项,然后根据需要提供它们。
<img src="https://s1.ax1x.com/2020/06/03/td6V81.png" alt="td6V81.png" border="0" />
<img src="https://s1.ax1x.com/2020/06/03/td6nKK.png" alt="td6nKK.png" border="0" />
由于Dagger在编译时完成所有工作,因此它在所有设备上都具有最佳的运行时性能,但代价是编译时间更长
Koin比Dagger慢,但是在使用应用程序时,设置和注入时间的差异并不是很明显。
对比框架 优劣
- Dagger2
优势:
- 三种中最佳的运行时性能,编译时产生样本代码
- 很多错误在编译过程中就能捕获
- 遵循 规则
劣势:
- 产生大量样本代码
- 框架细节多,学习成本高
- 编译时间长
- Koin & Kodein
优势:
- 学习时间短,容易掌握
- 不增加编译时间,以及不生成代码
- 专门为Kotlin而建,可以使用Kotlin 的 新的语言特性
缺点:
- 在编译的时候不会抛错,所以只有等到程序崩溃之后,才能进行报错。
- 不遵循 标准