在開始說Dagger之前先說下什麼叫依賴注入。
依賴:
在建立對象A的過程中,需要用到對象B的執行個體,這種情況較調用者A對被調用者B有一個依賴。
例如下面的例子: 組裝一台電腦時,要用到Cpu,那麼電腦這個對象,依賴Cpu對象。
public class Computer {
CPU cpu;
public Computer(CPU cpu) {
this.cpu = cpu;
}
}
這個例子中Computer的這種初始化也叫HardInit (HI)方式,弊端在于兩個類之間不夠獨立,如果我們更改了Cpu的構造函數,那麼所有使用到Cpu的初始化方法的代碼都要進行更改。
依賴注入:
指程式運作過程中,調用者(Computer)需要被調用者的輔助(CPU),但是建立被調用者(CPU)的工作不再由調用者(Computer)來完成,而是由相關的容器控制程式被調用者 (CPU)的對象在外部去建立出來并注入到調用者的引用中(Computer),是以也稱為控制反轉(IOC: Inversion of Control)。
如下面:
public class Computer {
//注入CPU
@Inject
Intel_CPU mCPU;
public Computer() {
}
}
那為什麼使用依賴注入?
看完上面的例子其實可以領會到它的作用了。依賴注入是實作控制反轉的方式之一(另一種是控制查找),目的就是為了讓調用者和被調用者之間的解耦;
可以注入依賴的模拟實作,使得測試變得更加簡單。
好了,再來介紹一遍: Dagger2起源于Dagger,Dagger是由Squre公司開發出的依賴注入開源庫,但是還是存在一些性能問題,是以Google在Dagger的基礎上進行了改造演變出了現在的Dagger2.
Dagger就是一款基于Java注解實作完全在編譯階段完成依賴注入的開源庫,主要用于子產品間解耦,提高代碼的健壯性和可維護性。
ok,道理我都懂了,那怎麼用呐?
先來說下幾個注解類的作用:
@inject : 它有兩個作用,一個是用來标記需要已被注入的變量(如 注入到 Computer中的mCpu 變量);二是用來标記被依賴對象的構造函數(CPU的構造函數)。Dagger 可以通過@Inject注解可以在需要這個類執行個體的時候來找到這個構造函數并把執行個體構造出來,以此為被@Inject标記了的變量提供依賴。
@Module : 用于标記提供依賴的類(可以了解為倉庫類,工廠類)。上面說到@Inject是用來标記被依賴對象的構造函數,而@Module的作用相當于 把這些被依賴對象統一收集到@Module标記的類中進行執行個體化。比如被依賴對象是在第三方庫中的,我們是無法對它的構造函數做@Inject标記的,又或者說這個被依賴對象的構造函數是有參數的,那這個參數我們從哪裡傳入?這時@Module就可以解決這個問題。
@Provides : 用于标記@Module标記的類中的方法,該方法是在把被依賴對象注入使用時會調用。它的傳回值就是被依賴對象。
@Component : 用于标注接口,是依賴需求方和依賴提供方之間的橋梁。被Compent标注的結構在編譯時會生成該接口的實作類(如被@Component标注的接口為 ComputeComponent,那麼編譯期間生成的實作類名為DaggerComputerComponent),那麼你可以通過調用這個實作類的方法完成注入。
@Qulifer : 用于自定義注解。被依賴對象被注入到依賴對象中時,依賴對象對被依賴對象的建立過程是不關心的。但是@Provide提供的被依賴對象是根據方法傳回值類型來識别的。那若果被依賴對象的建立過程有兩種,那依賴對象需要的是哪一種呐?比如上面的例子,把Cpu注入到電腦的過程,電腦是不關心Cpu的生産過程的,但是cpu生成時有Intel和Amd兩種可選。那電腦怎麼去拿到想要的那種Cpu呢?
這時使用@Qulifer來定義自己的注解,然後通過自定義注解去标注提供依賴的方法和依賴需求方,這樣就可以精準地拿到所需要的被依賴執行個體。
@Scope: 同樣用于自定義注解,可以通過@Scope自定義的注解來限定注解作用于,實作局部的單例。
@Singleton : @Singleton其實就是一個通過@Scope定義的注解,我們一般通過它來實作全局單例。但實際上它并不能提供全局單例,是否能提供全局單例還要取決于對應的Component是否為一個全局對象。
說了那麼多,是不是一臉懵逼。偉大的XXX說過,沒有經過實踐的概念都是蝦扯蛋。好 ,下面我們循序漸進地看看它的使用:
栗子1:
電腦 依賴 Cpu。 那需要把Cpu注入到 電腦中。
被依賴對象 CPU :
public class CPU {
//使用@Inject标記 被依賴對象 Cpu的構造方法。 這是注入時就會通過@Inject找到該構造函數并把執行個體構造出來
@Inject
public CPU () {
}
public void run() {
Log.d("======", "Intel CPU運作了");
}
}
橋梁(rebuild之後會生成實作類,DaggerComputerComponent):
@Component
public interface ComputerComponent {
void inject(Computer computer);
}
依賴對象:
public class Computer {
//注入被依賴對象
@Inject
Intel_CPU mCPU;
public Computer() {
//把依賴使用方傳入到橋梁中
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
上面就是Dagger2的簡單使用。
栗子2
這時問題來了。上面的@Inject是标記CPU類的無參構造函數的,那現在CPU的構造函數需要傳入一個品牌的參數呢?這參數從哪裡傳進來。還有上面的@Inject是直接對CPU類進行操作的,那這時如果這個類是在類庫中的,那這時是修改不了的,也就無法使用@Inject對其進行标記了。這時@Module和@Provide就出現了。
被依賴對象 CPU :
public class CPU {
private String brand;
//這時構造函數已不需要使用@Inject來标記
public CPU(String brand) {
this.brand = brand;
}
public void run() {
Log.d("======", brand + " CPU運作了");
}
}
被依賴對象的倉庫類,被@Module标記:
@Module
public class CpuModule {
//在此處執行個體化被依賴對象,并通過@Provide提供出去
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
橋梁:
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
輸出:
栗子3
現在問題又來了,假如CPU有兩個牌子,Intel的和AMD,那執行個體化時也就有傳入Indel和Amd兩種。因為@Provide标記的方法也就僅僅知道其是CPU這個類型,并不知道它是Intel還是AMD的。這時就用到@Qulifier。
首先要使用@Qualifier自定義兩個注解:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifilerIntel {
}
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
public @interface QualifilerAmd {
}
被依賴對象 CPU:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
}
public void run() {
Log.d("======", brand + " CPU運作了");
}
}
CPUModule 類:
@Module
public class CpuModule {
// 使用 @QualifilerIntel 标注
@QualifilerIntel
@Provides
public CPU provideIntelCPU() {
return new CPU("Intel");
}
// 使用 @QualifilerAmd标注區分
@QualifilerAmd
@Provides
public CPU provideAmdCPU() {
return new CPU("Amd");
}
}
橋梁
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
//使用 @QualifilerIntel标記拿到指定牌子的CPU
@QualifilerIntel
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
輸出:
栗子4: @Scope
在上面的例子2中,我們對CPU和Computer類修改下:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
Log.d("======", "Create CPU");
}
public void run() {
Log.d("======", brand + " CPU運作了");
}
}
public class Computer {
@Inject
CPU mCPU1;
@Inject
CPU mCPU2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU1.run();
mCPU2.run();
}
}
輸出如下,可以看到,注入兩個變量,邊建立兩次CPU對象:
那現在我要這個CPU是單例的,不管注入多少次,它隻建立一次,這是就用到了@Scope注解。
@Scope是通過限定作用域,實作局部單例的。
首先我們需要通過@Scope定義一個CPUScope注解:
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface CPUScope {
}
然後我們拿這個注解去标記依賴提供方的倉庫類CPUModule:
@Module
public class CpuModule {
@CPUScope
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
同時還需要使用@Scope來标注注入器Component:
@CPUScope
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
依賴對象Computer:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
被依賴對象CPU類:
public class CPU {
private String brand;
public CPU(String brand) {
this.brand = brand;
Log.d("======", "Create CPU");
}
public void run() {
Log.d("======", brand.hashCode() +"==" + brand + " CPU運作了");
}
}
調用:
Computer computer = new Computer();
computer.run();
輸出如下,可以看到注入兩個CPU,但是隻執行了一個構造方法,證明隻建立了一次Cpu執行個體,cpu1和cpu2為同一對象。
但是,這個單例僅僅是對目前執行個體化的computer這個對象生效,并不是全局單例的。假如我再執行個體化一個Computer:
Computer computer = new Computer();
computer.run();
Computer computer2 = new Computer();
computer2.run();
我們再來看看會輸出是怎樣:
看以看到單例沒有在computer和computer2之間生效。那現在我需要全局單例,怎麼辦呐?先别急,這時我們肯定會疑問,為什麼是隻對目前依賴對象生效?那就回頭看看這個Computer的類:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
裡面關鍵代碼就那行: DaggerComputerComponent.builder().build().inject(this);
上面的代碼作用就是:通過Budiler模式,生成DaggerComputerComponent這個注入器,通過這個注入器把被依賴對象(CPU)注入到這個類中。既然上面說到兩個Computer執行個體拿到的CPU并不是同一對象,也就是說這個注解器也不是 同一對象,那實作全局單例的思路是不是來了?我隻需要保證這個注入器Compoment是唯一的就ok啦。那我隻要把這Compoment的生成過程放到Application的onCreate中不就ok了嗎?撸起袖子就是幹。
栗子5 全局單例 : ApplicationComponent
定義ApplicationCompoment:
@CPUScope
@Component(modules = CpuModule.class)
public interface ApplicationCompoment {
void inject(Computer computer);
}
自定義Application,并且初始化ApplicationCompoment:
public class MApplication extends Application {
private static ApplicationCompoment mApplicationCompoment;
@Override
public void onCreate() {
super.onCreate();
mApplicationCompoment = DaggerApplicationCompoment.builder().cpuModule(new CpuModule()).build();
}
public static ApplicationCompoment getApplicationCompoment() {
return mApplicationCompoment;
}
}
然後Computer的使用如下,利用Application中的ApplicationComponent進行注入:
public class Computer {
@Inject
CPU cpu1;
@Inject
CPU cpu2;
public Computer() {
MApplication.getApplicationCompoment().inject(this);
}
public void run() {
cpu1.run();
cpu2.run();
}
}
調用:
Computer computer = new Computer();
computer.run();
Computer computer2 = new Computer();
computer2.run();
輸出如下:
栗子6
@SingleTon 就是一個現成的被@Scope标注的注解,用來限定作用域。使用和上面的@CPUScope一樣。
栗子7: include
當然,當你的Compoment可以注入多個Module的依賴對象時,可以使用花括号把多個Module.class 括起來,如以下:
@CPUScope
@Component(modules = {CpuModule.class, RamModule.class})
public interface ComputerComponent {
void inject(Computer computer);
}
當某個Module包含另外一個Module時,可以使用includes字段:
舉個例子,現在有個需求,CPU包含高配置CPU,我想把高配置的Cpu內建到一個獨立Module中,專門提供給高端客戶,而原來的CpuModule也仍然可以使用這個高配置的Cpu。
那首先肯定是建立一個提供高端Cpu的Module : AndvancedModule
@Module
public class AdvancedCpuModule {
@Provides
AdvanedCPU provideCpu() {
return new AdvanedCPU("Indel i9");
}
}
然後在CpuModule 中使用includes 即可以包含AnvancedModule:
@Module(includes = AdvancedCpuModule.class)
public class CpuModule {
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
而當你的Module中提供的依賴對象需要參數進行執行個體化,而這個參數也是作為一個依賴對象存在,那麼也可以使用includes解決:
如下,Disk 依賴于DiskBrand:
public class Disk {
private DiskBrand mDiskBrand;
public Disk(DiskBrand diskBrand) {
mDiskBrand = diskBrand;
}
public void run() {
Log.d("======", this.hashCode() + "==" + mDiskBrand.brand + " 硬碟運轉了");
}
}
而DiskBrand也作為依賴對象存在:
@Module
public class DiskBrandModule {
@QualifilerA
@Provides
DiskBrand provideDiskBrand1() {
return new DiskBrand("三星");
}
@QualifilerB
@Provides
DiskBrand provideDiskBrand2() {
return new DiskBrand("希捷");
}
}
那麼如下,Disk的提供方法的參數就可以使用DiskModule中提供的依賴對象DiskBrand:
@Module(includes = DiskBrandModule.class)
public class DiskModule {
@Provides
Disk provideDisk(@QualifilerAmd DiskBrand diskBrand) {
return new Disk(diskBrand);
}
}
注入:
public class Computer {
@Inject
Disk mDisk;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mDisk.run();
}
}
輸出如下:
栗子8:dependencies
dependencies :用于component之間的依賴。如ApplicationComponent是全局的component,而其他的component需要用到ApplicationComponent的注入。那這時就可以使用dependencies,使得可以依賴于ApplicationComponent了。
被依賴對象: 顯示器類 Display:
public class Display {
public int size;
public String brand;
public Display(int size, String brand) {
this.size = size;
this.brand = brand;
}
public void run() {
Log.d("======", this.hashCode() + "==" + size + "寸" + brand + "顯示器");
}
}
被依賴對象提供方(倉庫/工具類) DisplayModule:注意下面提供了兩種Display 執行個體
@Module
public class DisplayModule {
@QualifilerA
@Singleton
@Provides
Display provideDisplayA() {
return new Display(, "Dell");
}
@QualifilerB
@Singleton
@Provides
Display provideDisplayB() {
return new Display(, "Philips");
}
}
被依賴的橋梁, ApplicationCpmponent:
@Singleton
@Component(modules = {DisplayModule.class})
public interface ApplicationCompoment {
//注意: 這裡通過這種形式把DisplayModule 中用 @QualifilerA标記的那種 Display顯示出來,而 @QualifilerB标記的那種 Display 并沒有
@QualifilerA Display display();
}
真正使用的Component , ComputerComponent,可以看到其通過dependencies依賴了ApplicationComponent:
@Component(dependencies = ApplicationCompoment.class)
public interface ComputerComponent {
void inject(Computer computer);
}
然後在Computer中進行注入:
public class Computer {
@QualifilerA
@Inject
Display mDisplay;
//這裡注入@QualifilerB 标注的Display,會編譯不通過,因為ApplicationComponent沒有顯示提供出來。
/*@QualifilerB
@Inject
Display mDisplay1;*/
public Computer() {
// 通過 applicationCompoment()把ApplicationComponent關聯
DaggerComputerComponent
.builder()
.applicationCompoment(MApplication.getApplicationCompoment())
.build()
.inject(this);
}
public void run() {
mDisplay.run();
}
}
調用:
Computer computer = new Computer();
computer.run();
列印:
源碼分析
好了,說了那麼多,肯定會産生疑問為什麼可以這樣用,這是怎麼實作的。其實很簡單,我們就去一探究竟。
我們就以例子2來說。
再來回顧下 : CpuModule:
@Module
public class CpuModule {
@Provides
public CPU provideCPU() {
return new CPU("Intel");
}
}
CPUComponent:
@Component(modules = CpuModule.class)
public interface ComputerComponent {
void inject(Computer computer);
}
注入:
public class Computer {
@Inject
CPU mCPU;
public Computer() {
DaggerComputerComponent.builder().build().inject(this);
}
public void run() {
mCPU.run();
}
}
在我們寫好ComputerComponent後,重新Rebuild之後會生成ComputerComponent的實作類DaggerComputerComponent。這裡不會說Dagger根據注解生成 DaggerComputerComponent的原理是怎樣的,主要是分析這個注入的是怎樣一個流程:
我們直接去DaggerComputerComponent源碼:
public final class DaggerComputerComponent implements ComputerComponent {
private CpuModule cpuModule;
private DaggerComputerComponent1(Builder builder) {
initialize(builder);
}
public static Builder builder() {
return new Builder();
}
public static ComputerComponent create() {
return new Builder().build();
}
@SuppressWarnings("unchecked")
private void initialize(final Builder builder) {
this.cpuModule = builder.cpuModule;
}
@Override
public void inject(Computer computer) {
injectComputer(computer);
}
private Computer injectComputer(Computer instance) {
Computer_MembersInjector.injectMCPU(
instance, CpuModule_ProvideCPUFactory.proxyProvideCPU(cpuModule));
return instance;
}
public static final class Builder {
private CpuModule1 cpuModule;
private Builder() {}
public ComputerComponent build() {
//執行個體化CpuModule
if (cpuModule == null) {
this.cpuModule = new CpuModule();
}
return new DaggerComputerComponent(this);
}
public Builder cpuModule(CpuModule cpuModule) {
this.cpuModule = Preconditions.checkNotNull(cpuModule);
return this;
}
}
}
可以看到上面的代碼是通過Builder模式生成DaggerComputerComponent執行個體和CpuModule執行個體。然後在inject()方法中調用 injectComputer(computer);
而在injectComputer(computer)這個方法中執行了
Computer_MembersInjector.injectMCPU(
instance, CpuModule1_ProvideCPUFactory.proxyProvideCPU(cpuModule));
我們先來來看看
CpuModule1_ProvideCPUFactory.proxyProvideCPU(cpuModule)這個裡面做了什麼:
public final class CpuModule_ProvideCPUFactory implements Factory<CPU> {
private final CpuModule module;
public CpuModule_ProvideCPUFactory(CpuModule module) {
this.module = module;
}
@Override
public CPU get() {
return provideInstance(module);
}
public static CPU provideInstance(CpuModule module) {
return proxyProvideCPU(module);
}
public static CpuModule_ProvideCPUFactory create(CpuModule module) {
return new CpuModule_ProvideCPUFactory(module);
}
public static CPU proxyProvideCPU(CpuModule instance) {
return Preconditions.checkNotNull(
instance.provideCPU(), "Cannot return null from a [email protected] @Provides method");
}
}
可以看到其實就是執行了我們定義的CpuModule 的provideCPU()方法,去生成了CPU執行個體。而這個CPU的執行個體最終是通過上面的get()方法提供出去的。
好了,我們在回到上面的
Computer_MembersInjector.injectMCPU(
instance, CpuModule_ProvideCPUFactory.proxyProvideCPU(cpuModule));
看看Computer_MembersInjector.injectMCPU()做了什麼:
public final class Computer_MembersInjector implements MembersInjector<Computer> {
private final Provider<CPU> mCPUProvider;
public Computer_MembersInjector(Provider<CPU> mCPUProvider) {
this.mCPUProvider = mCPUProvider;
}
public static MembersInjector<Computer> create(Provider<CPU> mCPUProvider) {
return new Computer_MembersInjector(mCPUProvider);
}
@Override
public void injectMembers(Computer instance) {
injectMCPU(instance, mCPUProvider.get());
}
public static void injectMCPU(Computer instance, CPU mCPU) {
instance.mCPU = mCPU;
}
}
咦,這不就是對Computer的mCpu變量進行指派操作嗎。綜上所述,CpuModule生成的執行個體就已經傳遞給Computer對象了。
好了,終于說完了。若有哪裡寫得不對的地方,歡迎留言指出。
上面的Demo代碼