Android 依賴注入 DI - Dagger2

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)類的注入

spring.png

(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ā)成本上。

app_size.png
dagger_app_get.png

從曲線中可以看出升敲,使用 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 注解

javax_annotation.png
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 注解

dagger_basic.png

除了 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)單的例子觉至。

dagger_plugin.png
@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ò)展

dagger_subcomponent.png

我們知道在設(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。

dagger_subcomponent_apply.png

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 的限制。

dagger_scope_question.png

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 原理

dagger2_how.png

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 同樣是可以使用稻轨。

mvp_basic.png

沒有使用 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ī)則旦万,也可以不放在一起闹击。

dagger_arms_apply.png

一個(gè)頁面中不一定僅僅有一個(gè) Component,可能有多個(gè) Component 和對(duì)應(yīng)的 Module成艘,但是僅有一個(gè) Component 對(duì) Activity 或者 Fragment 進(jìn)行注入赏半,Component 提供的對(duì)象,都是 M 層和 P 層需要的對(duì)象淆两。

4.2 MVPArms 框架分析

mvp_arms.png

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ì)困難躯肌,

參考

Demo

Dagger2

Dagger 官方文檔

Using Dagger in Android apps

Dependency Injection guidance on Android — ADS 2019

MVPArms

Dagger 2. Part II. Custom scopes, Component dependencies, Subcomponents

輕松學(xué)者春,淺析依賴倒置(DIP)、控制反轉(zhuǎn)(IOC)和依賴注入(DI)

輕松學(xué)清女,聽說你還沒有搞懂 Dagger2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末钱烟,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拴袭,老刑警劉巖传惠,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異稻扬,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)羊瘩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門泰佳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尘吗,你說我怎么就攤上這事逝她。” “怎么了睬捶?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵黔宛,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我擒贸,道長(zhǎng)臀晃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任介劫,我火速辦了婚禮徽惋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘座韵。我一直安慰自己险绘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布誉碴。 她就那樣靜靜地躺著宦棺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪黔帕。 梳的紋絲不亂的頭發(fā)上代咸,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音成黄,去河邊找鬼侣背。 笑死,一個(gè)胖子當(dāng)著我的面吹牛慨默,可吹牛的內(nèi)容都是我干的贩耐。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼厦取,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼潮太!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤铡买,失蹤者是張志新(化名)和其女友劉穎更鲁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體奇钞,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澡为,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了景埃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媒至。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖谷徙,靈堂內(nèi)的尸體忽然破棺而出拒啰,到底是詐尸還是另有隱情,我是刑警寧澤完慧,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布谋旦,位于F島的核電站,受9級(jí)特大地震影響屈尼,放射性物質(zhì)發(fā)生泄漏册着。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一脾歧、第九天 我趴在偏房一處隱蔽的房頂上張望指蚜。 院中可真熱鬧,春花似錦涨椒、人聲如沸摊鸡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽鼎姊。三九已至陵霉,卻和暖如春私蕾,著一層夾襖步出監(jiān)牢的瞬間穆役,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工旁蔼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锨苏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓棺聊,卻偏偏與公主長(zhǎng)得像伞租,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子限佩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容