
和人類需要群居一樣,程式界的行程、執行緒也需要通信往來,它們的交流則依賴模塊之間、檔案之間產生的關系,如何快速地搞清和構建這種關系,同時還能減輕彼此的依賴,需要開發者們認真思考,
我們將這種需求稱之為依賴注入(DI,Dependency Injection),這個編程技術由來已久,在講述之前想來簡單回顧下依賴和關聯的基本概念,
依賴和關聯
像下圖示意的那樣,模塊或類之間的關系大體可以分為依賴(Dependency)和關聯(Association)兩種,依賴一般表現為區域引數,關聯則表現為屬性的持有,

按照被關聯物件的生命周期的不同,又可以將關聯分為聚合(Aggregation)和組合(Composition),耦合度依次增強,
依賴注入
依賴注入編程技術最終要構建的并非特指上面的依賴關系,也包含了關聯關系,只是構建的入口多表現為引數注入,
不依賴框架的情況下我們也可以手動注入這些關系,
- 通過建構式傳參
- 通過setter函式傳參
但面對依賴縱深復雜的大型專案,手動注入這些依賴非常繁瑣和易錯,互相的依賴關系難以避免得混亂而丑陋,久而久之,耦合度越來越強,難以擴展,這時候自動注入這些依賴關系顯得尤為必要,
自動注入可以選擇通過反射在運行階段構建依賴關系的框架,比如Guice,也可以選擇在編譯階段即可構建依賴的更優方案,
比如今天的主角Dagger2,像它的名字一樣,是實作DI技術的一把利器,
Dagger和Dagger2
Dagger由開源了okHttp的Square公司開發,廣為人知,但其部分功能仍然依靠反射來實作,美中不足,
https://github.com/square/dagger
于是Google接過了接力棒,在其基礎之上進行了改善,推出了Dagger2,它通過APT在編譯階段決議注解生成代碼實作依賴注入,
https://github.com/google/dagger
Dagger2簡單實戰 🔪
我們通過Dagger2、ViewModel和Retrofit查詢電影介面,簡單演示下如何使用Dagger2,
DEMO: https://github.com/ellisonchan/JetpackDemo

1. 匯入框架
apply plugin: 'kotlin-kapt'
dependencies {
implementation 'com.google.dagger:dagger:2.x'
kapt 'com.google.dagger:dagger-compiler:2.x'
}
2. 創建Dagger組件介面
@Singleton
@Component(modules = [NetworkModule::class])
interface ApplicationGraph {
fun inject(activity: DemoActivity)
}
class MyApplication: Application() {
val appComponent = DaggerApplicationComponent.create()
}
3. Activity欄位注入ViewModel
class DemoActivity: Activity() {
@Inject
lateinit var movieViewModel: MovieViewModel
override fun onCreate(savedInstanceState: Bundle?) {
(applicationContext as MyApplication).appGraph.inject(this)
super.onCreate(savedInstanceState)
val binding = ActivityDaggerBinding.inflate(layoutInflater)
...
}
}
注意:Activity由系統實體化只能通過欄位注入,
4. 宣告ViewModel需要注入MovieRepository
class MovieViewModel @Inject constructor(
private val movieRepository: MovieRepository
)
5. 宣告MovieRepository和Data的注入
@Singleton
class MovieRepository @Inject constructor(
private val localData: MovieLocalData,
private val remoteData: MovieRemoteData
)
class MovieLocalData @Inject constructor()
@Singleton
class MovieRepository @Inject constructor(
private val localData: MovieLocalData,
private val remoteData: MovieRemoteData
)
6. 添加Network模塊
@Module
class NetworkModule {
...
@Singleton
@Provides
fun provideLoginService(okHttpClient: OkHttpClient, gsonConverterFactory: GsonConverterFactory): MovieService {
return Retrofit.Builder()
.baseUrl("http://omdbapi.com/")
.addConverterFactory(gsonConverterFactory)
.client(okHttpClient)
.build()
.create(MovieService::class.java)
}
}
依賴關系圖

Dagger2的功能十分強大,上述實戰仍有不少未提及的高階用法,感興趣者可進一步嘗試,
- 自定義作用域的@Scope
- 注釋子組件的@Subcomponent
- 注釋抽象方法的@Binds
- 一個介面指定多個實作的@Named
…
Dagger2導航的支持
Android Studio針對Dagger2的導航進行了支持,方便開發者快速回溯依賴關系,

- 點擊向上的箭頭可以查看該實體注入的提供方
- 點擊向下的樹形圖會將您轉到或展開該實體被用作依賴項的位置或串列
Dagger2在SystemUI上應用
對于小型專案而言,引入DI框架顯得大材小用、大動干戈,而且對于后期接手人員,如果對于DI框架不熟悉的話,維護將變得尤為困難,似乎只有大型專案才能讓它自由地施展拳腳,
前些年我在調查某個導航欄Bug的時候查閱過SystemUI的代碼,當時意外地發現大量的模塊包括StatusBar、Recents、Keyguard等都是DI方式引入的,雖然對Dagger略有耳聞,但仍看得云里霧里,不得其解,
SystemUI作為Android系統里最核心最復雜的App,稱之為大型專案毫不過分,現在就來看看Dagger2如何助力這個大型App管理大量的系統組件,
※ 原始碼版本:Android 11
SystemUI中主要的依賴實體都管理在Denpency類中,
public class Dependency {
...
@Inject @Background Lazy<Executor> mBackgroundExecutor;
@Inject Lazy<ClockManager> mClockManager;
@Inject Lazy<ActivityManagerWrapper> mActivityManagerWrapper;
@Inject Lazy<DevicePolicyManagerWrapper> mDevicePolicyManagerWrapper;
@Inject Lazy<PackageManagerWrapper> mPackageManagerWrapper;
@Inject Lazy<SensorPrivacyController> mSensorPrivacyController;
@Inject Lazy<DockManager> mDockManager;
@Inject Lazy<INotificationManager> mINotificationManager;
@Inject Lazy<SysUiState> mSysUiStateFlagsContainer;
@Inject Lazy<AlarmManager> mAlarmManager;
@Inject Lazy<KeyguardSecurityModel> mKeyguardSecurityModel;
@Inject Lazy<DozeParameters> mDozeParameters;
@Inject Lazy<IWallpaperManager> mWallpaperManager;
@Inject Lazy<CommandQueue> mCommandQueue;
@Inject Lazy<Recents> mRecents;
@Inject Lazy<StatusBar> mStatusBar;
@Inject Lazy<DisplayController> mDisplayController;
@Inject Lazy<SystemWindows> mSystemWindows;
}
后面以StatusBar實體的注入為例闡述下SystemUI里Dagger2的注入流程,
隨著SystemServer發出啟動SystemUIService的請求,SystemUI的Application將首先被實體化,在實體化之前,指定的AppComponentFactory實作類將會收到回呼,
// AndroidManifest.xml
<application
android:name=".SystemUIApplication"
...
tools:replace="android:appComponentFactory"
android:appComponentFactory=".SystemUIAppComponentFactory">
</Application>
呼叫super得到Application實體之后向其注冊Context準備完畢的回呼,該回呼會執行SystemUIFactory和DI組件的初始化,
public class SystemUIAppComponentFactory extends AppComponentFactory {
@Inject
public ContextComponentHelper mComponentHelper;
...
@Override
public Application instantiateApplicationCompat(
@NonNull ClassLoader cl, @NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
Application app = super.instantiateApplicationCompat(cl, className);
if (app instanceof ContextInitializer) {
// 注冊Context成功取得的回呼
((ContextInitializer) app).setContextAvailableCallback(
context -> {
SystemUIFactory.createFromConfig(context);
SystemUIFactory.getInstance().getRootComponent().inject(
SystemUIAppComponentFactory.this);
}
);
}
return app;
}
...
}
Application的onCreate()回呼的時候意味著Context已準備完畢,接著執行上述回呼,
public class SystemUIApplication extends Application implements
SystemUIAppComponentFactory.ContextInitializer {
...
@Override
public void setContextAvailableCallback(
SystemUIAppComponentFactory.ContextAvailableCallback callback) {
mContextAvailableCallback = callback;
}
@Override
public void onCreate() {
...
log.traceBegin("DependencyInjection");
mContextAvailableCallback.onContextAvailable(this);★
mRootComponent = SystemUIFactory.getInstance().getRootComponent();
mComponentHelper = mRootComponent.getContextComponentHelper();
...
}
}
回呼將先創建SystemUIFactory實體,并初始化SystemUI App的Dagger組件,之后初始化DI子組件并向Dependency實體注入依賴,
public class SystemUIFactory {
public static void createFromConfig(Context context) {
...
try {
Class<?> cls = null;
cls = context.getClassLoader().loadClass(clsName);
// 1. 創建SystemUIFactory實體
mFactory = (SystemUIFactory) cls.newInstance();
mFactory.init(context);
}
}
private void init(Context context) {
// 2. 取得SystemUI的Dagger組件實體
mRootComponent = buildSystemUIRootComponent(context);
// 3. 創建Dependency實體并系結到DependencyInjector子組件中
Dependency dependency = new Dependency();
mRootComponent.createDependency().createSystemUI(dependency);
// 4. 初始化Dependency
dependency.start();
}
// 初始化Dagger組件
protected SystemUIRootComponent buildSystemUIRootComponent(Context context) {
return DaggerSystemUIRootComponent.builder()
.dependencyProvider(new DependencyProvider())
.contextHolder(new ContextHolder(context))
.build();
}
...
}
Dependency類里掌管著各式各樣的依賴,被依賴的各實體通過Map管理,但并不是在初始化的時候就快取它們,而先將各實體對應的懶加載回呼快取進去,其后在各實體確實需要使用的時候通過注入的懶加載獲取和快取,
public class Dependency {
// 使用class作為key將對應實體快取的Map
private final ArrayMap<Object, Object> mDependencies = new ArrayMap<>();
// 快取實體的懶加載回呼的Map
private final ArrayMap<Object, LazyDependencyCreator> mProviders = new ArrayMap<>();
protected void start() {
mProviders.put(ActivityStarter.class, mActivityStarter::get);
mProviders.put(Recents.class, mRecents::get);
mProviders.put(StatusBar.class, mStatusBar::get);
mProviders.put(NavigationBarController.class, mNavigationBarController::get);
...
}
// 根據class查詢快取,尚未快取的話通過懶加載回呼獲取注入的實體并快取
private synchronized <T> T getDependencyInner(Object key) {
T obj = (T) mDependencies.get(key);
if (obj == null) {
obj = createDependency(key);
mDependencies.put(key, obj);
if (autoRegisterModulesForDump() && obj instanceof Dumpable) {
mDumpManager.registerDumpable(obj.getClass().getName(), (Dumpable) obj);
}
}
return obj;
}
protected <T> T createDependency(Object cls) {
Preconditions.checkArgument(cls instanceof DependencyKey<?> || cls instanceof Class<?>);
LazyDependencyCreator<T> provider = mProviders.get(cls);
return provider.createDependency();
}
private interface LazyDependencyCreator<T> {
T createDependency();
}
}
Application創建好之后SystemUI的主Service將啟動起來,并逐個啟動其他Service,
public class SystemUIService extends Service {
...
@Override
public void onCreate() {
super.onCreate();
// Start all of SystemUI
((SystemUIApplication) getApplication()).startServicesIfNeeded();
...
}
}
通過ContextComponentHelper決議預設的service類名得到實體并啟動,
public class SystemUIApplication {
public void startServicesIfNeeded() {
String[] names = SystemUIFactory.getInstance().getSystemUIServiceComponents(getResources());
startServicesIfNeeded(/* metricsPrefix= */ "StartServices", names);
}
private void startServicesIfNeeded(String metricsPrefix, String[] services) {
...
final int N = services.length;
for (int i = 0; i < N; i++) {
String clsName = services[i];
try {
// 從ContextComponentHelper里獲取對應的實體
SystemUI obj = mComponentHelper.resolveSystemUI(clsName);
if (obj == null) {
Constructor constructor = Class.forName(clsName).getConstructor(Context.class);
obj = (SystemUI) constructor.newInstance(this);
}
mServices[i] = obj;
}
mServices[i].start();
...
}
mRootComponent.getInitController().executePostInitTasks();
}
}
配置的Service串列,
// config.xml
<string-array name="config_systemUIServiceComponents" translatable="false">
...
<item>com.android.systemui.recents.Recents</item>
<item>com.android.systemui.volume.VolumeUI</item>
<item>com.android.systemui.stackdivider.Divider</item>
<item>com.android.systemui.statusbar.phone.StatusBar</item> ★
...
</string-array>
ContextComponentHelper單例已宣告由Dagger組件提供,
@Singleton
@Component(modules = {...})
public interface SystemUIRootComponent {
...
/**
* Creates a ContextComponentHelper.
*/
@Singleton
ContextComponentHelper getContextComponentHelper();
}
模塊SystemUIModule負責注入ContextComponentHelper實體,實際注入的是ContextComponentResolver實體,
@Module(...)
public abstract class SystemUIModule {
...
/** */
@Binds
public abstract ContextComponentHelper bindComponentHelper(
ContextComponentResolver componentHelper);
}
ContextComponentResolver用于決議Activity和Service等實體,通過class實體從Map查詢得到的Provider里取得對應的Service實體,
@Singleton
public class ContextComponentResolver implements ContextComponentHelper {
@Inject
ContextComponentResolver(Map<Class<?>, Provider<Activity>> activityCreators,
Map<Class<?>, Provider<Service>> serviceCreators,
Map<Class<?>, Provider<SystemUI>> systemUICreators,
Map<Class<?>, Provider<RecentsImplementation>> recentsCreators,
Map<Class<?>, Provider<BroadcastReceiver>> broadcastReceiverCreators) {
mSystemUICreators = systemUICreators;
...
}
...
@Override
public SystemUI resolveSystemUI(String className) {
return resolve(className, mSystemUICreators);
}
// 依據名稱得到的class實體去查詢Provider實體,進而取得對應SystemUI的實體
private <T> T resolve(String className, Map<Class<?>, Provider<T>> creators) {
try {
Class<?> clazz = Class.forName(className);
Provider<T> provider = creators.get(clazz);
return provider == null ? null : provider.get();
} catch (ClassNotFoundException e) {
return null;
}
}
}
而Provider#get()的實體將到StatusBar的Class對應的LazyDependencyCreator里get,StatusBar的實體將由StatusBarPhoneModule模塊注入,(StatusBar構造器的引數竟有76個之多,簡直恐怖,,,)
@Module(includes = {RecentsModule.class, StatusBarModule.class...})
public abstract class SystemUIBinder {
/** Inject into StatusBar. */
@Binds
@IntoMap
@ClassKey(StatusBar.class)
public abstract SystemUI bindsStatusBar(StatusBar sysui);
...
}
@Module(includes = {StatusBarPhoneModule.class...})
public interface StatusBarModule {
}
@Module(includes = {StatusBarPhoneDependenciesModule.class})
public interface StatusBarPhoneModule {
@Provides
@Singleton
static StatusBar provideStatusBar(
Context context,
NotificationsController notificationsController,
LightBarController lightBarController,
AutoHideController autoHideController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
StatusBarIconController statusBarIconController,
...) {
return new StatusBar(...);
}
}
SystemUI里DI關系圖

結語
回顧下依賴注入技術的必要性,
- 代碼的復用:通過引數傳遞復用實體減少樣板代碼
- 測驗的方便:通過注入模擬引數可以快速測驗邏輯
- 耦合度降低:類專注于自己的邏輯互相之間只通過引數連接
是否一定非要選擇Dagger2這種自動方案呢?我覺得依據對專案的了解程度決定,
因為無論是采用手動還是自動的依賴注入方案,都需要我們理清各模塊各類之前的關系,正確地定位每個類的角色,把握每個實體的作用域,
況且必須要認識到Dagger2這種框架的局限性,
- 使用起來比較復雜,存在一定的學習門檻
- 一定程度上更適合大型專案
技術人的腳步永遠不會停滯不前,優化和改善是他們永恒的追求,
Google在Dagger2的基礎上再次進行了改良,一來簡化了DI的使用,二來強化了Android上的使用,這個框架也收錄在Jetpack系列中,命名為Hilt,
針對Hilt的解讀已經安排,盡情期待,
本文DEMO
https://github.com/ellisonchan/JetpackDemo
推薦閱讀
Jetpack Compose助我快速打造電影App
除了SQLite一定要試試Room
鴻蒙Harmony談了這么久,和Android到底啥區別?
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/272911.html
標籤:其他
