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提供的基本型別:
- DaggerActivity
- DaggerFragment
- DaggerService
- DaggerIntentService
- DaggerBroadcastReceiver
- 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例項一樣,以相同的方式定義子元件。不同的是:
- 需使用Fragment替換Activity型別引數;
- 將@ActivityKey替換為@FragmentKey;
- 將HasActivityInjector替換為HasFragmentInjector;
- AndroidInjection.inject(Fragment)方法,在Fragment的onAttach()中呼叫,而不是在onCreate()中;
- 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,示例程式碼如下:
建立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);
建立子元件 - 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> { } }
建立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>;
- 如果使用v4包中的Fragment,必須繫結AndroidInjector.Factory<? extends android.support.v4.app.Fragment>;
將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依賴注入廣播例項:
建立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(); } } }
在AndroidManifest.xml中宣告CoffeeReceiver
<receiver android:name=".broadcast.CoffeeReceiver"> <intent-filter> <action android:name="com.todo.daggerlearn.coffee" /> </intent-filter> </receiver>
建立子元件 - CoffeeReceiverSubcomponent
@Subcomponent public interface CoffeeReceiverSubcomponent extends AndroidInjector<CoffeeReceiver> { @Subcomponent.Builder public abstract class Builder extends AndroidInjector.Builder<CoffeeReceiver> { } }
建立Module - CoffeeModule
@Module(subcomponents = CoffeeReceiverSubcomponent.class) public abstract class CoffeeModule { @Binds @IntoMap @BroadcastReceiverKey(CoffeeReceiver.class) abstract AndroidInjector.Factory<? extends BroadcastReceiver> bind(CoffeeReceiverSubcomponent.Builder builder); }
將CoffeeModule新增至ApplicationComponent的modules屬性中
@Component(modules = {AndroidInjectionModule.class, CoffeeModule.class,...}) public interface TodoComponent extends AndroidInjector<TodoApplication> { @Component.Builder abstract class Builder extends AndroidInjector.Builder<TodoApplication> {} }
在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的使用流程
對於其他的基本型別,不再舉例子,建立模式基本是一樣的,比較呆瓜式流程如下:
在ApplicationComponent中注入AndroidInjectionModule,如果專案中用到v4包的Fragment,還需注入AndroidSupportInjectionModule.建議把兩個Module都注入XxComponent中,說不定哪位同仁和你想法一樣呢…
@Component(modules = {AndroidInjectionModule.class, AndroidSupportInjectionModule,...}) public interface XxComponent { void inject(XxApplication application); }
建立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(); } } }
建立子元件 - @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> { } }
建立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); }
將建立的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> { } }
建立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();
}
這樣,是不是節省了很多工作,不用再機械式的建立。