1. 程式人生 > >Dagger 2應用於Android的完美擴充套件庫-dagger.android

Dagger 2應用於Android的完美擴充套件庫-dagger.android

Dagger系列:

概述

在使用Dagger開發Android時,不可避免的一個問題是,許多Android的類都是由系統例項化的,比如Activity、Fragment等,如果使用Dagger依賴注入例項,我們不由得這麼寫:

public class FrombulationActivity extends Activity {
  @Inject Frombulator frombulator;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // DO THIS FIRST. Otherwise frombulator might be null!
    ((SomeApplicationBaseType) getContext().getApplicationContext())
        .getApplicationComponent()
        .newActivityComponentBuilder()
        .activity(this)
        .build()
        .inject(this);
    // ... now you can write the exciting code
  }
}

從而引發了,不可避免的問題:

  • 複製程式碼使得以後很難重構。 隨著越來越多的開發人員複製貼上該塊,更少的知道它實際上做了什麼。
  • 更重要的是,它要求注射型別(FrombulationActivity)知道其注射器。 即使這是通過介面而不是具體型別完成的,它打破了依賴注入的核心原則:一個類不應該知道如何注入它。

自從Googgle接手Dagger維護以來,大神們腦洞全開,如何能讓Dagger完美的與Android系統相容?確實是個頭疼的問題。dagger.android也就這時候應運而生了。Dagger.Android庫是Dagger庫的補充,dagger.android中的類提供了一種簡化此模式的方法,從而在一定程度上避免了上述問題的發生。

核心類

  • AndroidInjection:注入Android核心庫的基本型別的例項
  • AndroidInjector<T>:注入Android庫的型別的介面, T為Android庫的基本型別T,比如Activity、Fragment、BroadcastReceive等;
  • AndroidInjector.Factory<T>:AndroidInjector<T>的工廠類介面
  • DispatchingAndroidInjector<T>:其為AndroidInjector<T>介面的實現類,將Android核心庫的的基本型別T的例項注入Dagger,該操作是由Android核心庫的類的例項本身執行,而不是Dagger。

注入例項

由於DispatchingAndroidInjector在執行時由類查詢相應的AndroidInjector.Factory,那麼,在基類中,實現HasActivityInjector/HasFragmentInjector介面,在相應的宣告週期(onCreate()或者onAttach())內呼叫AndroidInjection.inject()方法,注入相應的例項。所有每個子類都需要做的是繫結相應的@Subcomponent,從而沒有必要在每個例項類中呼叫AndroidInjection.inject()方法。

在dagger.android庫中,Dagger提供了一些基本型別,比如DaggerActivity和DaggerFragment。對於Appliaction,還提供了DaggerApplication,繼承其後,只需重寫applicationInjectoer()方法來返回AndroidInjector&lxtXxApplication>.

Dagger提供的基本型別:

  1. DaggerActivity
  2. DaggerFragment
  3. DaggerService
  4. DaggerIntentService
  5. DaggerBroadcastReceiver
  6. DaggerContentProvider

下面,我們來了解Dagger所提供的基本型別的用法:

注入Activity例項

1.注入AndroidInjectionModule:在應用程式的ApplicationComponent中,注入AndroidInjectionModule,以確保Android的類(Activity、Fragment、Service、BroadcastReceiver及ContentProvider等)可以繫結。 一般把AndroidInjectionModule放在ApplicationCoponent中,其他的Component依賴Application即可。

    @Component(modules = AndroidInjectionModule.class)
    public interface TodoComponent {
        void inject(TodoApplication application);
    }

2. 建立子元件 - MainSubcomponent:其繼承自AndroidInjector,在該子元件中含有一個抽象類Builder,
該Builder繼承自AndroidInjector.Builder,並由@Subcomponent.Builder註解

    @Subcomponent(modules = AndroidInjectionModule.class)
    public interface MainSubcomponent extends AndroidInjector<MainActivity> {

        @Subcomponent.Builder
        public abstract class Builder extends AndroidInjector.Builder<MainActivity>{
        }
    }

3. 建立Module - MainModule:定義子元件MainActivitySubcomponent後,定義該子元件的Module - MainctivityModule,通過將該Module注入到AppComponentd的modules列表中,以便將MainActivitySubcomponent.Builder新增到元件層次機構中。

    @Module(subcomponents = MainSubcomponent.class)
    public abstract class MainModule {
        @Binds
        @IntoMap
        @ActivityKey(MainActivity.class)
        abstract AndroidInjector.Factory<? extends Activity>
        bindYourActivityInjectorFactory(MainSubcomponent.Builder builder);
    }

    @Component(modules = {..., YourActivityModule.class})
    interface YourApplicationComponent {}


注意:
- 如果在Module中提供依賴例項,@Provides方法必須為靜態方法,否則編譯不通過。

4. 自定義Application,並實現HasActivityInjector介面,同時注入(@Inject)DispatchingAndroidInjector<Activity>例項,該例項在activityInjector()方法中返回:

    public class TodoApplication extends Application  implements HasActivityInjector {
        @Inject
        DispatchingAndroidInjector<Activity> dispatchingActivityInjector;

        @Override
        public void onCreate() {
            super.onCreate();
            DaggerTodoComponent.create()
                    .inject(this);
        }
        /**
         * Returns an {@link AndroidInjector} of {@link Activity}s.
         */
        @Override
        public AndroidInjector<Activity> activityInjector() {
            return dispatchingActivityInjector;
        }
    }

5. 在MainActivity.onCreat()方法中,在呼叫super.onCreate()之前呼叫AndroidInjection.inject(this)

    public class MaiActivity extends Activity {
      public void onCreate(Bundle savedInstanceState) {
        AndroidInjection.inject(this);
        super.onCreate(savedInstanceState);
      }
    }

注入Fragment例項

注入Fragment例項, 與注入Activity例項一樣,以相同的方式定義子元件。不同的是:

  1. 需使用Fragment替換Activity型別引數;
  2. 將@ActivityKey替換為@FragmentKey;
  3. 將HasActivityInjector替換為HasFragmentInjector;
  4. AndroidInjection.inject(Fragment)方法,在Fragment的onAttach()中呼叫,而不是在onCreate()中;
  5. Fragment的Module的新增位置,與Activity定義的Module新增不同,其取決於Fragment內部所需要的其他繫結的依賴注入;

與為Activity定義的模組不同,您可以選擇為Fragment新增模組的位置。 您可以使您的Fragment元件成為另一個Fragment元件,一個Activity元件或Application元件的子元件,這一切都取決於片段需要哪個其他繫結。確定元件位置後,使相應的型別實現HasFragmentInjector。

比如,FragmentA中,綁定了Apple例項,而在Fragment所在的ActivityA的Module中,提供了該例項的,那麼只需要將FragmenA的Module新增至ActivityA的Component即可:

@Subcomponent(modules = { FragmentAModule.class, ... }
public interface ActivityAComponent { ... }

下面,以FruitActivity中新增OrangeFragment為例,其中OrangeFragment中繫結AppleBean例項,該例項由FruitModule提供,而FruitModule為FruitActivity的Module,示例程式碼如下:

  1. 建立Fragment - OrangeFragment,在onAttach()方法中,super.onAttach(context)方法前呼叫AndroidSupportInjection.inject(this)。

    public class OrangeFragment extends Fragment{
    
        ***
    
        @Inject
        AppleBean mAppleBean;
    
        @Override
        public void onAttach(Context context) {
            AndroidSupportInjection.inject(this);
            super.onAttach(context);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_orange, container, false);
    
            ButterKnife.bind(this, rootView);
    
            // AppleBean{name='這是一個蘋果'}
            Log.d("testTodo", mAppleBean.toString());
    
            ***
    
            return rootView;
        }
    
    }
    


    注意:

    • 如果使用v4包中的Fragment,在onAttach()方法中,應呼叫AndroidSupportInjection.inject(this);
    • 如果使用普通的Fragment(即android.app.Fragment),在onAttach()方法中,應呼叫AndroidInjection.inject(this);
  2. 建立子元件 - OrangeSubcomponent,其繼承自AndroidInjector,在該子元件中含有一個抽象類Builder,
    該Builder繼承自AndroidInjector.Builder,並由@Subcomponent.Builder註解

    @Subcomponent
    public interface OrangeSubcomponent extends AndroidInjector<OrangeFragment> {
    
        @Subcomponent.Builder
        public abstract class Builder extends AndroidInjector.Builder<OrangeFragment> {
    
        }
    }
    
  3. 建立Module - OrangeModule:定義該Fragment的Module,通過將該Module注入到相應的Fragment、Activity、AppComponent的Module中,具體注入到哪個Module中,依據繫結的例項的依賴關係

    @Module(subcomponents = OrangeSubcomponent.class)
    public abstract class OrangeModule {
    
        @Binds
        @IntoMap
        @FragmentKey(OrangeFragment.class)
        abstract AndroidInjector.Factory<? extends Fragment> bind(FruitSubcomponent.Builder builder);
    }
    


    注意:

    • 如果使用v4包中的Fragment,必須繫結AndroidInjector.Factory<? extends android.support.v4.app.Fragment>;
  4. 將OrangeModule注入到FruitComponent中,即將OrangeModule新增至FruiltCoponent的modules屬性中

    @Subcomponent(modules = OrangeModule.class)
    public interface FruitSubcomponent extends AndroidInjector<FruitActivity> {
    
        @Subcomponent.Builder
        abstract class Builder extends AndroidInjector.Builder<FruitActivity> {
    
        }
    }
    

注入BroadcastReceiver例項

這裡我們先看看如何動態註冊一個廣播。動態註冊需要在程式碼中動態的指定廣播地址並註冊,通常我們是在Activity或Service註冊一個廣播,下面我們就來看一下注冊的程式碼:

CoffeeReceiver receiver = new CoffeeReceiver();  

IntentFilter filter = new IntentFilter();  
filter.addAction("android.intent.action.MY_BROADCAST");  

registerReceiver(receiver, filter); 

registerReceiver是android.content.ContextWrapper類中的方法,Activity和Service都繼承了ContextWrapper,所以可以直接呼叫。在實際應用中,我們在Activity或Service中註冊了一個BroadcastReceiver,當這個Activity或Service被銷燬時如果沒有解除註冊,系統會報一個異常,提示我們是否忘記解除註冊了。所以,記得在特定的地方執行解除註冊操作:

@Override  
protected void onDestroy() {  
    super.onDestroy();  
    unregisterReceiver(receiver);  
} 

執行這樣行程式碼就可以解決問題了。注意,這種註冊方式與靜態註冊相反,不是常駐型的,也就是說廣播會跟隨程式的生命週期。
我們可以根據以上任意一種方法完成註冊,當註冊完成之後,這個接收者就可以正常工作了。我們可以用以下方式向其傳送一條廣播:

@OnClick(R.id.acb_coffee)
public void onViewClicked() {
    Intent intent = new Intent();
    intent.setAction(CoffeeReceiver.ACTION_COFFEE);
    sendBroadcast(intent);
} 

這樣,就完成了一次簡單的廣播的簡單註冊,看起來也不算太複雜。接下來,讓我們來看看使用Dager2依賴注入廣播例項:

  1. 建立BroadcastReceiver - CoffeeReceiver,其繼承自DaggerBroadcastReceiver。在CoffeeReceiver中,依賴注入AppleBean例項。

    public class CoffeeReceiver extends DaggerBroadcastReceiver {
    
        public static final String ACTION_COFFEE = "com.todo.daggerlearn.coffee";
    
        @Inject
        AppleBean mAppleBean;
    
        @Override
        public void onReceive(Context context, Intent intent) {
            super.onReceive(context, intent);
    
            if (TextUtils.equals(intent.getAction(), ACTION_COFFEE)) {
                Toast.makeText(context, "這是一杯" + mAppleBean.getName() + "味咖啡", Toast.LENGTH_SHORT).show();
            }
        }
    }
    
  2. 在AndroidManifest.xml中宣告CoffeeReceiver

    <receiver android:name=".broadcast.CoffeeReceiver">
        <intent-filter>
            <action android:name="com.todo.daggerlearn.coffee" />
        </intent-filter>
    </receiver>
    
  3. 建立子元件 - CoffeeReceiverSubcomponent

    @Subcomponent
    public interface CoffeeReceiverSubcomponent extends AndroidInjector<CoffeeReceiver> {
        @Subcomponent.Builder
        public abstract class Builder extends AndroidInjector.Builder<CoffeeReceiver> {
    
        }
    }
    
  4. 建立Module - CoffeeModule

    @Module(subcomponents = CoffeeReceiverSubcomponent.class)
    public abstract class CoffeeModule {
    
        @Binds
        @IntoMap
        @BroadcastReceiverKey(CoffeeReceiver.class)
        abstract AndroidInjector.Factory<? extends BroadcastReceiver> bind(CoffeeReceiverSubcomponent.Builder builder);
    }
    
  5. 將CoffeeModule新增至ApplicationComponent的modules屬性中

    @Component(modules = {AndroidInjectionModule.class, CoffeeModule.class,...})
    public interface TodoComponent extends AndroidInjector<TodoApplication> {
        @Component.Builder
        abstract class Builder extends AndroidInjector.Builder<TodoApplication> {}
    }
    
  6. 在DringkActivity中傳送廣播

    public class DrinkActivity extends AppCompatActivity {
    
        @BindView(R.id.toolBar)
        Toolbar mToolbar;
        @BindView(R.id.acb_coffee)
        AppCompatButton mAcbCoffee;
    
        ***
    
        @OnClick(R.id.acb_coffee)
        public void onViewClicked() {
            Intent intent = new Intent();
            intent.setAction(CoffeeReceiver.ACTION_COFFEE);
            sendBroadcast(intent);
        }
    }
    


注意:

  • 只有當BroadcastReceiver在AndroidManifest.xml中註冊時,才能使用DaggerBroadcastReceiver。 當在動態註冊BroadcastReceiver時,推薦使用建構函式注入。

一看前面的程式碼,使用Dagger2注入廣播時,步驟顯得複雜了。而,原生動態註冊廣播也沒有幾行程式碼。不過,值得注意的是,使用原生動態註冊廣播時,每次都得重複的new和註冊等操作,而Dagger只要提供依賴關係即可。

關於Dagger的使用流程

對於其他的基本型別,不再舉例子,建立模式基本是一樣的,比較呆瓜式流程如下:

  1. 在ApplicationComponent中注入AndroidInjectionModule,如果專案中用到v4包的Fragment,還需注入AndroidSupportInjectionModule.建議把兩個Module都注入XxComponent中,說不定哪位同仁和你想法一樣呢…

    @Component(modules = {AndroidInjectionModule.class, AndroidSupportInjectionModule,...})
    public interface XxComponent {
        void inject(XxApplication application);
    }   
    
  2. 建立Android庫的核心類(Activity、Fragment、Service、IntentService、BroadcasReceiver),需要注意的就是:AndroidInjection.inject(T)方法的呼叫位置

    • 在Activity、Service及IntentService的onCreate()中,super.onCreate()方法以前。
    • 在Fragment中onAttach()中,super.onAttach()方法以前。如果是v4包的Fragment,應呼叫AndroidInjection.inject()方法。
    • 在BroadcastReceiver的onReceive()中,super.onReceive之前呼叫。

      public class CoffeeReceiver extends DaggerBroadcastReceiver {
      
          public static final String ACTION_COFFEE = "com.todo.daggerlearn.coffee";
      
          @Inject
          AppleBean mAppleBean;
      
          @Override
          public void onReceive(Context context, Intent intent) {
              super.onReceive(context, intent);
      
              if (TextUtils.equals(intent.getAction(), ACTION_COFFEE)) {
                  Toast.makeText(context, "這是一杯" + mAppleBean.getName() + "味咖啡", Toast.LENGTH_SHORT).show();
              }
          }
      }
      
  3. 建立子元件 - @Subcomponent,其繼承自AndroidInjector<T>,而T就是step2建立的Android庫的型別

    @Subcomponent
    public interface CoffeeReceiverSubcomponent extends AndroidInjector<CoffeeReceiver> {
        @Subcomponent.Builder
        public abstract class Builder extends AndroidInjector.Builder<CoffeeReceiver> {
    
        }
    }
    


    注意:

    • 如果該Activity或者Fragment中,含有子Fragment,可以其modules屬性中注入其子Fragment的Module。當然這樣這也限制了子Fragment的依賴關係。

      @Subcomponent(modules = OrangeModule.class)
      public interface FruitSubcomponent extends AndroidInjector<FruitActivity> {
      
          @Subcomponent.Builder
          abstract class Builder extends AndroidInjector.Builder<FruitActivity> {
      
          }
      }
      
  4. 建立Module,其subcomponents屬性值就是step3建立的子元件。在其內必須宣告一個抽象方法,該抽象方法返回AndroidInjector.Factory<?>例項,而其引數為step1建立的XxSubcomponent.Builder例項。

    @Module(subcomponents = CoffeeReceiverSubcomponent.class)
    public abstract class CoffeeModule {
    
        @Binds
        @IntoMap
        @BroadcastReceiverKey(CoffeeReceiver.class)
        abstract AndroidInjector.Factory<? extends BroadcastReceiver> bind(CoffeeReceiverSubcomponent.Builder builder);
    }   
    
  5. 將建立的Module注入到ApplicationComponent中,即把其新增至ApplicationComponent的modules屬性列表

    @Component(modules = {AndroidInjectionModule.class, AndroidSupportInjectionModule.class,  CoffeeModule.class, ...})
    public interface TodoComponent extends AndroidInjector<TodoApplication> {
        @Component.Builder
        abstract class Builder extends AndroidInjector.Builder<TodoApplication> {
        }
    }
    
  6. 建立Application。在建立時,繼承了DaggerApplication,因為dagger把該新增的DispatchingAndroidInjector<T>新增進去了。但是,並沒有實現v4包的HasSupportFragmentInjector,這裡需要手動新增進去。

    public class TodoApplication extends DaggerApplication implements HasSupportFragmentInjector{
    
        @Inject
        DispatchingAndroidInjector<Fragment> fragmentSupportInjector;
    
        public void onCreate() {
            super.onCreate();
        }
    
        @Override
        protected AndroidInjector<TodoApplication> applicationInjector() {
            return DaggerTodoComponent.builder().create(this);
        }
    
        @Override
        public AndroidInjector<Fragment> supportFragmentInjector() {
            return fragmentSupportInjector;
        }
    }
    

值得一提的是,對於Android庫的核心類注入要求比較嚴格,甚至在哪個生命週期內注入都有嚴格的要求。我們首選的應該是在構造器注入,因為因為javac將確保在設定之前沒有引用欄位,這樣,有助於避免NullPointerExceptions。從而,當需要注入Android庫的核心類時,應儘早注入。所以,DaggerActivity在呼叫super.onCreate()之前,呼叫AndroidInjection.inject()。而DaggerFragment在呼叫super.onAttach()之前呼叫。

對於DaggerFragment而言,可能覺得奇怪,為什麼不再super.onCreate()之前呼叫呢?一是,當Fragment被重新繫結,這樣也可以防止不一致;二是,如果已經綁定了Activity的Fragment,又依賴注入了一個Fragment,這時,super.onAttach(fragment)呼叫AndroidInjection.inject()顯得尤為重要了。

更人性化的Dagger2

看了上面的流程,是不是覺得有點模板化了,每建立一個元件,都要重複著這些步驟,有沒有優化的可能呢?接下來,我們先看 Dagger2中的一個註解@ContributesAndroidInjector

@Target(METHOD)
public @interface ContributesAndroidInjector {
  // 要注入到生成的dagger.Subcomponent中的Module。
  Class<?>[] modules() default {};
}

官方文件對它是這麼解釋的:為其註解的方法生成相應的AndroidInjector。該注射器是 dagger.Subcomponent的實現,而且dagger.Module 的子類。

  • 此註釋必須應用於返回具體的Android框架型別(例如:FooActivity、BarFragment、MyService等)的dagger.Module中的抽象方法。
  • 該方法應該沒有引數。

這樣,可以將前面的 Dagger的使用流程中的步驟3、4、5省略為:

@Module
public abstract class BookModule {

    @ContributesAndroidInjector
    abstract BookActivity contributesBookActivity();
}

這樣,是不是節省了很多工作,不用再機械式的建立。