主頁 > 移動端開發 > Dagger2和它在SystemUI上的應用

Dagger2和它在SystemUI上的應用

2021-04-06 11:41:21 移動端開發

和人類需要群居一樣,程式界的行程、執行緒也需要通信往來,它們的交流則依賴模塊之間、檔案之間產生的關系,如何快速地搞清和構建這種關系,同時還能減輕彼此的依賴,需要開發者們認真思考,

我們將這種需求稱之為依賴注入(DI,Dependency Injection),這個編程技術由來已久,在講述之前想來簡單回顧下依賴和關聯的基本概念,

依賴和關聯

像下圖示意的那樣,模塊或類之間的關系大體可以分為依賴(Dependency)和關聯(Association)兩種,依賴一般表現為區域引數,關聯則表現為屬性的持有,

按照被關聯物件的生命周期的不同,又可以將關聯分為聚合(Aggregation)和組合(Composition),耦合度依次增強,

<style>#mermaid-svg-o40bRoOS1D9VpiSO .label{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);fill:#333;color:#333}#mermaid-svg-o40bRoOS1D9VpiSO .label text{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .node rect,#mermaid-svg-o40bRoOS1D9VpiSO .node circle,#mermaid-svg-o40bRoOS1D9VpiSO .node ellipse,#mermaid-svg-o40bRoOS1D9VpiSO .node polygon,#mermaid-svg-o40bRoOS1D9VpiSO .node path{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-o40bRoOS1D9VpiSO .node .label{text-align:center;fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .node.clickable{cursor:pointer}#mermaid-svg-o40bRoOS1D9VpiSO .arrowheadPath{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-svg-o40bRoOS1D9VpiSO .flowchart-link{stroke:#333;fill:none}#mermaid-svg-o40bRoOS1D9VpiSO .edgeLabel{background-color:#e8e8e8;text-align:center}#mermaid-svg-o40bRoOS1D9VpiSO .edgeLabel rect{opacity:0.9}#mermaid-svg-o40bRoOS1D9VpiSO .edgeLabel span{color:#333}#mermaid-svg-o40bRoOS1D9VpiSO .cluster rect{fill:#ffffde;stroke:#aa3;stroke-width:1px}#mermaid-svg-o40bRoOS1D9VpiSO .cluster text{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-svg-o40bRoOS1D9VpiSO .actor{stroke:#ccf;fill:#ECECFF}#mermaid-svg-o40bRoOS1D9VpiSO text.actor>tspan{fill:#000;stroke:none}#mermaid-svg-o40bRoOS1D9VpiSO .actor-line{stroke:grey}#mermaid-svg-o40bRoOS1D9VpiSO .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333}#mermaid-svg-o40bRoOS1D9VpiSO .messageLine1{stroke-width:1.5;stroke-dasharray:2, 2;stroke:#333}#mermaid-svg-o40bRoOS1D9VpiSO #arrowhead path{fill:#333;stroke:#333}#mermaid-svg-o40bRoOS1D9VpiSO .sequenceNumber{fill:#fff}#mermaid-svg-o40bRoOS1D9VpiSO #sequencenumber{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO #crosshead path{fill:#333;stroke:#333}#mermaid-svg-o40bRoOS1D9VpiSO .messageText{fill:#333;stroke:#333}#mermaid-svg-o40bRoOS1D9VpiSO .labelBox{stroke:#ccf;fill:#ECECFF}#mermaid-svg-o40bRoOS1D9VpiSO .labelText,#mermaid-svg-o40bRoOS1D9VpiSO .labelText>tspan{fill:#000;stroke:none}#mermaid-svg-o40bRoOS1D9VpiSO .loopText,#mermaid-svg-o40bRoOS1D9VpiSO .loopText>tspan{fill:#000;stroke:none}#mermaid-svg-o40bRoOS1D9VpiSO .loopLine{stroke-width:2px;stroke-dasharray:2, 2;stroke:#ccf;fill:#ccf}#mermaid-svg-o40bRoOS1D9VpiSO .note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-o40bRoOS1D9VpiSO .noteText,#mermaid-svg-o40bRoOS1D9VpiSO .noteText>tspan{fill:#000;stroke:none}#mermaid-svg-o40bRoOS1D9VpiSO .activation0{fill:#f4f4f4;stroke:#666}#mermaid-svg-o40bRoOS1D9VpiSO .activation1{fill:#f4f4f4;stroke:#666}#mermaid-svg-o40bRoOS1D9VpiSO .activation2{fill:#f4f4f4;stroke:#666}#mermaid-svg-o40bRoOS1D9VpiSO .mermaid-main-font{font-family:"trebuchet ms", verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .section{stroke:none;opacity:0.2}#mermaid-svg-o40bRoOS1D9VpiSO .section0{fill:rgba(102,102,255,0.49)}#mermaid-svg-o40bRoOS1D9VpiSO .section2{fill:#fff400}#mermaid-svg-o40bRoOS1D9VpiSO .section1,#mermaid-svg-o40bRoOS1D9VpiSO .section3{fill:#fff;opacity:0.2}#mermaid-svg-o40bRoOS1D9VpiSO .sectionTitle0{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .sectionTitle1{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .sectionTitle2{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .sectionTitle3{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .sectionTitle{text-anchor:start;font-size:11px;text-height:14px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .grid .tick{stroke:#d3d3d3;opacity:0.8;shape-rendering:crispEdges}#mermaid-svg-o40bRoOS1D9VpiSO .grid .tick text{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .grid path{stroke-width:0}#mermaid-svg-o40bRoOS1D9VpiSO .today{fill:none;stroke:red;stroke-width:2px}#mermaid-svg-o40bRoOS1D9VpiSO .task{stroke-width:2}#mermaid-svg-o40bRoOS1D9VpiSO .taskText{text-anchor:middle;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .taskText:not([font-size]){font-size:11px}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-svg-o40bRoOS1D9VpiSO .task.clickable{cursor:pointer}#mermaid-svg-o40bRoOS1D9VpiSO .taskText.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutsideLeft.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutsideRight.clickable{cursor:pointer;fill:#003163 !important;font-weight:bold}#mermaid-svg-o40bRoOS1D9VpiSO .taskText0,#mermaid-svg-o40bRoOS1D9VpiSO .taskText1,#mermaid-svg-o40bRoOS1D9VpiSO .taskText2,#mermaid-svg-o40bRoOS1D9VpiSO .taskText3{fill:#fff}#mermaid-svg-o40bRoOS1D9VpiSO .task0,#mermaid-svg-o40bRoOS1D9VpiSO .task1,#mermaid-svg-o40bRoOS1D9VpiSO .task2,#mermaid-svg-o40bRoOS1D9VpiSO .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutside0,#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutside2{fill:#000}#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutside1,#mermaid-svg-o40bRoOS1D9VpiSO .taskTextOutside3{fill:#000}#mermaid-svg-o40bRoOS1D9VpiSO .active0,#mermaid-svg-o40bRoOS1D9VpiSO .active1,#mermaid-svg-o40bRoOS1D9VpiSO .active2,#mermaid-svg-o40bRoOS1D9VpiSO .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-svg-o40bRoOS1D9VpiSO .activeText0,#mermaid-svg-o40bRoOS1D9VpiSO .activeText1,#mermaid-svg-o40bRoOS1D9VpiSO .activeText2,#mermaid-svg-o40bRoOS1D9VpiSO .activeText3{fill:#000 !important}#mermaid-svg-o40bRoOS1D9VpiSO .done0,#mermaid-svg-o40bRoOS1D9VpiSO .done1,#mermaid-svg-o40bRoOS1D9VpiSO .done2,#mermaid-svg-o40bRoOS1D9VpiSO .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-svg-o40bRoOS1D9VpiSO .doneText0,#mermaid-svg-o40bRoOS1D9VpiSO .doneText1,#mermaid-svg-o40bRoOS1D9VpiSO .doneText2,#mermaid-svg-o40bRoOS1D9VpiSO .doneText3{fill:#000 !important}#mermaid-svg-o40bRoOS1D9VpiSO .crit0,#mermaid-svg-o40bRoOS1D9VpiSO .crit1,#mermaid-svg-o40bRoOS1D9VpiSO .crit2,#mermaid-svg-o40bRoOS1D9VpiSO .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-svg-o40bRoOS1D9VpiSO .activeCrit0,#mermaid-svg-o40bRoOS1D9VpiSO .activeCrit1,#mermaid-svg-o40bRoOS1D9VpiSO .activeCrit2,#mermaid-svg-o40bRoOS1D9VpiSO .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-svg-o40bRoOS1D9VpiSO .doneCrit0,#mermaid-svg-o40bRoOS1D9VpiSO .doneCrit1,#mermaid-svg-o40bRoOS1D9VpiSO .doneCrit2,#mermaid-svg-o40bRoOS1D9VpiSO .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-svg-o40bRoOS1D9VpiSO .milestone{transform:rotate(45deg) scale(0.8, 0.8)}#mermaid-svg-o40bRoOS1D9VpiSO .milestoneText{font-style:italic}#mermaid-svg-o40bRoOS1D9VpiSO .doneCritText0,#mermaid-svg-o40bRoOS1D9VpiSO .doneCritText1,#mermaid-svg-o40bRoOS1D9VpiSO .doneCritText2,#mermaid-svg-o40bRoOS1D9VpiSO .doneCritText3{fill:#000 !important}#mermaid-svg-o40bRoOS1D9VpiSO .activeCritText0,#mermaid-svg-o40bRoOS1D9VpiSO .activeCritText1,#mermaid-svg-o40bRoOS1D9VpiSO .activeCritText2,#mermaid-svg-o40bRoOS1D9VpiSO .activeCritText3{fill:#000 !important}#mermaid-svg-o40bRoOS1D9VpiSO .titleText{text-anchor:middle;font-size:18px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO g.classGroup text{fill:#9370db;stroke:none;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family);font-size:10px}#mermaid-svg-o40bRoOS1D9VpiSO g.classGroup text .title{font-weight:bolder}#mermaid-svg-o40bRoOS1D9VpiSO g.clickable{cursor:pointer}#mermaid-svg-o40bRoOS1D9VpiSO g.classGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-o40bRoOS1D9VpiSO g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5}#mermaid-svg-o40bRoOS1D9VpiSO .classLabel .label{fill:#9370db;font-size:10px}#mermaid-svg-o40bRoOS1D9VpiSO .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-o40bRoOS1D9VpiSO .dashed-line{stroke-dasharray:3}#mermaid-svg-o40bRoOS1D9VpiSO #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #compositionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #aggregationStart{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #aggregationEnd{fill:#ECECFF;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #dependencyStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #dependencyEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO #extensionEnd{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO .commit-id,#mermaid-svg-o40bRoOS1D9VpiSO .commit-msg,#mermaid-svg-o40bRoOS1D9VpiSO .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .pieTitleText{text-anchor:middle;font-size:25px;fill:#000;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .slice{font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO g.stateGroup text{fill:#9370db;stroke:none;font-size:10px;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO g.stateGroup text{fill:#9370db;fill:#333;stroke:none;font-size:10px}#mermaid-svg-o40bRoOS1D9VpiSO g.statediagram-cluster .cluster-label text{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO g.stateGroup .state-title{font-weight:bolder;fill:#000}#mermaid-svg-o40bRoOS1D9VpiSO g.stateGroup rect{fill:#ECECFF;stroke:#9370db}#mermaid-svg-o40bRoOS1D9VpiSO g.stateGroup line{stroke:#9370db;stroke-width:1}#mermaid-svg-o40bRoOS1D9VpiSO .transition{stroke:#9370db;stroke-width:1;fill:none}#mermaid-svg-o40bRoOS1D9VpiSO .stateGroup .composit{fill:white;border-bottom:1px}#mermaid-svg-o40bRoOS1D9VpiSO .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px}#mermaid-svg-o40bRoOS1D9VpiSO .state-note{stroke:#aa3;fill:#fff5ad}#mermaid-svg-o40bRoOS1D9VpiSO .state-note text{fill:black;stroke:none;font-size:10px}#mermaid-svg-o40bRoOS1D9VpiSO .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.7}#mermaid-svg-o40bRoOS1D9VpiSO .edgeLabel text{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .stateLabel text{fill:#000;font-size:10px;font-weight:bold;font-family:'trebuchet ms', verdana, arial;font-family:var(--mermaid-font-family)}#mermaid-svg-o40bRoOS1D9VpiSO .node circle.state-start{fill:black;stroke:black}#mermaid-svg-o40bRoOS1D9VpiSO .node circle.state-end{fill:black;stroke:white;stroke-width:1.5}#mermaid-svg-o40bRoOS1D9VpiSO #statediagram-barbEnd{fill:#9370db}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-cluster rect{fill:#ECECFF;stroke:#9370db;stroke-width:1px}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-cluster rect.outer{rx:5px;ry:5px}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-state .divider{stroke:#9370db}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-state .title-state{rx:5px;ry:5px}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-cluster.statediagram-cluster .inner{fill:white}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-cluster.statediagram-cluster-alt .inner{fill:#e0e0e0}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-cluster .inner{rx:0;ry:0}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-state rect.basic{rx:5px;ry:5px}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#efefef}#mermaid-svg-o40bRoOS1D9VpiSO .note-edge{stroke-dasharray:5}#mermaid-svg-o40bRoOS1D9VpiSO .statediagram-note rect{fill:#fff5ad;stroke:#aa3;stroke-width:1px;rx:0;ry:0}:root{--mermaid-font-family: '"trebuchet ms", verdana, arial';--mermaid-font-family: "Comic Sans MS", "Comic Sans", cursive}#mermaid-svg-o40bRoOS1D9VpiSO .error-icon{fill:#522}#mermaid-svg-o40bRoOS1D9VpiSO .error-text{fill:#522;stroke:#522}#mermaid-svg-o40bRoOS1D9VpiSO .edge-thickness-normal{stroke-width:2px}#mermaid-svg-o40bRoOS1D9VpiSO .edge-thickness-thick{stroke-width:3.5px}#mermaid-svg-o40bRoOS1D9VpiSO .edge-pattern-solid{stroke-dasharray:0}#mermaid-svg-o40bRoOS1D9VpiSO .edge-pattern-dashed{stroke-dasharray:3}#mermaid-svg-o40bRoOS1D9VpiSO .edge-pattern-dotted{stroke-dasharray:2}#mermaid-svg-o40bRoOS1D9VpiSO .marker{fill:#333}#mermaid-svg-o40bRoOS1D9VpiSO .marker.cross{stroke:#333} :root { --mermaid-font-family: "trebuchet ms", verdana, arial;}</style> <style>#mermaid-svg-o40bRoOS1D9VpiSO { color: rgba(0, 0, 0, 0.75); font: ; }</style>
依賴
關聯
聚合
組合

依賴注入

依賴注入編程技術最終要構建的并非特指上面的依賴關系,也包含了關聯關系,只是構建的入口多表現為引數注入,

不依賴框架的情況下我們也可以手動注入這些關系,

  • 通過建構式傳參
  • 通過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

標籤:其他

上一篇:Dell G7 7588 雙硬碟 UEFI啟動模式 安裝Ubuntu18.04雙系統(已有Win10系統)

下一篇:Activity生命周期學習總結2

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more