更清晰的Dagger2 + MVP 架構(gòu)

Dagger2 與 MVP

Dagger2是Google提供的依賴注入框架膏燃,依賴注入為Android中組件之間的解耦提供了很好的解決方案捆昏。Dagger2已經(jīng)在越來越多的開源項(xiàng)目中被使用,其已經(jīng)發(fā)展成為未來的一個(gè)技術(shù)趨勢店归。
MVP設(shè)計(jì)模式大家也非常熟悉听绳,其概念不在此處贅述。MVP的實(shí)現(xiàn)方式非常多樣坯门,和Dagger2相結(jié)合正式本文要論述和討論的。

MVP模式將原來一個(gè)Activity可以解決的問題逗扒,分成了M古戴、V、P三部分矩肩,這在項(xiàng)目中會產(chǎn)品大量的類现恼,如果利用Dagger2來進(jìn)行這些類的有效管理,是本文思考的問題黍檩。本文將從Dagger2基礎(chǔ)論述叉袍,MVP的實(shí)現(xiàn)方式以及Dagger2和MVP的結(jié)合,三個(gè)方面來討論刽酱,希望對大家有用喳逛。

Dagger2 基礎(chǔ)

Dagger2主要基于注解來實(shí)現(xiàn),剛接觸Dagger2的時(shí)候都會有這樣的困惑:

  • Inject棵里,Component润文,Module姐呐,Provides,Scope典蝌,Qualifier是什么鬼曙砂?
  • Dagger2如何應(yīng)用到項(xiàng)目中?

Inject
Inject,即注入骏掀,該注解標(biāo)示地方表示需要通過DI框架來注入實(shí)例麦轰。Inject有三種方式,分別是Constructor injection砖织、Fields injection款侵、Methods injection。申明了Inject之后侧纯,會從注入框架中去查找需要注入的類實(shí)例新锈,然后注入進(jìn)來,也就是通過Component去查找眶熬。這三種注入的表現(xiàn)形式如下:

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的實(shí)例從哪里來妹笆,所以它承擔(dān)的就是一個(gè)連接器的作用。Component需要引用到目標(biāo)類的實(shí)例娜氏,Component會查找目標(biāo)類中用Inject注解標(biāo)注的屬性拳缠,查找到相應(yīng)的屬性后會接著查找該屬性對應(yīng)的用Inject標(biāo)注的構(gòu)造函數(shù)(這時(shí)候就發(fā)生聯(lián)系了),剩下的工作就是初始化該屬性的實(shí)例并把實(shí)例進(jìn)行賦值贸弥。

Module
Module 是類實(shí)例提供的工廠模式窟坐,Module里面的方法基本都是創(chuàng)建類實(shí)例的方法。這樣绵疲,Dagger2中就有2個(gè)維度可以創(chuàng)建類實(shí)例:

  • 通過用Inject注解標(biāo)注的構(gòu)造函數(shù)來創(chuàng)建(以下簡稱Inject維度)
  • 通過工廠模式的Module來創(chuàng)建(以下簡稱Module維度)

Provides
Provides 用在Module中哲鸳,聲明方法可以返回dependencies,也就是方法可以提供被注入的類實(shí)例盔憨。

@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類的依賴實(shí)例郁岩。

Scope
Dagger2中Scope關(guān)心的問題就是類實(shí)例的生命周期婿奔,@ApplicationScope 是希望類實(shí)例和Application一樣,也就是唯一單例问慎;@ActivityScope則希望類實(shí)例和Activity生命周期一致萍摊。如此,Scope就有“l(fā)ocal singletons” 的概念了蝴乔。舉個(gè)例子:AppComponent 用 @Singleton 進(jìn)行標(biāo)識记餐,由于AppComponent只在Application中創(chuàng)建一次,這就保證了AppComponent所引用的ApplicationModule 中用 @Singleton 標(biāo)識的類都是單例薇正;同樣的片酝,ActivityComponent 用 @ActivityScope進(jìn)行標(biāo)識,每次創(chuàng)建新的Activity都會創(chuàng)建一個(gè)ActivityComponent挖腰,這就是的ActivityComponent所引用的ActivityModule中用@ActivityScope 標(biāo)識的類都具有和Activity一樣的生命周期雕沿。
在后面的講解中,讀者可以去體會Scope標(biāo)識后的猴仑,類實(shí)例和Component的相互依賴關(guān)系审轮。

Qualifier
如果類實(shí)例創(chuàng)建有多種相同的方式,就需要通過標(biāo)簽tag來區(qū)分之辽俗,并在注入的時(shí)候通過標(biāo)簽來區(qū)分疾渣。

Dagger2如何應(yīng)用到項(xiàng)目中
Dagger2的使用首先要擺脫傳統(tǒng)的類創(chuàng)建模式,即摒棄各種new的使用方式崖飘,要有類倉庫的概念榴捡,所有類的引用采用Inject來注入。其次朱浴,要組織好Component吊圾,Component是連接器,提供了不同的類注入翰蠢,有為Application提供的项乒,有為Activity提供的,按照個(gè)人經(jīng)驗(yàn)來講梁沧,Component的劃分有一下規(guī)則:

  • 要有全局Component, 負(fù)責(zé)管理整個(gè)app的全局類實(shí)例, 這些類基本都是單例模式檀何,一般都用@Singleton標(biāo)識
  • 每個(gè)頁面對應(yīng)一個(gè)Component,即每個(gè)Activity 和 Fragment廷支,谷歌官方Dagger2示例是采用這種方式埃碱,這種方式有個(gè)不好地方,Component太多
  • 將頁面依賴的類抽出酥泞,公用一個(gè)Component砚殿,有特殊需要的時(shí)候繼承該公有Component
  • 不同類型的Component及Module,采用Scope進(jìn)行區(qū)分

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

MVP 實(shí)現(xiàn)探討

MVP的實(shí)現(xiàn)方式很多芝囤,這里介紹兩種:

  1. 以Activity和Fragment作為View(視圖層)似炎,Presenter管理業(yè)務(wù)邏輯;
  2. 使用Activity和Fragment作為presenters悯姊,View單獨(dú)抽出處理視圖相關(guān)羡藐。

使用Activity和Fragment作為View

這種方式是比較傳統(tǒng)的實(shí)現(xiàn)方式,也比較符合我們的習(xí)慣悯许,因?yàn)橹岸际窃贏ctivity里面處理View相關(guān)仆嗦,改用MVP模式也只是也Activity里面的業(yè)務(wù)邏輯抽成Presenter。這里要提的是Google官方MVP也是采用這種方式先壕。
下面說一下我的實(shí)現(xiàn)方式瘩扼,將Presenter和View都抽出一個(gè)基類接口谆甜,每個(gè)Activity和Fragment都分別定義對應(yīng)的Presenter和View接口,并集成基類接口集绰。具體實(shí)現(xiàn)如下:

基類接口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<T> {
  /**
  * 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<SolutionCreateView>, View.OnClickListener, View.OnFocusChangeListener {
  Solution getSolution();
  void setSolution(Solution solution);
}

// 接口實(shí)現(xiàn)
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;
  }
 
  // 繼續(xù)添加其他 override 方法
}

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

使用Activity和Fragment作為Presenters
為何要采用這種方式呢栽燕,基于兩點(diǎn)來考慮:

  1. Activity Fragment本身就有生命周期的管理罕袋,這種管理類似于業(yè)務(wù)邏輯,所以要?dú)w為Presenter碍岔;
  2. Activity Fragment生命周期變化時(shí)浴讯,會帶來業(yè)務(wù)邏輯的變化,直接作為Presenter蔼啦,可以避免業(yè)務(wù)邏輯的復(fù)雜榆纽。

個(gè)中意味,讀者自己體驗(yàn)體驗(yàn)询吴,當(dāng)自己有需求時(shí)掠河,可以根據(jù)這兩種方式自由變換。

參考鏈接:
一種在android中實(shí)現(xiàn)MVP模式的新思路:
https://github.com/hehonghui/android-tech-frontier/tree/master/androidweekly/一種在android中實(shí)現(xiàn)MVP模式的新思路
Google Samples:
https://github.com/googlesamples/android-architecture

Dagger2 與 MVP 的結(jié)合

這一部分猛计,重點(diǎn)介紹Component 和 Module的設(shè)計(jì)唠摹,目的在于更好適應(yīng)MVP模式。
基本思路:

  1. 全局Component通過AppComponent進(jìn)行管理奉瘤,大多設(shè)置單例模式勾拉;
  2. 將Activity和Fragment Component中通用的抽出,為BaseViewComponent盗温;
  3. 上層PresenterComponent繼承BaseViewComponet藕赞,DataBandingComponent只能繼承android.databinding.DataBindingComponent,但可以將BaseViewModule 包含進(jìn)來卖局。其他Component使用時(shí)可以繼承BaseViewComponent斧蜕。

架構(gòu)圖如下:


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,并在這些類中初始化對應(yīng)的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時(shí),遵循如下步驟:

  1. 創(chuàng)建新的Presenter 繼承 BasePresenter;
  2. 實(shí)現(xiàn)該接口单鹿,PresenterImpl掀宋;
  3. 在PresenterModule中提供PresenterImpl的注入方法;
  4. 創(chuàng)建新的View,繼承BaseView劲妙;
  5. 創(chuàng)建新的頁面Activity或者Frament湃鹊,implements View;
  6. PresenterComponent添加inject方法是趴,在新的頁面中inject Presenter涛舍。

結(jié)語

本文想要系統(tǒng)的描述Dagger2和MVP的架構(gòu)思想澄惊,但是文筆有限唆途,行文中還是感覺有很多描述不到位的地方,希望讀者在閱讀時(shí)把握核心思想掸驱,而不局限于具體的實(shí)現(xiàn)步驟肛搬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市毕贼,隨后出現(xiàn)的幾起案子温赔,更是在濱河造成了極大的恐慌,老刑警劉巖鬼癣,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陶贼,死亡現(xiàn)場離奇詭異,居然都是意外死亡待秃,警方通過查閱死者的電腦和手機(jī)拜秧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來章郁,“玉大人枉氮,你說我怎么就攤上這事∨” “怎么了聊替?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長培廓。 經(jīng)常有香客問我惹悄,道長,這世上最難降的妖魔是什么肩钠? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任泣港,我火速辦了婚禮,結(jié)果婚禮上蔬将,老公的妹妹穿的比我還像新娘爷速。我一直安慰自己,他們只是感情好霞怀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布惫东。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉沮。 梳的紋絲不亂的頭發(fā)上颓遏,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天,我揣著相機(jī)與錄音滞时,去河邊找鬼叁幢。 笑死,一個(gè)胖子當(dāng)著我的面吹牛坪稽,可吹牛的內(nèi)容都是我干的曼玩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼窒百,長吁一口氣:“原來是場噩夢啊……” “哼黍判!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起篙梢,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顷帖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后渤滞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贬墩,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年妄呕,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了陶舞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,146評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡趴腋,死狀恐怖吊说,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情优炬,我是刑警寧澤颁井,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蠢护,受9級特大地震影響雅宾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜葵硕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一眉抬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懈凹,春花似錦蜀变、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽爬舰。三九已至,卻和暖如春寒瓦,著一層夾襖步出監(jiān)牢的瞬間情屹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冬骚。 一個(gè)月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像率触,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容