­

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

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實例為止。

示例代碼