注解和依赖注入框架

news2024/11/14 2:12:52

依赖注入是一种比较流行的设计模式,在 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,并将它们串联起来。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1079923.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven系列第2篇:安装、配置、mvn运行过程详解

maven系列目标&#xff1a;从入门开始开始掌握一个高级开发所需要的maven技能。 这是maven系列第2篇。 本文主要内容 linux中安装maven window中安装maven mvn命令运行的原理 maven配置设置 本篇环境 jdk1.8 maven3.6.2 我们要写java代码&#xff0c;需要安装jdk&…

Zend Framework 3.1.3 gadget chain

前言 在推特上的PT SWARM账号发布了一条消息。 一个名为Zend Framework的php框架出现了新的gadget chain&#xff0c;可导致RCE。笔者尝试复现&#xff0c;但失败了。所幸&#xff0c;我基于此链&#xff0c;发现在这个框架的最新版本中的另一条链。 复现过程 这里使用vscod…

利达卓越:关注环保事业,持续赋能科技

随着全球环境问题的日益突出,绿色金融作为一种新兴的金融模式逐渐受到各国的重视。绿色金融是指在金融活动中,通过资金、信贷和风险管理等手段,支持环境友好和可持续发展的项目和产业。绿色金融的出现是为了应对气候变化、资源短缺、污染问题等现实挑战,促进经济的绿色转型和可…

【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化教程

详情点击公众号链接&#xff1a;【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化教程 第一&#xff1a;基础 1、Earth Engine平台及应用、主要数据资源 2、Earth Engine遥感云重要概念、数据类型与对象等 3、JavaScript与Python遥感云编程比较与选择 4、…

南美哥伦比亚市场最全分析开发攻略,收藏一篇就够了

哥伦比亚作为南美洲最大的经济体之一&#xff0c;其外贸市场潜力巨大&#xff0c;吸引了越来越多的国际企业寻求商机。哥伦比亚跟中国的贸易往来也是非常的密切&#xff0c;今天就来分享一下如何开发南美比较重要的国家哥伦比亚&#xff0c;文章略长&#xff0c;建议大家点赞收…

【RocketMQ】(十一)Dledger模式下的日志复制

RocketMQ在开启Dledger时&#xff0c;使用DLedgerCommitLog&#xff0c;其他情况使用的是CommitLog来管理消息的存储。在Dledger模式下&#xff0c;消息写入时Leader节点还需要将消息转发给Follower节点&#xff0c;有过半的节点响应成功&#xff0c;消息才算写入成功。 Leade…

spring学习小笔记

spring学习小笔记&#xff08;1&#xff09; 一、Spring开发1.1 Spring简介1.2 Spring Framework系统架构1.3 Spring Framework学习路线1.4 Spring Farmework核心概念1.5 Spring入门 二、Bean的基础配置2.1 Bean的别名配置2.2 Bean的作用范围2.3 Bean的实例化2.3.1 构造方法实例…

本地vscode安装GPU版本PyTorch

操作系统 windows, IDE环境vscode&#xff0c;本地GPU 可以新建一个jupyter文件&#xff0c;运行一些测试代码 确保装好显卡驱动 在底下调出终端窗口&#xff0c;默认是power shell&#xff0c;我喜欢用cmd窗口 激活自己的虚拟环境&#xff0c;输入命令 nvidia-smi 确保自己…

ctfshow-web12(glob绕过)

打开链接&#xff0c;在网页源码里找到提示 要求以get请求方式给cmd传入参数 尝试直接调用系统命令&#xff0c;没有回显&#xff0c;可能被过滤了 测试phpinfo&#xff0c;回显成功&#xff0c;确实存在了代码执行 接下来我们尝试读取一下它存在的文件&#xff0c;这里主要介…

E. Li Hua and Array

Problem - E - Codeforces 思路&#xff1a;观察给定的函数&#xff0c;其实就是求与这个数互质的数的个数&#xff0c;即欧拉函数&#xff0c;我们发现一个数迭代欧拉函数不会很多&#xff0c;那么对于第一个操作来说我们可以直接暴力修改&#xff0c;而对于第二个操作来说&am…

软件测试/测试开发丨为什么接口自动化测试是提升职业技能的关键?

接口测试背景和必要性 接口测试是测试系统组件间接口&#xff08;API&#xff09;的一种测试&#xff0c;主要用于检测内部与外部系统、内部子系统之间的交互质量&#xff0c;其测试重点是检查数据交换、传递的准确性&#xff0c;控制和交互管理过程&#xff0c;以及系统间相互…

ElementPlus Switch 开关基础使用

昨天开发用到开关组件 后台返回字段是 can_write 默认是0 or 1 但是Switch 组件绑定的默认值默认是 true or false 直接绑定会导致默认是关闭状态 在页面一加载 值发生变化时 会自己调用 查了文档 需要使用 active-value 和 inactive-value 来指定绑定的数据类型 …

C#,工业化软件与院校软件的对比及编程语言的选择建议

飞机发动之之一&#xff0c;涡轮喷气航空发动机&#xff08;JET ENGINE&#xff09; 火箭发动机之一&#xff0c;俄罗斯RD-180煤油和液氧发动机&#xff08;ROCKET ENGINE&#xff09; 1 飞机发动机与火箭发动机的简明对比 2 工业软件与院校软件的简单对比 除了以上类似的对比…

【java学习】方法的参数传递(21)

文章目录 相关概念1. 方法传递之基本数据类型2. 方法的参数传递之引用对象3. 总结 相关概念 方法&#xff0c;必须有其所在类或对象调用才有意义。若方法含有参数&#xff1a; 形参&#xff1a;方法声明时的参数 实参&#xff1a;方法调用时实际传给形参的参数值 问题&#xf…

数据建模设计

数据库系统——建模与设计 一、数据建模 数据库的设计不仅需要处理规则的理解&#xff0c;更重要的是数据需求的理解与表达。 表达计算机世界的模型称为数据模型&#xff0c;而表达信息世界的模型称为概念模型。抽象是具有层次的&#xff0c;将现实世界的问题抽象成概念模型…

[ValueError: not enough values to unpack (expected 3, got 2)]

项目场景&#xff1a; 在使用opencv进行关键点识别、边缘轮廓提取的时候&#xff0c;提示以上错误。 import cv2 import numpy as npdef preprocess(image):# 进行图像预处理&#xff08;例如灰度化、高斯模糊等&#xff09;gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)blu…

Vuex的使用,详细易懂

目录 一.前言 二.Vuex的简介 三.vuex的使用 3.1 安装Vuex 3.2 使用Vuex的步骤&#xff1a; 四.vuex的存值取值&#xff08;改变值&#xff09; 五.vuex的异步请求 好啦&#xff0c;今天的分享就到这啦&#xff01;&#xff01;&#xff01; 一.前言 今天我们继续前面的E…

openGauss Meetup(天津站.10月13日),欢迎报名

由openGauss社区、天开发展集团、天津市软件行业协会、天大智图&#xff08;科技&#xff09;有限公司联合主办,天津鲲鹏生态创新中心、天津市计算机学会、天津市人工智能学会、天津市系统集成协会、麒麟软件有限公司、天津南大通用数据技术股份有限公司、AI知学社协办的“open…

【Vuex+ElementUI】Vuex中取值存值以及异步加载的使用

一、导言 1、引言 Vuex是一个用于Vue.js应用程序的状态管理模式和库。它建立在Vue.js的响应式系统之上&#xff0c;提供了一种集中管理应用程序状态的方式。使用Vuex&#xff0c;您可以将应用程序的状态存储在一个单一的位置&#xff08;即“存储”&#xff09;中&#xff0c;…

北斗高精度定位为无人车成为机场运营新常态提供技术保障

在现代快节奏的生活中&#xff0c;人们对交通效率和安全性的需求越来越高。为了满足这一需求&#xff0c;无人驾驶技术被广泛研究和应用。而随着北斗卫星系统的发展&#xff0c;机场无人车正成为潜在的未来运输解决方案。本文将深入探讨北斗卫星如何改变机场运营&#xff0c;以…