美文网首页
Dagger2使用学习笔记

Dagger2使用学习笔记

作者: 乌龟爱吃肉 | 来源:发表于2017-02-07 14:40 被阅读0次

    注:本文是小生自学时做(fanyi)的笔记,可能含有少儿不宜的误导性内容,学习Dagger请移步原博。
    原博地址:http://frogermcs.github.io/

    Dagger2使用:
    需求:主页上有一个TextView,在TextView中显示人名和年龄
    使用Dagger2做依赖注入,需要创建两样东西:
    1.Component接口: 想象成是注射器的针管,单词的含义貌似跟作用完全没关系啊,待我查查看。Component类使用 @Component(modules = {xxx.class}) 注解。
    UserComponent:

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

    2.Module: 想象是注射器的针筒。注意,Module不是提供的依赖(注射液),而是提供依赖的组件(针筒)。Module类使用 @Module 注解,提供依赖的方法用 @Provides 标识,方法名以 provide 开头。

    @Module
    public class UserModule {
    
        UserModule() {}
    
        @Provides
        UserModel provideUsers() {
            UserModel user = new UserModel();
            user.setName("lala");
            user.setAge(18);
            return user;
        }
    }
    

    以上就是我们为了使用Dagger需要额外添加的东西,针筒加针管,还缺少:1.注射器的活塞。2.需要注射的液体。3.接受注射的病人。

    1.注射器的活塞
    也就是用来真正实施注入的东西。Dagger会根据注解生成一个实现了MembersInjector接口的类。MembersInjector直译过来就是成员注射器,我们把它看成是注射器的活塞。此接口只有一个方法,就是 void injectMembers(T instance).调用生成类的 injectMembers 方法即相当于推动活塞的动作,真正实施注射。对于这个东西我们不需要添加额外的代码,它由Dagger在预编译时期自动生成。

    2.需要注射的液体
    这就是我们需要注入的依赖了。它可以是任何可以实例化的东西。
    UserModel:

    //UserModel没有添加任何额外的东西
    public class UserModel {
    
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    3.接受注射的病人
    也就是接收依赖的需求者。我们用 @inject 注解来标识。
    MainActivity:

    //需要注入的依赖使用 @Inject 标记
    public class MainActivity extends AppCompatActivity {
        private TextView textView;
    
        @Inject
        UserModel user;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            textView = (TextView) findViewById(R.id.text_view);
    
            //进行真正的注入操作
            DaggerUserComponent.builder()
                    .userModule(new UserModule())
                    .build()
                    .inject(this);
    
            textView.setText("Name:" + user.getName() + "::Age:" + user.getAge());
        }
    }
    

    效果:


    successInject.png

    咦,这么看起来整个实现还是比较麻烦的。直接在MainActivity new一个user出来不就完了,为什么要用Dagger绕这么大一圈。
    先看看什么是依赖。
    在MainActivity需要一个UserModel的实例,此时,我们称MainActivity对UserModel有依赖,如果我们要改UserModel,比如在构造函数里就传进去姓名和年龄,而不通过set方法。此时,我们就要更改MainActivity中的代码,这里的MainActivity和UserModel是高耦合的。
    而使用Dagger后,即使UserModel有了改变,我们只要改一下Module里的provide方法即可,而不用动MainActivity的代码,此时,MainActivity和UserModel是低耦合的。真真是在平坦的路面上曲折前行啊...

    上面是使用Dagger做一个最基本的依赖注入。下面来详解一下各个部分。

    1. @Inject 注解

    构造函数注入

    public class LoginActivityPresenter {
        
        private LoginActivity loginActivity;
        private UserDataStore userDataStore;
        private UserManager userManager;
        
        @Inject
        public LoginActivityPresenter(LoginActivity loginActivity,
                                      UserDataStore userDataStore,
                                      UserManager userManager) {
            this.loginActivity = loginActivity;
            this.userDataStore = userDataStore;
            this.userManager = userManager;
        }
    }
    

    直接在构造函数上加 @Inject 注解,所有的参数都从Module里获取。同时,这个类也可以被用于注入。如下:

    public class LoginActivity extends BaseActivity {
    
        @Inject
        LoginActivityPresenter presenter;
        
        //...
    }
    

    限制是一个类中只能有一个构造函数有 @Inject 注解。

    成员注入

    public class SplashActivity extends AppCompatActivity {
        
        @Inject
        LoginActivityPresenter presenter;
        @Inject
        AnalyticsManager analyticsManager;
        
        @Override
        protected void onCreate(Bundle bundle) {
            super.onCreate(bundle);
            getAppComponent().inject(this);
        }
    }
    

    也就是上面我们使用的注入方式,这种方式比较好理解,不过需要手动调用注入才能完成注入过程。注入前该成员一直都是null。
    限制是被注入成员不能是private的,因为Dagger生成的代码是这么注入的:
    splashActivity.analyticsManager = analyticsManagerProvider.get();
    这个后面分析生成代码的时候会谈到。

    方法注入

    public class LoginActivityPresenter {
        
        private LoginActivity loginActivity;
        
        @Inject 
        public LoginActivityPresenter(LoginActivity loginActivity) {
            this.loginActivity = loginActivity;
        }
    
        @Inject
        public void enableWatches(Watches watches) {
            watches.register(this);    //Watches instance required fully constructed LoginActivityPresenter
        }
    }
    

    被注解的方法的所有参数都由Module提供。当被注入方需要当前类的实例(this)时可以用这种方式把自己传给被注入方。注入方法会在构造函数调用完毕后立马被调用。

    @Module 注解
    用来标注那些提供依赖的类,Dagger通过这个注解来找到提供依赖的类。

    @Module
    public class GithubApiModule {
        
        @Provides
        @Singleton
        OkHttpClient provideOkHttpClient() {
            OkHttpClient okHttpClient = new OkHttpClient();
            okHttpClient.setConnectTimeout(60 * 1000, TimeUnit.MILLISECONDS);
            okHttpClient.setReadTimeout(60 * 1000, TimeUnit.MILLISECONDS);
            return okHttpClient;
        }
    
        @Provides
        @Singleton
        RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
            RestAdapter.Builder builder = new RestAdapter.Builder();
            builder.setClient(new OkClient(okHttpClient))
                   .setEndpoint(application.getString(R.string.endpoint));
            return builder.build();
        }
    }
    

    @Provides 注解
    标注返回依赖的方法。

    @Module
    public class GithubApiModule {
        
        //...
        
        @Provides   //This annotation means that method below provides dependency
        @Singleton
        RestAdapter provideRestAdapter(Application application, OkHttpClient okHttpClient) {
            RestAdapter.Builder builder = new RestAdapter.Builder();
            builder.setClient(new OkClient(okHttpClient))
                   .setEndpoint(application.getString(R.string.endpoint));
            return builder.build();
        }
    }
    

    @Component 注解
    标注Module与Inject之间的桥梁接口。在这个接口中定义从哪个module获取依赖,也用于定义那些Module可以用于注入,以及可以注入对象到哪里。
    这个例子表示:当前Component使用两个Modules,可以注入依赖到GithubClientApplication,可以使三个依赖公开可见:

    @Singleton
    @Component(
        modules = {
            AppModule.class,
            GithubApiModule.class
        }
    )
    public interface AppComponent {
    
        void inject(GithubClientApplication githubClientApplication);
    
        Application getApplication();
    
        AnalyticsManager getAnalyticsManager();
    
        UserManager getUserManager();
    }
    

    Component也可以依赖别的Component,也可以有自己的生命周期(详见下面的Scope)

    @ActivityScope
    @Component(      
        modules = SplashActivityModule.class,
        dependencies = AppComponent.class
    )
    public interface SplashActivityComponent {
        SplashActivity inject(SplashActivity splashActivity);
    
        SplashActivityPresenter presenter();
    }
    

    @Scope 注解

    @Scope
    public @interface ActivityScope {
    }
    

    用于定义自定义作用域注解。有点类似于单例,不同的是,单例的作用域是整个application,而自定义作用域可以自定义(特么废话么)。下面还会详解Scope,这里按下不表。先说好,Scope是拿来干这个的:保持对象的单一实例。

    然后附赠一些不咋用,不太重要的东西:

    @MapKey 注解
    用于定义依赖的集合。
    定义:

    @MapKey(unwrapValue = true)
    @interface TestKey {
        String value();
    }
    

    提供依赖:

    @Provides(type = Type.MAP)
    @TestKey("foo")
    String provideFooKey() {
        return "foo value";
    }
    
    @Provides(type = Type.MAP)
    @TestKey("bar")
    String provideBarKey() {
        return "bar value";
    }
    

    使用:

    @Inject
    Map<String, String> map;
    
    map.toString() // => „{foo=foo value, bar=bar value}”
    

    @Qualifier 注解
    给高阶程序猿(强迫症)准备的,用于为继承自同一个接口的依赖打 TAG 来区分他们。比如有两个Adapter继承自同一个Adapter接口,你还想让代码看上去整齐有型,就这么干。
    命名依赖:

    @Provides
    @Singleton
    @GithubRestAdapter  //Qualifier
    RestAdapter provideRestAdapter() {
        return new RestAdapter.Builder()
            .setEndpoint("https://api.github.com")
            .build();
    }
    
    @Provides
    @Singleton
    @FacebookRestAdapter  //Qualifier
    RestAdapter provideRestAdapter() {
        return new RestAdapter.Builder()
            .setEndpoint("https://api.facebook.com")
            .build();
    }
    

    注入依赖:

    @Inject
    @GithubRestAdapter
    RestAdapter githubRestAdapter;
    
    @Inject
    @FacebookRestAdapter
    RestAdapter facebookRestAdapter;
    

    @Singleton 注解(Java自带)
    Dagger不单可以注入实例,还可以注入单例。当年老大要咱们做单元测试,结果因为项目里各种单例用得太乱导致大量代码处于不可测的状态。一开始注意到Dagger也是因为有了Dagger,就不需要写那种很难Mock对象的单例了。
    我们更改一下一开始的示例的界面,加一个TextView,MainActivity如下:

    public class MainActivity extends AppCompatActivity {
        private TextView textView1;
        private TextView textView2;
    
        @Inject
        UserModel user1;
    
        @Inject
        UserModel user2;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            textView1 = (TextView) findViewById(R.id.text_view_1);
            textView2 = (TextView) findViewById(R.id.text_view_2);
    
            DaggerUserComponent.builder()
                    .userModule(new UserModule())
                    .build()
                    .inject(this);
            user2.setName("Stark");
            user2.setAge(10);
            textView1.setText("Name:" + user1.getName() + "::Age:" + user1.getAge());
            textView2.setText("Name:" + user2.getName() + "::Age:" + user2.getAge());
        }
    }
    

    可以看到这里我注入了两个UserModel的实例,在注入后重新设置user2的name和Age,然后显示在两个TextView上。结果如图:


    differentName.png

    可以看到,上面的user1还是我在Module中创建的UserModel实例,user2是另一个独立的对象,所以它们之间互不干扰。
    好,下面我们改动一丢丢Component和Module的代码,如下:

    @Component(modules = {UserModule.class})
    @Singleton
    public interface UserComponent {
        void inject(MainActivity mainActivity);
    }
    
    @Module
    public class UserModule {
    
        UserModule() {}
    
        @Provides
        @Singleton
        UserModel provideUsers() {
            UserModel user = new UserModel();
            user.setName("lala");
            user.setAge(18);
            return user;
        }
    }
    

    为Component加了 @Singleton 的类注解,为Module创建相应实例的方法加了同样的 @Singleton 注解,再运行一下看看。


    singleton.png

    恩,就是这么简单,我只更改了user2的数据,结果user1也更改了,因为它们指向同一个对象。就这样,成功使用Dagger注入了单例。且这个单例的代码是可测的,因为它的实例很容易Mock。

    Scope 详解
    Scope,直译过来是范围、圈子的意思。就像前面提到的,在Dagger中,Scope用来保证在指定作用域中,拿到的依赖对象是唯一的(指定范围内是单例)。比如,我有一个登录系统,用户登录后,一直到登出前,拿到的UserModel就应该是单例。此时,我可以自定义一个UserScope的作用域,即实现一个 @UserScope 注解,用此注解标识的Component在指定的范围内(从用户登录到用户登出)注入的一定是同一个实例。有没有很爽的感觉。。。
    Dagger是没有默认自带的各种Scope的,什么 @ActivityScope 啊,@ApplicationScope 啊统统没有,只有Java自带的一个@Singleton,也就是上面讲到的那个,它与Apllication处于同一作用域,从App开启到结束只有一个实例。那么下面我们来看看如何自定义一个 Scope 注解。

    Scope的实现在Dagger2里头就是对Component做正确的配置工作。有两种方式实现自定义Scope,使用 @Subcomponent 注解 或 使用 Components 依赖。这两种实现方式最终出来的结果是不同的,使用 @Subcomponent 注解的话,子域可以获取到父域的所有依赖;而如果使用 Components 依赖,则子域只能获取到父域通过Component接口暴露出来的依赖。

    先来看看使用 @Subcomponent 注解的方式实现:

    @Singleton
    @Component(
            modules = {
                    AppModule.class,
                    GithubApiModule.class
            }
    )
    public interface AppComponent {
    
        UserComponent plus(UserModule userModule);
    
        SplashActivityComponent plus(SplashActivityModule splashActivityModule);
    
    }
    

    可以看到,在AppComponent接口中有两个plus方法,这两个方法的意思是,我们可以从APPComponent中创建两个子Component: UserComponent 和 SplashActivityComponent. 因为它们是AppComponent的子Component,所以都可以获取到AppModule和GithubApiModule产生的实例。
    规则:返回类型是子Component类型,方法名任性着来,参数是子Component所需的就行。

    可以看到,UserComponent需要另一个Module,该Module被当做plus方法的参数传入。这样,我们我们用新Module产生的额外对象拓展了AppComponent可注入的依赖表。UserComponent如下:

    @UserScope
    @Subcomponent(
            modules = {
                    UserModule.class
            }
    )
    public interface UserComponent {
        RepositoriesListActivityComponent plus(RepositoriesListActivityModule repositoriesListActivityModule);
    
        RepositoryDetailsActivityComponent plus(RepositoryDetailsActivityModule repositoryDetailsActivityModule);
    }
    

    @UserScope 注解肯定是自定义的咯:

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

    在UserComponent中,可以创建另外两个子Component: RepositoriesListActivityComponent 和 RepositoryDetailsActivityComponent.
    重要的东西在这儿呢。所有从UserComponent拿到的从AppComponent继承下来的实例都是单例(范围是Application)。而那些UserModule自己产生的实例都是“本地单例”,其周期就是UserComponent存在的周期。
    所以,每次我们调用UserComponent userComponent = appComponent.plus(new UserComponent(user));时,从userComponent拿到的对象都是不同的实例。
    对于这个我们自定义的UserScope,它的生命周期当然也得咱们自己维护,要维护它的初始化和销毁。

    public class GithubClientApplication extends Application {
    
        private AppComponent appComponent;
        private UserComponent userComponent;
    
        //...
    
        public UserComponent createUserComponent(User user) {
            userComponent = appComponent.plus(new UserModule(user));
            return userComponent;
        }
    
        public void releaseUserComponent() {
            userComponent = null;
        }
    
        //...
    }
    

    当我们从网络获取到User对象时,调用createUserComponent方法,UserScope从此时开始。当RepositoriesListActivity finish时,调用releaseUserComponent终结UserScope。

    到此为止,对于Dagger的使用已经学习地差不多了。但是只知道怎么用怎么能满足咱们欲求不满的内心呢?所以下一篇要赏析一下Dagger自动生成的代码,来看一下整个注入流程是如何走通的。

    相关文章

      网友评论

          本文标题:Dagger2使用学习笔记

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