美文网首页android相关android框架学习第三方框架的学习
Google官方MVP+Dagger2架构详解【从零开始搭建an

Google官方MVP+Dagger2架构详解【从零开始搭建an

作者: 程序员Anthony | 来源:发表于2016-06-02 12:59 被阅读56352次

    博客原地址:http://www.jianshu.com/p/01d3c014b0b1

    1 前言

    前段时间分享了一篇文章:google官方架构MVP解析与实战 ,针对这是对google官方示例架构(googlesamples/android-architecture)的一个分支todo-mvp/ 的项目解析与实际运用

    google官方示例架构项目

    在我的前一篇文章分享的时候,当时todo-mvp-dagger/ 这个分支也还没有开发完毕。最近的项目中也在用到Dagger2 作为依赖注入,所以通过这个项目一起来学习下,mvp+Dagger2 的实现吧。

    git 分支切换:参考实际项目,请使用命令“git clone
    https://github.com/googlesamples/android-architecture.git” 将项目clone到本地,当前是master分支,需要使用“git checkout todo-mvp-dagger” 切换到todo-mvp-dagger分支。


    2 Dagger2基础

    以下Dagger2基础部分主要是对参考资料里面的几篇外文链接的知识点的整合,所以翻译的语句可能有些生硬,在适当的地方会出现英文原文。
    原文章链接(70%来自于下面的原文,做出了适当修改):
    Dependency Injection with Dagger 2

    2.1 什么是Dagger2

    面向对象编程经常需要处理各种依赖关系。安卓开发也不例外,比如说网络访问中使用Retrofit,Gson,本地存储中使用shared preference。无一例外,我们都都需要在使用它们的地方进行实例对象构建,对象之间可能还存在着各种各样的依赖关系。

    依赖注入(Dependency Injection,简称DI)是用于削减计算机程序的耦合问题的一个法则。对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

    在J2EE后台开发中,当前比较知名的依赖注入框架有:Pico Container、Avalon 、Spring、JBoss、HiveMind、EJB等。

    Dagger2 (A fast dependency injector for Android and Java. http://google.github.io/dagger)正是一个安卓和Java的依赖注入框架,使用代码自动生成创建依赖关系需要的代码。减少很多模板化的代码,更易于测试,降低耦合,创建可复用可互换的模块。

    2.2 Dagger2的优点

    • 全局对象实例的简单访问方式
      和ButterKnife 库定义了view,事件处理以及资源的引用一样,Dagger2 提供全局对象引用的简易访问方式。声明了单例的实例都可以使用@inject进行访问。比如下面的MyTwitterApiClient 和SharedPreferences 的实例:
    public class MainActivity extends Activity {
       @Inject MyTwitterApiClient mTwitterApiClient;
       @Inject SharedPreferences sharedPreferences;
    
       public void onCreate(Bundle savedInstance) {
           // assign singleton instances to fields
           InjectorClass.inject(this);
       } 
    
    • 复杂的依赖关系只需要简单的配置
      Dagger2 会通过依赖关系并且生成易懂易分析的代码。以前通过手写的大量模板代码中的对象引用将会由它给你创建并传递到相应对象中。因此你可以更多的关注模块中构建的内容而不是模块中的对象实例的创建顺序。
    • 让单元测试和集成测试更加方便
      因为依赖关系已经为我们独立出来,所以我们可以轻松的抽取出不同的模块进行测试。依赖的注入和配置独立于组件之外。因为对象是在一个独立、不耦合的地方初始化,所以当注入抽象方法的时候,我们只需要修改对象的实现方法,而不用大改代码库。依赖可以注入到一个组件中:我们可以注入这些依赖的模拟实现,这样使得测试更加简单。
    • 作用域实例(Scoped instances)
      我们不仅可以轻松的管理全局实例对象,也可以使用Dagger2中的scope定义不同的作用域。(比如根据user session,activity的生命周期)

    2.3 Dagger2的引用

    • 在整个项目的build.gradle中加入:
     dependencies {
         // other classpath definitions here
         classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
     }
    
    • app/build.gradle中分别加入:
    // add after applying plugin: 'com.android.application'  
    apply plugin: 'com.neenbedankt.android-apt'
    
    dependencies {
        // apt command comes from the android-apt plugin
        apt 'com.google.dagger:dagger-compiler:2.2'
        compile 'com.google.dagger:dagger:2.2'
        provided 'javax.annotation:jsr250-api:1.0'
    }
    

    需要注意的是provided代表编译时需要的依赖,Dagger的编译器生成依赖关系的代码,并在编译时添加到IDE 的class path中,只参与编译,并不会打包到最终的apk中。apt是由android-apt插件提供,它并不会添加这些类到class path中,这些类只用于注解解析,编写代码的时候应当避免使用这些类。

    2.4 Dagger2的使用,依赖注入流程

    Dagger2 中使用了很多注解,接下来一步一步的分析Dagger2的使用,先来一张表和一张图把Dagger2中的注解讲解一下。如果有点不清晰,请接着往下看,然后再回来看一遍。

    注解 用法
    @Module Modules类里面的方法专门提供依赖,所以我们定义一个类,用@Module注解,这样Dagger在构造类的实例的时候,就知道从哪里去找到需要的 依赖。modules的一个重要特征是它们设计为分区并组合在一起(比如说,在我们的app中可以有多个组成在一起的modules)
    @Provide 在modules中,我们定义的方法是用这个注解,以此来告诉Dagger我们想要构造对象并提供这些依赖。
    @Singleton 当前提供的对象将是单例模式 ,一般配合@Provides一起出现
    @Component 用于接口,这个接口被Dagger2用于生成用于模块注入的代码
    @Inject 在需要依赖的地方使用这个注解。(你用它告诉Dagger这个 构造方法,成员变量或者函数方法需要依赖注入。这样,Dagger就会构造一个这个类的实例并满足他们的依赖。)
    @Scope Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。

    没看懂?接着往下看

    看看Dagger2 的流程:


    Dagger2 流程

    首先看看下面这段代码,我们需要使用Okhttp,Retrofit和Gson做一个Twitter 客户端的网络访问,如果我们需要在onCreate中加载,我们可能会这样初始化。

    public class MainActivity extends Activity {
    
    OkHttpClient client = new OkHttpClient();
    
    // Enable caching for OkHttp
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(getApplication().getCacheDir(), cacheSize);
    client.setCache(cache);
    
    // Used for caching authentication tokens
    SharedPreferences sharedPrefeences = PreferenceManager.getDefaultSharedPreferences(this);
    
    // Instantiate Gson
    Gson gson = new GsonBuilder().create();
    GsonConverterFactory converterFactory = GsonConverterFactory.create(Gson);
    
    // Build Retrofit
    Retrofit retrofit = new Retrofit.Builder()
                                    .baseUrl("https://api.github.com")
                                    .addConverterFactory(converterFactory)
                                    .client(client)  // custom client
                                    .build();
    
    public void onCreate(Bundle savedInstance) {
        retrofit.xxx();
        sharedPrefeences.xxx();
        gson.xxx()
     } 
    } 
    

    如果我们使用Dagger2完成上述过程呢?

    首先我们要创建几个Dagger模块(Module),我们的第一个Dagger模块(Module)AppModule.java(使用@Module进行类注解),将会提供Application 的context引用。我们使用@Provides注解告诉Dagger providesApplication()这个方法是Application的实例的提供者。使用@Singleton注解告诉Dagger整个生命周期中只会被初始化一次。

    @Module
    public class AppModule {
    
        Application mApplication;
    
        public AppModule(Application application) {
            mApplication = application;
        }
    
        @Provides
        @Singleton
        Application providesApplication() {
            return mApplication;
        }
    }
    

    和上面类似构建第二个模块(Module),下面这段代码我们进行了Gson,Cache,OkHttpClient以及Retrofit 的实例化,这些方法的返回类型都会在定义到依赖关系(依赖表 dependency graph)中。在这里我们需要关注的是三个注解的@Module,@Provides,@Singleton的定义位置。

    @Module
    public class NetModule {
    
        String mBaseUrl;
    
        // Constructor needs one parameter to instantiate.  
        public NetModule(String baseUrl) {
            this.mBaseUrl = baseUrl;
        }
    
        // Dagger will only look for methods annotated with @Provides
        @Provides
        @Singleton
        // Application reference must come from AppModule.class
        SharedPreferences providesSharedPreferences(Application application) {
            return PreferenceManager.getDefaultSharedPreferences(application);
        }
    
        @Provides
        @Singleton
        Cache provideOkHttpCache(Application application) { 
            int cacheSize = 10 * 1024 * 1024; // 10 MiB
            Cache cache = new Cache(application.getCacheDir(), cacheSize);
            return cache;
        }
    
       @Provides 
       @Singleton
       Gson provideGson() {  
           GsonBuilder gsonBuilder = new GsonBuilder();
           gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
           return gsonBuilder.create();
       }
    
       @Provides
       @Singleton
       OkHttpClient provideOkHttpClient(Cache cache) {
          OkHttpClient client = new OkHttpClient();
          client.setCache(cache);
          return client;
       }
    
       @Provides
       @Singleton
       Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
          Retrofit retrofit = new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create(gson))
                    .baseUrl(mBaseUrl)
                    .client(okHttpClient)
                    .build();
            return retrofit;
        }
    }
    

    可以看到我们通过@Module标注类,@Provide@Singleton标注方法完成了这些对象实例的创建。那么我们怎么获取这些对象实例呢?

    Dagger2通过@inject注解提供了实例的获取,通过调用@inject会让Dagger2 在依赖关系(依赖表 dependency graph)中找到对应的实例对象并赋值给该字段。比如下面的例子就会返回MyTwitterApiClient,SharedPreferences的实例对象。

    public class MainActivity extends Activity {
       @Inject MyTwitterApiClient mTwitterApiClient;
       @Inject SharedPreferences sharedPreferences;
    
      public void onCreate(Bundle savedInstance) {
           // assign singleton instances to fields
           InjectorClass.inject(this);
       } 
    

    上面的Module类都会需要一个context,有的时候是Activity context,有的时候是Application context,所以上面完成了提供 和使用实例 。

    可以看到上面通过InjectorClass.inject(this)把当前activity对象注入到InjectorClass,那么InjectorClass是什么呢?正是这个关联过程。

    在Dagger2 中 ,注入类(injector class)被称作组件(Component),我们通过inject方法传递activity,service或者fragment对象到注入类component中。比如下面这个类。我们通过@Component注解当前类,并且把之前的两个模块AppModule.class, NetModule.class也添加到component中。(Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁。

    @Singleton
    @Component(modules={AppModule.class, NetModule.class})
    public interface NetComponent {
       void inject(MainActivity activity);
       // void inject(MyFragment fragment);
       // void inject(MyService service);
    }
    

    到这里我们就把Dagger2 的大致流程梳理了一遍。

    提供(@Module)<->关联(@Component)<->使用(@Inject)

    依赖注入,这让我想起了小时候最怕的打针。就好像打针过程一样,我们有了药物(提供的实例),你的身体生病了需要药物(使用这个实例),我们需要注射器把药物注入你的身体里面。(关联这个实例)
    那么你就会好奇这个注解类是怎么完成整个注入的呢?(也就是说这个关联过程)

    Dagger2中很重要的一点就是它会为@Component注解的类生成代码。它会在类的前面添加上Dagger前缀(比如上面的类就会生成DaggerNetComponent .java),也就是这个类负责初始化依赖关系(依赖表 dependency graph)中的实例,并为注解了@Inject 的字段执行注入操作。接着往下看。

    2.5 初始化组件(Instantiating the component)

    初始化组件操作应当在Application中进行操作,因为这些实例在整个application生命周期中只会被实例化一次。

    public class MyApp extends Application {
    
        private NetComponent mNetComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            // Dagger%COMPONENT_NAME%
            mNetComponent = DaggerNetComponent.builder()
                    // list of modules that are part of this component need to be created here too
                    .appModule(new AppModule(this)) // This also corresponds to the name of your module: %component_name%Module
                    .netModule(new NetModule("https://api.github.com"))
                    .build();
    
            // If a Dagger 2 component does not have any constructor arguments for any of its modules,
            // then we can use .create() as a shortcut instead:
            //  mAppComponent = com.codepath.dagger.components.DaggerNetComponent.create();
        }
    
        public NetComponent getNetComponent() {
           return mNetComponent;
        }
    }
    

    可以看到的是我们直接使用NetComponent生成的类DaggerNetComponent并且生成的方法appModulenetModule完成了两个对应module的初始化。

    因为这里我们继承了Application并作出了修改,所以需要在AndroidManifest.xml中作出修改如下。

    <application
          android:allowBackup="true"
          android:name=".MyApp">
    

    在activity中,我们需要获取component并且调用inject()方法。注意需要将获取的Application强制转换为MyApp。这也完成了上面InjectorClass.inject(this);代码的替换。

    public class MyActivity extends Activity {
      @Inject OkHttpClient mOkHttpClient;
      @Inject SharedPreferences sharedPreferences;
    
      public void onCreate(Bundle savedInstance) {
            // assign singleton instances to fields
            // We need to cast to `MyApp` in order to get the right method
            ((MyApp) getApplication()).getNetComponent().inject(this);
        } 
    
    到这里就完成了整个Dagger2的依赖注入流程.

    Dagger2的使用还有一些注意点。下面进行讲解。包括下面的限定类型,作用域,组建依赖,以及子组件。

    2.6 限定类型(Qualified types)

    Dagger 修饰符
    如果对于不同的对象有同样的返回类型,我们可以使用@Named修饰符注解。你需要在提供单例的地方(@Provides注解)和注入的地方(@Inject注解)都使用@Named注解。
    比如,对于同样的返回OkHttpClient ,这里提供不同的方法,和java中多态一样,只不过这里需要额外通过@Named注解来标注:
    @Provides @Named("cached")
    @Singleton
    OkHttpClient provideOkHttpClient(Cache cache) {
        OkHttpClient client = new OkHttpClient();
        client.setCache(cache);
        return client;
    }
    
    @Provides @Named("non_cached") 
    @Singleton
    OkHttpClient provideOkHttpClient() {
        OkHttpClient client = new OkHttpClient();
        return client;
    }
    
    @Inject @Named("cached") OkHttpClient client;
    @Inject @Named("non_cached") OkHttpClient client2;
    

    如下,@Named是在Dagger中预先定义好的修饰符,你也可以创建自己的修饰符注解。关于自定义注解,我之前的一篇文章【译】从java注解分析ButterKnife工作流程有所提及。

    @Qualifier
    @Documented
    @Retention(RUNTIME)
    public @interface DefaultPreferences {
    }
    

    2.7 作用域(Scopes)

    dagger 作用域

    Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。@Singleton是被Dagger预先定义的作用域注解( scope annotation )。没有指定作用域的@Provides方法将会在每次注入的时候都创建新的对象。同样的,你也可以定义自己的Scope注解。

    @Scope
    @Documented
    @Retention(value=RUNTIME)
    public @interface MyActivityScope
    

    你可以在官方文档中找到这样一段文字

    /** * In Dagger, an unscoped component cannot depend on a scoped component. As
     * {@link edu.com.app.injection.component.ApplicationComponent} is a scoped component ({@code @Singleton}, we create a custom 
    * scope to be used by all fragment components. Additionally, a component with a specific scope
     * cannot have a sub component with the same scope. */
    

    也就是说一个没有scope的组件component不可以以来一个有scope的组件component。子组件和父组件的scope不能相同。我们通常的ApplicationComponent都会使用Singleton注解,也就会是说我们如果自定义component必须有自己的scope。在下面组件依赖中会再次提及。

    2.8 组件依赖(Component Dependencies)

    dagger 依赖

    上面的例子我们创建了application的全局单例.如果我们想在内存中总是拥有多个组件(例如在activity和fragment生命周期,用户登录注册创建的component),我们可以使用组件依赖(Component Dependencies),使用组件依赖有下面几个考虑点:

    • 两个依赖的组件不能共享作用域,比如两个组件不能共享@Singleton作用域。这个限制产生的原因看这里。依赖的组件需要定义自己的作用域。
    • 尽管Dagger2 有创建作用域实例的能力,你也需要创建和删除引用来满足行为的一致性。Dagger2 不会知道任何底层的实现。可以看看Stack Overflow 的这个 讨论
    • 当创建依赖组件的时候,父组件需要显示的暴露对象给子组件。比如子组件需要知道Retrofit 对象,也就需要显示的暴露出来。
    @Singleton
    @Component(modules={AppModule.class, NetModule.class})
    public interface NetComponent {
    //依赖于当前component的component会得到Retrofit实例,这里的方法名不固定
        Retrofit retrofit();
    }
    

    这里参考我自己项目的ActivityComponent,依赖于ApplicationComponent,所以这里我们会自定义ScopePerActivity。也可以看到的是@ActivityContext会在component和module中得到使用。参考下面三段代码。

    @PerActivity
    @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class)
    public interface ActivityComponent {
    
        void inject(MainActivity mainActivity);
    
      //......
    
        void inject(SettingsFragment settingsFragment);
    
    }
    
    
    @Documented
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface PerActivity {
    }
    
    @Module
    public class ActivityModule {
        private Activity mActivity;
    
        public ActivityModule(Activity activity) {
            mActivity = activity;
        }
    
        @Provides
        Activity provideActivity() {
            return mActivity;
        }
    
        @Provides
        @ActivityContext
        Context providesContext() {
            return mActivity;
        }
    }
    
    

    2.9 子组件(Subcomponents)

    dagger 子组件
    除了依赖关系,也可以使用子组件进行对象关系(对象表/图 object graph)继承。和组件之间添加依赖关系一样,子组件也有自己的生命周期,也会在所有对其应用不在的时候被垃圾回收,也有同样的作用域限制。区别于组件依赖的不同点主要是:
    • 需要在父组件的接口中声明(在接口中定义的方法对于生成的对象是可访问的。)。
    • 能够获取父组件的所有元素(不仅仅是在接口中声明的元素)。
      比如下面这段代码:
    @Module
    public class MyActivityModule {
        private final MyActivity activity;
        public MyActivityModule(MyActivity activity) { this.activity = activity; }
    
        @Provides @MyActivityScope @Named("my_list")
        public ArrayAdapter providesMyListAdapter() {
            return new ArrayAdapter<String>(activity, android.R.layout.my_list);
        }
        ...
    }
    
    @MyActivityScope
    @Subcomponent(modules={ MyActivityModule.class })
    public interface MyActivitySubComponent {
        @Named("my_list") ArrayAdapter myListAdapter();
    }
    
    @Singleton
    @Component(modules={ ... })
    public interface MyApplicationComponent {
        MyActivitySubComponent newMyActivitySubcomponent(MyActivityModule activityModule);
    }
    

    在上面的例子中,子组件的实例在每次我们调用newMyActivitySubcomponent()的时候都会被创建。使用子模块去注入一个activity:

    public class MyActivity extends Activity {
      @Inject ArrayAdapter arrayAdapter;
    
      public void onCreate(Bundle savedInstance) {
            // assign singleton instances to fields
            // We need to cast to `MyApp` in order to get the right method
            ((MyApp) getApplication()).getApplicationComponent())
                .newMyActivitySubcomponent(new MyActivityModule(this))
                .inject(this);
        } 
    }
    

    最后再来梳理一下Dagger2 中的一些注意点:

    • Components从根本上来说就是一个注入器,也可以说是@Inject和@Module的桥梁。 Components可以提供所有定义了的类型的实例,比如:我们必须用@Component注解一个接口然后列出所有的@Modules组成该组件,如 果缺失了任何一块都会在编译的时候报错。@Component接口定义了对象提供者(module)和对象之间的联系,也表述了一种依赖关系。
    • 由于Dagger2使用生成的代码去访问字段,所以字段使用了Dagger2 是不允许标注为private的。
    • Dagger2 基于JSR 330(为了最大程度的提高代码的复用性、测试性和维护性,java的依赖注入为注入类中的使用定义了一整套注解(和接口)标准。Dagger1和Dagger2(还有Guice)都是基于这套标准,给程序带来了稳定性和标准的依赖注入方法。)
    • 使用@inject注解表示依赖关系可以用于三个地方。构造函数,字段或者方法中)。
    • Dagger2会在编译时通过apt生成代码进行注入。

    以后的开发中,那么多需要使用实例的地方,只需要简简单单地来一个@inject,而不需要关心是如何注入的。Dagger2让你爱不释手。
    那么接下来我们分析官方架构Dagger2 又是怎么使用的吧?


    3 google官方MVP架构回顾


    上一篇文章google官方架构MVP解析与实战 中,我们分析到整个项目是按照功能模块进行划分(addedittask,statistics,taskdetail,tasks四个模块)并且将数据和工具类分别提取到data和util包中。我们对taskdetial模块进行了分析。这里提取上一篇文章中的结论

    3.1 官方MVP实例,通过协议类XXXContract来对View和Presenter的接口进行内部继承。是对BaseView和BasePresenter的进一步封装,所以我们实现的View和Presenter也只需要继承XXXContract中的对应内部接口就行。这也是一个非常不错的方式管理MVP中的view和presenter。(局限在于XXXContract 以接口的形式进行提供,所以它的内部类view和presenter都不能做一些公共初始化操作,只能以接口形式提供给子类实现。)

    3.2 activity的作用主要是创建MVP中View(这里是相应的fragment),以及创建presenter,并把view和presenter绑定。(在实际开发中可以灵活运用,activity,fragment以及自定义view都可以作为MVP中的view使用。)

    3.3 在presenter的实现类的构造函数中,通过view的setPresenter,让view获得了presenter实例。这样view中就可以对Presenter中的方法进行操作了。

    3.4 在presenter的实现类中,可以对Model数据(这里的TaskRespository)进行操作。实例中,数据的获取、存储、数据状态变化都是model层的任务,presenter会根据需要调用该层的数据处理逻辑并在需要时将回调传入。这样model、presenter、view都只处理各自的任务,此种实现确实是单一职责最好的诠释。


    4 Google官方架构MVP+Dagger2架构详解

    4.1 对比

    这里我们接着MVP项目讲解MVP+Dagger2项目,也是对taskdetial模块做出分析。


    通过上图我们可以看到,这里添加了四个个类文件,分别是全局的ApplicationModuleToDoApplication。以及对应XXX模块中的XXXComponentXXXPresenterModule。其他模块也类似。

    4.2 分析

    • 首先看看ToDoApplication,提供了TasksRepositoryComponent的初始化。
    public class ToDoApplication extends Application {
    
        private TasksRepositoryComponent mRepositoryComponent;
    
        @Override
        public void onCreate() {
            super.onCreate();
    
            mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
                    .applicationModule(new ApplicationModule((getApplicationContext())))
                    .tasksRepositoryModule(new TasksRepositoryModule()).build();
        }
    
        public TasksRepositoryComponent getTasksRepositoryComponent() {
            return mRepositoryComponent;
        }
    }
    
    

    DaggerTasksRepositoryComponent 是由Dagger2生成的代码。我们通过它来初始化TasksRepositoryComponent。并且可以看到的是ApplicationModuleTasksRepositoryModule也在这里进行了一次性初始化。TasksRepository需要说明的是整个数据model层的核心。

    • 来看看ApplicationModule
    @Module
    public final class ApplicationModule {
    
        private final Context mContext;
    
        ApplicationModule(Context context) {
            mContext = context;
        }
        @Provides
        Context provideContext() {
            return mContext;
        }
    }
    

    可以看到的是这里需要的是一个application context 的实例,也就是我们在上面ToDoApplicationonCreate中初始化的时候传入的getApplicationContext()。它最终会提供一个通过provideContext()方法提供一个Context实例。

    • 来看看TasksRepositoryModule
    @Module
    public class TasksRepositoryModule {
    
        @Singleton
        @Provides
        @Local
        TasksDataSource provideTasksLocalDataSource(Context context) {
            return new TasksLocalDataSource(context);
        }
    
        @Singleton
        @Provides
        @Remote
        TasksDataSource provideTasksRemoteDataSource() {
            return new FakeTasksRemoteDataSource();
        }
    }
    

    这是用于mock测试的一个类,里面的两个方法分别表示本地数据和远程数据,最终返回的都是TasksDataSource。mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。这里对于数据对象直接在这里进行初始化,而不是在所有的用到该数据的地方new一遍。这也就体现了Dagger2的引入对测试是一个极大的便利。

    • 现在回到整个应用的核心TasksRepositoryComponent,也就是在ToDoApplication中初始化的核心类。
    @Singleton
    @Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
    public interface TasksRepositoryComponent {
    
        TasksRepository getTasksRepository();
    }
    

    可以看到这里Dagger2允许我们为Component使用@Singleton来保持单例模式,但是我们在ToDoApplication也再次进行了单例创建,这是必要的一步。同时这里定义的TasksRepositoryModule.classApplicationModule.class 也是在ToDoApplication进行初始化创建的。
    都说Component就是一个注入器,也可以说是@Inject@Module的桥梁。 那么链接了@Module,我们看看是如何链接@Inject的吧?

    • 现在进入对应模块taskdetail模块,首先看看TaskDetailComponent.
    @FragmentScoped
    @Component(dependencies = TasksRepositoryComponent.class, modules = TaskDetailPresenterModule.class)
    public interface TaskDetailComponent {
        
        void inject(TaskDetailActivity taskDetailActivity);
    }
    
    @Documented
    @Scope
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FragmentScoped {
    }
    

    这也就是我们提供注入inject方法的地方。从注解中可以看到依赖于TasksRepositoryComponent.class所以其中的TaskRespository对于当前component是可用的。

    需要注意的是在Dagger中,一个没有作用域(unscoped )的组件不可以依赖有作用域的组件。比如这里的TasksRepositoryComponent作用域为@Singleton。所以我们在这里自定义了一个由所有fragment使用的FragmentScoped。另外,组件有确定作用域,那么依赖它的组件不能有相同的作用域。

    • 接下来看看TaskDetailComponent中定义的模块TaskDetailPresenterModule.class
    @Module
    public class TaskDetailPresenterModule {
    
        private final TaskDetailContract.View mView;
    
        private final String mTaskId;
    
        public TaskDetailPresenterModule(TaskDetailContract.View view, String taskId) {
            mView = view;
            mTaskId = taskId;
        }
    
        @Provides
        TaskDetailContract.View provideTaskDetailContractView() {
            return mView;
        }
    
        @Provides
        String provideTaskId() {
            return mTaskId;
        }
    }
    

    主要是提供MVP中相应模块的View的返回,这在上面一节中提到过,所以可以看到返回类型是TaskDetailContract.View 。也是在这里完成MVP模式中重要的一环,也就是Presenter和View的实例的获取,不然Presenter怎么告诉View怎么更新View呢!

    • 接下来看看Presenter的创建。在上一节中我们就知道了Presenter由TaskDetailActivity进行创建。实际上的MVP中的View是TaskDetailFragment。因为这里是通过view.setPresenter方式完成presenter和view的链接。所以这里不再赘述View中的细节。
    public class TaskDetailActivity extends AppCompatActivity {
        @Inject TaskDetailPresenter mTaskDetailPresenter;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
           ......
    
            if (taskDetailFragment == null) {
                taskDetailFragment = TaskDetailFragment.newInstance(taskId);
    
                ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
                        taskDetailFragment, R.id.contentFrame);
            }
    
            // Create the presenter
            DaggerTaskDetailComponent.builder()
                    .taskDetailPresenterModule(new TaskDetailPresenterModule(taskDetailFragment, taskId))
                    .tasksRepositoryComponent(((ToDoApplication) getApplication())
                    .getTasksRepositoryComponent()).build()
                    .inject(this);
        }
    ......
    }
    
    • 看看TaskDetailPresenter
    final class TaskDetailPresenter implements TaskDetailContract.Presenter {
    
        private TasksRepository mTasksRepository;//Model
    
        private TaskDetailContract.View mTaskDetailView;//View
    
        /**
         * Dagger strictly enforces that arguments not marked with {@code @Nullable} are not injected
         * with {@code @Nullable} values.
         */
        @Nullable String mTaskId;
        /**
         * Dagger strictly enforces that arguments not marked with {@code @Nullable} are not injected
         * with {@code @Nullable} values.
         */
        @Inject
        TaskDetailPresenter(@Nullable String taskId,
                TasksRepository tasksRepository,
                TaskDetailContract.View taskDetailView) {
            mTasksRepository = tasksRepository;
            mTaskDetailView = taskDetailView;
            mTaskId = taskId;
        }
    
        /**
         * Method injection is used here to safely reference {@code this} after the object is created.
         * For more information, see Java Concurrency in Practice.
         */
        @Inject
        void setupListeners() {
            mTaskDetailView.setPresenter(this);
        }
    ...Presenter中的操作...
    }
    
    

    除了Presenter中的操作,这里主要就是有一个@inject标注的方法,构造函数,还有字段。到这里也就完成了MVP中Dagger2 的使用 。还在等什么?赶快将它用到你的项目中吧!


    5 Dagger2添加步骤:

    这里再次总结一下Dagger2添加步骤。

    • step 1:添加android-apt, dagger 2, dagger2-compiler以及javax annotation到build.gradle.(注意他们不都是compile的形式)
    • step 2:添加模块(module),ApplicationModule将会注入Application Context 到需要的类中。
    • step 3:添加组件Component, Dagger2 将会为你创建的所有component生成代码。使用文件名Dagger(Component)的形式。Component可以拥有多个module。(比如DaggerTaskDetailComponent拥有TaskDetailPresenterModule模块)
    • step 4: 继承android.app.Application类,并且在AndroidManifest.xml中声明使用的application类。在它的onCreate()方法中构建主要组件(main component)
           mRepositoryComponent = DaggerTasksRepositoryComponent.builder()
                    .applicationModule(new ApplicationModule((getApplicationContext())))
                    .tasksRepositoryModule(new TasksRepositoryModule()).build();
    
    • step 5: 添加注入方法(inject)到Component 接口中,你需要为每一个参与到依赖注入的类添加inject()方法。(注意在dagger2中:为父类注入的依赖并不会为子类注入依赖关系,为子类注入的依赖关系则可以为父类注入依赖关系)参考上面的TaskDetailPresenter方法。
    • step 6: 注入依赖,用inject,替换你新建对象实例的地方。把这些新建实例的地方移到Modules中并且添加@Provides标注。可以参考上面的 ApplicationModule.java,在使用@Inject,请确保调用Component.inject()方法。可以参考上面的TaskDetailActivity.
    • step 7: (可选,推荐)将getApplicationComponent()移到父类中(一般是指BaseActivity)

    实践出真知,还是希望你可以亲手写写,然后再回过头来看这篇文章。


    6 参考资料:

    示例代码网上真的有很多。推荐大家关注官方标准。我已经在自己的开源项目MVPCommon中添加Dagger2 +MVP的使用。欢迎查看

    相关文章

      网友评论

      • baa453879a3e:从原来的mvc慢慢的把项目改成mvp,以前一个页面一个类就搞定了,现在contract,presenter,view,三个类了,dagger又在原来的基础上增加了Component,Module,如果项目工期比较紧的话不建议大家使用,java就是喜欢弄这些虚而不实的东西了,设计模式不应该简化我们的开发吗?真的希望能出来一个优秀的框架
        return_toLife:感觉是方便维护吧,如果不用维护和扩展,确实不用这么麻烦:innocent:
      • 监控都不:什么dagger不dagger的,就是为了迎合前端新技术,让Android跟前端开发更相似。为了依赖注入而依赖注入真的对开发项目不利,遇到bug,苦死人。
      • shlockfan:看完感觉难度堪比自定义控件......
        def294ec8e5b:没自定义控件难:joy:
      • Duke丶Lee:这可能是我最看不懂的一篇文章了,以前还懂点:joy:
      • 克罗克达尔:小项目写成大项目,java就爱干这事
      • 风雪武者_3867:我感觉这个很难懂,不明觉厉
      • summer_lz:public class TaskDetailActivity extends AppCompatActivity {

        public static final String EXTRA_TASK_ID = "TASK_ID";

        @Inject TaskDetailPresenter mTaskDetailPresenter;

        你好博主,TaskDetailPresenter是在哪个Module中提供的?
      • 碧海鱼龙:大神,模块TaskDetailPresenterModule只需要在构造方法上加上@Inject注解就可以生成实例吗!
      • 碧海鱼龙:provideOkHttpClient方法报Error:(63, 18) 错误: Cannot have more than one @Provides method with the same name in a single module错误,名字不能一样吗!
      • 奈落落:如果你在文章里告诉读者@Inject的作用有3种,相信大家就不会有这么多疑问了。
      • 宋兴超:写的不错,我人生第一次打赏就给楼主了。
      • SavySoda:你好,TasksRepositoryModule这个类在哪里,我怎么找不到啊
      • 60fd15e74895:必须支持下~~~看了别的看完一头雾水 这篇我看了好几天了,边实战边回过头看看
      • 938b0e0dfc52:不错,现在正在学习
      • 3481319ea87f:写的挺不错的。之前看了一些没看懂,看了你的这篇看明白了dagger。但是还是觉得用它挺麻烦的。就像用了mvp挺麻烦的,牺牲了很多时间在代码上,为了就是让代码更灵活。
        3481319ea87f:@CameloeAnthony 是的。
        程序员Anthony:@潇然001 确实,现在用了Dagger2 也有一段时间了。使用起来确实稍显麻烦。 但是代码的耦合度确实降低了。要真正的用到测试中才会发现Dagger2的精妙之处。
      • AlexueQ:棒棒哒
      • EdwdChen:你好,我想请问一下,NetModule中有像provideRetrofit(Gson gson, OkHttpClient okHttpClient) 这样的方法,他的参数gson和okHttpClient应该在哪里传入呢?
        程序员Anthony:@TheGreatEdwardW 同样的是在那个类NetModule里面提供了实例Gson和OkhttpClient
      • 石器时代小古董:非常棒,我看的最详细的dagger2中文资料 :+1:
        石器时代小古董:@CameloeAnthony 我来的太是时候啦 :stuck_out_tongue_winking_eye:
        程序员Anthony:@疯狂橘子侠 真巧 ,我昨天才把这篇文章更新了。添加了许多内容。哈哈
      • MeloDev:等拿出大块时间一定好好读一读你的文章,😃
        程序员Anthony:@MeloDev 哈哈 ,越往前面文章质量越不好,就随便看看就行 :smile:
      • idioitcbear:博主,就拿你这个NetModule来说。如果我现在要程序中更换Retrofit的BaseUrl的话,也就是说,我要更换后台服务器的访问地址。这个已经注入进去的Retrofit对象怎么重新创建?
        idioitcbear:@CameloeAnthony Dagger2 产生单例的原理你有了解过么?有没有一种情况是可以重新注入这个对象的呢?
        idioitcbear:@CameloeAnthony 我大概明白你的意思,可以事实情况比你想的要复杂一些。就是这个server的地址是不可例举的。意思就是地址我是先并不知道。他可能多次更换地址。
        程序员Anthony:@想你0开心 retrofit 的巧妙就是采用接口方式进行网络请求,每次调用接口方法。retrofit 在初始化的时候都是采用builder模式,会要求传入baseUrl。也就是服务器的请求地址,所以初始化一个retrofit对象,也就意味着服务器地址固定了。所以你可以尝试构建不同的retrofit对象。类似于上面章节2.6中,利用 @named 注解,这样就可以同样是返回一个retrofit对象,但是也是在一个NetMoudle中了
      • 02af9b1cbd13:本来还明白点,你一说就全迷糊了
        风雪武者_3867:跟你一样:smile:
      • Sinyuk: :sob: Dagger从入门到放弃再捡起再放弃了....有个问题 上面提到的组件依赖 是这样写的
        @Component(modules={AppModule.class, NetModule.class})
        那dependencies是干嘛的....那个不是依赖吗
        比如我在NetModule里面要用到了一个Application context 然后这个在AppModule中有提供,
        那么我应该这怎么写... :sweat:
        会这样报错
        android.app.Application cannot be provided without an @Inject constructor or from an @Provides-annotated method.
        android.app.Application is injected at
        com.sinyuk.yuk.ApiModule.provideOkHttpClient(context, …)
        idioitcbear:@Sinyuk 你要在component这个接口中,写一个无参的,返回对象是context的一个方法的申明。然后Dagger2下一层的Component才会有context的对象。我觉得,你可以稍微阅读一下Dagger2的生成的代码,然后你就明白了!
      • 接地气的二呆:> 一个没有作用域(unscoped )的组件不可以依赖有作用域的组件。

        这是为啥呢~
        程序员Anthony:@接地气的二呆 官方原话是: In Dagger, an unscoped component cannot depend on a scoped component. As
        * {@link ApplicationComponent} is a scoped component ({@code @singleton}, we create a custom
        scope to be used by all other components. Additionally, a component with a specific scope cannot have a sub component with the same scope.
        举个例子,在全局的ApplicationComponent中,我们会有一个getContext 返回的是全局的context,在依赖ApplicationComponent的ActivityComponent中,我也会有一个getContext 返回Activity的context。 所以同样的方法可以在依赖的组件中得到定义,这里提供不同的scope,正是为了区分这个关系吧!
      • lpnpcs:您好 我之前也对这个做过分析又一个不太明白的地方:
        /**
        * Method injection is used here to safely reference {@code this} after the object is created.
        * For more information, see Java Concurrency in Practice.
        */
        @Inject
        void setupListeners() {
        mTaskDetailView.setPresenter(this);
        }
        这个Inject怎么解释呢 又是如何运行的您能解释一下么? :grin:
        lpnpcs:@D阿艺DCH 这个我知道 我debug了 但是原理不太明白。
        ccbeb2ce36e7:@lpnpcs
        “after the object is created”
        从字面意思,就是TaskDetailPresenter创建完后,就调用该方法
        ccbeb2ce36e7:@lpnpcs 同问!
      • 周公在世:提个小问题 “ @Module”的用法有个“ ) ”没写 :smile:
        程序员Anthony:@周公在世 哈哈。好的,已经修改。看得太仔细了
      • 程序员Anthony:推荐文章Android:dagger2让你爱不释手系列:http://www.jianshu.com/p/65737ac39c44
      • 程序员Anthony:关于在base class 中使用Dagger2 的问题:https://github.com/google/dagger/issues/73
        程序员Anthony:@CameloeAnthony http://stackoverflow.com/questions/29312911/can-i-just-inject-super-class-when-use-dagger2-for-dependency-injection

      本文标题:Google官方MVP+Dagger2架构详解【从零开始搭建an

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