注解与依赖注入框架(ButterKnife 和 Dagger2)

一、注解分类

1.1、标准注解

  1. @Override:对覆盖超类中的方法进行标记,如果被标记的方法并没有实际覆盖超类中的方法,则编译器会发出错误警告。
  2. @Deprecated:对不鼓励使用或者已过时的方法添加注解,当编程人员使用这些方法时,将会在编译时显示提示信息。
  3. @SuppressWarnings:选择性的取消特定代码段中的警告。
  4. @SafeVarargs:JDK 7 新增,用来声明使用了可变长度参数的方法,其在与泛型类一起使用时不会出现类型安全问题。

1.2、元注解

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

  1. @Targe:注解所修饰的对象范围。
    1. ElementType.TYPE:能修饰类、接口或枚举类型。
    2. ElementType.FIELD:能修饰成员变量。
    3. ElementType.METHOD:能修饰方法。
    4. ElementType.PARAMETER:能修饰参数。
    5. ElementType.CONSTRUCTOR:能修饰构造方法。
    6. ElementType.LOCAL_VARIABLE:能修饰局部变量。
    7. ElementType.ANNOTATION_TYPE:能修饰注解。
    8. ElementType.PACKAGE:能修饰包。
    9. ElementType.TYPE_PARAMETER:类型参数声明。
    10. ElementType.TYPE_USE:使用类型。
  2. @Inherited:表示注解可以被继承。
  3. @Documented:表示这个注解应该被 JavaDoc 工具记录。
  4. @Retention:用来声明注解的保留策略。
    1. RetentionPolicy.SOURCE:源码级注解。注解信息只会保留在 .java 源码中,源码在编译后,注解信息被丢弃,不会保留在 .class 中。
    2. RetentionPolicy.CLASS:编译时注解。注解信息会保留在 .java 源码以及 .class 中。当运行 Java 程序时,JVM 会丢弃该注解信息,不会保留在 JVM 中。
    3. RetentionPolicy.RUNTIME:运行时注解。当运行 Java 程序时,JVM 也会保留该注解信息,可以通过反射获取该注解信息。
  5. @Repeatable:JDK 8 新增,允许一个注解在同一声明类型(类、属性或方法)上多次使用。

二、定义注解

2.1、基本定义

定义新的注解类型使用 @interface 关键字,这与定义一个接口很像。

public @interface Person {
}
@Person
public class AnnotationTest {
}

2.2、定义成员变量

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

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

定义了成员变量后,使用该注解时就应该为该注解的成员变量指定值。

@Person(name = "张三", age = 20)
public class AnnotationTest {
}

成员变量可以使用 default 关键字为其指定默认值。指定默认值后,使用注解就可以不再指定值。

public @interface Person {
    String name() default "李四";
    int age() default 18;
}

2.3、定义运行时注解

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

一般如果需要在运行时去动态获取注解信息,那只能用 RetentionPolicy.RUNTIME;

如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就用 RetentionPolicy.CLASS;

如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarning,则可选用 RetentionPolicy.SOURCE。

当设定为 RetentionPolicy.RUNTIME 时,这个注解就是运行时注解。

@Retention(RetentionPolicy.RUNTIME)
public @interface Person {
    String name() default "李四";
    int age() default 18;
}

2.4、定义编译时注解

将 @Retention 的保留策略设定为 RetentionPolicy.CLASS,这个注解就是编译时注解。

@Retention(RetentionPolicy.CLASS)
public @interface Person {
    String name() default "李四";
    int age() default 18;
}

2.5、定义源码级注解

将 @Retention 的保留策略设定为 RetentionPolicy.SOURCE,这个注解就是源码级注解。

@Retention(RetentionPolicy.SOURCE)
public @interface Person {
    String name() default "李四";
    int age() default 18;
}

三、注解处理器

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

3.1、运行时注解处理器

处理运行时注解需要用到反射机制。首先我们要定义运行时注解。比如 Retrofit 中的 @GET 注解,其定义了 @Target(METHOD),等效于 @Target(ElmentType.METHOD),意味着 GET 注解应用于方法。

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

为 @GET 的成员变量赋值。

public class AnnotationTest {
    @GET(value = "http://ip.taobao.com/outGetIpInfo?ip=125.84.85.202&accessKey=alibaba-inc")
    public String getIpMsg() {
        return "";
    }

    @GET(value = "http://ip.taobao.com/")
    public String getIp() {
        return "";
    }
}

接下来写一个简单的注解处理器。用到了两个反射方法:getDeclaredMethods 和 getAnnotation,它们都属于 AnnotatedElement 接口,追溯源码可以看到 Class、Method 和 Filed 等类都直接或间接的实现了该接口。

调用 getAnnotation 方法返回指定类型的注解对象,也就是 GET。最后调用 GET 的 value 方法返回从 GET 对象中提取元素的值。

    public void annotationProcessor() {
        Method[] methods = AnnotationTest.class.getDeclaredMethods();
        for (Method m : methods) {
            GET get = m.getAnnotation(GET.class);
            Log.d("TAG", get.value());
        }
    }

在这里插入图片描述

3.2、编译时注解处理器(------未测试成功--------)

参考博客1
参考博客2

处理编译时注解的步骤稍微有点多,首先仍旧需要先定义注解。

我们在新建一个编译注解的 demo 时,我们需要用到 javax 包下的内容,而 android 的普通 module 和 library 工程是没有的,所以我们不能新建这两个工程,而是需要新建 java 工程,即 Java Library。

我们需要创建两个 java 工程。其中 ClassProcessor 这个类是帮助我们生成需要的 java 源文件的,我们最后需要的是他生成的文件,最后打包进 apk 也是他生成的文件,processor 库本身是不需要打包的,所以我们把需要打包的放进 annotations 库中,不需要打包的放进 processor 库中。最后 processor 库需要对具体的注解类进行操作,需要引用 annotations 库。

3.2.1、定义注解

首先在项目中新建一个 Java Library 来专门存放注解,名为 annotations。接下来定义注解,类似于 ButterKnife 的 @BindView 注解。

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
    int value() default 1;
}

3.2.2、编写注解处理器

在项目中再新建一个 Java Library 来存放注解处理器,名为 processor,并配置 build.gradle。

apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation project(path: ':annotations')
}

sourceCompatibility = "1.7"
targetCompatibility = "1.7"

接下来编写注解处理器 ClassProcessor,继承自 AbstractProcessor。在 process 方法中,用到了 Messager 的 printMessage 方法来打印出注解修饰的成员变量的名称,这个名称会在 Build Output 中打印出来。

public class ClassProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            if (element.getKind() == ElementKind.FIELD) {
                messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + element.toString());
            }
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(BindView.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}

  • init:被注解处理工具调用,并输入 ProcessingEnvironment 参数。ProcessingEnvironment 提供很多有用的工具类,比如 Elements、Types、Filer 和 Messager 等。
  • process:相当于每个处理器的主函数 main(),在这里写你的扫描,评估和处理注解的代码,以及生成 Java 文件。输入参数 RoundEnvironment,可以让你查询出包含特定注解的被注解元素。
  • getSupportedAnnotationTypes:这是必须指定的方法,指定这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。
  • getSupportedSourceVersion:用来指定你使用的 Java 版本,通常这里返回 SourceVersion.latestSupported()。

在 Java 7 后,也可以使用注解来代替 getSupportedAnnotationTypes 方法和 getSupportedSourceVersion 方法。但是考虑到 Android 兼容性问题,这里不建议采用这种方式。

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.mryuan.annotations.BindView")
public class ClassProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        Messager messager = processingEnv.getMessager();
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            if (element.getKind() == ElementKind.FIELD) {
                messager.printMessage(Diagnostic.Kind.NOTE, "printMessage:" + element.toString());
            }
        }
        return true;
    }
}

3.2.3、注册注解处理器

为了能使用注解处理器,需要用一个服务文件来注册它。首先在 processor 库的 main 目录下新建 resources 资源文件夹,接下来再 resources 中再建立 META-INF/services 目录文件夹。最后在 META-INF/services 中创建 javax.annotation.processing.Processor 文件,这个文件中是注解处理器的名称,在这里为 com.mryuan.processor.ClassProcessor。
在这里插入图片描述
上面是手动创建服务文件,也可以用 Google 开源的 AutoService 自动生成。首先配置 processor 的 build.gradle。

    implementation 'com.google.auto.service:auto-service:1.0-rc6'

注意还需要配置 app 的 build.gradle。

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

最后在注解处理器 ClassProcessor 中添加 @AutoService(Processor.class)。

@AutoService(Processor.class)
public class ClassProcessor extends AbstractProcessor

重新 rebuild 项目,会自动生成文件。
在这里插入图片描述
3.2.4、应用注解

主工程项目 app 中引用注解。在主工程 app 中引用了 processor 库,但注解处理器只在编译处理期间需要用到,编译处理完后就没有实际作用了,而主工程添加了这个库会引入很多不必要的文件。为了处理这个问题这里使用了 annotationProcessor,它主要有两个作用。

  • 仅仅在编译时期去依赖注解处理器所在的函数库进行工作,但不会打包到 APK 中。
  • 为注解处理器生成的代码设置好路径,以便 Android Studio 能够找到它。
    annotationProcessor project(path: ':processor')
    implementation project(path: ':annotations')
public class MainActivity extends AppCompatActivity {
    @BindView(value = R.id.tvText)
    TextView tvText;

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

运行程序,会在 Build Output 窗口中打印结果。我在测试的时候一直没有打印,不知道哪里的问题。。。。

四、依赖注入

编写代码时常常会发现类 A 是依赖于类 B 的,类 A 中就可能需要一个类 B 的引用或对象。这里举一个汽车的例子,汽车类 Car 包含了引擎 Engine 类,Engine 类又分为汽油引擎 PetrolEngine 和柴油引擎 DieselEngine。那么一辆汽油引擎的汽车就应该如下。

public class Car {
    private Engine mEngine;

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

在汽车类 Car 中需要自己创建引擎类 Engine,并且 Car 还需要指定 Engine 的实现方法,也就是实现类 PetrolEngine 的存在。此时如果 Engine 的类型变为柴油引擎 DieselEngine,就需要修改汽车类 Car 的构造方法。可以看出 Car 和 Engine 的耦合度是非常高的,这时候就应该使用依赖注入。

4.1、构造方法注入

public class Car {
    private Engine mEngine;

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

4.2、Setter 方法注入

public class Car {
    private Engine mEngine;

    public void setEngine(Engine engine) {
        this.mEngine = engine;
    }
}

4.3、接口注入

public interface ICar {
    void setEngine(Engine engine);
}
public class Car implements ICar {
    private Engine mEngine;

    @Override
    public void setEngine(Engine engine) {
        this.mEngine = engine;
    }
}

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

五、依赖注入框架

5.1、ButterKnife

github 地址

参考博客,我这里没写完整。

ButterKnife 从严格意义讲不算是依赖注入框架,它只是专注于 Android 系统的 View 注入框架,并不支持其他方面的注入。

5.1.1、配置 Gradle

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    implementation 'com.jakewharton:butterknife:10.2.3'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3'

5.1.2、绑定控件

绑定单个控件。

public class MainActivity extends AppCompatActivity {
    @BindView(R.id.tvText)
    TextView tvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        tvText.setText("测试文本");
    }
}

同时绑定多个控件。

public class MainActivity extends AppCompatActivity {
    @BindViews({R.id.tvText, R.id.tvText1, R.id.tvText2})
    List<TextView> textViews;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        textViews.get(0).setText("测试文本");
        textViews.get(1).setText("测试文本1");
        textViews.get(2).setText("测试文本2");
    }
}

5.1.3、绑定资源

// 绑定string里面array数组
@BindArray(R.array.city )
String[] citys ;

// 绑定图片资源为Bitmap
@BindBitmap(R.mipmap.wifi )
Bitmap bitmap;

// 绑定boolean值
@BindBool 

// 绑定color
@BindColor(R.color.colorAccent)
int black;

// 绑定Dimen
@BindDimen(R.dimen.borth_width)
int mBorderWidth;

// 绑定Drawable
@BindDrawable(R.drawable.test_pic)
Drawable mTestPic;

// 绑定float
@BindFloat 

// 绑定int
@BindInt 

// 绑定一个String id为一个String变量
@BindString(R.string.app_name )
String meg;

5.1.4、绑定监听

    @OnClick(R.id.tvText)
    public void showToast() {
        Toast.makeText(this, "onClick", Toast.LENGTH_SHORT).show();
    }

    @OnLongClick(R.id.tvText)
    public void setText(TextView textView) {
        textView.setText("长按事件");
    }
    @OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.BEFORE_TEXT_CHANGED)
    void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }
    @OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.TEXT_CHANGED)
    void onTextChanged(CharSequence s, int start, int before, int count) {

    }
    @OnTextChanged(value = R.id.nameEditText, callback = OnTextChanged.Callback.AFTER_TEXT_CHANGED)
    void afterTextChanged(Editable s) {

    }
    @OnTouch({R.id.tv_back, R.id.Rl_my_info})
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN: //手指按下
                break;
            case MotionEvent.ACTION_MOVE: //手指移动(从手指按下到抬起 move多次执行)
                break;
            case MotionEvent.ACTION_UP: //手指抬起
                break;
        }
        return false;
    }

5.2、Dagger2

github 地址

5.1.1、注解使用方法

一、配置 gradle

    implementation 'com.google.dagger:dagger:2.23.2'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.23.2'

二、@Inject 和 @Component

现在有一个手表类 Watch,里面有一个工作方法 work,如果要在 MainActivity 中调用 work 方法,使用注解前,应该如下。

public class Watch {
    public void work() {
        Log.d("TAG", "手表工作");
    }
}
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 注解,@Inject 注解用来标记需要注入的依赖。首先改写 Watch 类,在构造方法上添加 @Inject,表示 Dagger2 可以使用 Watch 构造方法构建对象。

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

    public void work() {
        Log.d("TAG", "手表工作");
    }
}

接下来用 @Component 注解来完成依赖注入。需要定义一个接口,可以理解为注入器,它会把目标类 MainActivity 依赖的实例注入到目标类中,接口中定义 inject 方法,传入需要注入依赖的目标类 MainActivity。接口命名建议:目标类名+Component,在编译后 Dagger2 会自动生成 Dagger+目标类名+Component 的辅助类。在这里会生成 DaggerMainActivityComponent,最后调用辅助类中的 inject 方法来完成注入。

@Component
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
public class MainActivity extends AppCompatActivity {
    @Inject
    Watch watch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainActivityComponent.create().inject(this);
        watch.work();
    }
}

三、@Module 和 @Providers

如果项目中使用了第三方的类库,比如 Gson,就不能再用上面的方法,因为 @Inject 不能应用到 Gson 的构造方法中。这时候可以采用 @Module 和 @Provides 来处理。

  implementation 'com.google.code.gson:gson:2.8.6'

首先创建一个 GsonModule 类。将 @Module 标注在类上,用来告诉 Component,可以从这个类中获取依赖对象,也就是 Gson 类。@Provides 标注在方法上,表示可以通过这个方法获取依赖对象的实例。@Module 标注的类可以理解为一个工厂,用来生成各种类,@Provides 标注的方法,就是用来生成这些类的实例的。

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

接着编写 Component 类。和之前不同,这里加上了 modules = GsonModule.class,用来指定 Module。并且 Component 中可以指定多个 Module。

@Component(modules = GsonModule.class)
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
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 json = "{'name': '张三','age': 20}";
        Person person = gson.fromJson(json, Person.class);
        Log.d("TAG", person.getName());
    }
}

还有一种情况,如果需要注入的对象是抽象的,则 @Inject 也无法使用,因为抽象的类并不能实例化,这时也可以采用 @Module 和 @Provides。

public abstract class AWatch {
    public abstract void work();
}
public class Watch extends AWatch {
    @Override
    public void work() {
        Log.d("TAG", "手表工作");
    }
}
@Module
public class WatchModule {
    @Provides
    public AWatch provideWatch() {
        return new Watch();
    }
}
@Component(modules = WatchModule.class)
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
public class MainActivity extends AppCompatActivity {
    @Inject
    AWatch watch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainActivityComponent.create().inject(this);
        watch.work();
    }
}

四、@Named 和 @Qualifier

@Qualifier 是限定符,@Named 是 @Qualifier 的一种实现。当有两个相同的依赖时,它们都继承同一个父类或者均实现同一个接口,当它们被提供给高层时,Component 就不知道到底要提供哪一个依赖对象了,因为它找到了两个。比如在上面的例子中,手表如果分电子表和机械表,那么 WatchModule 改写如下。

@Module
public class WatchModule {
    @Provides
    public AWatch provideDzWatch() {
        return new DzWatch();
    }

    @Provides
    public AWatch provideJxWatch() {
        return new JxWatch();
    }
}

这时候编译的话,Dagger2 就会报错,因为我们提供了多个 Provides,Component 不知道要选哪个。此时就可以使用 @Named,给不同的 Provides 定义不同的 @Named,并在使用时指定要采用哪种 Provides。

@Module
public class WatchModule {
    @Provides
    @Named("DzWatch")
    public AWatch provideDzWatch() {
        return new DzWatch();
    }

    @Provides
    @Named("JxWatch")
    public AWatch provideJxWatch() {
        return new JxWatch();
    }
}
public class MainActivity extends AppCompatActivity {
    @Inject
    @Named("DzWatch")
    AWatch watch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainActivityComponent.create().inject(this);
        watch.work();
    }
}

@Named 传递的值只能是字符串,而@Qualifier 则更加灵活一些,@Qualifier 不是直接标记在属性上的,而是用来自定义注解的。分别定义两个注解 @DianZi 和 @JiXie。

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface DianZi {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface JiXie {
}

修改 WatchModule,最后在应用的时候用定义的注解来指定采用哪种依赖。

@Module
public class WatchModule {
    @Provides
    @DianZi
    public AWatch provideDzWatch() {
        return new DzWatch();
    }

    @Provides
    @JiXie
    public AWatch provideJxWatch() {
        return new JxWatch();
    }
}
public class MainActivity extends AppCompatActivity {
    @Inject
    @DianZi
    AWatch dzWatch;

    @Inject
    @JiXie
    AWatch jxWatch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DaggerMainActivityComponent.create().inject(this);
        dzWatch.work();
        jxWatch.work();
    }
}

五、@Singleton 和 @Scope

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

    @Inject
    Gson gson;
    @Inject
    Gson gson1;

gson 和 gson1 的内存地址不同,也就是新创建了两个 Gson。如果我们想让 Gson 在 MainActivity 中是单例,可以使用 @Singleton。首先在 GsonModule 中添加 @Singleton,再在 MainActivityComponent 中添加 @Singleton,最后打印 Gson 的 hashCode 值,这时候就会发现值是相同的。

@Module
public class GsonModule {
    @Singleton
    @Provides
    public Gson provideGson() {
        return new Gson();
    }
}
@Singleton
@Component(modules = GsonModule.class)
public interface MainActivityComponent {
    void inject(MainActivity activity);
}
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);
        Log.d("TAG", gson.hashCode() + "/" + gson1.hashCode());
    }
}

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

查看 @Singleton 的源码可以看出,它其实就是 @Scope 标识的注解。为了使 Gson 变为全局单例,我们可以用 @Scope 结合 Application 来实现,当然也可以用 @Sington 结合 Application 来实现,只不过用 @Scope 可以自定义注解名称,会更加灵活。首先定义 @ApplicationScope 注解。

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

接来下在 GsonModule 中使用。

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

为了处理多个 Activity,我们将 MainActivityComponent 改为 ActivityComponent,并使用 @ApplicationScope 注解。

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

    void inject(SecondActivity activity);
}

创建 App 类继承自 Application,用来提供 ActivityComponent 实例,注意 App 需要在 AndroidManifest 中注册。

public class App extends Application {
    private ActivityComponent activityComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        activityComponent = DaggerActivityComponent.builder().build();
    }

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

    ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

最后在 MainActivity 和 SecondActivity 中分别使用依赖注入。

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);
        App.get(MainActivity.this).getActivityComponent().inject(this);
        Log.d("TAG", gson.hashCode() + "/" + gson1.hashCode());

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this, SecondActivity.class));
            }
        });
    }
}
public class SecondActivity extends AppCompatActivity {
    @Inject
    Gson gson;

    @Inject
    Gson gson1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        App.get(SecondActivity.this).getActivityComponent().inject(this);
        Log.d("TAG", gson.hashCode() + "/" + gson1.hashCode());
    }
}

运行程序可以发现两个 Activity 中 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。还是手表的例子,创建手表类。

public abstract class AWatch {
    public abstract void work();
}
public class Watch extends AWatch {
    @Override
    public void work() {
        Log.d("TAG", "手表工作");
    }
}

创建 WatchModule 和 WatchComponent。

@Module
public class WatchModule {
    @Provides
    public AWatch provideWatch() {
        return new Watch();
    }
}
@Component(modules = WatchModule.class)
public interface WatchComponent {
    AWatch getWatch();
}

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

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

    void inject(SecondActivity activity);
}

随后在此前定义的 App 中引入 WatchComponent。

public class App extends Application {
    private ActivityComponent activityComponent;

    @Override
    public void onCreate() {
        super.onCreate();
        activityComponent = DaggerActivityComponent.builder()
                .watchComponent(DaggerWatchComponent.builder().build())
                .build();
    }

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

    ActivityComponent getActivityComponent() {
        return activityComponent;
    }
}

最后我们在 SecondActivity 中使用。

public class SecondActivity extends AppCompatActivity {
    @Inject
    AWatch watch;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        App.get(SecondActivity.this).getActivityComponent().inject(this);
        watch.work();
    }
}

5.1.2、懒加载

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

public class SecondActivity extends AppCompatActivity {
    @Inject
    Lazy<AWatch> watchLazy;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        App.get(SecondActivity.this).getActivityComponent().inject(this);
        AWatch watch = watchLazy.get();
        watch.work();
    }
}

5.3、Dagger Hilt

参考地址1
参考地址2
视频地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
A powerful library that manage Fragment for Android!为"单Activity + 多Fragment","多模块Activity 多Fragment"架构而生,简化开发,轻松解决动画、嵌套、事务相关等问题。为了更好的使用和了解该库,推荐阅读下面的文章:Fragment全解析系列(一):那些年踩过的坑Fragment全解析系列(二):正确的使用姿势Demo演示:均为单Activity 多Fragment,第一个为简单流式demo,第二个为仿微信交互的demo(全页面支持滑动退出),第三个为仿知乎交互的复杂嵌套demo下载APK   特性1、可以快速开发出各种嵌套设计的Fragment App2、悬浮球/摇一摇实时查看Fragment的栈视图Dialog,降低开发难度3、增加启动模式、startForResult等类似Activity方法4、类似Android事件分发机制的Fragment回退方法:onBackPressedSupport(),轻松为每个Fragment实现Back按键事件5、提供onSupportVisible()等生命周期方法,简化嵌套Fragment的开发过程; 提供统一的onLazyInitView()懒加载方法6、提供 Fragment转场动画 系列解决方案,动态更换动画7、提供Activity作用域的EventBus辅助类,Fragment通信更简单、独立(需要使用EventBusActivityScope库)8、支持SwipeBack滑动边缘退出(需要使用Fragmentation_SwipeBack库)      如何使用1. 项目下app的build.gradle中依赖:// appcompat-v7包是必须的,v1.1.9兼容v4-27.0.0 compile 'me.yokeyword:fragmentation:1.1.9' // 如果不想继承SupportActivity/Fragment,自己定制Support,可仅依赖: // compile 'me.yokeyword:fragmentation-core:1.1.9' // 如果想使用SwipeBack 滑动边缘退出Fragment/Activity功能,完整的添加规则如下: compile 'me.yokeyword:fragmentation:1.1.9' // swipeback基于fragmentation, 如果是自定制SupportActivity/Fragment,则参照SwipeBackActivity/Fragment实现即可 compile 'me.yokeyword:fragmentation-swipeback:1.1.9' // Activity作用域的EventBus,更安全,可有效避免after onSavenInstanceState()异常 compile 'me.yokeyword:eventbus-activity-scope:1.1.0' // Your EventBus's version compile 'org.greenrobot:eventbus:{version}'2. Activity继承SupportActivity:// v1.0.0开始,不强制继承SupportActivity,可使用接口+委托形式来实现自己的SupportActivity public class MainActivity extends SupportActivity {     @Override     protected void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         setContentView(...);         // 建议在Application里初始化         Fragmentation.builder()              // 显示悬浮球 ; 其他Mode:SHAKE: 摇一摇唤出   NONE:隐藏              .stackViewMode(Fragmentation.BUBBLE)              .debug(BuildConfig.DEBUG)              ... // 更多查看wiki或demo              .install();         if (findFragment(HomeFragment.class) == null)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值