Android的依賴注入

  • 概述

    依賴注入(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)行了初始化。

  • 對(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ū)分馆里。

    具體流程如下:

    1. 用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)
      }
      
    2. 創(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作為中介銜接管理永票。

    3. 將新模塊(即 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() 方法。

    4. 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):

    1. 使用HildAndroidApp注解生成應(yīng)用級(jí)容器峰档;
    2. 使用InstallIn注解聲明依賴可在什么層面使用,比如ApplicationComponent還是ActivityComponent寨昙;
    3. Hilt 可以為帶有 @AndroidEntryPoint 注釋的其他 Android 類提供依賴項(xiàng)讥巡,Hilt 目前支持以下 Android 類:
      • Application(通過使用 @HiltAndroidApp
      • Activity
      • Fragment
      • View
      • Service
      • BroadcastReceiver
    4. 在組件內(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)化的工作裂问。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侧啼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子堪簿,更是在濱河造成了極大的恐慌痊乾,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件椭更,死亡現(xiàn)場(chǎng)離奇詭異哪审,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)虑瀑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門湿滓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舌狗,你說我怎么就攤上這事叽奥。” “怎么了把夸?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵而线,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我恋日,道長(zhǎng)膀篮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任岂膳,我火速辦了婚禮誓竿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谈截。我一直安慰自己摊聋,他們只是感情好挣磨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布潭流。 她就那樣靜靜地躺著,像睡著了一般燎潮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上扼倘,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天确封,我揣著相機(jī)與錄音,去河邊找鬼再菊。 笑死爪喘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纠拔。 我是一名探鬼主播秉剑,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼稠诲!你這毒婦竟也來了侦鹏?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤吕粹,失蹤者是張志新(化名)和其女友劉穎种柑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匹耕,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡聚请,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了稳其。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片驶赏。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖既鞠,靈堂內(nèi)的尸體忽然破棺而出煤傍,到底是詐尸還是另有隱情,我是刑警寧澤嘱蛋,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布蚯姆,位于F島的核電站,受9級(jí)特大地震影響洒敏,放射性物質(zhì)發(fā)生泄漏龄恋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一凶伙、第九天 我趴在偏房一處隱蔽的房頂上張望郭毕。 院中可真熱鬧,春花似錦函荣、人聲如沸显押。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乘碑。三九已至挖息,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蝉仇,已是汗流浹背旋讹。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工殖蚕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轿衔,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓睦疫,卻偏偏與公主長(zhǎng)得像害驹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蛤育,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容