Dagger2融合篇(三)

稀飯_發表於2018-08-29

第一篇講解了無參情況兩種注入方式和有參情況的兩種注入方式。

第二篇講解了注入迷失怎麼解決,區域性單例和全域性單例模式的寫法,以及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…



相關文章