放棄dagger?Anrdoi依賴注入框架koin

夢和遠方發表於2020-07-23

Koin 是什麼

Koin 是為 Kotlin 開發者提供的一個實用型輕量級依賴注入框架,採用純 Kotlin 語言編寫而成,僅使用功能解析,無代理、無程式碼生成、無反射。
官網地址

優勢

依賴注入好處

  • 增加開發效率、省去重複的簡單體力勞動
    首先new一個例項的過程是一個重複的簡單體力勞動,依賴注入可以把new一個例項的工作做了,因此我們把主要精力集中在關鍵業務上、同時也能增加開發效率上。
  • 程式碼更具可讀性
  • 省去寫單例的方法
  • 解耦
    假如不用依賴注入的話,一個類的new程式碼是非常可能充斥在app的多個類中的,假如該類的建構函式發生變化,那這些涉及到的類都得進行修改。

和dagger相比

  1. 編譯生成的程式碼少
  2. 編譯時間少
  3. 上手簡單

使用方法

1.新增依賴

// Add Jcenter to your repositories if needed
repositories {
    jcenter()    
}
dependencies {
    // Koin for Android
    compile "org.koin:koin-android:$koin_version"
}

2.比如建立一個HelloRepository來提供一些資料:

interface HelloRepository {
    fun giveHello(): String
}

class HelloRepositoryImpl() : HelloRepository {
    override fun giveHello() = "Hello Koin"
}

3.建立一個presenter類,用來使用這些資料:

class MySimplePresenter(val repo: HelloRepository) {

    fun sayHello() = "${repo.giveHello()} from $this"
}

4.編寫Koin模組,使用該module函式宣告模組。

val appModule = module {

    // single instance of HelloRepository
    single<HelloRepository> { HelloRepositoryImpl() }

    // Simple Presenter Factory
    factory { MySimplePresenter(get()) }
}

factory每次Activity需要一個例項時都會建立一個新例項。
single 區別在於其提供的例項是單例的
get()這裡的功能是直接檢索例項(非延遲)

5.啟動koin
現在有了一個模組,只需要在Application裡呼叫startKoin()函式:

class MyApplication : Application(){
    override fun onCreate() {
        super.onCreate()
        // Start Koin
        startKoin{
            androidLogger()
            androidContext(this@MyApplication)
            modules(appModule)
        }
    }
}

6.注入依賴
該MySimplePresenter元件將使用HelloRepository例項建立。用by inject()委託注入器注入它:

class MySimpleActivity : AppCompatActivity() {

    // Lazy injected MySimplePresenter
    val firstPresenter: MySimplePresenter by inject()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        //...
    }
}

該by inject()功能使我們能夠在Android元件執行時(活動,片段,服務…)中檢索Koin例項。

原理

行內函數

  • Koin使用了很多的行內函數,它的作用簡單來說就是方便進行型別推導,能具體化型別引數。
  • 被inline標記的函式就是行內函數,其原理就是:在編譯時期,把呼叫這個函式的地方用這個函式的方法體進行替換
fun <T> method(lock: Lock, body: () -> T): T {
        lock.lock()
        try {
            return body()
        } finally {
            lock.unlock()
        }
    }


method(lock, {"我是body方法體"})//lock是一個Lock物件

其實上面呼叫的方法,在編譯時期就會把下面的內容替換到呼叫該方法的地方,這樣就會減少方法壓棧,出棧,進而減少資源消耗;

        lock.lock()
        try {
            return "我是body方法體"
        } finally {
            lock.unlock()
        }

也就是說inline關鍵字實際上增加了程式碼量,但是提升了效能,而且增加的程式碼量是在編譯期執行的,對程式可讀性不會造成影響

Reified

  • 由於 Java 中的泛型存在型別擦除的情況,任何在執行時需要知道泛型確切型別資訊的操作都沒法用了。比如你不能檢查一個物件是否為泛型型別 T 的例項,所以需要反射。
  • 而reified,字面意思:具體化,,其實就是具體化泛型。
  • 主要還是有行內函數inline,才使得kotlin能夠直接通過泛型就能拿到泛型的型別,只有行內函數的型別引數可以具體化。

例子
定義實現一個擴充套件函式啟動 Activity,一般都需要傳 Class 引數:

// Function
private fun <T : Activity> Activity.startActivity(context: Context, clazz: Class<T>) {
    startActivity(Intent(context, clazz))
}

// Caller
startActivity(context, NewActivity::class.java)

使用 reified,通過新增型別傳遞簡化泛型引數

// Function
inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

// Caller
startActivity<NewActivity>(context)

注入流程

  • 行內函數支援具體化的型別引數,使用 reified 修飾符來限定型別引數,可以在函式內部訪問它,由於函式是內聯的,所以不需要反射。
  • koin裡有一個全域性的容器,提供了應用所有所需例項的構造方式,那麼當我們需要新建例項的時候,就可以直接從這個容器裡面獲取到它的構造方式然後拿到所需的依賴,構造出所需的例項就可以了。
startKoin(this, appModule, logger = AndroidLogger(showDebug = BuildConfig.DEBUG))
  • Koin提供一個全域性容器,將所有的依賴構造方式轉換成 BeanDefinition 進行註冊,這是一個HashSet,名字是 definitions。
    UhlZpF.png

BeanDefinition
UhlM01.png

  • name以及primaryType,這兩個是get()關鍵字依賴檢索所需的key。
  • definition: Definition,它的值代表了其構造方式來源於那個module,對應前文的appModule,通過它可以反向推導該例項需要哪些依賴。
  override fun <T> get(parameters: ParameterDefinition): Instance<T> {
        val needCreation = instance == null
        if (needCreation) {
            instance = create(parameters)
        }
        return Instance(instance as T, needCreation)
    }
    
    
        fun <T> create(parameters: ParameterDefinition): T {
        try {
            val parameterList = parameters()
            val instance = bean.definition.invoke(parameterList) as Any
            instance as T  //建立引數的例項
            return instance
        } catch (e: Throwable) {
              // ....
        }
    }

總結

  • 現在需要一個 MainViewModel 的例項,那麼通過clazz為Class的key在definitions中進行查詢。
  • 查到有一個 MainViewModel 的 BeanDefinition,通過註冊過的 definition: Definition找到其構造方式的位置(module)。
  • 當通過 MainViewModel(get() 的構造方式去構造 MainViewModel 例項的時候,發現又有一個get(),然後就是再重複前面的邏輯,一直到生成ViewModel例項為止。

示例程式碼

相關文章