-
概述
依賴注入(DependencyInject)是一個(gè)存在已久的概念了,起初,這個(gè)概念在服務(wù)器盛行颇蜡,我們熟知的服務(wù)端的控制反轉(zhuǎn)(IOC)中就包含了依賴注入。
我們知道辆亏,傳統(tǒng)依賴注入的原理就是利用反射來替代new形式的創(chuàng)建风秤,這可以幫我們省去大量的創(chuàng)建代碼,而且整個(gè)代碼看起來也要優(yōu)雅很多扮叨,但是因?yàn)榉瓷涫峭ㄟ^動(dòng)態(tài)地查找類缤弦、方法或字段的字節(jié)碼入口來完成同new一樣的對(duì)象創(chuàng)建和類變量訪問,這使得性能上會(huì)出現(xiàn)影響彻磁。
但是影響大小是相對(duì)的碍沐,對(duì)服務(wù)端來說狸捅,網(wǎng)絡(luò)的響應(yīng)、請(qǐng)求的轉(zhuǎn)換和資源的IO訪問等對(duì)性能的影響和反射對(duì)性能的影響相比要大得多累提,再加上服務(wù)器的內(nèi)存容量要大得多尘喝,而且還有集群,所以通常常用的對(duì)象會(huì)被提前創(chuàng)建以節(jié)省請(qǐng)求時(shí)的時(shí)間斋陪。而對(duì)于移動(dòng)端來說瞧省,內(nèi)存容量相對(duì)來說要小的多,而且本地代碼執(zhí)行的速度直接會(huì)影響到用戶的視覺體驗(yàn)鳍贾,所以反射對(duì)移動(dòng)端來說是一瓶毒藥鞍匾,要盡量的避免在移動(dòng)端使用反射。
但是我們又想使用依賴注入的好處怎么辦呢骑科?于是橡淑,有了一種新的依賴注入的概念。
-
Dagger的實(shí)現(xiàn)原理
當(dāng)前比較熱門一個(gè)DI庫—Dagger咆爽,就是移動(dòng)端應(yīng)用依賴注入的答案梁棠,他用的就是另一種依賴注入的思路,我們通過手動(dòng)創(chuàng)建依賴注入的過程來了解Dagger的實(shí)現(xiàn)原理斗埂。
我們拿Android中的一個(gè)普通依賴流程來說明符糊。
現(xiàn)在有一個(gè)Activity,它依賴于它的ViewModel呛凶,ViewModel又依賴于Repository男娄,Repository內(nèi)部又依賴著LocalDataSource(本地?cái)?shù)據(jù)庫)和RemoteDataSource,LocalDataSource又依賴Room漾稀,RemoteDataSource又依賴Retrofit:
代碼如下:
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // In order to satisfy the dependencies of LoginViewModel, you have to also // satisfy the dependencies of all of its dependencies recursively. // First, create retrofit which is the dependency of UserRemoteDataSource val retrofit = Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) // Then, satisfy the dependencies of UserRepository val remoteDataSource = UserRemoteDataSource(retrofit) val localDataSource = UserLocalDataSource() // Now you can create an instance of UserRepository that LoginViewModel needs val userRepository = UserRepository(localDataSource, remoteDataSource) // Lastly, create an instance of LoginViewModel with userRepository loginViewModel = LoginViewModel(userRepository) } }
我們可以看到模闲,每次創(chuàng)建一個(gè)ViewModel都要事先創(chuàng)建很多依賴對(duì)象,這沒辦法復(fù)用代碼崭捍,會(huì)產(chǎn)生很多重復(fù)的樣板代碼尸折,創(chuàng)建的順序也是固定的。
所以我們能改進(jìn)的一點(diǎn)就是把這塊代碼抽出來殷蛇,至于抽取到哪实夹,我覺得還是看具體的業(yè)務(wù),如果是我們舉例的這種情況的話粒梦,考慮到Retrofit亮航、LocalDataSource、RemoteDataSource等在所有的ViewModel都會(huì)用到谍倦,所以如果為每一個(gè)ViewModel建立一個(gè)類去集中這部分代碼的話塞赂,那站在整個(gè)項(xiàng)目的角度來看,Retrofit昼蛀、LocalDataSource和RemoteDataSource等的邏輯還是會(huì)產(chǎn)生重復(fù)代碼宴猾,所以對(duì)于這個(gè)示例來說圆存,還是抽取到一個(gè)App級(jí)別的公用容器類中為好。比如像下面這樣:
// Container of objects shared across the whole app class AppContainer { // Since you want to expose userRepository out of the container, you need to satisfy // its dependencies as you did before private val retrofit = Retrofit.Builder() .baseUrl("https://example.com") .build() .create(LoginService::class.java) private val remoteDataSource = UserRemoteDataSource(retrofit) private val localDataSource = UserLocalDataSource() // userRepository is not private; it'll be exposed val userRepository = UserRepository(localDataSource, remoteDataSource) }
// Custom Application class that needs to be specified // in the AndroidManifest.xml file class MyApplication : Application() { // Instance of AppContainer that will be used by all the Activities of the app val appContainer = AppContainer() }
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Gets userRepository from the instance of AppContainer in Application val appContainer = (application as MyApplication).appContainer loginViewModel = LoginViewModel(appContainer.userRepository) } }
這樣一來仇哆,我們的代碼就清爽多了沦辙。還有一點(diǎn)值得理解的是,這里的Repository沒有使用單例讹剔,所有的場(chǎng)景都使用同一個(gè)實(shí)例不太好測(cè)試油讯,因?yàn)闃I(yè)務(wù)場(chǎng)景是隨時(shí)會(huì)改變的,必須考慮擴(kuò)展和其他變化的測(cè)試延欠,單例只適用于那些基礎(chǔ)性的陌兑、不太可能會(huì)變動(dòng)、不需要考慮不同情況的場(chǎng)景由捎,比如Retrofit這些基礎(chǔ)框架兔综。
再進(jìn)一步,我們可以把ViewModel的創(chuàng)建代碼也放到容器管理類中:
// Definition of a Factory interface with a function to create objects of a type interface Factory { fun create(): T } // Factory for LoginViewModel. // Since LoginViewModel depends on UserRepository, in order to create instances of // LoginViewModel, you need an instance of UserRepository that you pass as a parameter. class LoginViewModelFactory(private val userRepository: UserRepository) : Factory { override fun create(): LoginViewModel { return LoginViewModel(userRepository) } }
// AppContainer can now provide instances of LoginViewModel with LoginViewModelFactory class AppContainer { ... val userRepository = UserRepository(localDataSource, remoteDataSource) val loginViewModelFactory = LoginViewModelFactory(userRepository) } class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Gets LoginViewModelFactory from the application instance of AppContainer // to create a new LoginViewModel instance val appContainer = (application as MyApplication).appContainer loginViewModel = appContainer.loginViewModelFactory.create() } }
現(xiàn)在還有一個(gè)問題狞玛,那就是性能方面的考慮软驰,我們的ViewModel只服務(wù)于具體的某個(gè)Activity或Fragment,當(dāng)它銷毀的時(shí)候我們需要釋放這些ViewModel占用的內(nèi)存空間心肪,所以在不再需要依賴的時(shí)候需要手動(dòng)去釋放依賴的內(nèi)存占用:
class LoginActivity: Activity() { private lateinit var loginViewModel: LoginViewModel private lateinit var loginData: LoginUserData private lateinit var appContainer: AppContainer override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) appContainer = (application as MyApplication).appContainer // Login flow has started. Populate loginContainer in AppContainer appContainer.loginContainer = LoginContainer(appContainer.userRepository) loginViewModel = appContainer.loginContainer.loginViewModelFactory.create() loginData = appContainer.loginContainer.loginData } override fun onDestroy() { // Login flow is finishing // Removing the instance of loginContainer in the AppContainer appContainer.loginContainer = null super.onDestroy() } }
這樣一來锭亏,就又多了很多額外的管理代碼。
所以硬鞍,當(dāng)應(yīng)用變大時(shí)慧瘤,你會(huì)發(fā)現(xiàn)編寫了大量樣板代碼(例如工廠),這可能容易出錯(cuò)膳凝。還必須自行管理容器的范圍和生命周期碑隆,優(yōu)化并舍棄不再需要的容器以釋放內(nèi)存恭陡。如果操作不當(dāng)蹬音,可能會(huì)導(dǎo)致應(yīng)用出現(xiàn)微小錯(cuò)誤和內(nèi)存泄露。
有樣板代碼產(chǎn)生休玩、流程相似就說明一定可以使用“腳本邏輯”來自動(dòng)話這些代碼生成過程著淆,很明顯我們需要一個(gè)能自動(dòng)幫我們管理這些依賴的工具,這個(gè)工具就是Dagger拴疤。
你可以根據(jù)上面說的理解出這和反射的區(qū)別永部,這就好像是AOT和JIT的區(qū)別,反射是不提前生成代碼呐矾,等到運(yùn)行調(diào)用時(shí)根據(jù)類和屬性的信息動(dòng)態(tài)加載苔埋,這很明顯會(huì)影響執(zhí)行速度;而Dagger的原理則是通過編譯器在編譯期間就生成樣板代碼蜒犯,在運(yùn)行時(shí)直接執(zhí)行無需再動(dòng)態(tài)查找加載组橄,這和我們手動(dòng)寫代碼再運(yùn)行是一樣的荞膘,只不過Dagger根據(jù)我們提供的信息幫我們?nèi)ァ皩憽焙昧诉@部分代碼。
-
關(guān)于Dagger使用
既然Dagger能幫我們生成上面的代碼玉工,那我們需要怎么跟Dagger“溝通”呢羽资。
首先,在構(gòu)建時(shí)遵班,Dagger 會(huì)走查代碼屠升,并執(zhí)行以下操作:
- 構(gòu)建并驗(yàn)證依賴關(guān)系圖,確保:
- 每個(gè)對(duì)象的依賴關(guān)系都可以得到滿足狭郑,從而避免出現(xiàn)運(yùn)行時(shí)異常腹暖。
- 不存在任何依賴循環(huán),從而避免出現(xiàn)無限循環(huán)翰萨。
- 生成在運(yùn)行時(shí)用于創(chuàng)建實(shí)際對(duì)象及其依賴項(xiàng)的類微服。
生成容器我們需要建立一個(gè)接口并給它添加@Component注解:
// @Component makes Dagger create a graph of dependencies @Component interface ApplicationGraph { // The return type of functions inside the component interface is // what can be provided from the container fun repository(): UserRepository }
build時(shí)Dagger會(huì)自動(dòng)生成該容器接口的實(shí)現(xiàn)類,你可以直接在容器類前面加上“Dagger”前綴后調(diào)用它的create方法來獲取它的實(shí)例:
// Create an instance of the application graph val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() // Grab an instance of UserRepository from the application graph val userRepository: UserRepository = applicationGraph.repository()
這里我們想創(chuàng)建的依賴是UserRepository缨历,所以我們需要告訴Dagger注入它:
// @Inject lets Dagger know how to create instances of this object class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
注意以蕴,@Inject注解要放在構(gòu)造方法前,然后UserRepository的依賴項(xiàng)—UserLocalDataSource和UserRemoteDataSource辛孵,同樣也需要注入:
// @Inject lets Dagger know how to create instances of these objects class UserLocalDataSource @Inject constructor() { ... } class UserRemoteDataSource @Inject constructor() { ... }
如果需要容器內(nèi)的某個(gè)依賴項(xiàng)是單例的話丛肮,需要添加作用域注解,這個(gè)注解可以是@Singleton魄缚,也可以是自定義注解宝与,結(jié)合使用,發(fā)現(xiàn)使用哪個(gè)注解作為作用域修飾并沒關(guān)系冶匹,但是要求修飾容器類的和修飾某個(gè)依賴的注解一致习劫,這樣在同一個(gè)容器實(shí)例中獲取的依賴項(xiàng)都會(huì)是同一實(shí)例:
// Scope annotations on a @Component interface informs Dagger that classes annotated // with this annotation (i.e. @Singleton) are bound to the life of the graph and so // the same instance of that type is provided every time the type is requested. @Singleton @Component interface ApplicationGraph { fun repository(): UserRepository } // Scope this class to a component using @Singleton scope (i.e. ApplicationGraph) @Singleton class UserRepository @Inject constructor( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { ... }
val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create() val userRepository: UserRepository = applicationGraph.repository() val userRepository2: UserRepository = applicationGraph.repository() assert(userRepository == userRepository2)
注意,使用作用域注釋的模塊只能在帶有相同作用域注釋的組件中使用(容器A中需要依賴項(xiàng)B是單例嚼隘,則需要A和B都用同一個(gè)作用域注解修飾)诽里,作用域注解的單例不在于“Singleton”這個(gè)名字,可以是其他自定義注解飞蛹,注解放在類上谤狡,只有針對(duì)Providers注解時(shí)才會(huì)放在方法上。
除了用在構(gòu)造函數(shù)上之外卧檐,還可以用于屬性注入墓懂,比如:
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel }
這種方式下由于不是調(diào)用容器類的方法來構(gòu)造的(Activity是系統(tǒng)實(shí)例化的,無法進(jìn)行構(gòu)造方法注入)霉囚,而Dagger又不是全域掃描注解解析的捕仔,所以我們需要在使用之前調(diào)用一下當(dāng)前實(shí)例以告知Dagger添加該實(shí)例中相關(guān)依賴的注入。
@Component interface ApplicationComponent { // This tells Dagger that LoginActivity requests injection so the graph needs to // satisfy all the dependencies of the fields that LoginActivity is requesting. fun inject(activity: LoginActivity) }
class LoginActivity: Activity() { // You want Dagger to provide an instance of LoginViewModel from the graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Make Dagger instantiate @Inject fields in LoginActivity (applicationContext as MyApplication).appComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } } // @Inject tells Dagger how to create instances of LoginViewModel class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
"inject"這個(gè)方法名不是限制的,但是必須寫在@Component修飾的容器類里榜跌,然后調(diào)用方法里必須傳入想要實(shí)例化依賴項(xiàng)的類實(shí)例闸天,因?yàn)樵赽uild的時(shí)候Dagger會(huì)把@Component修飾的容器類中所有的參數(shù)類型、返回類型的類的所有依賴項(xiàng)的創(chuàng)建代碼提前寫好斜做,比如:
class DemoCC { @Inject lateinit var userRepository: UserRepository } @Component interface Container { fun inject(demo: DemoCC) }
他生成的代碼是這樣的:
@Override public void inject(DemoCC demo) { injectDemoCC(demo); } private DemoCC injectDemoCC(DemoCC instance) { DemoCC_MembersInjector.injectUserRepository(instance, repository()); return instance; } @Override public UserRepository repository() { return new UserRepository(new LocalDataSource(), new RemoteDataSource()); } //DemoCC_MembersInjector @InjectedFieldSignature("com.mph.review.dependency_inject.dagger.DemoCC.userRepository") public static void injectUserRepository(DemoCC instance, UserRepository userRepository) { instance.userRepository = userRepository; }
所以當(dāng)你調(diào)用inject方法的時(shí)候就會(huì)調(diào)用到這部分提前創(chuàng)建好的代碼苞氮,自然就給該實(shí)例的屬性進(jìn)行了初始化。
- 構(gòu)建并驗(yàn)證依賴關(guān)系圖,確保:
-
對(duì)于第三方框架等不屬于本地項(xiàng)目中的依賴項(xiàng)的實(shí)例化
因?yàn)镈agger只會(huì)掃描本地項(xiàng)目類信息瓤逼,所以對(duì)于第三方框架庫中的類的依賴加載怎么完成呢笼吟?
Dagger通過@Providers注解來調(diào)用生成非本地類的實(shí)例代碼,@Providers注解必須存在于@Module修飾的類里霸旗,否則無效贷帮。
class RemoteDataSource @Inject constructor( private val retrofit: Retrofit )
比如上面構(gòu)造RemoteDataSource需要依賴Retrofit實(shí)例,因?yàn)镽etrofit不屬于本地代碼诱告,所以Dagger無法自動(dòng)生成創(chuàng)建代碼撵枢。
@Module class NetWorkModule { @Provides fun provideRetrofit():Retrofit{ return Retrofit.Builder().build() } }
像這樣,Dagger會(huì)在build時(shí)在需要Retrofit實(shí)例的時(shí)候去查找@Providers注解修飾的方法看看有沒有對(duì)應(yīng)的方法返回類型精居,如果有就會(huì)把Provides修飾的方法放入自動(dòng)生成的創(chuàng)建邏輯中去锄禽,說起來有些繞,直接看生成的代碼:
private RemoteDataSource remoteDataSource() { return new RemoteDataSource(NetWorkModule_ProvideRetrofitFactory.provideRetrofit(netWorkModule)); }
可以看到靴姿,根據(jù)@Module和@Providers注解生成了NetWorkModule_ProvideRetrofitFactory類:
public static Retrofit provideRetrofit(NetWorkModule instance) { return Preconditions.checkNotNullFromProvides(instance.provideRetrofit()); }
可以看到沃但,instance是NetWorkModule,provideRetrofit方法正是@Providers修飾的方法佛吓,NetWorkModule是在容器類構(gòu)造時(shí)創(chuàng)建的宵晚,所以最終Dagger把我們創(chuàng)建非本地類實(shí)例的邏輯嵌入到了Dagger依賴生成流程中。
對(duì)于接口實(shí)例的依賴項(xiàng)维雇,需要使用@Binds注解完成和實(shí)現(xiàn)類的綁定淤刃,比如:
interface AnalyticsService { fun analyticsMethods() } // Constructor-injected, because Hilt needs to know how to // provide instances of AnalyticsServiceImpl, too. class AnalyticsServiceImpl @Inject constructor( ... ) : AnalyticsService { ... } @Module @InstallIn(ActivityComponent::class) abstract class AnalyticsModule { @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
可以看到,@Binds修飾的方法只需要定義不需要實(shí)現(xiàn)吱型,返回參數(shù)表示依賴項(xiàng)的接口類型逸贾,參數(shù)則表示需要注入的該接口的實(shí)現(xiàn)類。
當(dāng)然你也可以使用@Providers實(shí)現(xiàn)唁影,只不過需要寫方法實(shí)現(xiàn)并返回手動(dòng)創(chuàng)建的對(duì)象耕陷。
-
Dagger 子組件
對(duì)于不同業(yè)務(wù),有著不同的生命周期作用域据沈,有的適合全局應(yīng)用作用域,比如創(chuàng)建成本高饺蔑、全局都會(huì)用到等锌介,而有些是僅針對(duì)某個(gè)流程的,這些依賴項(xiàng)的生命周期應(yīng)該是短暫的,比如一個(gè)只服務(wù)于某個(gè)Activity的依賴項(xiàng)孔祸,你希望在Activity運(yùn)行期間多個(gè)Fragment可以共用一個(gè)實(shí)例隆敢,但是下次重啟Activity的時(shí)候你希望它會(huì)是一個(gè)全新的依賴對(duì)象,如果放在應(yīng)用容器中崔慧,那么這個(gè)單例會(huì)存在于整個(gè)應(yīng)用運(yùn)行期間(這個(gè)原理在于在Application中創(chuàng)建全局容器實(shí)例保證該容器實(shí)例一直在應(yīng)用運(yùn)行期間存在拂蝎,然后對(duì)于和該容器擁有相同作用域的屬性,容器第一次創(chuàng)建時(shí)會(huì)創(chuàng)建一個(gè)DoubleCheck實(shí)例惶室,這個(gè)實(shí)例中會(huì)維持一個(gè)volatile類型的實(shí)例來保存這個(gè)和該容器具有相同作用域的屬性實(shí)例温自,從而達(dá)到單例的效果),哪怕我們不再使用它皇钞,這顯然不太合適悼泌。
所以我們需要一個(gè)新的作用域容器,但是應(yīng)用級(jí)容器又需要管理子業(yè)務(wù)容器夹界,所以需要@Subcomponent來區(qū)分馆里。
具體流程如下:
-
用Subcomponent注解修飾子組件,并且必須在
LoginComponent
內(nèi)定義子組件 factory或者builder(這個(gè)就是Dagger設(shè)計(jì)要求的可柿,出于規(guī)范設(shè)計(jì))鸠踪。@Subcomponent interface LoginComponent { // Factory that is used to create instances of this subcomponent @Subcomponent.Factory interface Factory { fun create(): LoginComponent } fun inject(loginActivity: LoginActivity) }
-
創(chuàng)建新的 Dagger 模塊(例如
SubcomponentsModule
),并將子組件的類傳遞給注釋的subcomponents
屬性复斥。// The "subcomponents" attribute in the @Module annotation tells Dagger what // Subcomponents are children of the Component this module is included in. @Module(subcomponents = LoginComponent::class) class SubcomponentsModule {}
也是出于代碼規(guī)范和簡(jiǎn)潔考慮慢哈,Component注解中并沒有subcomponent屬性,所以必須通過Module作為中介銜接管理永票。
-
將新模塊(即
SubcomponentsModule
)添加到ApplicationComponent
卵贱。// Including SubcomponentsModule, tell ApplicationComponent that // LoginComponent is its subcomponent. @Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent {}
請(qǐng)注意,
ApplicationComponent
不再需要注入LoginActivity
侣集,因?yàn)楝F(xiàn)在由LoginComponent
負(fù)責(zé)注入键俱,因此您可以從ApplicationComponent
中移除inject()
方法。 -
ApplicationComponent
的使用者需要知道如何創(chuàng)建LoginComponent
的實(shí)例世分。父組件必須在其接口中添加方法编振,確保使用者能夠根據(jù)父組件的實(shí)例創(chuàng)建子組件的實(shí)例,所以需要提供在接口中創(chuàng)建LoginComponent
實(shí)例的 factory.@Singleton @Component(modules = [NetworkModule::class, SubcomponentsModule::class]) interface ApplicationComponent { // This function exposes the LoginComponent Factory out of the graph so consumers // can use it to obtain new instances of LoginComponent fun loginComponent(): LoginComponent.Factory }
class LoginActivity: Activity() { // Reference to the Login graph lateinit var loginComponent: LoginComponent // Fields that need to be injected by the login graph @Inject lateinit var loginViewModel: LoginViewModel override fun onCreate(savedInstanceState: Bundle?) { // Creation of the login graph using the application graph loginComponent = (applicationContext as MyDaggerApplication) .appComponent.loginComponent().create() // Make Dagger instantiate @Inject fields in LoginActivity loginComponent.inject(this) // Now loginViewModel is available super.onCreate(savedInstanceState) } }
注意看臭埋,現(xiàn)在當(dāng)前Activity依賴的loginComponent只會(huì)在當(dāng)前Activity活動(dòng)期間保存踪央,如果Activity重建則loginComponent也會(huì)是一個(gè)新的實(shí)例,這就既能保證了loginComponent內(nèi)部的單例又能保證在Activity銷毀后不會(huì)存在于內(nèi)存中瓢阴。
注意畅蹂,如果父容器中使用了Singleton或者其他的單例作用域,則子組件中不得使用相同的荣恐,否則區(qū)別不出屬于哪個(gè)作用域液斜,所以以父容器使用了Singleton為例累贤,我們子組件中就使用自定義作用域來實(shí)現(xiàn)子組件中的單例:
// Definition of a custom scope called ActivityScope @Scope @Retention(value = AnnotationRetention.RUNTIME) annotation class ActivityScope // Classes annotated with @ActivityScope are scoped to the graph and the same // instance of that type is provided every time the type is requested. @ActivityScope @Subcomponent interface LoginComponent { ... } // A unique instance of LoginViewModel is provided in Components // annotated with @ActivityScope @ActivityScope class LoginViewModel @Inject constructor( private val userRepository: UserRepository ) { ... }
-
-
關(guān)于多模塊應(yīng)用
項(xiàng)目中多個(gè)Module的情況下,分為靜態(tài)的app主模塊implement所有其他的功能模塊和動(dòng)態(tài)的按需下載對(duì)應(yīng)的功能模塊兩種少漆。
前者容易理解臼膏,和父子組件的應(yīng)用是一樣的,關(guān)于動(dòng)態(tài)的情況有些特殊示损。
功能模塊的獨(dú)特優(yōu)勢(shì)在于能夠自定義應(yīng)用的不同功能以及何時(shí)下載到搭載 Android 5.0(API 級(jí)別 21)或更高版本的設(shè)備上渗磅。例如,為了減小應(yīng)用的初始下載大小检访,您可以將某些功能配置為按需下載始鱼,或者只能由支持特定功能(比如拍照或增強(qiáng)現(xiàn)實(shí))的設(shè)備下載。
這樣一來烛谊,app模塊是不包含這些動(dòng)態(tài)加載的模塊的风响,本身不在它的構(gòu)建路徑中,只能是后面下載的功能塊來找app模塊丹禀,因?yàn)閍pp模塊是先安裝的状勤,那時(shí)還沒有下載動(dòng)態(tài)模塊,所以無法從app模塊來尋找動(dòng)態(tài)模塊双泪,但是動(dòng)態(tài)模塊是后下載的持搜,它知道app模塊的構(gòu)建路徑,它可以找到并加載它的代碼焙矛,這就是Dagger的組件依賴關(guān)系機(jī)制原理葫盼。具體做法就是通過動(dòng)態(tài)模塊的Component中添加dependencies屬性來依賴app模塊的Component。
-
Hilt
Hilt 通過為項(xiàng)目中的每個(gè) Android 類提供容器并自動(dòng)管理其生命周期村斟,提供了一種在應(yīng)用中使用 DI(依賴項(xiàng)注入)的標(biāo)準(zhǔn)方法贫导。Hilt 在熱門 DI 庫 Dagger 的基礎(chǔ)上構(gòu)建而成,因而能夠受益于 Dagger 的編譯時(shí)正確性蟆盹、運(yùn)行時(shí)性能孩灯、可伸縮性和 Android Studio 支持。
原理都是一樣的逾滥,列一些不同的特點(diǎn):
- 使用HildAndroidApp注解生成應(yīng)用級(jí)容器峰档;
- 使用InstallIn注解聲明依賴可在什么層面使用,比如ApplicationComponent還是ActivityComponent寨昙;
- Hilt 可以為帶有
@AndroidEntryPoint
注釋的其他 Android 類提供依賴項(xiàng)讥巡,Hilt 目前支持以下 Android 類:-
Application
(通過使用@HiltAndroidApp
) Activity
Fragment
View
Service
BroadcastReceiver
-
- 在組件內(nèi)部,具體的依賴還是需要Inject注解來完成舔哪;
這么看好像沒啥變化欢顷,Hilt背后做的其實(shí)很多。我們只需要維護(hù)組件內(nèi)部的依賴項(xiàng)和其創(chuàng)建邏輯即可尸红,生成的依賴會(huì)自動(dòng)歸于當(dāng)前的組件容器中管理吱涉,也就是說會(huì)和組件的生命周期綁定刹泄,所以我們不需要擔(dān)心內(nèi)存泄露等問題外里。
在使用Dagger時(shí)怎爵,至少我們需要?jiǎng)?chuàng)建一個(gè)容器類對(duì)象,然后調(diào)用它的相關(guān)方法獲取需要的依賴項(xiàng)盅蝗,但是在Hilt中我們自始至終沒有看到過觸發(fā)依賴注入的入口邏輯鳖链,這是因?yàn)镠ilt在編譯期會(huì)通過插件自動(dòng)生成依賴項(xiàng)所在的目標(biāo)組件類的Hilt類,然后在運(yùn)行時(shí)會(huì)修改底層字節(jié)碼墩莫,會(huì)將目標(biāo)類繼承自動(dòng)生成的類芙委,以Activity為例,會(huì)在自動(dòng)生成類的onCreate方法中調(diào)用完自動(dòng)生成的類的inject方法后再調(diào)用super的onCreate方法(即目標(biāo)類狂秦、你自己編寫的組件類的onCreate方法)灌侣。
比如:
@AndroidEntryPoint class TestHomeActivity : BaseActivity<ActivityTestHomeBinding>() { @Inject lateinit var myService:MyService }
在編譯后會(huì)生成:
public abstract class Hilt_TestHomeActivity<T extends ViewBinding> extends BaseActivity<T> implements GeneratedComponentManager<Object> { ... ... @CallSuper @Override protected void onCreate(@Nullable Bundle savedInstanceState) { inject(); super.onCreate(savedInstanceState); }
inject方法最終會(huì)回調(diào)到ActivityCImpl(不同組件的是不同的類)的injectTestHomeActivity方法中:
@Override public void injectTestHomeActivity(TestHomeActivity testHomeActivity) { injectTestHomeActivity2(testHomeActivity); } private TestHomeActivity injectTestHomeActivity2(TestHomeActivity instance) { TestHomeActivity_MembersInjector.injectMyService(instance, new MyServiceImpl()); return instance; }
@InjectedFieldSignature("com.mph.review.TestHomeActivity.myService") public static void injectMyService(TestHomeActivity instance, MyService myService) { instance.myService = myService; }
從這個(gè)過程可以看到Hilt插件相較于Dagger來說,它的主要工作在于針對(duì)Android的組件做了很多自動(dòng)化的工作裂问。
Android的依賴注入
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
- 文/潘曉璐 我一進(jìn)店門湿滓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舌狗,你說我怎么就攤上這事叽奥。” “怎么了把夸?”我有些...
- 文/不壞的土叔 我叫張陵而线,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我恋日,道長(zhǎng)膀篮,這世上最難降的妖魔是什么? 我笑而不...
- 正文 為了忘掉前任岂膳,我火速辦了婚禮誓竿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谈截。我一直安慰自己摊聋,他們只是感情好挣磨,可當(dāng)我...
- 文/花漫 我一把揭開白布潭流。 她就那樣靜靜地躺著,像睡著了一般燎潮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扼倘,一...
- 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼稠诲!你這毒婦竟也來了侦鹏?” 一聲冷哼從身側(cè)響起,我...
- 序言:老撾萬榮一對(duì)情侶失蹤吕粹,失蹤者是張志新(化名)和其女友劉穎种柑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匹耕,經(jīng)...
- 正文 獨(dú)居荒郊野嶺守林人離奇死亡聚请,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
- 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稳其。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驶赏。...
- 正文 年R本政府宣布蚯姆,位于F島的核電站,受9級(jí)特大地震影響洒敏,放射性物質(zhì)發(fā)生泄漏龄恋。R本人自食惡果不足惜,卻給世界環(huán)境...
- 文/蒙蒙 一凶伙、第九天 我趴在偏房一處隱蔽的房頂上張望郭毕。 院中可真熱鬧,春花似錦函荣、人聲如沸显押。這莊子的主人今日做“春日...
- 文/蒼蘭香墨 我抬頭看了看天上的太陽乘碑。三九已至挖息,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝉仇,已是汗流浹背旋讹。 一陣腳步聲響...
- 正文 我出身青樓睦疫,卻偏偏與公主長(zhǎng)得像害驹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛤育,可洞房花燭夜當(dāng)晚...
推薦閱讀更多精彩內(nèi)容
- 點(diǎn)贊關(guān)注宛官,不再迷路,你的支持對(duì)我意義重大瓦糕!?? Hi底洗,我是丑丑。本文 「Android 路線」| 導(dǎo)讀 —— 從零到...
- Dagger2 1.依賴注入 (Dependency Injection) 1.1 面向接口編程 方式 1 中直接...
- github blog[https://lzyprime.github.io] qq: 23835181...
- Jetpack推薦的DI庫 依賴項(xiàng)注入(DI)是一種廣泛用于編程的技術(shù)咕娄,非常適合Android開發(fā)亥揖,該類將依賴項(xiàng)提...
- 點(diǎn)贊關(guān)注,不再迷路圣勒,你的支持對(duì)我意義重大费变!?? Hi,我是丑丑圣贸。本文 「Android 路線」| 導(dǎo)讀 —— 從零到...