嗨,我是哈利迪~《看完不忘系列》將以從樹干到細枝的思路分析一些技術(shù)框架,本文將對開源項目dagger進行介紹。
本文約3800字,閱讀大約10分鐘。
Dagger源碼基于最新版本2.28.3

背景
依賴注入(Dependency Injection,DI)遵循控制反轉(zhuǎn)(Inversion of Control,IoC)原則,簡單來說就是創(chuàng)建對象時給對象傳入依賴,通過傳入不同實例來實現(xiàn)不同行為(控制),比如常見的構(gòu)造方法和setter都叫注入。
簡單概括一下谷歌的造車?yán)踝?/a>,
一、不注入,由Car類自己創(chuàng)建依賴的Engine實例,當(dāng)需要替換汽車引擎時,需要修改Car類,違背了開放封閉原則,
class Car {
private Engine engine = new Engine();
}
二、手動依賴注入,如構(gòu)造方法和setter,當(dāng)需要替換汽車引擎時,傳入不同的引擎實現(xiàn)(如ElectricEngine extends Engine)即可,無需修改Car類,
//1.構(gòu)造方法注入
class Car {
private final Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}
//2.setter注入
class Car {
private Engine engine;
public void setEngine(Engine engine) {
this.engine = engine;
}
}
三、隨著汽車配件變多,依賴注入的代碼也會越來越多,如汽車需要引擎、輪胎,引擎又需要氣缸和火花塞,這樣一層層下去注入代碼成指數(shù)級上升,出現(xiàn)了大量創(chuàng)建對象的樣板代碼,

class Car {
private final Engine engine;//引擎
private final Wheel wheel;//輪胎
public Car(Engine engine,Wheel wheel) {
this.engine = engine;
this.wheel = wheel;
}
}
class Engine {
private final Cylinder cylinder;//氣缸
private final SparkPlug sparkPlug;//火花塞
public Car(Cylinder cylinder,SparkPlug sparkPlug) {
this.cylinder = cylinder;
this.sparkPlug = sparkPlug;
}
}
void main(){
//為了造輛車,要new出一堆對象,還要構(gòu)造方法注入,套娃既視感
Cylinder cylinder = new Cylinder();
SparkPlug sparkPlug = new SparkPlug();
Engine engine = new Engine(cylinder,sparkPlug);
Wheel wheel = new Wheel();
Car car = new Car(engine,wheel);
}
從代碼可以看出有兩大痛點,一是要創(chuàng)建很多對象,二是對象的創(chuàng)建過程還需有序,即要先有氣缸和火花塞才能造引擎,先有引擎和輪胎才能造車,這樣使用方還需花時間了解配件間的依賴關(guān)系,很是不便。
于是就有了一些庫來實現(xiàn)自動依賴注入,有兩個實現(xiàn)思路(koin的實現(xiàn)以后再聊~),
- 一是運行期反射連接依賴項,編譯影響小,但運行慢
- 二是編譯期就連接依賴項,創(chuàng)建輔助類需要額外的io和編譯耗時,會拖慢編譯速度,但運行快
像Android內(nèi)存和算力都有限的終端設(shè)備,dagger當(dāng)然是選擇思路2啦。dagger通過注解標(biāo)記對象創(chuàng)建姿勢、依賴關(guān)系、作用域等信息,在編譯期創(chuàng)建輔助類,從而實現(xiàn)自動依賴注入。不過dagger的上手成本略高,谷歌后來又推出了Hilt,旨在讓我們用得舒心,
Hilt 是推薦用于在 Android 中實現(xiàn)依賴項注入的 Jetpack 庫。Hilt 通過為項目中的每個 Android 類提供容器并自動為您管理其生命周期,定義了一種在應(yīng)用中執(zhí)行 DI 的標(biāo)準(zhǔn)方法。
Hilt 在熱門 DI 庫 Dagger 的基礎(chǔ)上構(gòu)建而成,因而能夠受益于 Dagger 提供的編譯時正確性、運行時性能、可伸縮性和 Android Studio 支持。
-- 谷歌
Hilt就先放一放,下面我們先開始dagger之旅吧~
補:關(guān)于手動注入的痛點,可以看下谷歌的手動依賴項注入(看完或許能更好的理解dagger的設(shè)計)。
樹干
簡單使用
依賴,
implementation 'com.google.dagger:dagger:2.28.3'
annotationProcessor 'com.google.dagger:dagger-compiler:2.28.3'
@Inject和@Component
@Inject標(biāo)記實例的創(chuàng)建姿勢,汽車和引擎類,
class Car {
private final Engine mEngine;
@Inject //告訴dagger如何創(chuàng)建Car
public Car(Engine engine) {
mEngine = engine;
}
public void start() {
mEngine.start();
}
}
class Engine {
@Inject //告訴dagger如何創(chuàng)建Engine
public Engine() {
}
public void start() {
Log.e("哈利迪", "引擎發(fā)動,前往秋名山");
}
}
這樣dagger就能生成類似 new Car(new Engine()) 的代碼來創(chuàng)建實例,
@Component標(biāo)記所要創(chuàng)建的實例有哪些,如在造車圖紙(接口)里聲明要造車,
@Component //告訴dagger要造啥
interface CarGraph {
//造車
Car makeCar();
}
make一下項目,生成Car_Factory、Engine_Factory和DaggerCarGraph三個類,在Activity中使用,
class DaggerActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dagger);
//得到造車圖紙
CarGraph carGraph = DaggerCarGraph.create();
//造出一輛汽車
Car car = carGraph.makeCar();
//引擎發(fā)動
car.start();
}
}
注入姿勢二,還可以直接把汽車注入給Activity,
@Component //告訴dagger要造啥
public interface CarGraph {
//造車
Car makeCar();
//新增:告訴dagger有個Activity要注入
void inject(DaggerActivity activity);
}
make一下,這時會多出一個類DaggerActivity_MembersInjector(成員注入器),我們后面再看,在Activity中,
class DaggerActivity extends AppCompatActivity {
//向Activity注入汽車
@Inject
Car mCar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_dagger);
//得到造車圖紙
CarGraph carGraph = DaggerCarGraph.create();
//告訴dagger有個Activity要注入
carGraph.inject(this);
//此時,mCar已被dagger自動賦值,我們可以直接發(fā)動引擎
mCar.start();
}
}
不管是哪種姿勢,都可以看出,Activity只管提車,無需關(guān)心造車內(nèi)部的細節(jié)(用了什么引擎),這樣以后要換引擎了,Activity和Car也不用改動代碼。
@Module和@Binds
那么問題來了,通常我們都是面向接口編程,現(xiàn)在想把Engine換成IEngine接口咋整呢?因為我有兩種引擎,分別是汽油車的GasEngine,和電動車的ElectricEngine,接口沒有構(gòu)造方法怎么注入?此時@Module和@Binds注解就派上用場了。
定義引擎接口和實現(xiàn)類,
interface IEngine {
public void start();
}
class GasEngine implements IEngine {//汽油引擎
@Inject
public GasEngine() {
}
@Override
public void start() {
Log.e("哈利迪", "汽油 引擎發(fā)動,前往秋名山");
}
}
class ElectricEngine implements IEngine {//電動引擎
@Inject
public ElectricEngine() {
}
@Override
public void start() {
Log.e("哈利迪", "電動 引擎發(fā)動,前往秋名山");
}
}
然后Car我們不要了,新寫一個汽車類NewCar,讓他依賴于接口IEngine而非類,
class NewCar {
//依賴于接口
private final IEngine mEngine;
@Inject //告訴dagger如何創(chuàng)建NewCar
public NewCar(IEngine engine) {
mEngine = engine;
}
public void start() {
mEngine.start();
}
}
下面有請@Module和@Binds登場!建一個抽象類GasEngineModule,表示汽油引擎模塊,用于提供汽油引擎,
@Module
abstract class GasEngineModule {//汽油引擎模塊
@Binds
abstract IEngine makeGasEngine(GasEngine engine);
}
然后在造車圖紙CarGraph里支持一下NewCar的創(chuàng)建,把Module引入Component,
@Component(modules = {GasEngineModule.class})
//引入汽油引擎模塊
public interface CarGraph {
//...
//造新車
NewCar makeNewCar();
}
Activity使用,
//DaggerActivity.java
void onCreate(Bundle savedInstanceState) {
CarGraph carGraph = DaggerCarGraph.create();
NewCar newCar = carGraph.makeNewCar();
newCar.start();
}
搞定~

這時如果想要把NewCar換成電動引擎,直接依賴新的模塊即可,新增模塊ElectricEngineModule,用于提供電動引擎,
@Module
public abstract class ElectricEngineModule {//電動引擎模塊
@Binds
abstract IEngine makeElectricEngine(ElectricEngine engine);
}
GasEngineModule模塊換成ElectricEngineModule,
@Component(modules = {ElectricEngineModule.class})//替換
public interface CarGraph {
//...
}
運行就能得到電動車了,可見這個過程我們不需要修改NewCar就能換掉引擎(偷梁換柱)。
@IntoMap和@StringKey
又有一天,突然想開雙混動汽車了咋整,就是說汽油引擎和電動引擎我全都要,我們發(fā)現(xiàn)@Component的modules可以傳入數(shù)組,那試試把兩個引擎模塊都傳進去,
@Component(modules = {GasEngineModule.class, ElectricEngineModule.class})
public interface CarGraph {
//...
}
make一下,不用想也知道會出錯,NewCar只有一個依賴IEngine接口,這時dagger已經(jīng)不知道,造NewCar到底要傳入哪個引擎了,

那怎么辦?支持多綁定的@IntoMap和@StringKey登場了。
IntoMap表示會把這個Module存進map里,StringKey表示Module名字,這個名字也會作為map的key,
@Module
abstract class GasEngineModule {//汽油引擎模塊
@Binds
@IntoMap
@StringKey("Gas")
abstract IEngine makeGasEngine(GasEngine engine);
}
@Module
abstract class ElectricEngineModule {//電動引擎模塊
@Binds
@IntoMap
@StringKey("Electric")
abstract IEngine makeElectricEngine(ElectricEngine engine);
}
然后修改一下NewCar,此時不再只是依賴一個引擎接口IEngine了,而是一組引擎,
class NewCar {
//我現(xiàn)在是混動車了,要用map接收引擎
private final Map<String, IEngine> mEngineMap;
@Inject //告訴dagger如何創(chuàng)建NewCar
public NewCar(Map<String, IEngine> engineMap) {
mEngineMap = engineMap;
}
public void start() {
//雙混動走起~
for (Map.Entry<String, IEngine> entry : mEngineMap.entrySet()) {
entry.getValue().start();
//汽油 引擎發(fā)動,前往秋名山
//電動 引擎發(fā)動,前往秋名山
}
}
}
Activity代碼不用改動,雙混動車就造好了。

@Singleton
如果想要給雙混動NewCar限量全球一款,可以用@Singleton指定為單例,
@Singleton //單例,我現(xiàn)在是限量款混動車
public class NewCar {
//...
}
@Singleton //造車圖紙也需跟著單例
@Component(modules = {GasEngineModule.class, ElectricEngineModule.class})
public interface CarGraph {
//...
}
Activity運行,
//DaggerActivity.java
void onCreate(Bundle savedInstanceState) {
CarGraph carGraph = DaggerCarGraph.create();
NewCar newCar = carGraph.makeNewCar();
newCar.start();
NewCar newCar2 = carGraph.makeNewCar();
newCar2.start();
//newCar和newCar2是同一個實例
Log.e("哈利迪", newCar.hashCode() + "," + newCar2.hashCode());
}
dagger的使用就先聊到這啦,相信對dagger也已經(jīng)有了初步認識,還有些注解沒講到,比如:
@Provides:當(dāng)我們沒法用@Inject來標(biāo)記實例的創(chuàng)建姿勢時,可以用@Module和@Provides來提供實例,比如Retrofit是三方庫的類我們沒法標(biāo)記其構(gòu)造方法,則可以用Provides提供,
@Module
public class NetworkModule {
@Provides
public Retrofit provideRetrofit() {
return new Retrofit.Builder()
.baseUrl("xxx")
.build();
}
}
@Scope:作用域 ...
@Subcomponent:子組件...
注解還有很多,準(zhǔn)備放到細枝篇來寫了...??
實現(xiàn)原理
dagger編譯期解析注解創(chuàng)建輔助類的過程就不分析了,我們直接看他生成的輔助類,

注:一開始寫接口名字時,用造車圖紙CarGraph而不是造車廠CarFactory,是為了避免和dagger的生成類搞混,用CarGraph有幾何圖的寓意,可以理解成造車藍圖(PPT),讓我們一起,為夢想窒息...
我們看到DaggerCarGraph,他實現(xiàn)了我們的CarGraph接口,
class DaggerCarGraph implements CarGraph {
//Provider,提供多引擎map,<引擎名字、引擎實例>
private Provider<Map<String, IEngine>> mapOfStringAndIEngineProvider;
//Provider,提供NewCar
private Provider<NewCar> newCarProvider;
private DaggerCarGraph() {
initialize();
}
public static Builder builder() {
return new Builder();
}
public static CarGraph create() {
//用builder創(chuàng)建DaggerCarGraph實例
return new Builder().build();
}
private void initialize() {
//創(chuàng)建提供汽油引擎的Provider和電動引擎的Provider
//用put將他們存起來,合并成一個提供多引擎map的Provider
this.mapOfStringAndIEngineProvider = MapFactory.<String, IEngine>builder(2)
.put("Gas", (Provider) GasEngine_Factory.create())
.put("Electric", (Provider) ElectricEngine_Factory.create())
.build();
//把提供多引擎map的Provider傳入NewCar_Factory,
//然后用DoubleCheck包一層,他會加鎖、處理單例邏輯
this.newCarProvider = DoubleCheck.provider(
NewCar_Factory.create(mapOfStringAndIEngineProvider));
}
@Override
public Car makeCar() {
//1. 老的造車:姿勢一,用makeCar直接造
return new Car(new Engine());
}
@Override
public void inject(DaggerActivity activity) {
//2. 老的造車:姿勢二,用@Inject注入 ↓
injectDaggerActivity(activity);
}
private DaggerActivity injectDaggerActivity(DaggerActivity instance) {
//2. 老的造車:姿勢二,先創(chuàng)建后注入
//實例的創(chuàng)建也用makeCar,如果我們接口沒有定義這個方法,dagger會生成一個功能一樣的getCar
DaggerActivity_MembersInjector.injectMCar(instance, makeCar());
return instance;
}
@Override
public NewCar makeNewCar() {
//3. 新的造車,從Provider獲取
return newCarProvider.get();
}
public static final class Builder {
private Builder() {
}
public CarGraph build() {
return new DaggerCarGraph();
}
}
}
- 老的造車:姿勢一,用makeCar直接造
在造老車Car時,姿勢一是直接調(diào)接口的makeCar方法來造,實現(xiàn)就是簡單的new出實例。
- 老的造車:姿勢二,用@Inject注入
姿勢二用@Inject注入實例,可見他也是先調(diào)makeCar()得到實例,然后調(diào)DaggerActivity_MembersInjector.injectMCar進行注入,
//DaggerActivity_MembersInjector.java
@InjectedFieldSignature("com.holiday.srccodestudy.dagger.DaggerActivity.mCar")
public static void injectMCar(DaggerActivity instance, Car mCar) {
//引用DaggerActivity的成員,為其賦值,可見mCar不能聲明為private
instance.mCar = mCar;
}
- 新的造車,從Provider獲取
在造新車NewCar時,是從Provider獲取的,跟進newCarProvider.get(),如果使用了單例@Singleton,NewCar_Factory會被DoubleCheck包一層,DoubleCheck會加鎖和處理單例邏輯,我們直接看NewCar_Factory的get就行了,
//Factory<T> extends Provider<T>
class NewCar_Factory implements Factory<NewCar> {
private final Provider<Map<String, IEngine>> engineMapProvider;
public NewCar_Factory(Provider<Map<String, IEngine>> engineMapProvider) {
this.engineMapProvider = engineMapProvider;
}
@Override
public NewCar get() {
//通過engineMapProvider獲取多引擎map
return newInstance(engineMapProvider.get());
}
public static NewCar_Factory create(Provider<Map<String, IEngine>> engineMapProvider) {
return new NewCar_Factory(engineMapProvider);
}
public static NewCar newInstance(Map<String, IEngine> engineMap) {
//new出新車實例
return new NewCar(engineMap);
}
}
看下多引擎map又是如何獲取的,engineMapProvider.get(),
//MapFactory.java
//MapFactory extends AbstractMapFactory implements Factory
public Map<K, V> get() {
//創(chuàng)建新的LinkedHashMap
Map<K, V> result = newLinkedHashMapWithExpectedSize(contributingMap().size());
//遍歷已存入的引擎,存進新map
for (Entry<K, Provider<V>> entry : contributingMap().entrySet()) {
result.put(entry.getKey(), entry.getValue().get());
}
//返回不可修改的map
return unmodifiableMap(result);
}
至此,dagger的各生產(chǎn)線流程就分析完了,

細枝
@Scope作用域、@Subcomponent子組件、還有SPI(Service Provider Interface)、grpc(谷歌的遠程過程調(diào)用框架)等,都留到細枝篇來寫啦。
使用場景
那dagger在Android中有哪些用武之地?首先是從架構(gòu)角度,在谷歌示例中,結(jié)合了Activity、偽ViewModel、Repository、DataSource和Retrofit來使用dagger,(還沒用過,不知道香不香、坑多不多,靠屏幕前的大佬們反饋了~)

然后我們在項目中的一些場景,是從業(yè)務(wù)角度切入,在個別復(fù)雜度較高的業(yè)務(wù)線上單獨使用dagger。例如錢包業(yè)務(wù),有大量實例和大量頁面/視圖存在多對多關(guān)系,比如錢包Act需要錢包Api、錢包用戶信息Manager;充值A(chǔ)ct需要支付Api、充值Service;銀行卡列表View需要銀行Service...像這種多對多、對象依賴關(guān)系雜亂無章的場景,很適合用dagger來幫我們注入。
@PurseScope //錢包作用域
@Component(modules = PurseModule.class) //由PurseModule提供實例
public interface PurseComponent {
//為act注入
void inject(退款詳情Act xxxAct);
void inject(銀行卡首頁Act xxxAct);
void inject(錢包首頁Act xxxAct);
void inject(錢包設(shè)置Act xxxAct);
void inject(余額首頁Act xxxAct);
//...
//為view注入
void inject(錢包首頁GridContainer xxxGridContainer);
void inject(銀行卡列表View xxxView);
//提供網(wǎng)絡(luò)調(diào)用
支付Api xxxApi();
//提供錢包用戶信息
錢包用戶信息Manager xxxManager();
//...
}
然后看到PurseModule,用于提供各種實例,可以new、手動單例、工廠生產(chǎn)...
@Module
public class PurseModule {
@Provides
@PurseScope
public 支付Api provide支付Api(...) {
return new 支付Api(...);
}
@Provides
@PurseScope
public 用戶信息Manager provide用戶信息Manager(...) {
return 用戶信息Manager.get(...);
}
@Provides
@PurseScope
public 充值Service provide充值Service(...) {
return new 充值ServiceFactory.create(...);
}
//...
}
尾聲
簡單總結(jié)下dagger的優(yōu)缺點~
- 優(yōu)勢:無反射、支持增量編譯
- 缺點:構(gòu)建過程需要而外的io和編譯時間、生成類會增大包體積、不夠好用、
后續(xù)計劃:dagger細枝、hilt、koin、順便看看spring上的注入@Autowired是怎么做的...學(xué)海無涯啊~

系列文章:
參考資料
- GitHub & 文檔 & API
- 谷歌 - Android 中的依賴項注入
- 谷歌 - 在 Kotlin 中使用 Dagger 會遇到的陷阱和優(yōu)化方法
- 掘金 - 從Dagger到Hilt,谷歌為何執(zhí)著于讓我們用依賴注入