Google官方MVP+Dagger2架构详解【从零开始搭建android框架系列(6)】

144
作者 CameloeAnthony
2016.06.02 12:59* 字数 6894

更多及时技术资讯,欢迎关注我的微博 :Anthony

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

1 前言

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


google官方示例架构项目


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

参考实际项目,请使用命令“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)是用于削减计算机程序的耦合问题的一个法则。对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
Dagger2 正是一个依赖注入框架,使用代码自动生成创建依赖关系需要的代码。减少很多模板化的代码,更易于测试,降低耦合,创建可复用可互换的模块。

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 创建单例(singleton)

接下来一步一步的分析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,Gson,Retrofit和Gson做一个Twitter 客户端的网络访问。

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();

可以看到上面使用缓存cache用到了Application的context,这也是在android中使用非常多的上下文对象。
我们的第一个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;
    }
}

和上面类似,下面这段代码我们进行了了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 的大致流程梳理了一遍。

那么你就会好奇这个注解类是怎么完成整个注入的呢?(也就是说这个关联过程)

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 {
    // downstream components need these exposed with the return type
    // method name does not really matter
    Retrofit retrofit();
}

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的使用。欢迎查看