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的实现方式很多,这里介绍两种:
- 以Activity和Fragment作为View(视图层),Presenter管理业务逻辑;
- 使用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
为何要采用这种方式呢,基于两点来考虑:
- Activity Fragment本身就有生命周期的管理,这种管理类似于业务逻辑,所以要归为Presenter;
- 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模式。
基本思路:
- 全局Component通过AppComponent进行管理,大多设置单例模式;
- 将Activity和Fragment Component中通用的抽出,为BaseViewComponent;
- 上层PresenterComponent继承BaseViewComponet,DataBandingComponent只能继承android.databinding.DataBindingComponent,但可以将BaseViewModule 包含进来。其他Component使用时可以继承BaseViewComponent。
架构图如下:
核心代码如下:
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时,遵循如下步骤:
- 创建新的Presenter 继承 BasePresenter;
- 实现该接口,PresenterImpl;
- 在PresenterModule中提供PresenterImpl的注入方法;
- 创建新的View,继承BaseView;
- 创建新的页面Activity或者Frament,implements View;
- PresenterComponent添加inject方法,在新的页面中inject Presenter。
结语
本文想要系统的描述Dagger2和MVP的架构思想,但是文笔有限,行文中还是感觉有很多描述不到位的地方,希望读者在阅读时把握核心思想,而不局限于具体的实现步骤。