第一篇講解了無參情況兩種注入方式和有參情況的兩種注入方式。
第二篇講解了注入迷失怎麼解決,區域性單例和全域性單例模式的寫法,以及Component依賴和繼承的寫法。
這一篇是Dagger2的融合篇,目標讓讀者能學會把Dagger2融合到mvp框架開發中。
在APP的開發中,有非常多的類都有依賴物件,總不能每一個目標類都配一個Component吧。應該以什麼樣的原則來劃分Component呢,Component應該劃分為多小的粒度呢,一般會遵循如下的組織原則:
- 建立一個全域性的Component(ApplicationComponent), 在application中對其進行例項化,一般會在這個component中用來管理APP中的全域性類例項。
- 對於每個頁面建立一個Component,一個Activity頁面定義一個Component,一個Fragment定義一個Component,使這些component繼承自applicationComponent。
一建立全域性的Component
全域性的例項都有哪些呢?聯網的需要單例,SP需要單例,還有全域性的Application物件,那麼就開始寫吧。
建立全域性module:
@Module
public class AppModule {
private MyApplication application;
public AppModule(MyApplication application) {
this.application = application;
}
public static final int DEFAULT_TIMEOUT = 30;
@Singleton
@Provides
public API provideAPI(Retrofit retrofit) {
return retrofit.create(API.class);
}
@Provides
public Retrofit provideRetrofit(OkHttpClient client) {
return new Retrofit.Builder()
.baseUrl("baseurl")
.addConverterFactory(GsonConverterFactory.create())//可以新增自定義解析器和預設的解析器
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())//新增響應式程式設計的介面卡
.client(client)
.build();
}
@Provides
public OkHttpClient provideOkHttpClient() {
return new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS))
.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)//設定連線超時時間
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.build();
}
@Singleton
@Provides
public SharedPreferences provideSharedPreferences() {
return application.getSharedPreferences("spfile", Context.MODE_PRIVATE);
}
@Singleton
@Provides
MyApplication provideApplication() {
return application;
}
}複製程式碼
這裡你應該能看懂,就是第一篇講解的有引數的第三方注入寫法。唯一要講解的就是 @Singleton註解,他是Scope的預設實現,大家習慣寫全域性單例用這個註解,而不是去自定義一個註解。
然後就是AppComponent介面:
@Singleton
@Component(modules = AppModule.class)
public interface AppCompoent {
//當這個Component被別的Component依賴時,必須提供下邊方法,不寫代表不對依賴Component暴漏物件
API provideAPI();
SharedPreferences provideSharedPreferences();
MyApplication provideApplication();
}複製程式碼
這裡有兩點要強調:
- 當這個Component被別的Component依賴時,必須提供下邊方法,不寫代表不對依賴Component暴漏物件
- Component的註解Singleton要和module的一致,要不編譯不通過。
然後就是再Application中獲取這個全域性的物件。
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
ComponentHolder.setAppComponent(DaggerAppCompoent.builder().appModule(new AppModule(this)).build());
}
}複製程式碼
然後把獲取到的物件儲存到另外一個類裡邊,這個類的程式碼如下:
public class ComponentHolder {
private static AppCompoent myAppComponent;
public static void setAppComponent(AppCompoent component) {
myAppComponent = component;
}
public static AppCompoent getAppComponent() {
return myAppComponent;
}
}複製程式碼
然後我們就完成一個一個全域性的Component。
二.mvp中P物件的V物件注入
在MVP模式中,V層持有P的物件,P層持有V的物件,而且,我們會把P物件和V物件抽取到父類。
如下程式碼:
public abstract class BaseActivity<P extends BasePersenter> extends AppCompatActivity {
@Inject
public P mPersenter;
@Override
protected void onDestroy() {
super.onDestroy();
if (mPersenter != null) {
mPersenter.detach();
}
}}複製程式碼
public class BasePresenterImpl<V extends BaseView> implements BasePersenter {
public V mView;
public BasePresenterImpl(V View) {
this.mView = View;
}
@Override
public void detach() {
this.mView = null;//釋放View,防止記憶體洩露
}複製程式碼
繼承BaseActivity的子類HomeActivity的module就要去提供對應的Persenter物件。我們可以在Persenter的構造方法中使用註解他的構造方法,然後通過module提供對應的View物件,而module中的View物件就通過module的構造方法從子類傳遞過來。這樣就完成了p物件的注入,同時view物件也就注入到P中去了(通過構造方法傳遞進去的)。
程式碼如下:
@Module
public class HomeModule {
HomeContact.view view;
public HomeModule(HomeContact.view view) {
this.view = view;
}
@Provides
public HomeContact.view provideHomeContactView() {
return view;
}
}複製程式碼
@ActivityScope
@Component(modules = HomeModule.class,
dependencies = AppCompoent.class)
public interface HomeComponent {
void inject(MainActivity activity);
}複製程式碼
public class HomePersenter extends BasePresenterImpl<HomeContact.view> {
@Inject
public HomePersenter(HomeContact.view View) {
super(View);
}
}複製程式碼
注意:這裡的ActivityScope註解是自定義的註解,這裡如果不標記這個註解,會編譯錯誤,因為依賴的Component有作用域註解,這裡就要提供一個和依賴作用域不同的註解。
三P層中獲取請求網路例項
我們請求網路的實力寫的是全域性單例模式,而HomeComponent依賴與全域性Component,所以我們請求網路的例項可以注入到Activity中,但是對應的P層卻注入不到請求網路物件。但是我們p層擁有View物件,我們就可以讓View提供一個返回請求網路物件的方法。這樣就可以獲取到請求的網路的例項。程式碼如下:
public interface BaseView {
API getAPI();
}複製程式碼
對應的BaseActivity中新增如下程式碼:
public abstract class BaseActivity<P extends BasePersenter> extends AppCompatActivity implements BaseView {
@Inject
public P mPersenter;
@Inject
public API api;
@Override
public API getAPI() {
return api;
}
......
}複製程式碼
到此我們就把Dagger2簡單的融合到mvp中,並且可以簡單使用。但是我們還會面臨一些細節需要去探討怎麼解決。
四簡單頁面沒有P層
我們在上邊探討了正常有P層關於Dagger2的融合,並在基類中注入了api和Persenter物件,但是實際開發中,有的頁面沒有P層,但是我們卻注入了P層,我們應該怎麼解決這個問題呢?
去建立一個EmptyPersenter類,並把構造方法注入,程式碼如下:
public class EmptyPersenter implements BasePersenter {
@Inject
public EmptyPersenter() {
}
......
}複製程式碼
這樣我們就可以在沒有P層的時候去泛型一個空的物件。
同時我們基類還有APi物件的注入,那麼就需要我們去建立一個HotComponent去依賴全域性的Component,否則會報錯。
@ActivityScope
@Component(modules = HotModule.class, dependencies = AppCompoent.class)
public interface HotComponent {
void inject(HotActivity activity);
}複製程式碼
同時還可以注入這個簡單頁面內,其他例項物件的注入。
五頁面物件全部用注入賦值
假如我們新建一個FindActivity繼承BaseActivity,讓這個頁面展示一個列表,讓adapter和layoutmanager物件全部用注入賦值。
@Module
public class FindModule {
private FindContact.View view;
public FindModule(FindContact.View view) {
this.view = view;
}
@Provides
public FindContact.View provideFindContactView() {
return view;
}
@Provides
RecyclerView.LayoutManager provideLayoutManager() {
return new LinearLayoutManager(view.getActivity(), LinearLayoutManager.VERTICAL, false);
}
@Provides
FindAdapter provideAdapter() {
return new FindAdapter(null);
}
}複製程式碼
注意:這裡的view.getActivity(),實現思想和p中獲取請求物件的思路是一樣,不做過多的說明。
對應的頁面程式碼:
public class FindActivity extends BaseActivity<FindPersenter> implements FindContact.View {
private RecyclerView recyclerView;
@Inject
RecyclerView.LayoutManager linearLayoutManager;
@Inject
FindAdapter adapter;
@Override
protected void inject() {
DaggerFindComonent.builder().appCompoent(ComponentHolder.getAppComponent()).findModule(new FindModule(this)).build().inject(this);
}
@Override
public int getLayout() {
return R.layout.activity_find;
}
@Override
protected void initView(Bundle savedInstanceState) {
recyclerView = findViewById(R.id.recyclerView);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
}
@Override
protected void initListener() {
}
@Override
public void getSerivceData() {
mPersenter.getData();
}
@Override
public void setData(List<String> list) {
adapter.setNewData(list);
}
//對module類提供的上下文
@Override
public Context getActivity() {
return this;
}
}複製程式碼
這樣就把所有物件通過依賴注入的思想去賦值。
以上就是dagger2融合到mvp中的用法。這裡說一下筆者自己的看法:dagger2的目的是為了解耦類之間的依賴關係而單生了,但是在小的專案中使用Dagger2會讓開發者感覺很多餘,原因就是因為專案小,邏輯簡單。導致感覺繁瑣。就比如上邊的例子,連adapter和LayoutManager物件都用注入去賦值,有種去你鄰居家裡 十米遠, 你卻要開車的感覺。
讀者有沒有發現我們在每個頁面都都寫入了注入的程式碼,而並不是抽取到父類,這就是Dagger2在Android中使用的缺陷。為了解決這個問題,又出來了Dagger-android框架。至於Dagger-Android筆者並不打算開文章講解用法,因為在實際專案中我並不打算用Dagger2,或者Dagger-Android去開發專案。並且在kotlin專案中更加顯得Dagger2無用~後續可能會對Kotlin的依賴注入框架Kodein做一下研究。
最後本文的Demo地址:github.com/XiFanYin/Da…