注解和依赖注入框架

依赖注入是一种比较流行的设计模式,在 Android 开发中有很多实用的依赖注入框架,可以帮助开发人员少些样板代码,达到各个类之间解耦的目的。

1 注解

从 JDK 5 开始,Java 增加了注解(Annotation),注解只是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。通过使用注解,开发人员可以在不改变原用逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证、处理或者进行部署。

从某些程度上来讲,注解就像修饰符一样被,可以用于修饰包、类、构造器、成员变量、成员方法等。Annotation 是一个接口,使用 @interface 标志,其定义如下:

@元注解1(...)
@元注解2(...)
...
public @interface 注解名称 {
  数据类型 变量名() default 默认值; // 元数据或成员变量
  ...
}
1.1 注解分类

注解分为标准注解、元注解和自定义注解。其中,标准注解和元注解由 JDK 实现,而自定义注解是开发人员根据项目需求自己定义的注解,需要用到元注解。

1.1.1 标准注解

标准注解有以下 4 种:

  • @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告;
@Target(ElementType.METHOD) // 元注解,表示当前的注解只能修饰方法
@Retention(RetentionPolicy.SOURCE) // 元注解,表示当前注解在源码中有效
public @interface Override {
}
  • @Deprecated:对不鼓励使用或者已经过时的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
    /**
     * Returns the version in which the annotated element became deprecated.
     * The version string is in the same format and namespace as the value of
     * the {@code @since} javadoc tag. The default value is the empty
     * string.
     *
     * @return the version string
     * @since 9
     */
    String since() default "";

    /**
     * Indicates whether the annotated element is subject to removal in a
     * future version. The default value is {@code false}.
     *
     * @return whether the element is subject to removal
     * @since 9
     */
    boolean forRemoval() default false;
}
  • @SuppressWarnings:选择性地取消特定代码段中的警告;
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}
  • @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {}
1.1.2 元注解

除了标准注解,还有元注解,它用来注解其他注解,从而创建新的注解。 元注解有以下几种:

  • @Target:注解所修饰的对象范围;
  • @Inherited:表示注解可以被继承;
  • @Documented:表示这个注解应该被 JavaDoc 工具记录;
  • @Retention:用来声明注解的保留策略;
  • @Repeatable:JDK 8 新增,允许一个注解在统一声明类型(类、属性或方法)上多次使用;

其中 @Target 注解取值是一个 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:使用类型;
public enum ElementType {
    // Class, interface (including annotation type), or enum declaration 修饰类、接口或者枚举类型 
    TYPE,
    // Field declaration (includes enum constants) 修饰成员变量
    FIELD,
    // Method declaration 修饰成员方法
    METHOD,
    // Formal parameter declaration 修饰参数
    PARAMETER,
    // Constructor declaration 修饰构造方法
    CONSTRUCTOR
    // Local variable declaration 修饰局部变量
    LOCAL_VARIABLE,
    // Annotation type declaration 修饰注释
    ANNOTATION_TYPE,
    // Package declaration 修饰包
    PACKAGE,
    // Type parameter declaration @since 1.8 修饰参数声明
    TYPE_PARAMETER,
    // Use of a type @since 1.8 使用类型
    TYPE_USE,
    // Module declaration. @since 9 模块声明
    MODULE
}

其中 @Retention 注解有 3 种类型,分别表示不同级别的保留策略:

  • RetentionPolicy.SOUCE:源码级别注解。注解信息只会保留在 .java 源码中,源码在编译后,注解信息被丢弃,不会保留在 .class 中;
  • RetentionPolicy.CLASS:编译时注解。注解信息会保留在 .java 源码以及 .class 中。当运行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中;
  • RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 会保留该注解信息,可以通过反射获取该注解信息;
public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
1.2 定义注解
第一步,基本定义

**定义新的注解类型使用 @interface 关键字,这与定义一个接口很像, ** 如下所示:

public @interface Hero {
  	
}

定义完注解后,就可以在程序中使用该注解:

@Hero
public class AnnotationTest {
    
}
第二步,定义成员变量

注解只有成员变量,没有方法。注解的成员变量在注解定义中以“无形参的方法”形式来声明,其“方法名”定义了该成员变量的名字,其返回值定义了该成员变量的类型:

public @interface Hero {
    String name();
    int age();
}

上面的代码定义了两个成员变量,这两个成员变量以方法的形式来定义。定义了成员变量后,使用该注解时就应该为该注解的成员变量指定值:

public class AnnotationTest {
    @Hero(name = "萧峰", age = 31)
    public void fighting() {

    }
}

也可以在定义注解的成员变量时,使用 default 关键字为其指定默认值, 如下所示:

public @interface Hero {
    String name() default "段誉";
    int age() default 25;
}

因为注解定义了默认值,所以使用时可以不为这些成员变量指定值,而是直接使用默认值:

public class AnnotationTest {
    @Hero
    public void fighting() {

    }
}
第三步,定义运行时注解

可以用 @Retention 来设定注解的保留策略,这 3 个策略的生命周期长度为 SOURCE < CLASS < RUNTIME。 生命周期短的能起作用的地方,生命周期长的一定也能起作用:

  • 一般如果需要在运行时去动态获取注解信息,那只能用 RetentionPolicy.RUNTIME;
  • 如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;
  • 如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,则可选用 RetentionPolicy.SOURCEE;

当设定为 RetentionPolicy.RUNTIME 时,这个注解就是运行时注解,如下所示:

@Retention(RetentionPolicy.RUNTIME)
public @interface Hero {
    String name() default "段誉";
    int age() default 25;
}
第四步,定义编译时注解

同样的,如果将 @Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解,如下所示:

@Retention(RetentionPolicy.CLASS)
public @interface Hero {
    String name() default "段誉";
    int age() default 25;
}
1.3 注解处理器

如果没有处理注解的工具,那么注解也不会有什么作用。对于不同的注解有不同的注解处理器。虽然注解处理器的编写会千变万化,但是其也有处理标准,比如:针对运行时注解会采用反射机制处理,针对编译时注解会采用 AbstractProcessor 来处理。

1.3.1 运行时注解处理器

处理运行时注解需要用到反射机制。 首先要定义运行时注解,如下所示:

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GET {
    String value() default "";
}

上面的代码是 Retrofit 中定义的 @GET 注解。其定义了 @Target(ElementType.METHOD),意味着 GET 注解应用于方法。接下来应用该注解,如下所示:

public class AnnotationTest {

    @GET(value = "https://haokan.baidu.com/haokan/wiseauthor?app_id=1541522111670746")
    public String getIPMessage() {
        return "";
    }

    @GET(value = "http://baidu.com")
    public String getIP() {
        return "";
    }
}

上面的代码为 @GET 的成员变量赋值。接下来洗衣歌简单的注解处理器,如下所示:

public class AnnotationProcessor {

    public static void main(String[] args) {
        Method[] methods = AnnotationTest.class.getDeclaredMethods();
        for (Method method : methods) {
            GET get = method.getAnnotation(GET.class);
            System.out.println(get.value());
        }
    }
    
}

上面的代码用到了两个反射方法:getDeclaredMethods 和 getAnnotation,它们都属于 AnnotateElement 接口,Class、Method 和 Field 等类都实现了该接口。调用 getAnnotation 方法返回指定类型的注解对象,也就是 GET。最后调用 GET 的 value 方法返回从 GET 对象中提取元素的值。从输出结果为:

// https://haokan.baidu.com/haokan/wiseauthor?app_id=1541522111670746
// http://baidu.com

2. 依赖注入的原理

2.1 控制反转与依赖注入
2.1.1 控制反转

在讲到依赖注入前,这里举一个比较形象的例子——机械手表,通过机械表的背透或者打开后盖,会发现里面有很多齿轮。这些齿轮相互啮合在一起,协同工作,组成一个齿轮组去完成某一项任务。如果这些齿轮中有一个出现问题,可能就会影响整个齿轮组的正常运作。如下图所示:

对象的耦合

齿轮组中齿轮之间的啮合关系与软件系统中对象之间的耦合关系非常相似。对象之间的耦合关系是无法避免的,而且随着工业级应用的规模越来越大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖关系。

为了解决对象之间耦合度过高的问题,软件专家提出了 IOC 理论,用来实现对象之间的解耦。 IOC 是 Inversion Of Control 的缩写,即控制反转。IOC 理论提出的观点大致是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦。这个“第三方”又被称为 IOC 容器。 如下所示:

IOC解耦

引入 IOC 容器后,使得 A、B、C、D 这四个对象之间已经没有了耦合关系,彼此之间毫无关联。

软件系统在没有引入 IOC 容器之前,对象 A 依赖对象 B ,在对象 A 初始化或者运行到某一点的时候,需要主动创建对象 B 或者使用已经创建的对象 B。引入 IOC 之后,对象 A 和对象 B 之间失去了直接联系,所以,对象 A 在运行到需要对象 B 的时候,IOC 容器会主动创建一个对象注入到对象 A 需要的地方。 通过引入 IOC 容器的前后对比可知,对象 A 获取依赖对象 B 的过程,由主动行为变为被动行为,控制权掉到过来了,这就是控制反转这个名词的由来。

2.1.2 依赖注入

获取依赖对象的过程被反转了。控制反转之后,获取依赖对象的过程由自身管理变为 IOC 容器主动注入。因此,控制反转也叫依赖注入(Dependency Injection),简称 DI。所谓依赖注入,是指由 IOC 容器在运行期间,动态地将某种以来关系注入到对象中。

2.2 依赖注入的实现方式

这里举例说明,汽车类 Car 包含了引擎 Engine 等组件,类 Car 需要类 Engine 的引用或对象,如下所示:

public class Car {
    private Engine engine;

    public Car() {
        engine = new PetrolEngine();
    }
}

代码本身是没有错的,但是 Car 和 Engine 高度耦合,在 Car 中需要自己创建 Engine,并且 Car 还需要知道 Engine 的实现方法,也就是 Engine 的实现类 PetrolEngine 的存在。另外,一旦 Engine 的类型变为其他的实现,比如 DieselEngine,则需要修改 Car 的构造方法。以上问题需要用依赖注入来解决。接下来,就用依赖注入的 3 种方法来改造上面的代码。

2.2.1 构造方法注入

通过 Car 的构造方法,向 Car 传递了 Engine 对象,如下所示:

public class Car {
    private Engine engine;

    public Car(Engine engine) {
        this.engine = engine;
    }
}
2.2.2 Setter 方法注入

通过 Car 的 set 方法向 Car 传递 Engine 对象,如下所示:

public class Car {
    private Engine engine;

    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}
2.2.3 接口注入

在接口中定义需要注入的信息,并通过接口完成注入。接口代码如下所示:

public interface ICar {
    public void setEngine(Engine engine);
}

接着 Car 类实现接口 ICar:

public class Car implements ICar {
    private Engine engine;
    
    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }
}

通过以上 3 种注入方法,明显将 Car 和 Engine 解耦合。Car 不关心 Engine 的实现,即使 Engine 的类型变换了,Car 无需做任何的修改。

3 依赖注入框架

Android 目前主流的以来注入框架有 ButterKnife 和 Dagger 2。

3.1 为什么使用依赖注入框架

从依赖注入的 3 种常用方式可以看出依赖注入似乎很简单,那么为什么还要用依赖注入框架呢?我们可以简单地通过一个构造方法或是使用 Setter 方法传递需要的依赖。这种做法对于简单的依赖来说是可行的,但是对于复杂的依赖就未必可行了。

回到 Car 的例子,汽车的引擎由曲柄连杆机构和配气机构两大机构,以及冷却、润滑、点火、燃料供给、启动系统等五大系统组成。而曲柄连杆机构由机体组、活塞连杆组、曲轴飞轮组三部分组成。同样地,其他机构和系统下也由很多部分组成。另外,汽车不只有引擎,其还有底盘、车身、电气设备等部件,每个部件又由很多部分组成。如果用依赖注入,那么就需要为汽车的每个部分都创建类,最终会有很多类,并且有复杂的树状或图状结构的依赖。因此,我们必须按照正确的顺序穿件对象才能创建好依赖,从叶子节点依赖开始,依次传递到每个父节点依赖,以此类推,知道传递到最高点或者根节点依赖。如果还是使用构造方法或者 Setter 方法注入,为了传递依赖就要编写相当多的代码,这些代码也是我们要避免编写的样板代码。为了避免此问题的产生,就诞生了依赖注入框架。

3.2 ButterKnife

ButterKnife 从严格意义上来讲不算是依赖注入框架,它只是专注于 Android 系统的 View 注入框架,并不支持其它方面的注入。它可以减少大量的 findViewById 以及 setOnClickListener 代码,简化代码并提升开发效率。

3.2.1 ButterKnife 的注解使用方法
第一步,添加依赖库

首先 Project 在 build.gradle 文件中添加如下代码:

buildscript {
  repositories {
    mavenCentral()
    google()
  }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3'
  }
}

接下来在 Module:app 的 build.gradle 文件中添加以下代码:

plugins {
    ...
    id 'com.jakewharton.butterknife'
}
android {
  ...
  // Butterknife requires Java 8.
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
}

dependencies {
  implementation 'com.jakewharton:butterknife:10.2.3'
  annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'
}
第二步,绑定控件

用注解 @BindView 绑定控件 id,代码如下所示:

public class MainActivity extends AppCompatActivity {

    @BindView(value = R.id.text_view)
    TextView textView; // 1

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
        textView.setText("Hello World!");
    }
}

需要注意的是注释 1 处的 TextView 的类修饰符不能是 private 或者 static,否则会报错。另外,用注解 @BindViews 绑定多个控件 id,代码如下所示:

public class MainActivity extends AppCompatActivity {

    @BindViews({R.id.button1, R.id.button2, R.id.button3})
    List<Button> buttons;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);

        textView.setText("Hello World!");
        buttons.get(0).setText("Button1");
        buttons.get(1).setText("Button2");
        buttons.get(2).setText("Button3");
    }
}
第三步,绑定资源

可以用注解 @BindString、@BindArray、@BindBool、@BindColor、@BindDimen、@BindDrawable 和 @BindBitmap。

@BindString(R.string.hello_main_activity)
String title;
@BindArray(R.array.fruits)
String[] fruits;
@BindDimen(R.dimen.fab_margin)
float margin;
第四步,绑定监听

用 @OnClick 来对“点击事件”监听,同理也可以用 @OnLongClick 对长按点击事件进行监听,如下所示:

@OnClick(R.id.button1)
public void showToast() {
    Toast.makeText(this, "Button 1 clicked", Toast.LENGTH_SHORT).show();
}

@OnLongClick(R.id.button2)
public boolean setText(Button button) {
    button.setText("长按点击事件");
    return true;
}

用 @OnTextChanged 来监听 EditText,如下所示:

@OnTextChanged(value = R.id.edit_text, callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
void beforeTextChanged(CharSequence s, int start, int count, int after) {
    System.out.println("before text changed");
}

@OnTextChanged(value = R.id.edit_text, callback = OnTextChanged.Callback.TEXT_CHANGED)
void onTextChanged(CharSequence s, int start, int before, int count) {
    System.out.println("text changed");
}

@OnTextChanged(value = R.id.edit_text, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
void afterTextChanged(Editable s) {
    System.out.println("after text changed");
}

使用 @OnTouch 处理触摸事件,如下所示:

@OnTouch(R.id.button3)
public boolean onTouch(View view, MotionEvent event) {
    System.out.println("onTouch");
    return true;
}

使用 @OnItemClick 对列表 item 的点击事件进行监听,如下所示:

@OnItemClick(R.id.list)
public void onItemCLick(int position) {
  	System.out.println("onItemClick " + position);
}
第五步,可选绑定

@ BindView 或者其他的注解操作符,如果不能找到目标资源,则会引发异常。为了防止一场,可以添加 @Nullable 注解:

@Nullable
@BindView(R.id.text_view)
TextView textView;
3.2.2 在 Fragment 和 Adapter 中使用 ButterKnife

除了前面在 Activity 中使用 ButterKnife 之外,还可以在 Fragment 中使用,如下所示:

public class MainFragment extends Fragment {

    @BindView(R.id.fragment_text)
    TextView textView;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_main, container, false);
        ButterKnife.bind(this, view);

        return view;
    }
}

同样,在 Adapter 中也需要引用控件资源,也可以使用 ButterKnife,如下所示:

public class MainAdapter extends ArrayAdapter<String> {

    public MainAdapter(@NonNull Context context, int resource) {
        super(context, resource);
    }

    @NonNull
    @Override
    public View getView(int position, @Nullable View convertView, 
                        @NonNull ViewGroup parent) {
        String name = (String) getItem(position);
        View view;
        ViewHolder viewHolder;
        if (convertView == null) {
            view = LayoutInflater.from(getContext()).
              inflate(R.layout.item_main_adapter, parent, false);
            viewHolder = new ViewHolder(view);
            view.setTag(viewHolder);
        } else {
            view = convertView;
            viewHolder = (ViewHolder) view.getTag();
        }
        viewHolder.viewHolderText.setText(name);
        return view;
    }

    class ViewHolder {

        @BindView(R.id.view_holder_text)
        TextView viewHolderText;

        public ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }

    }
}

ButterKnife 在 ViewHolder 类中完成绑定操作,剩余的代码像平常一样调用即可。

3.2.3 ButterKnife 原理解析

ButterKnife 采用的是运行时注解,ButterKnife 自定义了很多我们常用的注解,比如 @BindView 和 @OnClick。首先来看 @BindView 的源码,如下所示:

@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

@Retention(RUNTIME) 表明 @BindView 注解是运行时注解,@Target(FIELD) 则表明 @BindView 注解用于修饰成员变量。接下来使用 @BindView 注解来绑定 TextView 控件,如下所示:

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.text_view)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ButterKnife.bind(this);
    }
}
3.2.3.1 ButterKnifeProcessor 源码分析

要处理注解需要注解处理器,ButterKnife 的注解处理器是 ButterKnifeProcessor,它的源码在 butterknife-compiler 中。ButterKnifeProcessor 继承自 AbstractProcessor,它的主要处理逻辑都在 process 方法中,如下所示:

@AutoService(Processor.class)
@IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC)
@SuppressWarnings("NullAway") // TODO fix all these...
public final class ButterKnifeProcessor extends AbstractProcessor {
  	...
  
    @Override public boolean process(Set<? extends TypeElement> elements,
                                     RoundEnvironment env) {
        Map<TypeElement, BindingSet> bindingMap = findAndParseTargets(env); // 1

        for (Map.Entry<TypeElement, BindingSet> entry : bindingMap.entrySet()) { // 2
          TypeElement typeElement = entry.getKey();
          BindingSet binding = entry.getValue(); // 3

          JavaFile javaFile = binding.brewJava(sdk, debuggable);
          try {
            javaFile.writeTo(filer); // 4
          } catch (IOException e) {
            error(typeElement, "Unable to write binding for type %s: %s", typeElement, 
                  e.getMessage());
          }
        }

        return false;
    }

  	...
}

注释 1 处调用的 findAndParseTargets 方法:

private Map<TypeElement, BindingSet> findAndParseTargets(RoundEnvironment env) {
    Map<TypeElement, BindingSet.Builder> builderMap = new LinkedHashMap<>();
    Set<TypeElement> erasedTargetNames = new LinkedHashSet<>();
  
  	...
  	for (Element element : env.getElementsAnnotatedWith(BindView.class)) {
      // we don't SuperficialValidation.validateElement(element)
      // so that an unresolved View type can be generated by later processing rounds
      try {
        parseBindView(element, builderMap, erasedTargetNames); // 1
      } catch (Exception e) {
        logParsingError(element, BindView.class, e);
      }
    }
  	
  	...
}

findAndParseTargets 方法会查找所有 ButterKnife 的注解来进行解析,前文中我们使用了 @BindView 注解,因此这里只截取了处理 @BindView 注解的部分。接着查看上面的代码注释 1 处的 parseBindView 方法,如下所示:

private void parseBindView(Element element, 
                           Map<TypeElement, BindingSet.Builder> builderMap, 
                           Set<TypeElement> erasedTargetNames) {
  	TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Start by verifying common generated code restrictions.
    boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element)
        || isBindingInWrongPackage(BindView.class, element); // 1

  	...
      
    if (hasError) {
      return;
    }

    // Assemble information on the field.
    int id = element.getAnnotation(BindView.class).value(); // 2
    BindingSet.Builder builder = builderMap.get(enclosingElement);
    Id resourceId = elementToId(element, BindView.class, id);
    if (builder != null) { // 3
      String existingBindingName = builder.findExistingBindingName(resourceId);
      if (existingBindingName != null) {
        error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)",
            BindView.class.getSimpleName(), id, existingBindingName,
            enclosingElement.getQualifiedName(), element.getSimpleName());
        return;
      }
    } else {
      builder = getOrCreateBindingBuilder(builderMap, enclosingElement);
    }

    String name = simpleName.toString();
    TypeName type = TypeName.get(elementType);
    boolean required = isFieldRequired(element);

    builder.addField(resourceId, new FieldViewBinding(name, type, required)); // 4

    // Add the type-erased version to the valid binding targets set.
    erasedTargetNames.add(enclosingElement);
  
}

对于注释 1 处的 isInaccessibleViaGeneratedCode 方法:

private boolean isInaccessibleViaGeneratedCode(Class<? extends Annotation> annotationClass,
    String targetThing, Element element) {
    boolean hasError = false;
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();

    // Verify field or method modifiers.
    Set<Modifier> modifiers = element.getModifiers();
    if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) { // 1
        error(element, "@%s %s must not be private or static. (%s.%s)",     
              annotationClass.getSimpleName(), targetThing, 
              enclosingElement.getQualifiedName(), 
              element.getSimpleName());
        hasError = true;
    }

    // Verify containing type.
    if (enclosingElement.getKind() != CLASS) {// 2
        error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)",
              annotationClass.getSimpleName(), targetThing, 
              enclosingElement.getQualifiedName(),
              element.getSimpleName());
        hasError = true;
    }

    // Verify containing class visibility is not private.
    if (enclosingElement.getModifiers().contains(PRIVATE)) { // 3
        error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)",
              annotationClass.getSimpleName(), targetThing, 
              enclosingElement.getQualifiedName(), element.getSimpleName());
        hasError = true;
    }

    return hasError;
}

isInaccessibleViaGeneratedCode 方法里面检查了 3 个点,它们分别是:

  • 方法修饰符不能为 private 和 static;(注释 1)
  • 包含类型不能为非 Class;(注释 2)
  • 包含类的修饰符不能是 private;(注释 3)

对于 isBindingInWrongPackage 方法:

private boolean isBindingInWrongPackage(Class<? extends Annotation> annotationClass,
    Element element) {
    TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
    String qualifiedName = enclosingElement.getQualifiedName().toString();

    if (qualifiedName.startsWith("android.")) { // 1
      error(element, "@%s-annotated class incorrectly in Android framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }
    if (qualifiedName.startsWith("java.")) { // 2
      error(element, "@%s-annotated class incorrectly in Java framework package. (%s)",
          annotationClass.getSimpleName(), qualifiedName);
      return true;
    }

    return false;
}

isBindingInWrongPackage 方法判断了这个类的包名不能以 android. 和 java. 开头(注释 1 和 注释 2)。

回到 parseBindView 方法中,注释 2 处获取注解的标注的值。接下来注释 3 处判断是否存在 BindingSet.Builder 的值,若没有则创建,有则复用。在注释 4 处可以看出,将注解修饰的类型的信息存储在 FieldViewBinding 中,并将 FieldViewBinding 传入 BindingSet.Builder 的 addField 方法中。这样注解锁修饰的类型的信息以及注解的成员变量的值都存储在 BindingSet 中。

接下来我们回到 process 方法中,从上面可知注释 1 处的 findAndParseTargets 方法主要用于查找和解析注解。在注释 2 处遍历 findAndParseTargets 方法返回的 Map 集合,在注释 3 处得到 BindingSet 的值,并调用了它的 brewJava 方法,如下所示:

final class BindingSet implements BindingInformationProvider {
    JavaFile brewJava(int sdk, boolean debuggable) {
      TypeSpec bindingConfiguration = createType(sdk, debuggable);
      return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration)
          .addFileComment("Generated code from Butter Knife. Do not modify!")
          .build();
    }
}

brewJava 方法将使用注解的类生成一个 JavaFile,在 process 方法的注释 4 处,将该 JavaFile 输出成 Java 文件。在 build-generared-source-apt 目录下可以找到生成的 Java 文件,这里生成的文件名为 MainActivity_ViewBinding。先不分析这个文件做了什么,先分析一下 ButterKnife 的 bind 方法。

3.2.3.2 ButterKnife 的 bind 方法

为了使用 ButterKnife,需要用 ButterKnife.bind 方法来绑定上下文。现在来看 bind 方法做了什么:

@NonNull @UiThread
public static Unbinder bind(@NonNull Activity target) {
    View sourceView = target.getWindow().getDecorView();
    return bind(target, sourceView);
}

bind 方法有很多重载方法,上面的代码只是其中的一种,也就是传入 Activity 的情况。得到 Activity 的 DecorView,并将 DecorView 和 Activity 传入 bind 方法中,这个 DecorView 后文会提到。bind 方法如下所示:

@NonNull @UiThread
public static Unbinder bind(@NonNull Object target, @NonNull View source) {
    Class<?> targetClass = target.getClass();
    if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName());
    Constructor<? extends Unbinder> constructor = 
      findBindingConstructorForClass(targetClass); // 1

    if (constructor == null) {
      return Unbinder.EMPTY;
    }

    //noinspection TryWithIdenticalCatches Resolves to API 19+ only type.
    try {
      return constructor.newInstance(target, source); // 2
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Unable to invoke " + constructor, e);
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException) cause;
      }
      if (cause instanceof Error) {
        throw (Error) cause;
      }
      throw new RuntimeException("Unable to create binding instance.", cause);
    }
}

注释 1 处调用了 findBindingConstructorForClass 方法,如下所示:

 @VisibleForTesting
  static final Map<Class<?>, Constructor<? extends Unbinder>> BINDINGS = new LinkedHashMap<>();

@Nullable @CheckResult @UiThread
private static Constructor<? extends Unbinder> findBindingConstructorForClass(Class<?> cls) {
    Constructor<? extends Unbinder> bindingCtor = BINDINGS.get(cls); // 1
    if (bindingCtor != null || BINDINGS.containsKey(cls)) {
      if (debug) Log.d(TAG, "HIT: Cached in binding map.");
      return bindingCtor;
    }
    String clsName = cls.getName();
    if (clsName.startsWith("android.") || clsName.startsWith("java.")
        || clsName.startsWith("androidx.")) {
      if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search.");
      return null;
    }
    try {
      Class<?> bindingClass = 
        cls.getClassLoader().loadClass(clsName + "_ViewBinding"); // 2
      //noinspection unchecked
      bindingCtor = (Constructor<? extends Unbinder>) 
        bindingClass.getConstructor(cls, View.class); // 3
      if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor.");
    } catch (ClassNotFoundException e) {
      if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName());
      bindingCtor = findBindingConstructorForClass(cls.getSuperclass());
    } catch (NoSuchMethodException e) {
      throw new RuntimeException("Unable to find binding constructor for " + clsName, e);
    }
    BINDINGS.put(cls, bindingCtor); // 4
    return bindingCtor;
}

在上面代码注释 1 处会先从 BINDINGS 中获取对应 Class 的 Constructor 实例,BINDINGS 是一个 Class 为 key、Constructor 为 value 的 Map。如果没有获取 Constructor,则在注释 2 处会通过反射来生成 Class 类,这个 Class 类就是我们此前生成的 MainActivity_ViewBinding。虽然反射会影响一些性能,但是因为有 BINDINGS 的存在(一个类只会在第一次反射生成,以后会从 BINDINGS 中去取),也可以解决一些性能问题。在注释 3 处通过调用 getConstructor 方法将 Class 转换为 Constructor,getConstructor 方法中并没有做什么主要的操作。在注释 4 处将 Constructor 作为 value,Class 作为 key 存储在 BINDINGS 中,最后返回该 Constructor。接着在 bind 方法的注释 2 处,生成该 Constructor 的实例,也就是 MainActivity_ViewBinding 的实例。接下来我们查看 MainActivity_ViewBinding 中做了什么。

3.2.3.3 生成辅助类分析

MainActivity_ViewBinding.java,如下所示:

public class MainActivity_ViewBinding implements Unbinder {
    private MainActivity target;

    @UiThread
    public MainActivity_ViewBinding(MainActivity target) {
      this(target, target.getWindow().getDecorView());
    }

    @UiThread
    @SuppressLint("ClickableViewAccessibility")
    public MainActivity_ViewBinding(final MainActivity target, View source) {
      this.target = target;

      target.textView = Utils.findOptionalViewAsType(source, R.id.text_view, 
                          "field 'textView'", TextView.class); // 1
    }

    @Override
    @CallSuper
    public void unbind() {
      MainActivity target = this.target;
      if (target == null) throw new IllegalStateException("Bindings already cleared.");
      this.target = null;

      target.textView = null;
    }
}

在前面,我们已经知道在 bind 方法中调用 newInstance 方法生成 MainActivity_ViewBinding 实例时传入的 source 值是 MainActivity 的 DecorView。而 target 值为 MainActivity。接着我们来查看 MainActivity_ViewBinding 的构造方法。很明显,构造方法的 source 的值就是 MainActivity 的 DecorView,而 target 值为 MainActivity。在上面代码注释 1 处调用了 Utils 的 findOptionalViewAsType 方法并将 source 值、R.id.text_view 等参数传入,findOptionalViewAsType 方法如下所示:

public static <T> T findOptionalViewAsType(View source, @IdRes int id, String who,
    Class<T> cls) {
    View view = source.findViewById(id); // 1
    return castView(view, id, who, cls); // 2
} 

在上面代码注释 1 处调用 DecorView.findViewById 方法,并将 R.id.text_view 对应的 View 返回。接着在注释 2 处调用 castView 方法:

public static <T> T castView(View view, @IdRes int id, String who, Class<T> cls) {
    try {
      return cls.cast(view);
    } catch (ClassCastException e) {
      String name = getResourceEntryName(view, id);
      throw new IllegalStateException("View '"
          + name
          + "' with ID "
          + id
          + " for "
          + who
          + " was of the wrong type. See cause for more info.", e);
    }
}

castView 方法会将 View 强制转换成传入的 Class 值的类型,这里的 Class 值从 MainActivity_ViewBinding 类的注释 1 处可以得知是 TextView.class,因此 castView 方法会将 View 强制转换为 TextView 并返回。再次回到 MainActivity_ViewBinding 辅助类,这个返回的 TextView 会赋值给 target,也就是 MainActivity,这样我们在 MainActivity 中就可以使用这个 TextView 了。

3.3 解析 Dagger2

Dagger 2 是一个基于 JSR-330(Java 依赖注入)标准的依赖注入框架,在编译期间自动生成代码,负责依赖对象的创建。

3.3.1 注解使用方法
第一步,添加依赖库

在 app 的 build.gradle 文件中添加如下代码:

dependencies {
		...

    implementation "com.google.dagger:dagger:2.28.3"
    annotationProcessor "com.google.dagger:dagger-compiler:2.28.3"
		...
}
第二步,@Inject 和 @Component

在使用这两个注解前,如果需要在 MainActivity 中调用一个类的方法,可能会按照下面的代码实现:

public class Watch {
    public void work() {
        System.out.println("I'm working");
    }
}

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Watch watch = new Watch();
        watch.work();
    }
}

如果使用 @Inject 和 @Component 注解,则会按照下面的步骤改写。首先改写 Watch 类,如下所示:

public class Watch {
    @Inject
    public Watch() {       
    }

    public void work() {
        System.out.println("I'm working");
    }
}

@Inject 注解是 JSR-330 标准中的一部分,用于标记需要注入的依赖。在这里标记 Watch 构造方法,则表明 Dagger2 可以使用 Watch 构造方法构建对象。接下来用 @Component 注解来完成依赖注入。我们需要定义一个接口,接口名称为:目标类名 + Component,在编译后 Dagger2 就会为我们生成名为 Dagger + 目标类名 + Component 的辅助类。代码如下所示:

@Component
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

Component 则可以理解为注入器,它会把目标类依赖的实例注入到目标类中。在这里需要定义 inject 方法,传入需要注入依赖的目标类。在这个例子中需要注入依赖的目标类是 MainActivity。最后在 MainActivity 中调用 Watch 的 work 方法,代码如下所示:

public class MainActivity extends AppCompatActivity {

    @Inject // 1
    Watch watch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.create().inject(this); // 2

        watch.work();
    }

}

上面代码注释 1 处用 @Inject 标记需要注入的属性。注释 2 处调用编译生成的 DaggerMainActivityComponent 的 inject 方法来完成注入,注入的目标为 MainActivity。

在这里我们提到 @Inject 有两种注入方式,分别是成员变量注入和构造方法注入。还有一种注入方式叫做方法注入,当我们需要传类实例来注入到依赖时,会使用到它,方法注入会在否早方法调用后立即调用。

第三步,@Module 和 @Provides

如果项目中使用了第三方的类库,比如 Gson,如果用以下的写法会报错,因为我们不能将 @Inject 应用到 Gson 的构造方法中:

@Inject
Gson gson;

这个时候可以采用 @Module 和 @Provides 来处理。首先创建 GsonModule 类,如下所示:

@Module
public class GsonModule {
    @Provides
    public Gson provideGson() {
        return new Gson();
    }  
}

将 @Module 标注在类上,用来告诉 Component,可以从这个类中获取依赖对象,也就是 Gson 类;@Provides 标记在方法上,表示可以通过这个方法来获取依赖对象的实例。通俗来讲,@Module 标注的类其实就是一个工厂,用来生成各种类;@Provides 标记的方法,就是用来生成这些类的实例的。接下来编写 Component 类,如下所示:

@Component(modules = GsonModule.class)
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

和此前的区别就是加上了 modules = GsonModule.class,用来指定 Module。需要注意的是,Component 中可以指定多个 Module。接下来在 MainActivity 中使用 Gson,如下所示:

public class MainActivity extends AppCompatActivity {

    @Inject
    Gson gson;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.create().inject(this);

        String jsonData = "{'name':'萧峰','age':'31'}";
        JsonObject object = gson.fromJson(jsonData, JsonObject.class);
        System.out.println(object.get("name") + " " + object.get("age"));
    }
}

还有一种情况,当我们使用依赖注入的时候,如果需要注入的对象是抽象的,则 @Inject 也无法使用,因为抽象的类并不能实例化,如下所示:

public abstract class Engine {
    public abstract String work();
}

上面定义一个 Engine 抽象类,接着定义它的实现类 GasolineEngine:

public class GasolineEngine extends Engine{
    
    @Inject
    public GasolineEngine(){
        
    }
    
    public String work() {
        return "汽油发动机发动";
    }
}

随后在 Car 中引用 Engine,如下所示:

public class Car {
    private Engine engine;

    @Inject
    public Car(Engine engine) {
        this.engine = engine;
    }

    public String run() {
        return engine.work();
    }
}

public class MainActivity extends AppCompatActivity {

    @Inject
    Car car;
  
  	...
  
}

这时编译程序,Dagger2 会报错,因为 Car 需要 Engine 对象,而且 Engine 对象是抽象的,@Inject 无法提供实例。这是也可以采用 @Module 和 @Provides。首先修改 GasolineEngine 类,去掉 @Inject:

public class GasolineEngine extends Engine{
    public String work() {
        return "汽油发动机发动";
    }
}

创建 EngineModule 类,如下所示:

@Module
public class EngineModule {
    @Provides
    public Engine provideEngine() {
        return new GasolineEngine();
    }
}

接着在 Component 中指定 EngineModule:

@Component(modules = EngineModule.class)
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

最后在 MainActivity 中使用,如下所示:

public class MainActivity extends AppCompatActivity {

    @Inject
    Car car;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.create().inject(this);

        String str = car.run();
        System.out.println(str);
    }

}
第四步,@Named 和 @Qualifier

@Qualifier 是限定符,@Named 则是 @Qualifier 的一种实现。

当有两个相同的依赖时,它们都继承同一个父类或者均实现同一个接口。当它们被提供给高层时,Component 就不知道我们到底要提供哪一个依赖对象了,因为它找到了两个。比如在上面的汽车例子中,如果我们再提供一个 DieselEngine 给它,EngineModule 就可以改写为如下代码所示:

@Module
public class EngineModule {
    @Provides
    public Engine provideGasoline() {
        return new GasolineEngine();
    }
    
    @Provides
    public Engine provideDiesel() {
        return new DieselEngine();
    }
}

编译时 Dagger2 会报错,因为我们提供了多个 Provides,Component 不知道要选哪个。这个时候我们就可以使用 @Named,代码如下所示:

@Module
public class EngineModule {
    @Provides
    @Named("Gasoline")
    public Engine provideGasoline() {
        return new GasolineEngine();
    }

    @Provides
    @Named("Diesel")
    public Engine provideDiesel() {
        return new DieselEngine();
    }
}

给不同的 Provides 定义不同的 @Named,接下来在 Car 类中指定要采用那种 Provides:

public class Car {
    private Engine engine;

    @Inject
    public Car(@Named("Diesel") Engine engine) {
        this.engine = engine;
    }

    public String run() {
        return engine.work();
    }
}

上面的代码通过 @Named 来制定采用 ProviedDiesel 方法来生成实例。上面的例子也可以用 @Qualifier 来实现,@Named 传递的值只能是字符串,而 @Qualifier 则更灵活一些,@Qualifier 不是直接标记在属性上的,而是用来自定义注解的,如下所示:

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Gasoline {

}

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface Diesel {

}

分别自定义两个注解 @Gasoline 和 @Diesel,接下来修改 EngineModule 类,如下所示:

@Module
public class EngineModule {
    @Provides
    @Gasoline
    public Engine provideGasoline() {
        return new GasolineEngine();
    }

    @Provides
    @Diesel
    public Engine provideDiesel() {
        return new DieselEngine();
    }
}

最后在 Car 类制定需要哪种依赖:

public class Car {
    private Engine engine;

    @Inject
    public Car(@Gasoline Engine engine) {
        this.engine = engine;
    }

    public String run() {
        return engine.work();
    }
}
第五步,@Singleton 和 @Scope

@Scope 是用来自定义注解的,而 @Singleton 则是用来配合实现局部单例和全局单例的。需要注意的是,@Singleton 本身不具备创建单例的能力。如果我们要两次使用 Gson,会这么做:

@Inject
Gson gson;
@Inject
Gson gson1;

gson 和 gson1 的内存地址不同,也就是新创建了两个 Gson,如果我们想让 Gson 在 MainActivity 中时单例,可以使用 @Singleton。首先在 GsonModule 中添加 @Singleton,如下所示:

@Module
public class GsonModule { 
    @Singleton
    @Provides
    public Gson provideGson() {
        return new Gson();
    }
}

接下来在 MainActivityComponent 中添加 @Singleton:

@Singleton
@Component(modules = GsonModule.class)
public interface MainActivityComponent {
    void inject(MainActivity mainActivity);
}

我们在 MainActivity 中打印 Gson 的hashCode 值,就会发现值是相同的,如下所示:

public class MainActivity extends AppCompatActivity {
    @Inject
    Gson gson;
    @Inject
    Gson gson1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DaggerMainActivityComponent.create().inject(this);

        System.out.println(gson.hashCode() + "  " + gson1.hashCode());
    }
}

Gson在 MainActivity 中是单例,如果再创建一个 SecondActivity,SecondActivity 创建的 Gson 的内存地址和 MainActivity 创建的 Gson 的内存地址是不同的。因为 Gson 只是保证在 MainActivityComponent 中时单例的,我们创建 SecondActivity,就会重新创建一个 Component,这样只能保证 Gson 是局部单例(MainActivity 中)。如果想要实现全局单例,就需要保证对应的 Component 只有一个实例。查看 Singleton 的源码:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

其实就是用 @Scope 标识的注解。为了使 Gson 变为全局单例,我们可以用 @Scope 结合 Application 来实现。当然也可以用 @Singleton 结合 Application 来实现。只是用 @Scope 可以自定义注解名称,这更灵活一些。先来定义 @ApplicationScope 注解,如下所示:

@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ApplicationScope {
}

接下来在 GsonModule 中使用 @ApplicationScope:

@Module
public class GsonModule {

    @ApplicationScope
    @Provides
    public Gson provideGson() {
        return new Gson();
    }
}

为了处理多个 Activity,我们创建 ActivityComponent,并使用 @ApplicationScope,如下所示:

@ApplicationScope
@Component(modules = GsonModule.class)
public interface ActivityComponent {
    void inject(MainActivity activity);

    void inject(SecondActivity activity);
}

创建 App 类继承自 Application,用来提供 ActivityComponent 实例,如下所示:

public class App extends Application {

    ActivityComponent activityComponent;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        activityComponent = DaggerActivityComponent.builder().build();
    }

    public static App get(Context context) {
        return (App) context.getApplicationContext();
    }

    ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

最后在 MainActivity 中实现如下代码:

public class MainActivity extends AppCompatActivity {

    private Button button;

    @Inject
    Gson gson;
    @Inject
    Gson gson1;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        App.get(MainActivity.this).getActivityComponent().inject(this);
        onClick();
        System.out.println("MainActivity "+gson.hashCode() + " " + gson1.hashCode());

    }

    private void onClick() {
        button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                startActivity(intent);
            }
        });
    }

}

SecondActivity 中的代码也是类似的,运行程序,发现 MainActivity 和 SecondActivity 的 Gson 的内存地址是一样的。这样也就实现了 Gson 的全局单例。当然这只是简单用到了 @Scope,@Scope 的作用远远不止如此。我们要做一个应用肯定要用到很多 Component,一般划分的规则就是有一个全局的 Component,比如 AppComponent。每个界面有一个 Component,比如一个 Activity 定义一个 Component,一个 Fragment 第一一个 Component。当然,这不是必需的,如果页面之间依赖的类是一样的,可以共用一个 Component。这样算来一个应用会有很多 Component,为了管理这些 Component,就可以使用自定义 Scope 注解。它可以更好地管理 Component 和 Module 之间的匹配关系。比如编译器会检查 Component 管理的 Module,若发现管理的 Module 中的标注创建类实例方法的 Scope 注解和 Component 类中的 Scope 注解不一样,就会报错。Scope 注解海可以更好地管理 Component 之间的组织方式,不同的组织方式定义为不同的 Scope 注解名称,方便管理。

第六步,@Component 和 dependencies

@Component 也可以用 @dependencies 依赖于其他 Component。创建一个 Hero 类:

public class Hero {
    
    @Inject
    public Hero(){
        
    }
    
    public String fighting(){
        return "武林至尊,宝刀屠龙。号令天下,莫敢不从。倚天不出,谁与争锋";
    }
}

接下来,按照惯例创建 HeroModule 和 HeroComponent,如下所示:

@Module
public class HeroModule {
    @Provides
    public Hero provideHero() {
        return new Hero();
    }
}

@Component(modules = HeroModule.class)
public interface HeroComponent {
    Hero getHero();
}

在 ActivityComponent 中通过 @Component 的 dependencies 来引入 HeroComponent。

@ApplicationScope
@Component(modules = GsonModule.class, dependencies = HeroComponent.class)
public interface ActivityComponent {
    void inject(MainActivity activity);

    void inject(SecondActivity activity);
}

随后在此前定义的 App 中引入 HeroComponent,如下所示:

public class App extends Application {

    ActivityComponent activityComponent;

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);

        activityComponent = DaggerActivityComponent.builder().
                heroComponent(DaggerHeroComponent.builder().build()).build();
    }

    public static App get(Context context) {
        return (App) context.getApplicationContext();
    }

    ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

最后我们在 SecondActivity 中使用 Hero,如下所示:

public class SecondActivity extends AppCompatActivity {

    @Inject
    Hero hero;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        App.get(SecondActivity.this).getActivityComponent().inject(this);
        String h = hero.fighting();
        System.out.println("SecondActivity " + h);

    }

}
3.3.2 懒加载

Dagger2 提供了懒加载模式,在 @Inject 的时候不初始化,而是使用的时候,调用 get 方法来获取实例。接着我们改写上面提到的 Hero,将它改写成懒加载模式。

public class SecondActivity extends AppCompatActivity {

    @Inject
    Lazy<Hero> heroLazy;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        App.get(SecondActivity.this).getActivityComponent().inject(this);

        Hero hero = heroLazy.get();
        hero.fighting();
        String s = hero.fighting();

        System.out.println("SecondActivity--lazy " + s);

    }

}
3.3.3 Dagger2 原理解析

因为 Dagger2 的使用情况很多,这里只对基本的使用方法进行分析。先写一个简单的例子,创建 Watch、WatchModule 和 ActivityComponent,代码如下所示:

public class Watch {
    public String work() {
        return "手表工作";
    }
}

@Module
public class WatchModule {
    @Provides
    public Watch provideWatch() {
        return new Watch();
    }
}

@Component(modules = WatchModule.class)
public interface ActivityComponent {
    void inject(MainActivity activity);
}

在 MainActivity 中使用 Watch,如下所示:

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);

        String str = watch.work();
        System.out.println(str);
    }
}

这时编译程序会在 build 目录中生成辅助类,分别是 WatchModule_ProvideWatchFactory、DaggerMainActivityComponent 和 MainActivity_MembersInjector。

编译生成的辅助类

从入口开始分析,DaggerActivityComponent.create().inject(this); ,首先从 DaggerActivityComponent 的代码开始分析,如下所示:

public final class DaggerActivityComponent implements ActivityComponent {
    private final WatchModule watchModule;

    private DaggerActivityComponent(WatchModule watchModuleParam) {
      this.watchModule = watchModuleParam;
    }

    public static Builder builder() {
      return new Builder();
    }

    public static ActivityComponent create() {
      return new Builder().build(); // 1 
    }

    @Override
    public void inject(MainActivity activity) {
      injectMainActivity(activity); // 3
    }

    private MainActivity injectMainActivity(MainActivity instance) {
      MainActivity_MembersInjector.injectWatch(instance, WatchModule_ProvideWatchFactory.provideWatch(watchModule)); // 4
      return instance;
    }

    public static final class Builder {
      private WatchModule watchModule;

      private Builder() {
      }

      public Builder watchModule(WatchModule watchModule) {
        this.watchModule = Preconditions.checkNotNull(watchModule);
        return this;
      }

      public ActivityComponent build() {
        if (watchModule == null) {
          this.watchModule = new WatchModule(); // 2
        }
        return new DaggerActivityComponent(watchModule);
      }
    }
}

从注释 1 处的代码可以看出 create 方法调用了 builder().build() 方法,调用此方法会在注释 2 处新建 WatchModule。注释 4 处调用 MainActivity_MembersInjector.injectWatch 方法,代码如下:

public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
    private final Provider<Watch> watchProvider;

    public MainActivity_MembersInjector(Provider<Watch> watchProvider) {
      this.watchProvider = watchProvider;
    }

    public static MembersInjector<MainActivity> create(Provider<Watch> watchProvider) {
      return new MainActivity_MembersInjector(watchProvider);
    }

    @Override
    public void injectMembers(MainActivity instance) {
      injectWatch(instance, watchProvider.get()); //1
    }

    @InjectedFieldSignature("com.example.myapplication.MainActivity.watch")
    public static void injectWatch(MainActivity instance, Watch watch) {
      instance.watch = watch;
    }
}

MainActivity_MembersInjector.injectMembers 方法会调用注释 1 处的代码,调用 watchProvider.get 方法并赋值给 MainActivity.watch。这里 watchProvider 是调用 MainActivity_MembersInjector 的 create 方法传进来的,也就是上文提到的 provideWatchProvider,它是 DaggerActivityComponent 类的注释 6 处调用的 WatchModule_ProvideWatchFactory 的 create 方法生成的,如下所示:

public final class WatchModule_ProvideWatchFactory implements Factory<Watch> {
    private final WatchModule module;

    public WatchModule_ProvideWatchFactory(WatchModule module) {
      this.module = module;
    }

    @Override
    public Watch get() {
      return provideWatch(module);
    }

    public static WatchModule_ProvideWatchFactory create(WatchModule module) {
      return new WatchModule_ProvideWatchFactory(module);
    }

    public static Watch provideWatch(WatchModule instance) {
      return Preconditions.checkNotNull(instance.provideWatch(), "Cannot return null from a non-@Nullable @Provides method"); // 1
    }
}

从 create 方法可以得知,watchProvider 实际上就是 WatchModule_ProvideWatchFactory,查看 WatchModule_ProvideWatchFactory 的 get 方法,它会返回注释 1 处的代码。最后查看 WatchMode 的 provideWatch 方法里做了什么:

@Module
public class WatchModule {
    @Provides
    public Watch provideWatch() {
        return new Watch();
    }
}

很显然这个 provideWatch 是我们此前定义的,它会返回一个 Watch 对象。其实这 3 个辅助类的作用就是在我们调用 inject 方法时,将新创建的 Watch 类赋值给 MainActivity 的成员变量 Watch。其中 WatchModule_ProvideWatchFactory 用来生成 Watch 实例;Dagger2Activity_MemberInject 将 Watch 实例赋值给 MainActivity 的成员变量 Watch;DaggerActivityComponent 则作为程序入口和桥梁,负责初始化 WatchModule_ProvideWatchFactory 和 Dagger2Activity_MemberInject,并将它们串联起来。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值