依賴注入框架--Dagger2

依賴注入(DI, Dependency Injection),目的是為了解決類之間的耦合,方便測(cè)試。

減少耦合的一個(gè)原則是,一個(gè)類A需要的類B的對(duì)象的實(shí)例化不應(yīng)該在A中實(shí)現(xiàn),而是通過(guò)一定的方式從外部傳入B的實(shí)例,這樣就解決了類AB之間的耦合。

(PS:寫這篇文章的目的不是說(shuō)自己已經(jīng)掌握了Dagger2,其實(shí)還有很多沒(méi)有深刻理解的地方。把自己研究這幾天的成果寫出來(lái),給想了解Dagger2的同學(xué)做個(gè)參考,同時(shí)也作為自己的一個(gè)備忘。有錯(cuò)誤的地方,請(qǐng)大家斧正。)

舉個(gè)例子:

public class ClassA {
    public ClassA() {
        //do some init work
    }
}

public class ClassB {
    
    private ClassA mInstanceA;

    public ClassB() {
        //ClassA的實(shí)例化在ClassB的構(gòu)造方法中執(zhí)行
        instanceA = new ClassA();
    }
}

如上,ClassB依賴于ClassA,并且在ClassB的構(gòu)造方法中完成了ClassA的實(shí)例化。這樣看上去似乎沒(méi)有什么問(wèn)題,但是當(dāng)CLassA的構(gòu)造方法改變,則必須要修改ClassB的代碼。這時(shí)ClassAClassB之間有耦合,牽一發(fā)動(dòng)全身。

依賴注入可以很好的解決這個(gè)問(wèn)題。有很多種注入依賴的方法:

1.通過(guò)接口注入依賴。

重構(gòu)一下ClassB的寫法:

public interface IClassB {
        
    void setClassA(ClassA a);

}
public class ClassB implements IClassB {
    
    private ClassA mInstanceA;
    
    @Override
    void setClassA(ClassA a) {
        mInstanceA = a;
    }
    
}

2.通過(guò)setter方法注入依賴。

public class ClassB {
    
    private ClassA mInstanceA;
    
    public void setInstanceA(ClassA a) {
        mInstanceA = a;
    }
    
}

3.通過(guò)構(gòu)造方法注入依賴。

public class ClassB {
    
    private ClassA mInstanceA;
    
    public ClassB(ClassA a) {
        mInstanceA = a;
    }
    
}

4.通過(guò)依賴注入框架注入依賴。

開(kāi)始講這篇文章的主角--Dagger2。

Dagger2是目前Android上比較流行的一個(gè)依賴注入框架,Google出品。之前在項(xiàng)目中@劉青也用到了這個(gè)框架,非常適合與MVP架構(gòu)配合使用。

大家知道在MVP架構(gòu)中,View持有Presenter的引用,Presenter持有Model的引用,其實(shí)說(shuō)白了就是依賴。使用Dagger2可以很方便的注入這些依賴。

貼出部分代碼:

public class LoginFragment extends BaseFragment implements LoginView {
    
    @Inject 
    LoginPresenter mLoginPresenter;
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        injectDependencies();
    }
    
    private void injectDependencies() {
        DaggerLoginComponent.builder()
                .appComponent(ComponentHolder.getAppComponent())
                .loginModule(ModuleProvider.getInstance().provideLoginModule(this, getActivity()))
                .build()
                .inject(this);
    }

}

可以看出,LoginFragment依賴于LoginPresenter。在mLoginPresenter上添加@Inject注解,然后在onCreate方法中做了某種操作后,LoginPresenter就被注入到LoginFragment中了,具體是哪種操作,我們往下看。

首先看下Dagger2中常見(jiàn)的幾個(gè)注解:

1.@Inject

在字段或構(gòu)造方法前加上這個(gè)注解,表明這個(gè)字段需要被注入依賴或者標(biāo)注依賴對(duì)應(yīng)的構(gòu)造方法。

2.@Module

@Module標(biāo)注的類作為依賴的提供者。

3.@Provide

在方法前加上這個(gè)注解,表明這個(gè)方法提供依賴,通常用在被@Module注解的類的方法上。舉個(gè)例子:

@Provides
InjectEntity provideInjectEntity() {
    return new InjectEntity();
}

表明這個(gè)方法提供InjectEntity的依賴。

@Component

Component是一個(gè)注入器。@Inject需要注入,@Module提供注入,Component就是這兩者之間的橋梁。被@Component注解的一定是接口。

@Scope

@Scope可以自定義注解,通過(guò)自定義注解可以限定注解的作用域。

@Qualifier

當(dāng)類的類型不足以鑒別一個(gè)依賴的時(shí)候,我們就可以使用這個(gè)注解標(biāo)示。

代碼

拿Google官方的demo來(lái)講:

@Module
public class AndroidModule {
  private final DemoApplication application;

  public AndroidModule(DemoApplication application) {
    this.application = application;
  }

  @Provides 
  @Singleton
  @ForApplication
  Context provideApplicationContext() {
    return application;
  }

  @Provides 
  @Singleton
  LocationManager provideLocationManager() {
    return (LocationManager) application.getSystemService(LOCATION_SERVICE);
  }
}

這是一個(gè)Module,用來(lái)提供依賴的???code>provideApplicationContext方法,@Provides注解表明它提供Context的依賴。當(dāng)某個(gè)Context類型的字段被@Inject注解時(shí),Dagger2會(huì)到Module中尋找返回值為Context,并且被@Provides標(biāo)注的方法,通過(guò)這個(gè)方法將依賴注入到需要的類中。

看到這里你可能會(huì)有疑問(wèn),如果一個(gè)Module中有多個(gè)方法被@Provides標(biāo)注,并且返回值是Context類型時(shí),該如何選擇?這時(shí)就需要用到@Qualifier標(biāo)注了,先看下@ForApplication的具體寫法:

@Qualifier @Retention(RUNTIME)
public @interface ForApplication {
}

在提供依賴的方法以及需要依賴注入的字段前都用@ForApplication標(biāo)注,這兩個(gè)就構(gòu)成了一一對(duì)應(yīng)關(guān)系,如果Module中還有一個(gè)返回值為Context的方法,但是沒(méi)有用@ForApplication標(biāo)注,就可以將其排除。

@ForApplication只是一個(gè)標(biāo)記,你可以改成別的名字,只要能區(qū)別開(kāi)其他有相同返回值的方法即可。

如下是被依賴字段的@Qualifier寫法:

@Named("ForApplication") 
@Provides
Person provideContext(Context context) {
    return new Person(context);
}
另一種寫法

其實(shí)還有一種簡(jiǎn)單的,不用定義新注解的寫法:

@Named("Context")
@Provides
Person provideApplicationContext() {
 
    return new application;
    
}
@Named("Context")
@Inject
Context context;

在提供依賴的方法以及需要依賴注入的字段前都加上@Named標(biāo)注,只要@Name里面的字符串相同,則構(gòu)成一一對(duì)應(yīng)的關(guān)系。這種寫法會(huì)比較簡(jiǎn)便,但是字符串的標(biāo)記容易導(dǎo)致不匹配,字符串寫錯(cuò)了是不會(huì)報(bào)錯(cuò)的。

再看@Singleton注解。這是Dagger2自帶的一個(gè)注解,用于保持在同一個(gè)Component中的對(duì)象是單例的。其實(shí)你去看@Singleton的源碼,就簡(jiǎn)單的幾行:

@Scope
@Documented
@Retention(RUNTIME)
public @interface Singleton {}

事實(shí)上,如果你自己定義一個(gè)注解,把Singleton名字換掉,效果和@Singleton是一樣的。

可以下這樣一個(gè)結(jié)論:

只要Component上添加的@Scope注解和Module中提供依賴的方法上添加的@Scope注解一樣,就會(huì)保持這個(gè)方法返回的對(duì)象在該Component實(shí)例中是單例的。

這也就是很多使用Dagger2的例子中使用的@PerApp、@PerActivity的來(lái)歷。

public class DemoApplication extends Application {
  
  @Singleton
  @Component(modules = AndroidModule.class)
  public interface ApplicationComponent {
    void inject(DemoApplication application);
    void inject(HomeActivity homeActivity);
    void inject(DemoActivity demoActivity);
  }
  
  @Inject LocationManager locationManager;
  
  private ApplicationComponent component;

  @Override public void onCreate() {
    super.onCreate();
    component = DaggerDemoApplication_ApplicationComponent.builder()
        .androidModule(new AndroidModule(this))
        .build();
    component().inject(this);
  }

  public ApplicationComponent component() {
    return component;
  }
}

DemoApplication中有一個(gè)接口ApplicationComponent,用@Component注解表示這是一個(gè)Component,后面的(modules = AndroidModule.class)指明了與其相關(guān)聯(lián)的Module。

需要注意的是,Component必須是一個(gè)接口,而且需要引用到目標(biāo)類的實(shí)例,上面代碼中的inject()方法就是為Component提供目標(biāo)類的,之所以這樣是因?yàn)?code>Component需要目標(biāo)類的實(shí)例來(lái)尋找目標(biāo)類中被@Inject標(biāo)記的字段,以便為這些字段到Module找相應(yīng)的提供依賴的方法。舉個(gè)例子:

上述Component中有這個(gè)方法

void inject(DemoApplication application);

Component拿到DemoApplication的實(shí)例后會(huì)在其中找到下面這個(gè)字段:

 @Inject LocationManager locationManager;

然后,Component會(huì)到其對(duì)應(yīng)的Module中去尋找用@Provides標(biāo)記的,并且返回值是LocationManager的方法,通過(guò)找到的這個(gè)方法得到LocationManager的實(shí)例。到這里也就完成了依賴的注入。

繼續(xù)看HomeActivity的寫法:

public class HomeActivity extends DemoActivity {
  @Inject LocationManager locationManager;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ((DemoApplication) getApplication()).component().inject(this);

    Log.d("HomeActivity", locationManager.toString());
  }
}

繼承自DemoActivity不用管,對(duì)理解Dagger沒(méi)什么意義。

可以看到有一個(gè)被@Inject注解的字段locationManager,表示這個(gè)字段需要依賴注入。在onCreate中調(diào)用了Componentinject方法,傳入目標(biāo)類的引用。流程就像我們上面分析的那樣,到Component到Module中尋找@Provides方法,不再贅述。

注意到之前AndroidModule中提供LocationManager依賴的方法是加入了Singletion依賴的,說(shuō)明所提供的LocationManagerComponent中是單例的。我們可以再加一個(gè)LocationManager驗(yàn)證一下是否真的是單例(提示一下,這里有個(gè)坑,不要被我?guī)нM(jìn)去了)。

HomeActivity代碼改成如下所示:

public class HomeActivity extends DemoActivity {
  @Inject LocationManager locationManager;
  //新加一個(gè)相同類型的字段,用來(lái)驗(yàn)證是否真的是單例的
  @Inject LocationManager locationManager2;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ((DemoApplication) getApplication()).component().inject(this);

    Log.d("HomeActivity", locationManager.toString());
    Log.d("HomeActivity", locationManager2.toString());
  }
}

運(yùn)行之后看下打?。?/p>

04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf

嗯,的確是一樣的。那如果將Singletion去掉是不是應(yīng)該就不一樣了呢?去掉試下:

04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf
04-06 21:18:26.984 13517-13517/? I/HomeActivity: android.location.LocationManager@3d740cbf

還是一樣的?
額,只能說(shuō)Google這個(gè)例子選的不好,為什么一定要用LocationManager這個(gè)類來(lái)舉例子呢?翻上去看看provideLocationManager()這個(gè)方法咋寫的:

getSystemService(LOCATION_SERVICE);

Android中的系統(tǒng)級(jí)別服務(wù)采用集合形式的單例模式,所以不管你調(diào)多少次,返回的都是同一個(gè)實(shí)例。其實(shí)可以在provideLocationManager()方法中加一個(gè)打印,你會(huì)發(fā)現(xiàn)加上@Singleton注解,provideLocationManager()只調(diào)用一次,去掉會(huì)調(diào)用兩次,這樣就符合我們預(yù)期了。

組件之間的依賴

看一個(gè)例子:

@Module
public class SubModule {
    
    @Provides
    public InjectEntity provideEntity(Context context) {
        return new InjectEntity();
    }
    
}

很顯然,SubModule提供一個(gè)InjectEntity的依賴,但是提供依賴的同時(shí)provideEntity方法還依賴一個(gè)Context對(duì)象,如果在SubModule里有@Provides方法返回Context對(duì)象,就像下面這樣:

@Module
public class SubModule {

    private Context mContext;

    public SubModule(Context context) {
        mContext = context;
    }
    
    @Provides
    public Context provideContext() {
        return mContext;
    }
    
    @Provides
    public InjectEntity provideEntity(Context context) {
        return new InjectEntity();
    }
    
}

這樣provideEntity方法會(huì)從provideContext方法中獲得Context對(duì)象的依賴。但是問(wèn)題是如果SubModule中就是沒(méi)有提供Context依賴的方法怎么辦呢?這時(shí)會(huì)到其所依賴的Component中尋找:

@Component(modules = MainModule.class)
public Interface MainComponent {

    void inject(DemoApplication application);
    
}
@Module
public class MainModule {
    
    private Context mContext;
    
    public MainModule(Context context) {
        mContext = context;
    }
    
    @Provide
    public Context provideContext() {
        return mContext;
    }
    
}
@Component(dependencies = MainComponent.class, Module = SubModule.class)
public interface SubComponent {

    void inject(HomeActivity activity);
    
}

上面代碼邏輯很清晰,SubComponent依賴于MainComponent,在SubModule中找不到的依賴會(huì)通過(guò)MainComponentMainModule中去尋找。

這里有一個(gè)需要注意的點(diǎn):

一個(gè)沒(méi)有作用域(unscoped )的組件不可以依賴有作用域的組件.

組件之間的依賴在Android中一般體現(xiàn)為ActivityFragment作用域的Component依賴于App作用域的Component,后者為前兩者提供Application實(shí)例或一些需要在App全局使用的單例對(duì)象。


以上

參考:

google dagger2 官方參考文檔:

https://google.github.io/dagger/

google官方demo地址:

https://github.com/google/dagger/tree/master/examples/android-simple

其他文章:

http://blog.csdn.net/lisdye2/article/details/51942511

http://www.itdecent.cn/p/39d1df6c877d

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容