Dagger2 入门笔记

作者: 业志陈 | 来源:发表于2018-07-08 22:13 被阅读122次

    网上对 Dagger2 进行介绍的文章也已经很多了,一开始看的时候却总是有种从入门到放弃的感觉,因为 Dagger2 中注解的配套使用是需要一定规则的,而文章介绍得并不算太详细,如果搭配不当,Dagger2 是不会为我们生成相应的文件的,这就导致应用在编译时总是遇到各种报错,然后就一脸蒙蔽,所以这就需要很多的实践操作了

    这里我就将本人在学习 Dagger2 的过程中的实践记录下来,希望对你有所帮助

    一、配置

    dependencies {
        implementation 'com.google.dagger:dagger:2.16'
        annotationProcessor 'com.google.dagger:dagger-compiler:2.16'
    }
    

    二、@Inject

    假设当前有一个 Person 类,其声明如下所示

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:21
     * 描述:
     */
    public class Person {
    
        private String name;
    
        public Person() {
            name = "person default name";
        }
    
        public Person(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    

    在一般情况下,如果我们要使用到一个 Person 变量,就需要以如下方式来声明

        Person person = new Person();
    

    而这隐藏着一个问题,就是没当 Person 类的构造函数发生了变化时(参数变多或变少),所有使用到 Person 的代码就需要都修改一遍,这对于较大的项目来说是一件很耗时耗力的工作,Dagger2 就是用来解决这一问题的依赖注入框架

    首先为 Person 的构造函数添加 @Inject 注解,指定 Dagger2 在为我们初始化 Person 变量时要调用的构造函数

    public class Person {
        
        @Inject
        public Person() {
            name = "person default name";
        }
        
        ···
            
    }
    

    此外,还需要一个接口来作为 Person 和需要进行依赖注入的类之间的桥梁

    此处即为 PersonComponent 接口,该接口需要使用 @Component 进行注解,且包含一个方法用于将需要使用到依赖注入的类对象传递进来,此外为 MainActivity,注意此处需要是确切的对象,而不能是任何父类对象

    此外,接口名和方法名没有硬性规定

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    @Component
    public interface PersonComponent {
    
        void inject(MainActivity mainActivity);
    
    }
    

    接下来就可以在 MainActivity 中进行依赖注入了

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class MainActivity extends AppCompatActivity {
    
        private static final String TAG = "MainActivity";
    
        @Inject
        Person person1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerPersonComponent.builder().build().inject(this);
    
            Log.e(TAG, "person1: " + person1);
            Log.e(TAG, "person1 name : " + person1.getName());
        }
    
    }
    

    在运行前需要先 build 工程,这样 DaggerPersonComponent 类才会生成,运行结果如下所示

    person1: com.leavesc.dagger2samples.test1.Person@420a5ee0
    person1 name : person default name
    

    使用 @Inject 进行注解的 person1 变量我们并没有对其进行初始化,但是应用在运行时并没有报空指针异常,说明 Dagger2 在后台为我们进行初始化操作了

    实际进行初始化操作的是以下代码

        DaggerPersonComponent.builder().build().inject(this);
    

    DaggerPersonComponent 是 Dagger2 依照 PersonComponent 的命名而生成的文件,可以点进去看下其源码

    DaggerPersonComponent 实现了 PersonComponent 接口,在为 person1 赋值时是直接调用了 Person 类的无参构造函数。因为 MainActivity_MembersInjector 是依靠 MainActivity 对象引用到 person1 变量,因此在 person1 不能声明为私有的,否则引用不到 person1 也就无法实现依赖注入了

    public final class DaggerPersonComponent implements PersonComponent {
      private DaggerPersonComponent(Builder builder) {}
    
      public static Builder builder() {
        return new Builder();
      }
    
      public static PersonComponent create() {
        return new Builder().build();
      }
    
      @Override
      public void inject(MainActivity mainActivity) {
        injectMainActivity(mainActivity);
      }
    
      private MainActivity injectMainActivity(MainActivity instance) {
        MainActivity_MembersInjector.injectPerson1(instance, new Person());
        return instance;
      }
    
      public static final class Builder {
        private Builder() {}
    
        public PersonComponent build() {
          return new DaggerPersonComponent(this);
        }
      }
    }
    
    
    public final class MainActivity_MembersInjector implements MembersInjector<MainActivity> {
        
      ···
    
      public static void injectPerson1(MainActivity instance, Person person1) {
        instance.person1 = person1;
      }
    }
    

    三、@Module、@Provides

    @Inject 注解在用于工程中自己建立的类时是可行的,但面对工程中依赖到的各种开源库却无能为力了,因为我们无法修改它们的构造函数,此时就需要用到 @Module@Provides 注解

    假设当前有个 User 类来自于项目中依赖到的开源库中,此时该类的构造函数并没有添加 @Inject 注解

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:11
     * 描述:
     */
    public class User {
    
        private String name;
    
        public User() {
            name = "user default name";
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    

    新建 UserModule 类用于对外部提供 User 类的实例,@Provides 注解用于告诉 Dagger2 ,如果需要 User 类的实例就调用此方法来获取

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:12
     * 描述:
     */
    @Module
    public class UserModule {
    
        @Provides
        public User provideUser() {
            return new User();
        }
    
    }
    

    此时一样需要一个 Component 类来作为依赖注入的入口,并为 @Component 注解提供注解值 UserModule.class

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:14
     * 描述:
     */
    @Component(modules = {UserModule.class})
    public interface UserComponent {
    
        void inject(Main2Activity mainActivity);
    
    }
    
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        User user1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().build().inject(this);
            Log.e(TAG, "user1: " + user1);
            Log.e(TAG, "user1 name : " + user1.getName());
        }
    
    }
    

    运行结果如下所示

    user1: com.leavesc.dagger2samples.test2.User@420cb218
    user1 name : user default name
    

    四、带有参数的依赖对象

    修改 User 类,为之添加一个带有参数的构造函数

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:11
     * 描述:
     */
    public class User {
    
        private String name;
    
        public User() {
            name = "user default name";
        }
    
        public User(String name) {
            this.name = name;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    

    假设我们在 UserModule 中要调用的是 User 的有参构造函数,那此时就需要通过 UserModule 的构造函数从外部向它传入字符串参数了

    此处也不直接将成员变量 name 传给 provideUser() 方法,而是新建一个 provideName() 方法用于实现依赖注入,这也是为了尽量解耦

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:12
     * 描述:
     */
    @Module
    public class UserModule {
    
        private String name;
    
        public UserModule(String name) {
            this.name = name;
        }
    
        @Provides
        public String provideName() {
            return name;
        }
    
        @Provides
        public User provideUser(String name) {
            return new User(name);
        }
    
    }
    

    由于之前 UserModule 只有无参构造函数,所以在使用 DaggerUserComponent 进行注入时无需显式传入 UserModule 对象,此时 UserModule 的构造函数需要传入参数了,所以现在只能显示调用 userModule() 方法传入 UserModule 对象

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        User user1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this);
            Log.e(TAG, "user1: " + user1);
            Log.e(TAG, "user1 name : " + user1.getName());
        }
    
    }
    

    运行结果如下所示

    user1: com.leavesc.dagger2samples.test2.User@420c4a30
    user1 name : leavesC
    

    五、@Singleton

    假设在 Main2Activity 中有两个 User 对象需要进行实例化,按照以上的使用方式,在依赖注入时是会为每个不同的变量重新 new 一个实例的

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        User user1;
    
        @Inject
        User user2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this);
            Log.e(TAG, "user1: " + user1);
            Log.e(TAG, "user1 name : " + user1.getName());
            Log.e(TAG, "user2: " + user2);
            Log.e(TAG, "user2 name : " + user2.getName());
        }
    
    }
    

    运行结果如下所示,可以看到 user1 和 user2 的内存地址并不相同

    user1: com.leavesc.dagger2samples.test2.User@420cb780
    user1 name : leavesC
    user2: com.leavesc.dagger2samples.test2.User@420cba90
    user2 name : leavesC
    

    而为了实现单例模式,此处需要使用到 @Singleton 注解

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:12
     * 描述:
     */
    @Module
    public class UserModule {
    
        private String name;
    
        public UserModule(String name) {
            this.name = name;
        }
    
        @Provides
        public String provideName() {
            return name;
        }
    
        @Provides
        public User provideUser(String name) {
            return new User(name);
        }
    
    }
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:14
     * 描述:
     */
    @Component(modules = {UserModule.class})
    public interface UserComponent {
    
        void inject(Main2Activity mainActivity);
    
    }
    

    此处重新运行应用,就可以看到 user1 和 user2 的内存地址是相同的了

    user1: com.leavesc.dagger2samples.test2.User@420c86d8
    user1 name : leavesC
    user2: com.leavesc.dagger2samples.test2.User@420c86d8
    user2 name : leavesC
    

    六、@Named

    由于 User 类有两个构造函数,有时候我们也需要指定要由哪个构造函数来初始化 User,此时就需要用到 @Named 注解

    修改 UserModule 类,增加 provideUser2() 方法,并为 provideUser2()provideUser2() 方法声明 @Named 注解,注解值用于配对需要实现依赖注入的成员变量,只要成员变量声明的 @Named 注解的属性值与这两个方法的某个注解值相等,就会依赖该方法来初始化成员变量

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 17:12
     * 描述:
     */
    @Module
    public class UserModule {
    
        private String name;
    
        public UserModule(String name) {
            this.name = name;
        }
    
        @Provides
        public String provideName() {
            return name;
        }
    
        @Provides
        @Singleton
        @Named("no empty")
        public User provideUser(String name) {
            return new User(name);
        }
    
        @Provides
        @Singleton
        @Named("empty")
        public User provideUser2() {
            return new User();
        }
    
    }
    
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        @Named("no empty")
        User user1;
    
        @Inject
        @Named("empty")
        User user2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this);
            Log.e(TAG, "user1: " + user1);
            Log.e(TAG, "user1 name : " + user1.getName());
            Log.e(TAG, "user2: " + user2);
            Log.e(TAG, "user2 name : " + user2.getName());
            startActivity(new Intent(this, Main3Activity.class));
        }
    
    }
    

    运行结果如下所示

    user1: com.leavesc.dagger2samples.test2.User@420cd128
    user1 name : leavesC
    user2: com.leavesc.dagger2samples.test2.User@420cd438
    user2 name : user default name
    

    七、@Qualifier

    先看下注解 @Named 的声明,该注解就使用到了 @Qualifier

    @Qualifier
    @Documented
    @Retention(RUNTIME)
    public @interface Named {
    
        /** The name. */
        String value() default "";
    }
    

    由于注解 @Named通过比较字符串的相等性来实现配对的,出错的可能性并不算低,而且也不够优雅,此时就可以通过 @Qualifier 来自己实现同样的功能

    声明两个注解,用来表示在初始化 User 变量时是调用哪个构造函数

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 20:34
     * 描述:
     */
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserWithoutParameter {
    
    }
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 20:34
     * 描述:
     */
    @Qualifier
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UserWithParameter {
    
    }
    

    然后直接替换 @Named 即可实现与之相同的功能

        @Provides
        @Singleton
        //@Named("no empty")
        @UserWithParameter
        public User provideUser(String name) {
            return new User(name);
        }
    
        @Provides
        @Singleton
        //@Named("empty")
        @UserWithoutParameter
        public User provideUser2() {
            return new User();
        }
    
        @Inject
        //@Named("no empty")
        @UserWithParameter
        User user1;
    
        @Inject
        //@Named("empty")
        @UserWithoutParameter
        User user2;
    

    八、延迟加载

    Dagger2 也支持延迟加载,在需要的时候才对成员变量进行初始化,需要依赖于泛型接口 Lazy

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        Lazy<User> user3;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this);
          
            User user = user3.get();
            Log.e(TAG, "user3-1: " + user);
            Log.e(TAG, "user3 name-1 : " + user.getName());
        }
    
    }
    
    

    九、强制加载

    Dagger2 支持在每次获取成员变量值时都返回一个重新初始化的对象,除非你使用了 @Singleton 注解要求只实例化一次

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main2Activity extends AppCompatActivity {
    
        private static final String TAG = "Main2Activity";
    
        @Inject
        Provider<User> user4;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerUserComponent.builder().userModule(new UserModule("leavesC")).build().inject(this);
    
            User user = user4.get();
            Log.e(TAG, "user4-1: " + user);
            Log.e(TAG, "user4 name-1 : " + user.getName());
            user = user4.get();
            Log.e(TAG, "user4-2: " + user);
            Log.e(TAG, "user4 name-2 : " + user.getName());
            user = user4.get();
            Log.e(TAG, "user4-2: " + user);
            Log.e(TAG, "user4 name-2 : " + user.getName());
        }
    
    }
    
    

    运行结果如下所示,可以看到 user 变量的内存地址每次各不相同

        user4-1: com.leavesc.dagger2samples.test2.User@420cad48
        user4 name-1 : Hello
        user4-2: com.leavesc.dagger2samples.test2.User@420cb148
        user4 name-2 : Hello
        user4-2: com.leavesc.dagger2samples.test2.User@420cb548
        user4 name-2 : Hello
    

    十、组件间的依赖

    假设现在有个需求,在多个地方中都需要获取系统服务 LocationManager,而获取 LocationManager 是需要通过 Context 来获取的,为了避免需要重复传递 Context 对象,此时就可以选择通过组件间的依赖将 Context 的获取方法移交给另外的 Component

    LocationManager locationManager = (LocationManager)this.getSystemService(Context.LOCATION_SERVICE);
    

    首先,通过 **ApplicationModule **和 ApplicationComponent 来统一对外提供 Context

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 19:25
     * 描述:
     */
    @Module
    public class ApplicationModule {
    
        private Context context;
    
        public ApplicationModule(Context context) {
            this.context = context;
        }
    
        @Provides
        public Context provideContext() {
            return context;
        }
    
    }
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 19:26
     * 描述:
     */
    @Component(modules = {ApplicationModule.class})
    public interface ApplicationComponent {
    
        Context getContext();
    
    }
    

    然后在 Application 类中实现依赖注入,使得对外提供的 Context 对象统一都是 ApplicationContext

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 19:45
     * 描述:
     */
    public class RealApplication extends Application {
    
        public static ApplicationComponent applicationComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
            applicationComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
        }
    
    }
    

    通过注解值 dependencies 来指定 ActivityComponent 需要的 Context 要从哪个 Component 中获取

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 19:33
     * 描述:
     */
    @Component(dependencies = {ApplicationComponent.class}, modules = {ActivityModule.class})
    public interface ActivityComponent {
    
        void inject(Main3Activity mainActivity);
    
    }
    
    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 19:27
     * 描述:
     */
    @Module
    public class ActivityModule {
    
        @Provides
        LocationManager provideLocationManager(Context context) {
            return (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        }
    
    }
    

    然后,在需要 LocationManager 的地方就可以通过 DaggerActivityComponent 来间接获取,而无需直接依赖于 Context 对象

    /**
     * 作者:叶应是叶
     * 时间:2018/7/8 16:35
     * 描述:
     */
    public class Main3Activity extends AppCompatActivity {
    
        private static final String TAG = "Main3Activity";
    
        @Inject
        LocationManager locationManager;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            DaggerActivityComponent.builder().applicationComponent(RealApplication.applicationComponent).build().inject(this);
            Log.e(TAG, "locationManager: " + locationManager);
        }
    
    }
    

    此外也提供上述所有示例代码:Dagger2入门笔记

    相关文章

      网友评论

        本文标题:Dagger2 入门笔记

        本文链接:https://www.haomeiwen.com/subject/gqsauftx.html