更清晰的Dagger2 + MVP 架构

Dagger2 与 MVP

Dagger2是Google提供的依赖注入框架,依赖注入为Android中组件之间的解耦提供了很好的解决方案。Dagger2已经在越来越多的开源项目中被使用,其已经发展成为未来的一个技术趋势。
MVP设计模式大家也非常熟悉,其概念不在此处赘述。MVP的实现方式非常多样,和Dagger2相结合正式本文要论述和讨论的。

MVP模式将原来一个Activity可以解决的问题,分成了M、V、P三部分,这在项目中会产品大量的类,如果利用Dagger2来进行这些类的有效管理,是本文思考的问题。本文将从Dagger2基础论述,MVP的实现方式以及Dagger2和MVP的结合,三个方面来讨论,希望对大家有用。

Dagger2 基础

Dagger2主要基于注解来实现,刚接触Dagger2的时候都会有这样的困惑:

  • Inject,Component,Module,Provides,Scope,Qualifier是什么鬼?
  • Dagger2如何应用到项目中?

Inject
Inject,即注入,该注解标示地方表示需要通过DI框架来注入实例。Inject有三种方式,分别是Constructor injection、Fields injection、Methods injection。申明了Inject之后,会从注入框架中去查找需要注入的类实例,然后注入进来,也就是通过Component去查找。这三种注入的表现形式如下:

public class SolutionCreatePresenterImpl implements SolutionCreatePresenter {
...
  @Inject
  public SolutionCreatePresenterImpl(Activity activity, StudioApiService apiService, RxBus rxBus, DataCenter dataCenter) {
  this.activity = activity;
   this.apiService = apiService;
   this.rxBus = rxBus;
   ...
 }
}
public class SolutionCreateActivity extends BasePresenterActivity implements SolutionCreateView {
 
 @Inject
 SolutionCreatePresenter solutionCreatePresenter;
 @Inject
 RxBus rxBus;
 @Inject
 DataCenter dataCenter;
 
 @Overrideprotected void onCreate(@Nullable Bundle savedInstanceState  {
  super.onCreate(savedInstanceState);
  component().inject(this);
  ...
 }

}
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
  }
}

Component
Component 要解决的问题就是Inject的实例从哪里来,所以它承担的就是一个连接器的作用。Component需要引用到目标类的实例,Component会查找目标类中用Inject注解标注的属性,查找到相应的属性后会接着查找该属性对应的用Inject标注的构造函数(这时候就发生联系了),剩下的工作就是初始化该属性的实例并把实例进行赋值。

Module
Module 是类实例提供的工厂模式,Module里面的方法基本都是创建类实例的方法。这样,Dagger2中就有2个维度可以创建类实例:

  • 通过用Inject注解标注的构造函数来创建(以下简称Inject维度)
  • 通过工厂模式的Module来创建(以下简称Module维度)

Provides
Provides 用在Module中,声明方法可以返回dependencies,也就是方法可以提供被注入的类实例。

@Module
public class NetApiModule {

  @Provides
  @Singleton
  StudioApiService provideStudioApiService(RestApi restApi) {
    return restApi.retrofitStudio(GlobalConfig.STUDIO_API_BASE_URL).create(StudioApiService.class);
  }

  @Provides
  @Singleton
  RxBus provideRxBus() {
    return RxBus.getInstance();
  }
}

如下所以,NetApiModule提供了StudioAPIService、RxBus类的依赖实例。

Scope
Dagger2中Scope关心的问题就是类实例的生命周期,@ApplicationScope 是希望类实例和Application一样,也就是唯一单例;@ActivityScope则希望类实例和Activity生命周期一致。如此,Scope就有“local singletons” 的概念了。举个例子:AppComponent 用 @Singleton 进行标识,由于AppComponent只在Application中创建一次,这就保证了AppComponent所引用的ApplicationModule 中用 @Singleton 标识的类都是单例;同样的,ActivityComponent 用 @ActivityScope进行标识,每次创建新的Activity都会创建一个ActivityComponent,这就是的ActivityComponent所引用的ActivityModule中用@ActivityScope 标识的类都具有和Activity一样的生命周期。
在后面的讲解中,读者可以去体会Scope标识后的,类实例和Component的相互依赖关系。

Qualifier
如果类实例创建有多种相同的方式,就需要通过标签tag来区分之,并在注入的时候通过标签来区分。

Dagger2如何应用到项目中
Dagger2的使用首先要摆脱传统的类创建模式,即摒弃各种new的使用方式,要有类仓库的概念,所有类的引用采用Inject来注入。其次,要组织好Component,Component是连接器,提供了不同的类注入,有为Application提供的,有为Activity提供的,按照个人经验来讲,Component的划分有一下规则:

  • 要有全局Component, 负责管理整个app的全局类实例, 这些类基本都是单例模式,一般都用@Singleton标识
  • 每个页面对应一个Component,即每个Activity 和 Fragment,谷歌官方Dagger2示例是采用这种方式,这种方式有个不好地方,Component太多
  • 将页面依赖的类抽出,公用一个Component,有特殊需要的时候继承该公有Component
  • 不同类型的Component及Module,采用Scope进行区分

参考链接:
Miroslaw Stanek: http://frogermcs.github.io/dependency-injection-with-dagger-2-the-api/
Google: https://github.com/google/dagger

MVP 实现探讨

MVP的实现方式很多,这里介绍两种:

  1. 以Activity和Fragment作为View(视图层),Presenter管理业务逻辑;
  2. 使用Activity和Fragment作为presenters,View单独抽出处理视图相关。

使用Activity和Fragment作为View

这种方式是比较传统的实现方式,也比较符合我们的习惯,因为之前都是在Activity里面处理View相关,改用MVP模式也只是也Activity里面的业务逻辑抽成Presenter。这里要提的是Google官方MVP也是采用这种方式。
下面说一下我的实现方式,将Presenter和View都抽出一个基类接口,每个Activity和Fragment都分别定义对应的Presenter和View接口,并集成基类接口。具体实现如下:

基类接口View

public interface LoadDataView {
  /**
  * Show a view with a progress_upload bar indicating a loading process.
  */
  void showLoading();

  /**
  * Hide a loading view.
  */
  void hideLoading();

  /**
  * Show a retry view in case of an error when retrieving data.
  */
  void showRetry();

  /**
  * Hide a retry view shown if there was an error when retrieving data.
  */
  void hideRetry();

  /**
  * Show an error message
  *
  * @param message A string representing an error.
  */
  void showError(String message);

  /**
  * Get a {@link Context}.
  */
  Context context();
}

基类接口Presenter

public interface Presenter {
  /**
  * Method that control the lifecycle of the view. It should be called in the view's
  * (Activity or Fragment) onStart() method.
  */
  void start();

  /**
  * Method that control the lifecycle of the view. It should be called in the view's
  * (Activity or Fragment) onDestroy() method.
  */
  void destroy();

  void setView(T view);
}

我们以本文之前用到的SolutionCreatePresenter来讲述具体页面的Presenter定义。

// 接口定义
public interface SolutionCreatePresenter extends
    Presenter, View.OnClickListener, View.OnFocusChangeListener {
  Solution getSolution();
  void setSolution(Solution solution);
}

// 接口实现
public class SolutionCreatePresenterImpl implements SolutionCreatePresenter {
 ...
  @Inject
  public SolutionCreatePresenterImpl(Activity activity, StudioApiService apiService, RxBus rxBus, DataCenter dataCenter) {
    this.activity = activity;
    this.apiService = apiService;
    this.rxBus = rxBus;
    ...
  }
 
  @Override
  public void setView(SolutionCreateView view) {
   this.solutionCreateView = view;
  }
 
  // 继续添加其他 override 方法
}

SolutionCreateActivity 见本文前面的代码,需要implements SoluionCreateView, 并且Inject SolutionPresenter.

使用Activity和Fragment作为Presenters
为何要采用这种方式呢,基于两点来考虑:

  1. Activity Fragment本身就有生命周期的管理,这种管理类似于业务逻辑,所以要归为Presenter;
  2. Activity Fragment生命周期变化时,会带来业务逻辑的变化,直接作为Presenter,可以避免业务逻辑的复杂。

个中意味,读者自己体验体验,当自己有需求时,可以根据这两种方式自由变换。

参考链接:
一种在android中实现MVP模式的新思路:
https://github.com/hehonghui/android-tech-frontier/tree/master/androidweekly/一种在android中实现MVP模式的新思路
Google Samples:
https://github.com/googlesamples/android-architecture

Dagger2 与 MVP 的结合

这一部分,重点介绍Component 和 Module的设计,目的在于更好适应MVP模式。
基本思路:

  1. 全局Component通过AppComponent进行管理,大多设置单例模式;
  2. 将Activity和Fragment Component中通用的抽出,为BaseViewComponent;
  3. 上层PresenterComponent继承BaseViewComponet,DataBandingComponent只能继承android.databinding.DataBindingComponent,但可以将BaseViewModule 包含进来。其他Component使用时可以继承BaseViewComponent。

架构图如下:


更清晰的Dagger2 + MVP 架构_第1张图片
dagger-mvp.jpeg

核心代码如下:

AppComponent

@Singleton
@Component(modules = {AppModule.class, AppManageModule.class})
public interface AppComponent {
  void inject(DajiaApplication app);
}

AppModule

@Module(includes = NetApiModule.class)
public class AppModule {
  private final Application application;

  public AppModule(Application app) {
    application = app;
  }

  @Provides
  @Singleton
  Application provideApplication() {
    return application;
  }

  @Provides
  @Singleton
  Context provideContext() {
    return application;
  }
}

BaseViewComponent

@Scope
@Retention(RUNTIME)
public @interface PerView { 
}
@PerView
@Component(dependencies = AppComponent.class, modules = BaseViewModule.class)
public interface BaseViewComponent {

  Activity activity();

  void inject(AbstractActivity activity);

  void inject(BaseFragment fragment);
}

BaseViewModule

@Module
public class BaseViewModule {

  private final Activity activity;

  public BaseViewModule(Activity activity) {
    this.activity = activity;
  }

  @Provides
  @PerView
  Activity provideActivity() {
    return this.activity;
  }
}

PresenterComponent

@PerView
@Component(dependencies = AppComponent.class, modules = {
    BaseViewModule.class, PresenterModule.class
})
public interface PresenterComponent extends BaseViewComponent {
 void inject(SolutionCreateActivity activity);
}

PresenterModule

@Module
public class PresenterModule {
 @Provides
  @PerView
  SolutionCreatePresenter provideSolutionCreatePresenter(SolutionCreatePresenterImpl presenter) {
    return presenter;
  }
}

此外,可以将Activity和Fragment中公用的东西抽出 AbstractActivity、BaseActivity、BasePresenterActiviy以及BaseFragment,并在这些类中初始化对应的Component。以AbstractActivity 和 BasePresenterActiviy为例:

public class AbstractActivity extends AppCompatActivity {

  private BaseViewComponent mBaseViewComponent;

  public BaseViewComponent component() {
    if (mBaseViewComponent == null) {
      mBaseViewComponent = DaggerBaseViewComponent.builder()
          .appComponent(DajiaApplication.getInstance().component())
          .baseViewModule(new BaseViewModule(this))
          .build();
    }
    return mBaseViewComponent;
  }

}
public class BasePresenterActivity extends BaseActivity {

  private PresenterComponent presenterComponent;

  public PresenterComponent component() {
    if (presenterComponent == null) {
      presenterComponent = DaggerPresenterComponent.builder()
          .appComponent(DajiaApplication.getInstance().component())
          .baseViewModule(new BaseViewModule(this))
          .presenterModule(new PresenterModule()).build();
    }
    return presenterComponent;
  }
}

如此,在使用MVP时,遵循如下步骤:

  1. 创建新的Presenter 继承 BasePresenter;
  2. 实现该接口,PresenterImpl;
  3. 在PresenterModule中提供PresenterImpl的注入方法;
  4. 创建新的View,继承BaseView;
  5. 创建新的页面Activity或者Frament,implements View;
  6. PresenterComponent添加inject方法,在新的页面中inject Presenter。

结语

本文想要系统的描述Dagger2和MVP的架构思想,但是文笔有限,行文中还是感觉有很多描述不到位的地方,希望读者在阅读时把握核心思想,而不局限于具体的实现步骤。

你可能感兴趣的:(更清晰的Dagger2 + MVP 架构)