在许多程序设计语言(比如 Java、CI)里,依赖注入是一种较流行的设计模式。在Andhold开发中有很多实用的依赖注入框架,利用这些框架我们可以少写一些样板代码,以达到在各个类之间解属的目的。
注解:
在Java中注解其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,与普通的//或/**/注释不同的是:普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。
1.注解的分类:
通常来说注解分为以下三类:
- 标准注解 : Java提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告。
- 元注解 : java内置的注解,标明该注解的使用范围、生命周期等。
- 自定义注解 : 第三方定义的注解,含义和功能由第三方来定义和实现。
1.1:标准注解:
标准注解有以下4种:
- @Override:对覆盖超类中的方法进行标注,如果被标注的方法并没有实际覆盖超类中的方法,则编译器会发出错送警告。
- @Deprecated:对不数励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息。
- @SuppresWarnings:选择性地取消特定代码段中的警告。
- @SafeVarargs: JDK7新增的注解,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。
1.2:元注解:
除了标准注解,还有元注解,它用来注解其他注解 ,从而创建新的注解。元注解有以下几种:
- @Targe:注解所修饰的对象范围。
- @Inherited:表示注解可以被继承。
- @Documented:表示这个注解应该被JavaDoc工具记录。
- @Retention:用来声明注解的保留策略。
- @Repeatable: JDK 8 新增的注解,允许一个注解在同一声明类型(类、属性或方法)中多次使用。
其中,@Targe注解的取值是一个ElementType类型的数组。这里有以下几种取值,对应不同的对象范围:
- ElementType.TYPE:能修饰类、接口或枚举类型。
- ElementType.FIELD:能修饰成员变量。
- ElementType.METHOD:能修饰方法。
- ElementType.PARAMETER:能修饰参数。
- ElementType.CONSTRUCTOR:能修饰构造方法。
- ElementType.LOCAL_VARIABLE:能修饰局部变量。
- ElementType.ANNOTATION_TYPE:能修饰注解。
- ElementType.PACKAGE:能修饰包。
- ElementType.TYPE_PARAMETER:类型参数声明。
- ElementType.TYPE_USE:使用类型。
@Retention注解有3种类型分别表示不同级别的保留策略:
- RetentionPolicy_SOURCE:源码级注解。注解信息只会保留在.java源码中。源码在编译后,注解信息被丢弃,不会保留在class中。
- RetentionPolicy_CLASS:编译时注解,注解信息会保留在java源码以及class中,当运行Java程序时,JVM (Java Vitual Machine,Java虚拟机)会丢弃该注解信息,不会保留在JVM中。
- RetentionPolioy_RUNTIME:运行时注解。当运行Java程序时,JVM也会保留该注解信息,可以通过反射获取该注解信息。
2:定义注解:
2.1:基本定义:
基本的定义:定义新的注解类型使用关键字@interface,这一定义一个接口很相像。
public @interface Test{
//
}
定义完注解之后,就可以在程序中使用这个注解了:
@Test
public class Test1{
//
}
2.2:定义成员变量:
注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”的形式来进行声明。其“方法名”定义了该成员变量的名字,其返回值,定义了该成员变量的类型。
public @interface Test{
String name();
int age();
}
定义了成员变量之后,就应该在使用该注解时为该成员变量指定值。
@Test(name="Job",age=23)
public class Test1{
//
}
当然也可以在定义注解的成员变量的时候,使用default关键字为其指定默认值。
public @interface Test{
String name() default "Job";
int age() default 23 ;
}
因为在定义注解的时候,定义了默认值,所以在使用的时候可以不为这些成员变量指定值,而是直接使用默认值。
2.3:@Retention:
可以用@Retention 来设定注解的保留策略。前面所述的3个策略的生命周期长度为SOURCE<CLASS<RUNTIME。对于生命周期短的能起作用的地方,生命周期长的一定也能起作用:一般如果需要在运行时动态获取注解信息,那么只能用RetentionPolicy_RUNTIME,如是要在编译时进行一些预处理操作,比如生成一些辅助代码,就用RerentionPotiey_CLASS;如果只是做一些检查性的操作,比如@Override和@SuppressWarmings,则可选用RetertionPolicy_SOURCE。当设定为RetentionPolicy_RUNTIME时,这个注解就是运行时注解,如下所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface Test{
String name() default "Job";
int age() default 23 ;
}
同样的,如果想要将@Retention 的保留策略设定为RerentionPotiey_CLASS,那么这个注解就是编译时注解,如下所示:
@Retention(RetentionPolicy.CLASS)
public @interface Test{
String name() default "Job";
int age() default 23 ;
}
3.注解处理器:
如果没有处理注解的工具,那么注解就不会有什么的作用。对于不同的注解,有不同的注解处理器。虽然注解处理器的编写会千变万化,但是其也有处理标准。比如,针对运行时注解,会采用反射机制处理;针对编译时注解,会采用AbstractProcessor处理。
3.1:运行时注解处理器:
处理运行时注解需要用到反射机制。首先,我们需要定义运行时注解。
@Retention(RetentionPolicy.RUNTIME)//运行时注解
@Target(ElementType.METHOD)// 作用于方法上
@Documented
public @interface Get {
String value() default "";
}
接下来应用该注解。
public class AnnotationTest {
@Get(value = "http://ip.taobao.com")
public String getIpMsg() {
return "";
}
@Get(value = "192.168.9.23")
public String getIp() {
return "";
}
}
然后编写一个简单的注解处理器。
public class AnnotationProcessor {
public static void main(String[] args) {
Method[] methods = AnnotationTest.class.getDeclaredMethods();
for (Method m : methods) {
Get get = m.getAnnotation(Get.class);
System.out.println(get.value());
}
}
}
这里应用了两个反射方法,分别是getDeclaredMethods()方法和getAnnotation()方法。
- getDeclaredMethods():方法返回一个Method对象,它反映此Class对象所表示的类或接口的指定已声明方法。
- getAnnotation():返回该元素的指定类型的注释,如果是这样的注释,否则返回null。
3.2:编译时注解处理器:
首先在工程中新建一个 Java Library用于专门存放注解,Library名称定义为 Annotation。再定义一个自定义的注解类:
@Retention(CLASS)
@Target(FIFLD)
public @interface BindView{
int value() default 1;
}
在工程中再创建一个 Java Library,名称定义为 AnnotationProcessor,并在 build.gradle 中加入如下依赖:
import org.gradle.internal.jvm.Jvm
apply plugin: 'java-library'//插件名称
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 刚才定义的 Annotation 模块
implementation project(":Annotation")
// 谷歌的 AutoService 可以让我们的注解处理器自动注册上
implementation 'com.google.auto.service:auto-service:1.0-rc4'
// 用于生成新的类、函数
implementation "com.squareup:javapoet:1.9.0"
// 谷歌的一个工具类库
implementation "com.google.guava:guava:24.1-jre"
implementation files(Jvm.current().toolsJar)
}
sourceCompatibility = "1.8"
targetCompatibility = "1.8"
再在项目级的 build.gradle 中增加 android-apt 的依赖:
classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
接着编写自定义注解处理器,它继承于AbstractProcessor,如下所示:
@AutoService(Processor.class)
public class DemoProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment environment) {
super.init(environment);
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Messager messager =processingEnv.getMessager();
for (Element element: roundEnv.getElementsAnnotatedWith(BindView.class)){
if (element.getKind() == ElementKind.FIELD)(
messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:"
+ element.toString());
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotataions = new LinkedHashSet<String>();
annotations.add(BindView.class.getCanonicalName();
return annotataions;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
上面有四个继承的方法,这里先写写4个方法的作用:
- init():被注解处理工具调用,并且输入ProcessingEnvironment 参数。其中ProcessingEnvironment 提供了很多有用的工具类,比如Elements,Types,Filer和Messager等等。
- process():相当于每个处理器的主函数main(),在这里编写扫描、评估和处理注解的代码以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。
- getSupportedAnnotationTypes():这是必须指定的方法,指定这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含该处理器想要处理的注解类型的合法全称。
- getSupportedSourceVersion():用来指定你使用的Java版本,通常这里返回SourceVersion.latestSupported()。
在Java7以后,也可以使用注解来代替getSupportedAnnotationTypes方法和getSupportedSourceVersion方法。但是考虑到兼容性的问题,这里不建议采用这种注解的方式。
接下来就可以在主工程项目中引用自定义的注解。首先要在主工程项目的build.gradle中引用。
implementation project(path: ':Annotation')
implementation project(path: ':AnnotationProcessor')
接下来就可以引用注解了。
依赖注入的原理:
1.控制反转和依赖注入:
IoC—Inversion of Control,即“控制反转”,它是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
- 传统程序设计中,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建。
- 因为传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象,对象只是被动的接受依赖对象,所以是反转。
- IoC能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
- 2004年,Matin Fowler给控制反转取了一个更合适的名字,叫作依赖注入(Dependeney Injection),简称DI。所谓依赖注入,指的是由IoC容器在运行期间,动态地将某种依赖关系注入到对象中。
2.依赖注入的实现方式:
这里举一个汽车的例子。汽车类Car包含了引擎Engine等组件,如下所示:
public class Car{
private Engine mEngine;
public Car(){
mEngine=new PetrolEngine();
}
}
这里的代码本身没有错,但是Car和Engine高度耦合,在Car中需要自己创建Engine,并县Car还需要知道Engine的实现方法,即Engine的实现类PetrolEngine的存在。另外,一旦Engine 的类型变为其他的实现,如DieselEngine,则需要修改Car的构造方法。以上问题需要用依赖注入来解决。
2.1:构造方法注入:
public class Car{
private Engine mEngine;
public Car(Engine mEngine){
this.mEngine=Engine;
}
}
通过Car的构造方法传入想要的Engine,且两者解耦。
2.2:setter方法注入:
public class Car{
private Engine mEngine;
public Car(){
}
public void set(Engine mEngine){
this.mEngine=Engine;
}
}
通过Car的set方法向Car传递一个Engine对象。
2.3:接口注入:
//接口
public interface ICar{
public void setEngine(Engine mEngine);
}
//Car类
public class Car implements ICar{
private Engine mEngine;
@Override
public void setEngine(Engine mEngine){
this.mEngine=mEngine;
}
}
在接口中定义需要注入的信息,并且通过接口完成注入。
依赖注入框架:
Android目前主流的依赖注入框架有ButterKnife和Dagger2。
1.为何使用依赖注入框架:
从依赖注入的3种常用实现方式可以看出,依赖注入似乎很简单,那么为何还有用依赖注 入的框架呢?我们可以简单地通过一个构造方法或使用setter方法传递需要的依赖。这种做法对于简单的依赖来说是可行的,但对于复杂的依赖就未必可行了。回到上面Car的例子,汽车的引擎由曲柄连杆机构和配气机构两大机构,以及冷却、润滑、点火、燃料供给、启动系统五大系统组成,而曲柄连杆机构由机体组、活塞连杆组、曲轴飞轮组三部分组成。同样地,其他机构和系统下也由很多部分组成。另外,汽车不只有引擎,其还有底盘、车身、电气设备等部件,每个部件又由很多部分组成。如果用依赖注入,那么就需要为汽车的每个部分都创建类,我们可以预料到最终将会得到许多类,并且有着复杂的树状或图状结构的依赖。为此,我们必须按照正确的顺序创建对象才能创建好依赖,从叶子节点依赖开始,依次传递到每个父节点依赖,依此类推,直到传递到最高点或根节点依赖。如果我们还使用构造方法注入或setter方法注入,为了传递依赖就要编写相当多的复杂代码,这些代码也是我们时常要避免编写的样板代码。为了避免此问题的产生,诞生了依赖注入框架。
2.解析ButterKnife:
从严格意义上来讲,ButterKnife不算是依赖注入框架,它只是专注于Android系统的View注入框架,并不支持其他方面的注入。它可以减少大量的findViewByld及 setOnClickListener代码,从而简化代码并提升开发效率。
2.1:添加依赖:
使用ButterKnife之前先在项目的build.gradle中添加依赖:
classpath "com.neenbedankt.gradle.plugins:android-apt:1.8"
这里引用了apt插件,然后在模块的build.gradle中添加依赖:
compile 'com.jakewharton:butterknife:8.4.0'
apt 'com.jakewharton:butterknife-compiler:8.4.0'
2.2:基本使用:
在Activity中绑定ButterKnife,调用的是ButterKnife.bind(this),而ButterKnife.bind(this)必须在初始化绑定布局文件之后调用,否则会报错。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}
在Fragment中绑定ButterKnife,在Fragment中需要在视图销毁时解绑Butterknife,否则会造成内存泄漏。
public class ExampleFragment extends Fragment {
private Unbinder mUnbinder;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = View.inflate(getContext(),R.layout.fragment_example,null);
mUnbinder = ButterKnife.bind(this,view);
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
mUnbinder.unbind();//视图销毁时必须解绑
}
}
同时还可以在Adapter中需要引用资源的时候,也可以使用ButterKnife。过程为:在ViewHolder类中调用ButterKnife.bind(this)完成绑定。
注意:使用ButterKnife修饰的方法和控件,不能用private or static 修饰,否则会报错。
绑定单一的控件:
//绑定
@BindView(R.id.text)
TextView t;
//调用
t.setText("BindView");
绑定多个控件:
//绑定
@BindViews(R.id.text1,R.id.text2,R.id.text3)
List<TextView> t;
//调用
t.get(0).setText("BindView");
绑定资源文件:
//绑定资源文件中string字符串
@BindString(R.string.app_name)
String name;
//绑定string资源里面array数组
@BindArray(R.array.swordsman)
String [] swordsman ;
//绑定color文件中颜色值
@BindColor( R.color.colorPrimary)
int colorPrimary;
//绑定真假boolean
@BindBool(R.bool.is_tablet)
boolean isTablet;
//绑定字体文字
@BindFont(R.font.comic_sans)
Typeface comicSans;
//绑定尺寸
@BindDimen(R.dimen.horizontal_gap)
int gapPx
//绑定尺寸
@BindDimen(R.dimen.horizontal_gap)
float gap;
//绑定动画
@BindAnim(R.anim.fade_in)
Animation fadeIn;
//绑定Drawable
@BindDrawable(R.drawable.placeholder)
Drawable placeholder;
绑定监听事件:
单个控件的点击事件:
@OnClick(R.id.button)
public void onClick(){
Toast.makeText(this, "onClick", Toast.LENGTH_SHORT).show();
}
多个控件触发同一个点击事件:
@OnClick({R.id.botton1,R.id.botton2})
public void onClick(){
Toast.makeText(this, "ww", Toast.LENGTH_SHORT).show();
}
多个控件的点击事件:
@OnClick({R.id.botton1,R.id.botton2,R.id.botton3})
public void onClick(View v) {
switch (v.getId()){
case R.id.botton1:
break;
case R.id.botton2:
break;
case R.id.botton3:
break;
}
}
监听EidtText的文本变化事件:
@OnTextChanged(R.id.example)
public void onTextChanged(CharSequence text) {
Toast.makeText(this, "Text changed: " + text, Toast.LENGTH_SHORT).show();
}
监听触摸事件:
@OnTouch(R.id.example)
public boolean onTouch() {
Toast.makeText(this, "Touched!", Toast.LENGTH_SHORT).show();
return false;
}
注意:在使用@BindView或者其他的注解操作符时,如果找不到对应的目标资源,则会引发异常,为了防止异常,可以添加@Nullable注解。
//绑定
@Nullable
@BindView(R.id.text)
TextView t;
2.3:原理解析:
ButterKnife采用的是编译时注解,,它的注解处理器是ButterKnifeProcessor,继承于AbstractProcessor,主要的处理逻辑在process方法中。process方法中的findAndParseTargets方法会查找所有ButterKnife注解来进行解析,例如查找的是@BindView注解,那么调用的是parseBindView方法。
parseBindView方法中的isInaccessibleViaGeneratedCode方法里面检查了3个点,分别是:方法修饰符不能为private 和static;包含的类型不能为非Class;包含的类的修饰符不能是private。接着isBindingInWrongPackage方法判断了这个类的包名不能以android.和java.开头。然后判断是否存在BindingSet.Builder的值,若没有创建,若有则复用。接着将注解所修饰的类型的信息存储在FieldViewBinding中,并将 FieldViewBinding传入 BindingSet.Builder的addField方法中。这样,注解所修饰的类型的信息及注解的成员变量的值都存储在BindingSet中。
然后继续查看process方法,在 findAndParseTargets 方法查找和解析注解之后。遍历findAndParseTargets方法返回的Map集合,然后得到BindingSet的值,并调用了它的brewJava方法。
brewlava方法将使用注解的类生成一个JavaFile,在process方法的javaFile.writeTo方法中将JavaFile输出成Java文件。在build-generated-source-apt目录下可以找到生成的Java文件。
2.4:ButterKnife的bind方法:
为了使用ButterKnife,我们需要使用ButterKnife.bind方法来绑定上下文。以传入activity为例子,查看工作流程(因为bind方法有很多重载方法):得到DecorView,,并且将DecorView和Activity传入createBinding方法中,该方法主要调用了findBindingConstructorForClass方法和返回constructor的newInstance方法。
findBindingConstructorForClass方法会从BINDINGS中获取对应的class的Constructor实例,如果没有获取到,则通过反射来生成class类,这个class类就是上面生成的Java文件,然后将获取到的class通过getConstructor方法转换为Constructor,然后将constructor存储在BINDINGS中,最后返回constructor的实例。其中BINDINGS是一个以class为key,constructor为value的Map。
3.解析Dagger2:
Dagger2是一个基于JSR-330 (Java依赖注入)标准的依赖注入框架,在编译期间自动生成 Pub]代码,负责依赖对象的创建。Dagger2是Dagger1(Dagger1是由Square公司受到Guice启发而 p创建的)的分支,由谷歌公司接手开发。Dagger2受到了AutoValue项目的启发。
3.1:添加依赖:
首先在项目的build.gradle文件中引入android-apt插件。
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
接下来在模块的build.gradle文件中引入如下。
apt 'com.google.dagger:dagger-compiler:2.7'
compile 'com.google.dagger:dagger:2.7'
3.2:@Inject和@Component:
public class Watch{
@Inject
public Watch(){
}
public void work(){
Log.d("Dagger2","2");
}
}
@Inject 注解是JSR-330标准中的一部分,用于标注需要注入的依赖。在这里标注Watch构造方法,则表明Dagger2可以使用Watch 构造方法构建对象。接下来用@Component 注解来完成依赖注入。我们需要定义一个接口,接口命名建议为“目标类名+Component”,在编译后Dagesr2就会为我们生成名为“Dagger+目标类名+Component”的辅助类。
@Componet
public interface MainActivityComponent{
void inject(MainActivity activity);
}
Component 则可以被理解为注入器,它会把目标类依赖的实例注入目标类中。在这里需要定义injest方法,传入需要注入依赖的目标类。
public class MainActivity extends AppCompatActivity {
@Inject
Watch watch;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注入依赖的目标类
DaggerMainActivityComponent.create().inject(this);
//这样就可以调用Watch的work方法
watch.work();
}
}
@Inject标注表示需要注入的属性,而编译生成的DaggerMainActivityComponent的inject方法来完成注入,注入的目标为MainActivity,也就是需要注入依赖的目标类名称。在这里我们提到@Inject有两种注入方式,分别是成员变量注入和构造方法注入。此外,还有一种注入方式——方法注入。而@Component还可以使用dependencies来依赖其他的Component。
3.3:@Module和@Provides:
如果项目中使用了第三方的类库,比如Gson,若我们像如下代码这样写,则会报错,因为我们不能将@Iniect应用到Gson的构造方法中,这个时候可以采用@Module和@Provides来处理。
@Module
public class GsonModule{
@Provides
public Gson provideGson(){
return new Gson();
}
}
将@Module标注在类上,用来告诉Component,可以从这个类中获取依赖对象,也就是Gson类;将@Provides 标注在方法上,表示可以通过这个方法来获取依赖对象的实例。通俗地讲,@Module标注的类其实就是一个工厂,用来生成各种类;@Provides标注的方法,就是用来生成这些类的实例的。然后就是Component类:
@Componet(modules = GsonModule.class)
public interface MainActivityComponent{
void inject(MainActivity activity);
}
这和此前的区别就是加上了modules=GsonModule.class,用来指定Module.需要注意的是,Component中可以指定多个Module。然后就可以在MainActivity中使用Gson了,这和上面无异。
注意:如果需要注入的对象是抽象类时,也可以使用@Module和@Provides,而@Inject无法使用。
3.4:@Named和@Qualifier:
@Qualifier是限定符,@Named则是@Qualiffer的一种实现。如果有两个相同的依赖,这两个依赖都继承自同一个父类或均实现同一个接口,当这两个依赖被提供给高层时,Component就不知道我们到底要提供哪一个依赖对象了,因为它找到了两个依赖。
3.5:@Singleton和@Scope:
@Scope是用于自定义注解的,而@Singleton是用于配合实现局部单例和全局单例的,但是它本身却没有创建单例的能力。
3.6:原理解析:
我们从注入的入口DaggerMainActivityComponent.create().inject(this)开始。首先是DaggerMainActivityComponent的create方法调用了builder().build(),调用builder的build方法会在后续新建WatchModule。而initialize 方法是用于初始化的,分别初始化了provide WatchProvider和 mainActivityMemberslnjector这两个成员变量。接着会将provideWatchProvider作为参数传入MainActivity_MembersInjector的create方法中,这个方法会返一个MainActivity_Memberslnjector类。当我们调用inject方法时,其实就是调用MainActivity_MembersInjector的injectMembers方法。
MainActivity_MembersInjector的injectMembers方法会调用watchProvider的 get方法并赋值给MainActivity的watch。这里watchProvider是通过调用MinActivity _Membersinjector 的 create 方法传入的,也就是上文提到的provideWatchErovider,而它是DaggerMainActivityComponent类调用WatchModule_ProvideWatchFactory的create方法生成的。
总而言之:其实辅助类的作用就是在我们调用inject 方法时,将新创建的Watch 类赋值给MainActiviy的成员变量 Watch。其中,WatchModule_ ProvideWatchFactory用来生成Watch实例;MainActivity_MembersInjector 将 Watch实例赋值给 MainActivity 的成员变量 Watch; DaggerActivity_Component则作为程序入口和桥梁,负责初始化WatchModule_ ProvideWatchFactory和MainActivity_MembersInjector,并将它们串联起来。