Google官方MVP+Dagger2架構詳解【從零開始搭建android框架系列(6)】

Google官方MVP+Dagger2架構詳解【從零開始搭建android框架系列(6)】

字數6894 閱讀13459 評論37 

更多及時技術資訊,歡迎關注我的微博 :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的使用。歡迎查看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章