據說你還不會用Dagger2?Dagger2 For Android最佳實踐教程

本文首發於個人我的博客java

點擊進入原文連接android

前言

Dagger2是如今很是火的一個依賴注入框架,目前由Google維護,在Github上面已經有12K star了。Dagger2的入門門檻實際上是比較高的,據瞭解,目前有不少Android工程師對Dagger2還不甚瞭解,沒有用上Dagger2或者是用法有問題,本文的主旨就是讓Android工程師快速掌握Dagger2而且優雅簡潔地使用Dagger2。這裏爲你們奉上一份Dagger2 在Android上的最佳實踐教程。git

注意: Dagger2框架的上手難度是比通常的框架更難一些的,因此在練習的時候應該儘可能減小干擾因素,儘可能少引入其它複雜的第三方庫,最佳作法是隻依賴Android基礎庫和Dagger2 For Android須要的庫。程序員

其它技術文章推薦

給你一個全自動的屏幕適配方案(基於SW方案)!—— 解放你和UI的雙手github

Gradle自動實現Android組件化模塊構建編程

技術教程Demo地址(本文的Demo也在裏面喲)👍設計模式

你的支持,是我前進的動力,若是個人項目對您有幫助的話,能夠點下star👍緩存

依賴注入

什麼是依賴注入?

維基百科上面的介紹是:在軟件工程中,依賴注入是種實現控制反轉用於解決依賴性設計模式。一個依賴關係指的是可被利用的一種對象(即服務提供端) 。依賴注入是將所依賴的傳遞給將使用的從屬對象(即客戶端)。該服務是將會變成客戶端的狀態的一部分。 傳遞服務給客戶端,而非容許客戶端來創建或尋找服務,是本設計模式的基本要求。bash

簡單來講依賴注入就是將實例對象傳入到另外一個對象中去。微信

依賴注入的實現

維基百科的說法很是抽象,其實在日常編碼中,咱們一直都在使用依賴注入。依賴注入主要有如下幾種方式。

  • 構造函數注入
public class Chef{
    Menu menu;
    public Man(Menu menu){
        this.menu = menu;
    }
}
複製代碼
  • setter方法注入
public class Chef{
    Menu menu;
    public setMenu(Menu menu){
        this.menu = menu;
    }
}
複製代碼
  • 接口注入
public interface MenuInject{
    void injectMenu(Menu menu);
}

public class Chef implements MenuInject{
    Menu menu;
    
    @Override
    public injectMenu(Menu menu){
        this.menu = menu;
    }
}
複製代碼
  • 依賴注入框架
public @Inject class Menu{
    ...
}

public class Chef{
    @Inject
    Menu menu;
}
複製代碼

從上面的例子能夠看出,依賴注入其實就是咱們每天都在用的東西。

Dagger2實現依賴注入

爲何要使用Dagger2?

從上面這個簡單的例子來看,爲了實現依賴注入,好像不必引入第三方的框架。在只有一我的開發,而且業務像上面這麼簡單的時候,確實是不必引入Dagger2。可是若是多人同時開發,而且業務很是複雜呢?例如,咱們這裏的Menu須要初始化,而菜單也要依賴具體的菜式的呢?若是隻是一個地方用到的話,仍是能接受的。若是項目中有不少地方同時用到呢?若是這個菜單要修改呢?有經驗的開發者可能會想到使用單例模式。可是若是項目中有不少類型的結構的話,那麼咱們就須要管理很是多的單例,而且單例可能也須要依賴其它對象。在這種狀況下若是有變動需求或者是更換維護人員,都會使簡單的改動變得很是繁瑣,而且容易致使各類各樣的奇怪BUG。因此這裏咱們就須要引入第三方的依賴注入工具,讓這個工具來幫助咱們實現依賴注入。

Dagger2就是咱們須要的第三方依賴注入工具。Dagger2較其它依賴注入工具備一個優點,就是它是採用靜態編譯的方式編譯代碼的,會在編譯期生成好輔助代碼,不會影響運行時性能,這一點很是適合用於移動端。

Dagger2的使用方式

Dagger是經過Component來確認需求與依賴對象的,能夠說Component是他們之間的紐帶。若是各位用過Dagger2或者瞭解過Dagger2的教程的話,那麼必定知道,Dagger2的使用方式是十分繁瑣的,每一個須要用到依賴注入的地方都須要經過編寫DaggerxxxComponent的模版代碼來實現依賴注入。要寫很是多的模版代碼,大大增長了系統的複雜度。筆者在使用Dagger 2.17的時候,發現Google對Dagger 2進行了優化,如今使用Dagger實現依賴注入要寫的代碼其實很是少,而且複雜度已經有了很大程度的下降了。在這裏,筆者就不介紹舊的使用方式了,使用過Dagger2的同窗能夠對比這兩種方式的差別,沒有使用過的直接學習新的使用方式就能夠了。

Dagger2最簡單的使用方式就是下面這種:

public class A{
    @Inject
    public A(){
        
    }
}

public class B{
    @Inject A a;
    ...
}
複製代碼

這種方法是最簡單的,沒什麼難度。可是在實際的項目中咱們會遇到各類各樣的複雜狀況,例如,A還須要依賴其它的類,而且這個類是第三方類庫中提供的。又或者A實現了C接口,咱們在編碼的時候須要使用依賴致使原則來增強咱們的代碼的可維護性等等。這個時候,用上面這種方法是沒辦法實現這些需求的,咱們使用Dagger2的主要難點也是由於上面這些緣由致使的。

仍是用上面的例子來解釋,假設須要作一個餐飲系統,須要把點好的菜單發給廚師,讓廚師負責作菜。如今咱們來嘗試下用Dagger2來實現這個需求。

首先,咱們須要引入Dagger For Android的一些列依賴庫:

implementation 'com.google.dagger:dagger-android:2.17'
    implementation 'com.google.dagger:dagger-android-support:2.17' // if you use the support libraries
    implementation 'com.google.dagger:dagger:2.17'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
    annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'
複製代碼

而後咱們實現Chef類和Menu類

Cooking接口

public interface Cooking{
    String cook();
}

複製代碼

Chef

public class Chef implements Cooking{

    Menu menu;

    @Inject
    public Chef(Menu menu){
        this.menu = menu;
    }

    @Override
    public String cook(){
        //key菜名, value是否烹飪
        Map<String,Boolean> menuList = menu.getMenus();
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String,Boolean> entry : menuList.entrySet()){
            if (entry.getValue()){
                sb.append(entry.getKey()).append(",");
            }
        }

        return sb.toString();
    }
}
複製代碼

Menu

public class Menu {

    public Map<String,Boolean> menus;

    @Inject
    public Menu( Map<String,Boolean> menus){
         this.menus = menus;
    }
    
    Map<String,Boolean> getMenus(){
        return menus;
    }

}
複製代碼

如今咱們寫一個Activity,做用是在onCreate方法中使用Chef對象實現cooking操做。咱們先來看看不使用Dagger2和使用Dagger2的代碼區別。

MainActivity

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        setContentView(R.layout.activity_main);
        Map<String, Boolean> menus = new LinkedHashMap<>();
        menus.put("酸菜魚", true);
        menus.put("土豆絲", true);
        menus.put("鐵板牛肉", true);
        Menu menu = new Menu(menus);
        Chef chef = new Chef(menu);
        System.out.println(chef.cook());
    }
}
複製代碼

DaggerMainActivity

public class DaggerMainActivity extends DaggerActivity {
    @Inject
    Chef chef;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Log.d(TAG,chef.cook());
    }
}
複製代碼

能夠看到,在使用Dagger2的時候,使用者的代碼會變得很是簡潔。可是,Dagger 2還須要一些列的輔助代碼來實現依賴注入的。若是用過Dagger2就知道要實現依賴注入的話,須要寫十分多模版代碼。那麼咱們可不能夠用更簡單的方式使用Dagger2呢?今天筆者就來介紹一下在Android上使用Dagger2的更簡潔的方案。

咱們先來看看在DaggerMainActivity上實現依賴注入還須要哪些代碼。

CookModules

@Module
public class CookModules {

    @Singleton
    @Provides
    public Map<String, Boolean> providerMenus(){
        Map<String, Boolean> menus = new LinkedHashMap<>();
        menus.put("酸菜魚", true);
        menus.put("土豆絲", true);
        menus.put("鐵板牛肉", true);
        return menus;
    }
}
複製代碼

ActivityModules

@Module
abstract class ActivityModules {

    @ContributesAndroidInjector
    abstract MainActivity contributeMainActivity();
}
複製代碼

CookAppComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        ActivityModules.class,
        CookModules.class})
public interface CookAppComponent extends AndroidInjector<MyApplication> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<MyApplication>{}

}
複製代碼

MyApplication

public class MyApplication extends DaggerApplication{

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerCookAppComponent.builder().create(this);
    }
}
複製代碼

Dagger2 For Android 使用要點分析

  1. CookModules CookModule很簡單,它的目的就是經過@Providers註解提供Menu對象須要的數據。由於Menu是須要依賴一個Map對象的,因此咱們經過CookModules給它構造一個Map對象,並自動把它注入到Menu實例裏面。
  2. ActivityModules ActivityModules的主要做用就是經過@ContributesAndroidInjector來標記哪一個類須要使用依賴注入功能,這裏標記的是ManActivity,因此MainActivity能經過@Inject註解來注入Chef對象。
  3. CookAppComponent CookAppComponent至關於一個注射器,咱們前面定義的Modules就是被注射的類,使用@Inject注入對象的地方就是接收者類。
  4. MyApplication MyAppliction的特色是繼承了DaggerAppliction類,而且在applicationInjector方法中構建了一個DaggerCookAppComponent注射器。

這就是Dagger 2在Android中的使用方案了,在這裏咱們能夠看到,接收這類(MainActivity)中的代碼很是簡單,實現依賴注入只使用了:

@Inject
Chef chef;
複製代碼

在接收類裏面徹底沒有多餘的代碼,若是咱們要拓展能夠SecondsActivity的話,在SecondsActivity咱們要用到Menu類。

那麼咱們只須要在ActivityModules中增長:

@ContributesAndroidInjector
abstract SecondsActivity contributeSecondsActivity();
複製代碼

而後在SecondsActivity注入Menu:

@Inject
Menu menu;
複製代碼

能夠看到,對於整個工程來講,實現使用Dagger2 For Android實現依賴注入要寫的模版代碼其實很是少,很是簡潔。只須要進行一次配置就能夠,不須要頻繁寫一堆模版代碼。總的來講,Dagger2形成模版代碼增長這個問題已經解決了。

Demo地址:目錄下的Dagger2Simple就是Demo地址,上面的例子爲Dagger2Simple中的simple Modules

Dagger2的優點

在這裏咱們總結下使用Dagger2帶來的優勢。

  1. 減小代碼量,提升工做效率 例如上面的例子中,咱們構建一個Chef對象的話,不使用Dagger2的狀況下,須要在初始化Chef對象以前進行一堆前置對象(Menu、Map)的初始化,而且須要手工注入到對應的實例中。你想像下,若是咱們再加一個Restaurant( 餐館 )對象,而且須要把Chef注入到Restaurant中的話,那麼初始化Restaurant對象時,須要的前置步驟就更繁瑣了。 可能有人會以爲,這也沒什麼啊,我不介意手工初始化。可是若是你的系統中有N處須要初始化Restaurant對象的地方呢?使用Dagger2 的話,只須要用註解注入就能夠了。
  2. 自動處理依賴關係 使用Dagger2的時候,咱們不須要指定對象的依賴關係,Dagger2會自動幫咱們處理依賴關係(例如Chef須要依賴Menu,Menu須要依賴Map,Dagger自動處理了這個依賴關係)。
  3. 採用靜態編譯,不影響運行效率 由於Dagger2是在編譯期處理依賴注入的,因此不會影響運行效率在必定的程度上還能提升系統的運行效率(例如採用Dagger2實現單例,不用加鎖效率更高)。
  4. 提升多人編程效率 在多人協做的時候,一我的用Dagger2邊寫完代碼後,其它全部組員都能經過@Inject註解直接注入經常使用的對象。加快編程效率,而且能大大增長代碼的複用性。

上面咱們介紹完了Dagger2 For Android的基本用法了。可能有些讀者意猶未盡,以爲這個例子太簡單了。那麼咱們來嘗試下構建一個更加複雜的系統,深度體驗下Dagger2 For Android的優點。如今咱們在上面這個例子的基礎上拓展下,嘗試開發一個簡單的點餐Demo來深度體驗下。

Dagger2應用實戰

如今咱們來看下如何使用Dagger2來開發一個簡單的Demo,這裏筆者開發的Demo是一個簡單的點餐Demo。這個Demo的功能很是簡單,提供了菜單展現、菜單添加/編輯/刪除和下單功能。而下單功能只是簡單地把菜品名用Snackbar顯示到屏幕上。

Demo展

操做展現

Demo地址:目錄下的Dagger2Simple就是Demo地址,上面的例子爲Dagger2Simple中的order Modules

代碼目錄

這個Demo採用經典的MVP架構,咱們先來簡單分析下Demo的細節實現。

  1. 使用SharedPreferences提供簡單的緩存功能(存儲菜單)。
  2. 使用Gson把列表序列化成Json格式數據,而後以String的形式保存在SharedPreferences中。
  3. 使用Dagger2實現依賴注入功能。

這樣基本就實現了一個簡單的點菜Demo了。

Dagger在Demo中的應用解釋

當咱們使用SharedPreferences和Gson實現緩存功能的時候咱們會發現,項目中不少地方都會須要這個SharedPreferences和Gson對象。因此咱們能夠得出兩個結論:

  1. 項目中多個模塊會用到一些公共實例。
  2. 這些公共實例應該是單例對象。

咱們看看是如何經過使用Dagger2提供全局的Modules來實現這類型對象的依賴注入。

CookAppModules

@Module
public abstract class CookAppModules {

    public static final String KEY_MENU = "menu";
    private static final String SP_COOK = "cook";

    @Singleton
    @Provides
    public static Set<Dish> providerMenus(SharedPreferences sp, Gson gson){
        Set<Dish> menus;
        String menuJson = sp.getString(KEY_MENU, null);
        if (menuJson == null){
            return new LinkedHashSet<>();
        }
        menus = gson.fromJson(menuJson, new TypeToken<Set<Dish>>(){}.getType());
        return menus;
    }

    @Singleton
    @Provides
    public static SharedPreferences providerSharedPreferences(Context context){
        return context.getSharedPreferences(SP_COOK, Context.MODE_PRIVATE);
    }

    @Singleton
    @Provides
    public static Gson providerGson(){
        return new Gson();
    }

    @Singleton
    @Binds
    public abstract Context context(OrderApp application);

}
複製代碼

在這裏以dishes模塊爲例子,dishes中DishesPresenter是負責數據的處理的,因此咱們會在DishesPresenter注入這些實例。

DishesPresenter

public class DishesPresenter implements DishesContract.Presenter{

   private DishesContract.View mView;

   @Inject
   Set<Dish> dishes;

   @Inject
   Gson gson;

   @Inject
   SharedPreferences sp;

   @Inject
   public DishesPresenter(){

   }

   @Override
   public void loadDishes() {
       mView.showDishes(new ArrayList<>(dishes));
   }

   @Override
   public String order(Map<Dish, Boolean> selectMap) {
       if (selectMap == null || selectMap.size() == 0) return "";
       StringBuilder sb = new StringBuilder();

       for (Dish dish : dishes){
           if (selectMap.get(dish)){
               sb.append(dish.getName()).append("、");
           }
       }
       if (TextUtils.isEmpty(sb.toString())) return "";

       return "烹飪: " + sb.toString();
   }

   @Override
   public boolean deleteDish(String id) {
       for (Dish dish : dishes){
           if (dish.getId().equals(id)){
               dishes.remove(dish);
               sp.edit().putString(CookAppModules.KEY_MENU, gson.toJson(dishes)).apply();
               return true;
           }
       }
       return false;
   }


   @Override
   public void takeView(DishesContract.View view) {
       mView = view;
       loadDishes();
   }

   @Override
   public void dropView() {
       mView = null;
   }
}

複製代碼

上面的代碼能很好地體驗Dagger2的好處,假如咱們項目中有比較複雜的對象在不少地方都會用到的話,咱們能夠經過這種方式來簡化咱們的代碼。

Dishes模塊的UI是由Activity加Fragment實現的,Fragment實現了主要的功能,而Activity只是簡單做爲Fragment的外層。它們分別是:DishesActivity和DishesFragment

DishesActivity依賴了DishesFragment對象,而在DishesFragment則依賴了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter對象。

咱們先來分別看看DishesActivity與DishesFragment的關鍵代碼。

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

    @Inject
    DishesFragment mDishesFragment;
    
    ...
}
複製代碼

DishesFragment

public class DishesFragment extends DaggerFragment implements DishesContract.View{

    RecyclerView rvDishes;

    @Inject
    DishesAdapter dishesAdapter;

    @Inject
    RecyclerView.LayoutManager layoutManager;

    @Inject
    DishesContract.Presenter mPresenter;
    
    @Inject
    public DishesFragment(){

    }
    
 }
複製代碼

DishesFragment經過Dagger2注入了DishesAdapter、RecyclerView.LayoutManager、DishesContract.Presenter,而這些實例是由DishesModules提供的。

DishesModules

@Module
public abstract class DishesModules {

    @ContributesAndroidInjector
    abstract public DishesFragment dishesFragment();

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }
    
    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}
複製代碼

這裏咱們先說明下這幾個註解的做用。

  • @ContributesAndroidInjector 你能夠把它當作Dagger2是否要自動把須要的用到的Modules注入到DishesFragment中。這個註解是Dagger2 For Android簡化代碼的關鍵,下面的小節會經過一個具體例子來講明。

  • @Module 被這個註解標記的類能夠看做爲依賴對象的提供者,能夠經過這個被標記的類結合其它註解來實現依賴關係的關聯。

  • @Provides 主要做用就是用來提供一些第三方類庫的對象或提供一些構建很是複雜的對象在Dagger2中相似工廠類的一個角色。

  • @Binds 主要做用就是肯定接口與具體的具體實現類,這樣說得比較抽象,咱們仍是看看例子吧。 在DishesFragment中有這麼一句代碼:

    @Inject
    DishesContract.Presenter mPresenter;
    複製代碼

    咱們知道DishesContract.Presenter是一個接口而這個接口可能有不少不一樣的實現類,而@Binds的做用就是用來肯定這個具體實現類的。以看看PresenterModules的代碼:

    @Module
    public abstract class PresenterModules {
        @Binds
        abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);
    
        ...
    }
    
    複製代碼

    從這句代碼能夠看出,使用@Inject注入的DishesContract.Presenter對象的具體實現類是DishesPresenter。

Dagger2 For Android是如何注入依賴的?

咱們在用Dagger2的時候是經過一些模版代碼來實現依賴注入的( DaggerXXXComponent.builder().inject(xxx) 這種模版代碼),可是在Demo中的DishesFragment根本沒看到相似的代碼啊,那麼這些對象是何時注入到DishesFragment重的呢?

答案就是**@ContributesAndroidInjector**註解

咱們先來看看Dagger2是經過什麼方式來實現自動把依賴注入到DishesActivity中的。

ActivityModules

@Module
public abstract class ActivityModules {

    @ContributesAndroidInjector(modules = DishesModules.class)
    abstract public DishesActivity contributesDishActivity();

    @ContributesAndroidInjector(modules = AddEditModules.class)
    abstract public AddEditDishActivity contributesAddEditDishActivity();

}
複製代碼

沒錯,就是@ContributesAndroidInjector這個註解,modules就表明這個DishesActivity須要依賴哪一個Modules。這篇教程咱們不解釋它的具體實現原理,你只須要知道@ContributesAndroidInjector的做用就能夠了。

咱們之前使用Dagger2的時候,須要些不少Component來輔助咱們實現依賴注入,而如今咱們整個App中只須要寫一個Component就能夠了。@ContributesAndroidInjector註解會幫助咱們生成其它須要的Component,而且自動處理Component之間的關係,自動幫咱們使用生成的Component來注入依賴。

咱們先看看咱們如今整個模塊中惟一存在的Component是怎麼使用的。

OrderAppComponent

@Singleton
@Component(modules = {
        AndroidSupportInjectionModule.class,
        LayoutManagerModules.class,
        CookAppModules.class,
        PresenterModules.class,
        ActivityModules.class})
public interface OrderAppComponent extends AndroidInjector<OrderApp>{

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<OrderApp>{
    }

}
複製代碼

OrderApp

public class OrderApp extends DaggerApplication {


    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        return DaggerOrderAppComponent.builder().create(this);
    }
}
複製代碼

爲了加深你們對@ContributesAndroidInjecto註解r的理解,咱們稍微修改下DishesModules

@Module
public abstract class DishesModules {

    //@ContributesAndroidInjector
    //abstract public DishesFragment dishesFragment();

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }

    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}
複製代碼

DishesActivity

public class DishesActivity extends DaggerAppCompatActivity {

    //@Inject
    DishesFragment mDishesFragment;

    Toolbar toolbar;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_dishes);

        DishesFragment dishesFragment
                = (DishesFragment) getSupportFragmentManager().findFragmentById(R.id.content_fragment);

        if (dishesFragment == null){
            mDishesFragment = new DishesFragment();//新增代碼
            dishesFragment = mDishesFragment;
            ActivityUtils.addFragmentToActivity(getSupportFragmentManager(), dishesFragment, R.id.content_fragment);
        }
        initView();

    }
    ...
}
複製代碼
//DaggerFragment改成Fragment
public class DishesFragment extends Fragment implements DishesContract.View{
}
複製代碼

這個時候,咱們運行的時候會發現,DishesFragment中的依賴注入失敗了,運行時會拋出空指針異常,沒注入須要的數據。致使這個緣由是由於咱們在這裏使用new來建立DishesFragment實例的,爲何使用new的時候會Dagger2沒有幫咱們注入實例呢?

當咱們使用@Inject來注入DishesFragment的時候,Dagger2會自動幫咱們判斷DishesFragment所依賴的對象(@Inject註解標記),若是能直接注入的對象則直接注入到Fragment中,不然則從DishesModules中尋找是否有須要的對象,有的話則注入到DishesFragment中。而咱們使用new來建立DishesFragment時Dagger2沒法經過DishesModules來查找對象,由於咱們沒有聲明DishesFragment與DishesModules的聯繫,DishesFragment也沒有自動注入註解的標記( 沒有實現HasSupportFragmentInjector )。因此Dagger2沒法判斷它們依賴關係也沒辦法自動幫DishesFragment自動注入依賴。

若是咱們堅持要使用new的方式來依賴DishesFragment的話,則能夠經過@ContributesAndroidInjecto註解來實現它們之間的關聯。具體實現方式以下:

DishesModules

@Module(includes = PresenterModules.class)
public abstract class DishesModules {

    @ContributesAndroidInjector
    abstract public DishesFragment dishesFragment(); //增長這個抽象方法

    @Provides
    static DishesAdapter providerDishesAdapter(){
        return new DishesAdapter();
    }

    @Binds
    abstract DishesContract.View dishesView(DishesFragment dishesFragment);

    @Binds
    abstract RecyclerView.LayoutManager layoutManager(LinearLayoutManager linearLayoutManager);


}
複製代碼

DishesFragment繼承於DaggerFragment

public class DishesFragment extends DaggerFragment implements DishesContract.View{
    ...
}
複製代碼

改爲這樣,咱們經過new方法來建立DishesFragment的時候也能實現經過註解進行依賴注入了,爲何會這樣呢?由於@ContributesAndroidInjector的做用時幫咱們生成須要的Subcomponent,而後在DaggerFragment經過 DispatchingAndroidInjector 對象來實現依賴注入( 底層原理和咱們使用DaggerXXXComponent手動實現依賴注入差很少 )。咱們能夠看看DishesModules中被@ContributesAndroidInjector註解的方法生成的代碼。

@Module(subcomponents = DishesModules_DishesFragment.DishesFragmentSubcomponent.class)
public abstract class DishesModules_DishesFragment {
  private DishesModules_DishesFragment() {}

  @Binds
  @IntoMap
  @FragmentKey(DishesFragment.class)
  abstract AndroidInjector.Factory<? extends Fragment> bindAndroidInjectorFactory(
      DishesFragmentSubcomponent.Builder builder);

  @Subcomponent
  public interface DishesFragmentSubcomponent extends AndroidInjector<DishesFragment> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DishesFragment> {}
  }
}
複製代碼

能夠看出,編生成的代碼符合咱們上面的結論。

Dagger2 For Android使用要點

咱們如今來總結下,簡化版的Dagger實現依賴注入的幾個必要條件:

  1. 第三方庫經過Modules的@provides註解來提供依賴
  2. 提供一個全局惟一的Component,而且Modules中須要添加AndroidSupportInjectionModule類,它的做用時關聯需求與依賴之間的關係
  3. Application須要繼承DaggerApplication類,而且在applicationInjector構建並返回全劇惟一的Component實例
  4. 其它須要使用依賴注入的組建都須要繼承Dagger組件名字類,而且須要在相應的Modules中經過@ContributesAndroidInjector註解標記須要注入依賴的組建。

上面四個步驟就是使用Dagger2實現依賴注入的要點了,總的來講,複雜度比以前的方法簡單了很是多,要寫的模版代碼也減小了很是多。

通常來講,上面的知識點已經足夠讓咱們在項目中正常使用Dagger2了,可是在使用中還會遇到一些其它的問題,Dagger2也提供瞭解決方法。若是但願進一步瞭解的話,能夠繼續閱讀下文。

Dagger2拓展

@Scope

Scope字面的意思是做用域,在咱們使用Dagger2的時候常常會用到@Singleton這個註解,這個註解的意思的做用是提供單例對象。而咱們在使用@Singleton這個註解的時候,會同時@Provides和@Component,爲何要這樣作呢?由於@Scope的做用範圍其實就是單例的做用範圍,這個範圍主要是經過Component來肯定的。

因此@Scope的做用就是以指定Component的範圍爲邊界,提供局部的單例對象。咱們能夠以上面的例子爲例驗證這個論點論點。

咱們在DishesActivity中增長一句代碼,做用時注入DishesPresneter對象。

@Inject
DishesContract.Presenter mPresenter;
複製代碼

從上面的代碼中,咱們知道DishesFragment中也用一樣的方式來注入過DishesPresneter對象,那麼它們有什麼區別的,咱們經過調試功能來看下。

能夠看出,DishesActivity和DishesFragment中的DishesPresenter不是同一個實例,它們的內存地址是不同的。若是咱們在PresenterModules的dishesPresenter方法中加上@Singleton

@Singleton
@Binds
abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);
複製代碼

能夠預見,DishesActivity和DishesFragment中的DishesPresenter會變成同一個實例,在這個例子中@Singleton的做用是提供全局的單例( 由於OrderAppComponent這個全局惟一的Component也被標註成@Singleton )。這種用法比較簡單,這裏再也不深刻。而比較難理解的就是自定義Scope了,下面咱們經過一個例子來加深你們對自定義Scope的理解。

@DishesScoped

@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface DishesScoped {
}
複製代碼

爲了使測試效果更明顯,咱們稍微修改下Order這個Demo。

DishesModules

@Module
public abstract class DishesModules {
   ...
    @DishesScoped  // 添加註解
    @Binds
    abstract DishesContract.Presenter dishesPresenter(DishesPresenter presenter);
   ...

}
複製代碼

ActivityModules

@Module
public abstract class ActivityModules {

    @DishesScoped  // 添加註解
    @ContributesAndroidInjector(modules = DishesModules.class)
    abstract public DishesActivity contributesDishActivity();
}
複製代碼

而後如今咱們來運行Demo,看下DishesActivity和DishesFragment中的DishesContract.Presenter的對象:

能夠看出,它們是同一個對象,這驗證了咱們上面的結論。這裏又個小問題就是,咱們以前說@Scope是經過Component來肯定做用邊界的,可是上面這個例子中,並無對任何Component類使用@Dishes註解啊?那麼這裏是如何確認邊界的呢?

咱們能夠看看Dagger生成的類ActivityModules_ContributesDishActivity,這個類是根據ActivityModules中的contributesDishActivity方法生成的。

@Module(subcomponents = ActivityModules_ContributesDishActivity.DishesActivitySubcomponent.class)
public abstract class ActivityModules_ContributesDishActivity {
  private ActivityModules_ContributesDishActivity() {}

  @Binds
  @IntoMap
  @ActivityKey(DishesActivity.class)
  abstract AndroidInjector.Factory<? extends Activity> bindAndroidInjectorFactory(
      DishesActivitySubcomponent.Builder builder);

  @Subcomponent(modules = DishesModules.class)
  @DishesScoped   //看這裏
  public interface DishesActivitySubcomponent extends AndroidInjector<DishesActivity> {
    @Subcomponent.Builder
    abstract class Builder extends AndroidInjector.Builder<DishesActivity> {}
  }
}
複製代碼

謎底揭曉,當咱們爲contributesDishActivity添加上@DishesScoped註解後,自動生成的DishesActivitySubcomponent類被@DishesScoped註解了。因此@DishesScoped是經過DishesActivitySubcomponent來確認做用範圍的,這也符合上面的結論。

@Scope的實現原理

@Scope實現單例的原理其實很簡單,咱們能夠看下加了@DishesScoped後Dagger爲咱們生成的注入輔助代碼。在這裏咱們只看關鍵方法:

private void initialize(final DishesActivitySubcomponentBuilder builder) {
      this.dishesFragmentSubcomponentBuilderProvider =
          new Provider<DishesModules_DishesFragment.DishesFragmentSubcomponent.Builder>() {
            @Override
            public DishesModules_DishesFragment.DishesFragmentSubcomponent.Builder get() {
              return new DishesFragmentSubcomponentBuilder();
            }
          };
      this.dishesPresenterProvider =
          DishesPresenter_Factory.create(
              DaggerOrderAppComponent.this.providerMenusProvider,
              DaggerOrderAppComponent.this.providerGsonProvider,
              DaggerOrderAppComponent.this.providerSharedPreferencesProvider);
      this.dishesPresenterProvider2 = DoubleCheck.provider((Provider) dishesPresenterProvider);   //這句代碼是實現單例的關鍵。
    }
複製代碼

能夠看到,咱們的dishesPresenterProvider2這個對象的初始化是經過雙鎖校驗的方式來實現單例的,因此這個對象是一個單例對象。而其它沒有使用@Spoce註解的類則沒有使用雙鎖校驗的方式實現初始化,Dagger經過@Scope實現單例的原理其實很是簡單。關於@Spoce的介紹就到這裏了,若是須要深刻的話,能夠進一步查看Dagger2生成的輔助代碼。

@Qualifier和@Named註解

除了做用域的問題以外咱們還會常常會遇到一個問題,總所周知,Dagger2是自動判斷依賴關係的,若是咱們的代碼中須要使用同一個類生成兩個或多個不一樣的對象呢?例如咱們的LinearManager,咱們如今想用Dagger提供一個橫向的Manager,若是直接寫在項目中是會報錯的,由於Dagger沒法判斷須要注入/依賴的對象是哪一個。以下面的代碼:

LayoutManagerModules

@Module
public class LayoutManagerModules {

    @Provides
    public LinearLayoutManager providesLinearLayoutManager(Context context){
        return new LinearLayoutManager(context);
    }
    
    @Provides 
    public LinearLayoutManager providesHorizonalLinearLayoutManager(Context context){
        return new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
    }

}

複製代碼

這段代碼確定是會報錯的,若是咱們想實現這個功能的話,這個時候咱們就須要用到@Qualifier或者@Named註解了。

咱們先用@Named來實現上面這個需求。

LayoutManagerModules

@Module
public class LayoutManagerModules {

    @Named("vertical")
    @Provides
    public LinearLayoutManager providesLinearLayoutManager(Context context){
        return new LinearLayoutManager(context);
    }

    @Named("horizontal")
    @Provides
    public LinearLayoutManager providesHorizonalLinearLayoutManager(Context context){
        return new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false);
    }


}
複製代碼

DishesModules

public class DishesFragment extends DaggerFragment implements DishesContract.View{

    RecyclerView rvDishes;

    @Inject
    DishesAdapter dishesAdapter;

    @Named("horizontal")
    @Inject
    LinearLayoutManager layoutManager;
}
複製代碼

在注入的時候,咱們經過 @Named("horizontal")就能控制實際是注入哪一個LayoutManager了。在定義依賴的時候@Name註解要配合@Providers,而在使用的時候配合@Inject來使用。

@Qualifier

@Qualifier的做用和@Named是同樣的,@Name也被@Qualifier註解。在使用@Named的時候須要加上咱們定義的key因此略顯麻煩,咱們能夠經過自定義@Qualifier註解來解決這個問題。而自定義@Qualifier註解的方式和自定義@Spoce是同樣的,很是簡單,這裏不做深刻介紹了。

Dagger2還提供了例如懶加載等功能,使用起來都是比較簡單的,這裏限於篇幅就不做進一步介紹了。有興趣的讀者能夠查閱源碼或者看官方文檔來體驗下。

小結

Dagger2 For Android是一款很是適合移動端使用的依賴注入框架。它提供了靜態編譯的方式來實現依賴注入,性能很是好。而且最新版本的Dagger 2.17對Android提供了很是友好的支持,如今使用Dagger2的時候,咱們不須要再手寫注入代碼,這一切Dagger2都幫咱們自動實現了。總的來講,Dagger2是很是適合於應用到咱們的項目中的。而且Dagger2實現依賴注入的方式很是有趣,能掌握這項技術的話,對咱們的提高是很是大的,但願各位讀者在閱讀了本文後可以去體驗一下。

若是這篇文章對你有幫助的話,能夠關注下筆者其它的文章,歡迎你們在個人github上面點star哦。

給你一個全自動的屏幕適配方案(基於SW方案)!—— 解放你和UI的雙手

Gradle自動實現Android組件化模塊構建

技術教程Demo地址(本文的Demo也在裏面喲)👍

你的支持,是我前進的動力,若是個人項目對您有幫助的話,能夠點下star👍

微信公衆號:

若是但願第一時間收到個人技術文章更新的同窗,能夠掃描下面二維碼關注個人我的公衆號:代碼以外的程序員,專一於精通Android技術

或收藏個人我的博客:TANG BLOG

相關文章
相關標籤/搜索