Google官方MVP+Dagger2架構詳解----非常詳細,值得多看幾遍(okhttp cache)
原文:http://www.jianshu.com/p/01d3c014b0b1#
1 前言
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
-
複雜的依賴關係只需要簡單的配置
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
並且生成的方法appModule
和netModule
完成了兩個對應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架構回顧
上一篇文章 中,我們分析到整個專案是按照功能模組進行劃分(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模組做出分析。
通過上圖我們可以看到,這裡添加了四個個類檔案,分別是全域性的ApplicationModule
和ToDoApplication
。以及對應XXX模組中的XXXComponent
和XXXPresenterModule
。其他模組也類似。
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
。並且可以看到的是ApplicationModule
和TasksRepositoryModule
也在這裡進行了一次性初始化。TasksRepository
需要說明的是整個資料model層的核心。
-
來看看
ApplicationModule
@Module public final class ApplicationModule { private final Context mContext; ApplicationModule(Context context) { mContext = context; } @Provides Context provideContext() { return mContext; } }
可以看到的是這裡需要的是一個application context 的例項,也就是我們在上面
ToDoApplication
的onCreate
中初始化的時候傳入的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.class
和ApplicationModule.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 參考資料:
示例程式碼網上真的有很多。推薦大家關注官方標準。我已經在自己的開源專案中新增Dagger2 +MVP的使用。歡迎檢視
文/CameloeAnthony(簡書作者)
原文連結:http://www.jianshu.com/p/01d3c014b0b1#
著作權歸作者所有,轉載請聯絡作者獲得授權,並標註“簡書作者”。