Dagger2
1.依賴注入 (Dependency Injection)
1.1 面向接口編程
public interface Drivable {
void drive();
}
public class Bike implements Drivable {
@Override
public void drive() {
System.out.println("騎車");
}
}
public class Car implements Drivable {
@Override
public void drive() {
System.out.println("開車");
}
}
public class Subway implements Drivable {
@Override
public void drive() {
System.out.println("乘地鐵");
}
}
public class Worker {
private void gotoWork() {
// 1.依賴具體,非面向接口編程
Bike bike = new Bike();
bike.drive();
// 2.依賴抽象,面向接口
Drivable drivable = new Bike();
// Drivable drivable = new Car();
// Drivable drivable = new Subway();
drivable.drive();
}
public static void main(String[] args) {
Worker worker = new Worker();
worker.gotoWork();
}
}
方式 1 中直接依賴 Bike 類愿待,Worker 依賴具體的實(shí)現(xiàn)類择膝,一旦改變具體的實(shí)現(xiàn)類怀各,就需要改動(dòng)幅垮。對(duì)于業(yè)務(wù)開發(fā)這是允許的岭接。
方式 2 使用一個(gè) Drivable 接口冕末,采用面向接口編程姆另,對(duì)于 Worker 這個(gè)上層類來說喇肋,不再依賴具體,而是依賴抽象迹辐,如果改變 gotoWork 的方式蝶防,無需做出改變,擴(kuò)展性得到了很大提升明吩,符合依賴倒置原則间学。
依賴倒置原則:高層模塊不要依賴低層模塊。高層模塊和低層模塊應(yīng)該通過抽象來互相依賴。除此之外低葫,抽象不要依賴具體實(shí)現(xiàn)細(xì)節(jié)详羡,具體實(shí)現(xiàn)細(xì)節(jié)依賴抽象。
1.2 什么是依賴注入嘿悬?
上面的例子实柠,改成面向接口編程后,雖然擴(kuò)展性提升善涨,但是仍然有具體的對(duì)象創(chuàng)建窒盐,如果 Bike 類的構(gòu)造函數(shù)改變,如改成有參構(gòu)造钢拧,那么 Worker 也要跟著變蟹漓;或者更換為其他出行方式,需要?jiǎng)?chuàng)建其他對(duì)象源内,Worker 依然要改牧牢。那么有沒有方式不用修改 Worker 類呢?
計(jì)算機(jī)領(lǐng)域的所有問題都可以通過增加一個(gè)中間層來解決
可以想象將具體的對(duì)象構(gòu)建放在 Worker 類的外面姿锭,把依賴的對(duì)象傳入進(jìn)來,這樣就不再需要改變 Worker 類伯铣,這就用到了依賴注入(Dependency Injection)呻此,依賴注入是控制反轉(zhuǎn)(Inversion Of Control)的實(shí)現(xiàn)手段之一,其實(shí)對(duì)于這個(gè)例子來說腔寡,就是將對(duì)象創(chuàng)建的過程交給其他職責(zé)類焚鲜,不再自己完成創(chuàng)建,這就是控制反轉(zhuǎn)放前。那么如果實(shí)現(xiàn)依賴注入呢忿磅?
- 方式一:構(gòu)造函數(shù)傳入?yún)?shù)
public class Worker {
private Drivable mDrivable;
public Worker(Drivable mDrivable) {
this.mDrivable = mDrivable;
}
private void gotoWork() {
// 3.1依賴注入
mDrivable.drive();
}
public static void main(String[] args) {
// 3.1交給使用者創(chuàng)建具體對(duì)象,IOC
Worker worker = new Worker(new Bike());
worker.gotoWork();
}
}
- 方式二:通過 setter 方式注入
public class Worker {
private Drivable mDrivable;
public void setDrivable(Drivable drivable) {
this.mDrivable = drivable;
}
private void gotoWork() {
// 3.2 依賴注入
mDrivable.drive();
}
public static void main(String[] args) {
// 3.2 交給使用者創(chuàng)建具體對(duì)象凭语,IOC
Worker worker = new Worker();
worker.setDrivable(new Bike());
worker.gotoWork();
}
}
- 方式三:通過接口方式注入
public interface DependencySetter {
void set(Drivable drivable);
}
public class Worker implements DependencySetter { {
private Drivable mDrivable;
@Override
public void set(Drivable drivable) {
this.mDrivable = drivable;
}
private void gotoWork() {
// 3.3依賴注入
mDrivable.drive();
}
public static void main(String[] args) {
// 3.3 作為接口配置
Worker worker = new Worker();
worker.set(new Bike());
worker.gotoWork();
}
}
- 方式四:使用依賴注入框架
(1)Spring 中的依賴注入葱她,依賴接口,通過注解可以完成實(shí)現(xiàn)類的注入
(2)Android 中 dagger 依賴注入框架:dagger 是由 Square 公司開發(fā)的似扔,Dagger2 是在 Dagger 的基礎(chǔ)上來的吨些,Dagger2 由 Google 公司開發(fā)并維護(hù)。現(xiàn)在比較『先進(jìn)』的框架都會(huì)采用注解的方式炒辉,Dagger2 也是基于注解開發(fā)的豪墅,而 Dagger2 中所涉及到的注解其實(shí)是基于 javax.inject 上開發(fā)的,它出自 JSR330黔寇。Dagger2 是適應(yīng)于 Java 和 Android 開發(fā)的依賴注入框架偶器,它不僅僅對(duì) Android 開發(fā)有效。但是對(duì)于后端一般很少使用 dagger2 作為依賴注入框架,因?yàn)?Spring 的依賴注入框架更強(qiáng)大屏轰。
對(duì)于較大的 App颊郎,Google 工程師推薦使用 Dagger,對(duì)于小型 App 手動(dòng)注入或者使用其他工具亭枷,甚至不使用依賴注入袭艺,基本都能滿足需求。但是把 Dagger 引入到項(xiàng)目中叨粘,對(duì)于 App 的長(zhǎng)久開發(fā)是收益比較大的猾编,特別是開發(fā)成本上。
從曲線中可以看出升敲,使用 Dagger 開始時(shí)答倡,可能相對(duì)成本比較高,學(xué)習(xí)成本等驴党,但是隨著 App 越來越大瘪撇,開發(fā)成本等各方面收益是很大的。Dagger 的優(yōu)勢(shì)主要在三個(gè)方面:
性能(Performance):Dagger >生成的中間代碼是在編譯期完成的港庄,沒有使用反射
正確性(Correctness):Dagger >在編譯其能保證類型的準(zhǔn)確性倔既,如果注入有問題在工程編譯時(shí)會(huì)報(bào)錯(cuò),不至于在>運(yùn)行時(shí)出現(xiàn) Crash
擴(kuò)展性(Scalability):Dagger 具有很好的擴(kuò)展性鹏氧,特別是大型 App渤涌,如 Gmail、Google Photos把还、 YouTube.
2.Dagger2
2.1 Java 注解
1实蓬、Inject 注解用于構(gòu)造函數(shù),方法吊履,字段的注解安皱;
(1)用于字段上代表要給改字段注入,即賦值
(2)用于構(gòu)造函數(shù)上時(shí)艇炎,代表該對(duì)象可以被注入酌伊,注入時(shí)會(huì)通過該構(gòu)造函數(shù)創(chuàng)建對(duì)象
2、Scope 注解用來表明一個(gè)注解器如何使用被注入的對(duì)象的缀踪,如果沒有使用 @Scope腺晾,每次創(chuàng)建時(shí)都會(huì)重新創(chuàng)建一個(gè)實(shí)例,如果使用 @Singleton 這樣一個(gè)注解辜贵,那么會(huì)保留同一個(gè)實(shí)例悯蝉,代表單例。@Singleton 是使用 @Scope 注解的注解托慨,也可以使用 @Scope 自己定義一個(gè)注解鼻由。
3、Qualifier 用來定義注解的注解,如 @Name蕉世,用于區(qū)分有歧義的注入蔼紧,比如注入兩個(gè)相同類型的變量,可以使用 @Name 來給提供值得地方和被注入的地方打上相同的注解狠轻,這樣就能正確的注入奸例。
2.2 dagger2 注解
除了 javax 中提供的注解,Dagger2 中也提供了很多注解向楼,常用的注解:@Component查吊、@Module、@Provides湖蜕、@Binds逻卖、@Subcomponent,還有提供集合類的注解昭抒,@IntoSet评也、@IntoMap、@@Multibinds灭返、@ClassKey盗迟、@StringKey 等
1.@Component 相當(dāng)于聯(lián)系紐帶,將 @inject 標(biāo)記的需求方和依賴綁定起來熙含,并建立了聯(lián)系诈乒,而 Dagger2 在編譯代碼時(shí)會(huì)依靠這種關(guān)系來進(jìn)行對(duì)應(yīng)的依賴注入。使用時(shí)一般我們只頂一個(gè)一個(gè) Component 接口婆芦,Dagger 會(huì)為我們生成 實(shí)現(xiàn)類,通過實(shí)現(xiàn)類來進(jìn)行注入
2.@Component 來完成注入喂饥,那么注入需要的對(duì)象實(shí)例是由 @Module 標(biāo)注的類提供的消约,或者由 @Inject 的類。@Module 中的方法一般使用 @Provides 注解的方法(可以是靜態(tài)方法) 來提供實(shí)例员帮,或者 @Binds 注解的方法(抽象方法)
3.@Subcomponent 能夠用父 Component 的實(shí)例集合或粮,同時(shí)可以有自己的實(shí)例對(duì)象,來進(jìn)行注入捞高,但是 @Scope 不能和父 Component 相同
2.3 dagger2 基本用法
(1)基本用法
- 定義 Component
@Component 定義一個(gè)接口氯材,接口中聲明我們需要的功能,如給 Activity 進(jìn)行注入硝岗,或者返回需要的類
@Singleton
@Component(modules = {EntityModule.class, BindsEntityModule.class})
public interface EntityComponent {
void injectEntity(InjectorDemoActivity activity);
void injectEntity(InjectorDemoActivity2 activity);
}
- 定義 Module 或者使用 @Inject 修飾構(gòu)造函數(shù)氢哮,注意需要注入對(duì)象的位置,不能同時(shí)使用 Module 和 @Inject 提供型檀,否則會(huì)報(bào)錯(cuò)
// 使用 Module 方式
@Module
public abstract class BindsEntityModule {
@Provides
@Named("static provide BindsEntity")
public static BindsEntity provideFirstEntity(@NonNull BindsEntity entity) {
return entity;
}
@Binds
@Named("Binds provide BindsEntity")
public abstract BindsEntity provideSecondEntity(@NonNull BindsEntity entity);
}
// 使用 @Inject 修飾構(gòu)造函數(shù)
public class InjectEntity {
@Inject
public InjectEntity() {
}
@NonNull
@Override
public String toString() {
return InjectEntity.class.getSimpleName();
}
}
public class InjectorDemoActivity extends AppCompatActivity {
public static final String TAG = "InjectorDemoActivity";
@Inject
InjectEntity mInjectEntity;
@Inject
SingletonEntity mSingletonEntity1;
@Inject
SingletonEntity mSingletonEntity2;
@Inject
ModuleEntity mModuleEntity;
@Named("static provide BindsEntity")
@Inject
BindsEntity mBindsEntity1;
@Named("Binds provide BindsEntity")
@Inject
BindsEntity mBindsEntity2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_injector_demo);
findViewById(R.id.button).setOnClickListener(v ->
startActivity(new Intent(this, InjectorDemoActivity2.class)));
// 構(gòu)建 Component 進(jìn)行注入
DaggerEntityComponent.builder()
.build()
.injectEntity(this);
}
@Override
protected void onResume() {
super.onResume();
// mInjectEntity
Log.e(TAG, mInjectEntity.toString());
// mModuleEntity和mSingletonEntity
Log.e(TAG, mModuleEntity.toString());
Log.e(TAG, mSingletonEntity1.toString());
Log.e(TAG, mSingletonEntity2.toString());
// mBindsEntity1 和 mBindsEntity2
Log.e(TAG, mBindsEntity1.toString());
Log.e(TAG, mBindsEntity2.toString());
}
}
(2)multibinds 集合注入
定義 MultiBindsComponent冗尤,給 MultiInjectActivity 中的變量進(jìn)行注入。MultiBindsComponent 中使用的 modules 是 MultiBindsModule.class,同時(shí)用到了 dependencies裂七,可以依賴 SetComponent.class皆看,MapComponent.class。modules 和 dependencies 都可以是一個(gè)或者多個(gè) Class背零,dependencies 可以理解為組合的形式腰吟。
@Component(modules = MultiBindsModule.class,
dependencies = {SetComponent.class, MapComponent.class})
public interface MultiBindsComponent {
void inject(MultiInjectActivity activity);
}
public class MultiInjectActivity extends AppCompatActivity {
public static final String TAG = "MultiInjectActivity";
// MultiBinds
@Inject
Set<String> stringSet;
@Inject
Set<Integer> integerSet;
@Inject
Map<String, Integer> mSIMap;
@Inject
Map<String, String> mSSMap;
@Inject
Map<MapEntityType, MapEntity> mEntityMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_muiti_inject);
inject();
print();
}
private void inject() {
DaggerMultiBindsComponent.builder()
.setComponent(DaggerSetComponent.create())
.mapComponent(DaggerMapComponent.builder().build())
.build()
.inject(this);
}
}
MultiBindsModule 是 抽象類,@Multibinds 的使用時(shí)要求抽象類徙瓶,Dagger2 會(huì)創(chuàng)建一個(gè)空的 Map 或者 Set 集合毛雇,如果我們需要注入的集合可能為空的時(shí)候才使用這個(gè)種方式。
@Module
public abstract class MultiBindsModule {
@Multibinds
abstract Set<Integer> provideIntegerSet();
@Multibinds
@Named("empty map")
abstract Map<String, Integer> provideStringMap();
}
在看下依賴的 Component倍啥,這里只看下 MapComponent禾乘。MapComponent 的定義和 MultiBindsComponent 的定義是一樣的,只不過 MapComponent 不會(huì)對(duì) Activity 直接注入虽缕,它負(fù)責(zé)給 MultiBindsComponent 的提供依賴始藕。
@Component(modules = MapModule.class)
public interface MapComponent {
Map<String, Integer> provideSIMap();
Map<String, String> provideSSMap();
Map<MapEntityType, MapEntity> provideClassMap();
@Component.Builder
interface Builder {
MapComponent build();
}
}
MapModule 中提供 Map 集合中的元素,Key 對(duì)用方法注解上的 Key氮趋,方法返回值對(duì)應(yīng)的是 Value伍派,Dagger2 會(huì)把相同 Key - Value 類型的元素放入同一個(gè) Map 中。
@Module
public class MapModule {
// 放入 Map<String,Integer> 集合中
@Provides
@IntoMap
@StringKey("first")
static Integer provideFirstInteger() {
return 11;
}
// 放入 Map<String,Integer> 集合中
@Provides
@IntoMap
@StringKey("second")
static Integer provideSecondInteger() {
return 22;
}
// 放入 Map<String,String> 集合中
@Provides
@IntoMap
@StringKey("first")
static String provideFirstString() {
return "AA";
}
// 放入 Map<String,String> 集合中
@Provides
@IntoMap
@StringKey("second")
static String provideSecondString() {
return "BB";
}
// 放入 Map<MapEntityType,MapEntity> 集合中
@Provides
@IntoMap
@MapEntityKey(MapEntityType.TYPE_A)
static MapEntity provideFirstClass(MapEntity entity) {
return entity;
}
// 放入 Map<MapEntityType,MapEntity> 集合中
@Provides
@IntoMap
@MapEntityKey(MapEntityType.TYPE_B)
static MapEntity provideSecondClass(MapEntity entity) {
return entity;
}
}
MapEntityKey 是自定義的一個(gè) MapKey, Dagger2 中提供了 4 種 MapKey剩胁,ClassKey诉植、IntKey、StringKey 和 LongKey昵观,這個(gè) MapKey 對(duì)用 Map 集合中的 Key晾腔,value 對(duì)應(yīng) Module 中返回的對(duì)象。
@MapKey
public @interface MapEntityKey {
MapEntityType value();
}
public enum MapEntityType {
TYPE_A, TYPE_B
}
利用 multibinds 可以實(shí)現(xiàn)一個(gè) spi(Service Provider Interface) 的 plugin啊犬,這種方式實(shí)現(xiàn)的 plugin 相比 APT 方式灼擂,相對(duì)代碼會(huì)多出一些,這里僅給出一個(gè)簡(jiǎn)單的例子觉至。
@Singleton
@Component(modules = {UserModule.class, ShoppingModule.class})
public interface PluginComponent {
Map<Class<?>, Plugin> getPlugins();
}
// module-user --> UserModule
@Module
public class UserModule {
@Singleton
@Provides
@IntoMap
@ClassKey(UserPlugin.class)
static Plugin provideUserPlugin(UserPluginImpl userPlugin) {
return userPlugin;
}
}
// module-shopping --> ShoppingModule
@Module
public class ShoppingModule {
@Singleton
@Provides
@IntoMap
@ClassKey(ShoppingPlugin.class)
static Plugin provideUserPlugin(ShoppingPluginImpl shoppingPlugin) {
return shoppingPlugin;
}
}
public class PluginManager {
public static final String TAG = "PluginManager";
private static final PluginManager INSTANCE = new PluginManager();
private Map<Class<?>, Plugin> mPluginMaps = new HashMap<>();
public static PluginManager getInstance() {
return INSTANCE;
}
private PluginManager() {
//no instance
}
@Nullable
public <T extends Plugin> T getPlugin(Class<T> clazz) {
if (mPluginMaps != null && !mPluginMaps.isEmpty()) {
return (T) mPluginMaps.get(clazz);
}
return null;
}
public void addPlugin(Map<Class<?>, ? extends Plugin> pluginMaps) {
if (pluginMaps == null || pluginMaps.isEmpty()) {
return;
}
for (Map.Entry<Class<?>, ? extends Plugin> entry : pluginMaps.entrySet()) {
if (!mPluginMaps.containsKey(entry.getKey())) {
mPluginMaps.put(entry.getKey(), entry.getValue());
}
}
}
plugin 接口
public interface Plugin {
}
public interface ShoppingPlugin extends Plugin {
void getShopping();
}
public interface UserPlugin extends Plugin {
void getUserInfo();
}
(3)subcomponent/
Subcomponent 的使用有一定的規(guī)則:
- Subcomponent 能獲取到父 Component 中能提供的所有對(duì)象
- Subcomponent 只能有一個(gè)父 Component
- Subcomponent 需要顯示的聲明一個(gè) Builder剔应,父 Component 中需要對(duì)外界提供 Subcomponent 接口
- Subcomponent 的 Scope 不能和 父 Component 的 Scope 相同
// ParentComponent
@Singleton
@Component(modules = ParentModule.class)
public interface ParentComponent {
ChildComponent.Builder childComponentBuilder();
}
// ParentModule
@Module(subcomponents = ChildComponent.class)
public class ParentModule {
@Singleton
@Provides
public ParentEntity provideParentEntity() {
return new ParentEntity();
}
}
// ChildComponent
@Subcomponent(modules = ChildModule.class)
public interface ChildComponent {
void inject(SubComponentDemoActivity activity);
// Subcomponent 需要 定義一個(gè) Builder
@Subcomponent.Builder
interface Builder {
ChildComponent build();
}
}
// ChildModule
@Module
public class ChildModule {
@Provides
public SubEntity provideEntity(ParentEntity entity){
return new SubEntity(entity);
}
}
對(duì)比 dependencies 形式的依賴,dependencies 形式上類似于組合语御,subcomponents 形式上類似于繼承峻贮,可以理解為橫向擴(kuò)展和縱向擴(kuò)展
我們知道在設(shè)計(jì)上優(yōu)先使用組合,具有更好的擴(kuò)展性应闯,但 Subcomponent 也是有一定應(yīng)用場(chǎng)景的纤控,父 Component 中的資源需要注入到 Subcomponent 中。這種情況雖然可以也使用組合依賴的形式碉纺,但是使用組合依賴嚼黔,會(huì)重新創(chuàng)建一個(gè) Component细层。如:ComponentB 和 ComponentC 均依賴 ComponentA,使用組合形式唬涧,那么 ComponentB 和 ComponentC 中的 ComponentA 不是同一個(gè)疫赎,那么導(dǎo)致 ComponentB 和 ComponentC 不能使用相同的資源,在 Android 中可以保證 ComponentA 是 Application 級(jí)別單例碎节,這種情況是沒問題的捧搞。假設(shè)一個(gè) Activity 中有個(gè)多個(gè) Fragment,這種情況下每個(gè) Fragment 都需要 Activity 級(jí)別的 Component 注入狮荔,那么每個(gè) Fragment 需要拿到同一個(gè) Activity 的 Component胎撇,這個(gè) Component 就需要維護(hù),這樣的情況使用 Subcomponent 會(huì)更好一些殖氏,每個(gè) Fragment 中的 SubComponent 都是在 Activity 中的 Component 創(chuàng)建的晚树,并且 能保證 Fragment 的 SubComponent 的 Scope 小于 Activity 的 Scope。
2.4 Scope
@Scope 代表作用域雅采,實(shí)際上是 Component 的作用域爵憎,并不是說打上 @Singleton ,就一定代表是單例婚瓜,單例的維護(hù)主要靠對(duì)應(yīng)的 Component 來維護(hù)宝鼓,將 Component 設(shè)置為 Application 級(jí)別的對(duì)象,也是在整個(gè)應(yīng)用中 Component 只有一個(gè)巴刻,那么它提供的對(duì)應(yīng)也只有一個(gè)愚铡。
@Scope
@Documented
@Retention(RUNTIME)
public @interface ActivityScope {
}
@Scope
@Documented
@Retention(RUNTIME)
public @interface FragmentScope {
}
上面提到 Subcomponent 不能和父 Component 的 Scope 一致,對(duì)于組合形式依賴的 Component胡陪,之間也不能有相同的 Scope沥寥,也就是說不同 Component 之間的 Scope 不能相同。JakeWharton 給出一個(gè)解釋柠座,如果兩個(gè)不同 Component 有相同的 Scope邑雅,會(huì)打破 Scope 的限制。
2.5 Dagger 在 Android 上使用的問題
1愚隧、因?yàn)锳ndroid 中的四大組件是有生命周期的,而且實(shí)例的創(chuàng)建是有系統(tǒng)來完成的锻全,而最好的方式使用 Dagger狂塘,是完全有 Dagger 來創(chuàng)建實(shí)例,所以就需要我們?cè)谏芷谥衼硗瓿勺⑷氲墓ぷ黯幔蜁?huì)出現(xiàn)這樣的代碼:
DaggerEntityComponent.builder()
.build()
.injectEntity(this);
這樣帶來的問題:大量類似的代碼導(dǎo)致后期的維護(hù)和重構(gòu)問題荞胡。根本上來講,它要求需要被注入的類型了嚎,如 Activity 依賴 injector泪漂,也就是 Component廊营,,盡管是面向接口的萝勤,但是仍舊破壞了依賴注入的原則露筒,需要被注入的類,不應(yīng)該知道如何被注入敌卓,只關(guān)心被注入的值慎式。
3 Dagger 原理簡(jiǎn)單分析
3.1 APT
APT(Annotation Processing Tool)是一種處理注釋的工具,它對(duì)源代碼文件進(jìn)行檢測(cè)找出其中的 Annotation,根據(jù)注解自動(dòng)生成代碼趟径。Annotation 處理器在處理 Annotation 時(shí)可以根據(jù)源文件中的 Annotation 生成額外的源文件和其它的文件(文件具體內(nèi)容由Annotation 處理器的編寫者決定),APT還會(huì)編譯生成的源文件和原來的源文件瘪吏,將它們一起生成 class 文件。ButterKnife蜗巧,dagger 等開源注解框架都采用了 APT 技術(shù)掌眠。APT 的使用能很好的簡(jiǎn)化開發(fā)工作,提高效率幕屹。
APT 主要流程
- 定義注解(如@automain)
- 定義注解處理器蓝丙,自定義需要生成代碼
- 使用處理器
- APT自動(dòng)完成如下工作。
3.2 Dagger 原理
1香嗓、APT 生成 DaggerComponent 生成迅腔,名稱為 Dagger + 自定義 Component 名字,如自定義 EntityComponent靠娱,生成后名字為 DaggerEntityComponent沧烈,提供構(gòu)建方法有兩種,通過 Builder 構(gòu)建像云,也可以通過 create() 靜態(tài)方法構(gòu)建锌雀,前提是 Builder 沒有參數(shù),create() 實(shí)際上也是調(diào)用 Builder 構(gòu)建迅诬。我們可以在自定義 Component 顯示生命 @Component.Builder,否則 Dagger2 為默認(rèn)生成一個(gè) Builder腋逆,參數(shù)則是依賴的 Module 和 Component
- Builder 構(gòu)建 Component
public static final class Builder {
private EntityModule entityModule;
private Builder() {
}
public Builder entityModule(EntityModule entityModule) {
this.entityModule = Preconditions.checkNotNull(entityModule);
return this;
}
public EntityComponent build() {
if (entityModule == null) {
this.entityModule = new EntityModule();
}
return new DaggerEntityComponent(entityModule);
}
}
- 使用 MemberInjector 注入
MemberInjector 的情況是 Component 接口中函數(shù)中帶參數(shù),所帶的參數(shù)就是需要被注入的對(duì)象侈贷,對(duì)該對(duì)象中 @Inject 的成員變量進(jìn)行注入惩歉。沒有參數(shù)的情況則不會(huì)生成 MemberInjector,而是對(duì)函數(shù)的返回參數(shù)提供實(shí)例俏蛮。這里僅分析一下函數(shù)中帶參數(shù)生成 MemberInjector 情況撑蚌。
生成 InjectorDemoActivity_MembersInjector,DaggerEntityComponent 通過 InjectorDemoActivity_MembersInjector 對(duì) InjectorDemoActivity 中參數(shù)進(jìn)行注入搏屑。
@Override
public void injectEntity(InjectorDemoActivity activity) {
injectInjectorDemoActivity(activity);
}
private InjectorDemoActivity injectInjectorDemoActivity(InjectorDemoActivity instance) {
InjectorDemoActivity_MembersInjector.injectMInjectEntity(instance, new InjectEntity());
InjectorDemoActivity_MembersInjector.injectMSingletonEntity1(instance, singletonEntityProvider.get());
InjectorDemoActivity_MembersInjector.injectMSingletonEntity2(instance, singletonEntityProvider.get());
InjectorDemoActivity_MembersInjector.injectMModuleEntity(instance, EntityModule_ProvideEntityFactory.provideEntity(entityModule));
InjectorDemoActivity_MembersInjector.injectMBindsEntity1(instance, namedBindsEntity());
InjectorDemoActivity_MembersInjector.injectMBindsEntity2(instance, new BindsEntity());
return instance;
}
InjectorDemoActivity_MembersInjector 需要注入的對(duì)象實(shí)例來源主要有以下幾種情況:
(1)Component 中的 Provider,這種一般是單例争涌,或者是 Module 中的方法中有參數(shù),對(duì)應(yīng)的參數(shù)會(huì)生成 Provider
(2)直接 new 一個(gè)實(shí)例辣恋,對(duì)應(yīng)構(gòu)造函數(shù)上有 @Inject 注解亮垫,或者 Module 中使用 @Binds 抽象方法中參數(shù)模软,官方文檔一般建議使用 @Binds,因?yàn)槟軌蛏偕偕梢恍┹o助代碼
(3)Component 中有 Module 實(shí)例或者其他 Component 實(shí)例,這種情況則使用 Module 實(shí)例或者其他 Component 對(duì)應(yīng)的方法提供的實(shí)例對(duì)象饮潦,如果是 Module,則不是直接調(diào)用 Module 實(shí)例的方法燃异,而是通過 Module_Factory 中間類來調(diào)用 Module 中的方法來提供實(shí)例
(4)Component 依賴的 Module 中提供實(shí)例的方式是靜態(tài)的,同樣 MemberInjector 會(huì)通過 Module_Factory 中間類來調(diào)用 Module 中方法來提供實(shí)例害晦。
上述就是注入過程需要的核心類特铝,注入的出發(fā)過程就是調(diào)用 Component 的中方法。
4 Dagger2 在 MVP 中的使用
Dagger2 是一個(gè)依賴注入框架壹瘟,在 Java 開發(fā)或者 Android 開發(fā)中都可以使用鲫剿,這里看下在 MVP 框架中的使用,當(dāng)然在 MVVM 同樣是可以使用稻轨。
沒有使用 Dagger 的 MVP灵莲,P 層和 M 層都需要自己創(chuàng)建,同時(shí) P 層和 M 層中依賴的對(duì)象也要手動(dòng)獲取殴俱,就前面所說政冻,如果 App 工程越來越大,對(duì)于開發(fā)成本從長(zhǎng)期角度會(huì)很高线欲, Dagger 的引入主要就是解決這個(gè)問題明场。
MVP 的注入從以下兩個(gè)方面來考慮:
- 每一層需要注入的實(shí)例
V 層 :對(duì)于 V 層(四大組件,F(xiàn)ragment)李丰,如果依賴 P 層苦锨,則需要給 V 層注入 P;
P 層:依賴 M 層和 V 層趴泌,需要注入 M 層以及對(duì)應(yīng) V 實(shí)現(xiàn)的 IView 接口實(shí)例,實(shí)際上就是四大組件或 Fragment舟舒;
M 層:依賴一些全局的實(shí)例,如網(wǎng)絡(luò)請(qǐng)求配置嗜憔,緩存等
- 注入的時(shí)機(jī)
由于 Android 中四大組件是由系統(tǒng)來創(chuàng)建實(shí)例秃励,所以對(duì)于這些組件中的 P 層注入就需要在合適時(shí)機(jī),即根據(jù)他們生命周期來注入吉捶,一般會(huì)在生命周期回調(diào)最先調(diào)用的方法中進(jìn)行注入夺鲜,對(duì)于全局的 Component,會(huì)設(shè)置成單例呐舔,在 Application 中進(jìn)行創(chuàng)建和保存币励,然后會(huì)傳遞給四大組件的 Component 作為依賴的 Component。
這里分析一個(gè)比較好的 MVP 的開源框架 MVPArms,該框架采用 MVP+Dagger2+Retrofit+RxJava滋早,集成了多個(gè)開源框架榄审,開發(fā)起來相對(duì)比較容易砌们,前提需要對(duì) Dagger2 有一定的理解杆麸。
4.1 MVPArms 簡(jiǎn)單說明
(1)需要全局配置 GlobalConfiguration搁进,需要包括網(wǎng)絡(luò)相關(guān)配置,異常處理昔头,緩存配置饼问,Gson 解析配置等,需要在 AndroidManifest.xml 中注冊(cè)
<!-- Arms 配置 -->
<meta-data android:name="me.jessyan.mvparms.demo.app.GlobalConfiguration"
android:value="ConfigModule" />
(2)從分包結(jié)構(gòu)上可以看出揭斧,di 包是依賴注入的相關(guān)類莱革,Component 和 Module,mvp 包除了 Model、Presenter讹开、View,還有一個(gè) contract盅视,用于生命 Model 和 View 的接口,只是存放的一個(gè)規(guī)則旦万,也可以不放在一起闹击。
一個(gè)頁面中不一定僅僅有一個(gè) Component,可能有多個(gè) Component 和對(duì)應(yīng)的 Module成艘,但是僅有一個(gè) Component 對(duì) Activity 或者 Fragment 進(jìn)行注入赏半,Component 提供的對(duì)象,都是 M 層和 P 層需要的對(duì)象淆两。
4.2 MVPArms 框架分析
Activity 級(jí)別的 Component 依賴 Application 級(jí)別的 Component(單例)断箫,對(duì) Presenter 和 Model 進(jìn)行注入,這里的注入時(shí)機(jī)設(shè)計(jì)的比較好:首先 AppComponent 在 Application 的 onCreate()方法中創(chuàng)建和注入秋冰,并保存成全局的對(duì)象仲义,然后注冊(cè) Application.ActivityLifecycleCallbacks 實(shí)現(xiàn),在 onActivityCreated 回調(diào)中對(duì) Activity 進(jìn)行注入丹莲,對(duì)于 Fragment 同樣光坝,在
Activity 回調(diào) onActivityCreated 中,判斷如果有 Fragment 則注冊(cè) FragmentLifecycleCallbacks,在 FragmentLifecycleCallbacks 的 onFragmentCreated 回調(diào)中對(duì) Fragment 進(jìn)行注入甥材。
該框架一個(gè)比較巧妙的設(shè)計(jì):將配置信息配置在 AndroidManifest.xml 中盯另,對(duì)于不同的模塊可以設(shè)置自己的獨(dú)特的配置,解析 AndroidManifest.xml 時(shí)會(huì)把所有的配置 Configuration 讀取出來洲赵,作為依賴注入的提供者鸳惯。
優(yōu)勢(shì):該框架高度集成,也具有很好的可配置性叠萍,對(duì)于前中期 APP 開發(fā)芝发,能夠完成快速開發(fā),對(duì)于 Component 和 Module苛谷,甚至是 Presenter 也能夠做到復(fù)用辅鲸。
缺點(diǎn) :多了 Module 和 Component,這個(gè)也是屬于 Dagger2 的缺點(diǎn)腹殿,由于 Android 組件生命周期的存在独悴,不能像 Spring 那樣僅僅用一個(gè)注解就完成依賴注入例书。此外框架中使用 Presser 的主要是在 Activity 或者 Fragment 中,Recyclerview 中 item 沒有使用 presenter刻炒,即沒有采用 MVP 模式决采,對(duì)于簡(jiǎn)單的列表頁面可以不用采用,對(duì)于復(fù)雜的列表情況坟奥,可以考慮集成 MVP 模式树瞭。
5 總結(jié)
- 介紹了依賴注入 (Dependency Injection) 的概念,是 IOC 的一種實(shí)現(xiàn)方式爱谁,以及 DI 的幾種方式晒喷,構(gòu)造函數(shù)傳入;setter 設(shè)置访敌;接口傳入厨埋;使用 DI 框架,Android 中的 Dagger2捐顷。
- Dagger2 是基本 javax 中的 @Inject荡陷、@Scope 等注解基礎(chǔ)上開發(fā)的,以及在 性能(Performance)迅涮、正確性(Correctness)废赞、擴(kuò)展性方面的特點(diǎn),在中型和大型 App 中使用 Dagger2 收益更高叮姑。
- 介紹 Dagger2 中常用幾個(gè)注解唉地,以及基本使用方式,利用 multibinds 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 plugin传透。
- Dagger2 是編譯器框架耘沼,利用 APT 技術(shù)生成輔助代碼,供開發(fā)者在運(yùn)行時(shí)調(diào)用朱盐,在合適的時(shí)機(jī)進(jìn)行注入群嗤,Dagger2 沒有使用反射等操作,所以性能比較高
- 分析了一個(gè)開源框架 MVPArms 對(duì)于 Dagger2 的應(yīng)用以及分析了其基本原理
- 對(duì)于 Dagger2 的應(yīng)用兵琳,官方也提供了一套 Android-Dagger2 框架狂秘,個(gè)人認(rèn)為集成度有點(diǎn)高,理解起來相對(duì)困難躯肌,
參考
Dependency Injection guidance on Android — ADS 2019
Dagger 2. Part II. Custom scopes, Component dependencies, Subcomponents