1. 程式人生 > >你必須知道的 Android 框架(使用最新的Kotlin語言與其他框架形成混編)

你必須知道的 Android 框架(使用最新的Kotlin語言與其他框架形成混編)

Kotlin 中使用依賴於註釋處理的流行的 Android 框架和庫。

Kotlin中和其他框架一起使(混編模式)

以 Dagger、 Butterknife、 Data Binding、 Auto-parcel 以及 DBFlow 為例(其它框架配置基本類似)。 以上框架均基於註解處理方式工作:通過對程式碼註解自動生成模板程式碼。 註解有助於減少冗餘程式碼,讓程式碼清晰可讀,想要了解執行時的程式碼,可以直接閱讀自動生成的原始碼。 但所有生成的程式碼均為 Java 程式碼而非 Kotlin。

注:希望大家相互學習討論,如有什麼問題請留言
在 Kotlin 中新增依賴與 Java 中類似,僅需要使用 Kotlin 註解處理工具(Kotlin Annotation processing tool)(kapt)替代 annotationProcessor 即可。

Dagger

Dagger 是著名的依賴注入框架。 如果對它還不瞭解,可以查閱使用者手冊。 Kotlin 程式碼與 Java 非常相似;所有示例程式碼可在同一個檔案內檢視。
與 Java 一樣,Dagger 通過 @Inject 對建構函式註解,進而建立類的例項。 而 Kotlin 使用更簡潔的語法同時宣告屬性和建構函式引數。 在 Kotlin 中對建構函式進行註解,必須顯式使用 constructor 關鍵字,並在關鍵字前宣告 @Inject。

class Thermosiphon 
@Inject constructor(
        private val heater: Heater
)
:
Pump { // …… }

註解方法看上去完全相同。 在下面的示例中,@Binds 決定了無論何時需要 Pump,使用都是 Thermosiphon 物件,@Provides 指定了 Heater 的構造方式,@Singleton 則表示 Heater 是全域性單例:

@Module
abstract class PumpModule {
    @Binds
    abstract fun providePump(pump: Thermosiphon): Pump
}

@Module(includes = arrayOf(PumpModule::class))
class
DripCoffeeModule {
@Provides @Singleton fun provideHeater(): Heater = ElectricHeater() }

@Module-註解的類定義如何提供不同物件。 需要注意的是,作為多引數傳遞註解引數時,需要顯示的使用 arrayOf 進行包裝,比如上文示例中的 @Module(includes = arrayOf(PumpModule::class))。

使用 @Component 為型別生成依賴注入的實現。 自動生成類檔案的類名帶有 Dagger 字首,比如下文示例 DaggerCoffeeShop:

@Singleton
@Component(modules = arrayOf(DripCoffeeModule::class))
interface CoffeeShop {
    fun maker(): CoffeeMaker
}

fun main(args: Array<String>) {
    val coffee = DaggerCoffeeShop.builder().build()
    coffee.maker().brew()
}

Dagger 為 CoffeeShop 所生成的實現,允許你獲得一個完全注入的 CoffeeMaker。 DaggerCoffeeShop 的具體程式碼實現可在 IDE 中檢視。

轉換到 Kotlin 時註解程式碼幾乎沒有發生改變。 接下來將介紹構建指令碼(build script)中需要修改的部分。

在 Java 中需要指定 Dagger 作為 annotationProcessor(或 apt)依賴:

dependencies {
  ...
  annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version"
}

在 Kotlin 中則需要新增 kotlin-kapt 外掛啟用 kapt,並使用 kapt 替換 annotationProcessor:

apply plugin: 'kotlin-kapt'
dependencies {
    ...
    kapt "com.google.dagger:dagger-compiler:$dagger-version"
}

就是這樣。 特別提示:kapt 也能夠處理 Java 檔案,所以不需要再保留 annotationProcessor 的依賴。

檢視示例專案的完整構建指令碼, 以及轉換後的 Android 示例程式碼。

ButterKnife

ButterKnife可以直接將view和變數進行繫結從而免去呼叫findViewById。

另外,Kotlin Android 擴充套件外掛(Android Studio 內建)具有同樣的效果:使用簡潔明瞭的程式碼替換findViewByid。 除非現在你正在使用 ButterKnife 而且沒有遷移計劃,那麼前者非常值得嘗試。

在 Kotlin 中使用 ButterKnife 與 Java 中完全一致。 在 Gradle 構建指令碼的修改如下,後面將重點介紹程式碼部分的差異。

在 Gradle 依賴中新增 kotlin-kapt 外掛,並使用 kapt 替代 annotationProcessor。

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    compile "com.jakewharton:butterknife:$butterknife-version"
    kapt "com.jakewharton:butterknife-compiler:$butterknife-version"
}

就這樣已經將整個 ButterKnife 示例程式碼轉換為 Kotlin。

現在看看有什麼變化嗎? 在 Java 中使用註解對將變數與之對應的 view 進行繫結:

@BindView(R2.id.title) TextView title;

在 Kotlin 中使用屬性而不是直接使用變數。 對屬性使用註解:

@BindView(R2.id.title)
lateinit var title: TextView

@BindView 被定義為僅應用於變數欄位,而將註解應用於整個屬性時,Kotlin 編譯器能夠理解並且覆蓋相應註解的欄位。

lateinit 修飾符允許宣告非空型別,並在物件建立後(建構函式呼叫後)初始化。 不使用 lateinit 則需要宣告可空型別並且有額外的空安全檢測操作。

使用 ButterKnife 註解可以將方法設定為監聽器:

@OnClick(R2.id.hello)
internal fun sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show()
}
以上程式碼表示點選“hello”按鈕後的事件響應。 然而在 Kotlin 中使用 lambda 表示式會讓程式碼更加簡潔清晰:

hello.setOnClickListener {
    //Anko 庫預設提供 toast 函式。
    toast("Hello, views!")
}

Data Binding

使用 Data Binding 開源庫能夠讓開發者以更簡潔的方式將應用程式資料與佈局介面進行繫結。

和使用 Java 一樣,開發者需要在 gradle 檔案中新增並激活配置。

android {
    ...
    dataBinding {
        enabled = true
    }
}

新增 kapt 的依賴後即可與 Kotlin 程式碼互動:

apply plugin: 'kotlin-kapt'
dependencies {
    kapt "com.android.databinding:compiler:$android_plugin_version"
} 

使用 Kotlin 並不需要修改任何的 xml 檔案。 例如,在 data 中使用 variable 來描述可能在佈局中使用的變數, 可以使用Kotlin型別宣告變數:

<data>
    <variable name="data" type="org.example.kotlin.databinding.WeatherData"/>
</data>

現在,可以使用 @{} 語法引用 Kotlin 的屬性:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@{data.imageUrl}"
    android:contentDescription="@string/image" />

值得一提的是,資料繫結表示式語言使用和 Kotlin 相同的語法對屬性進行引用:data.imageUrl。 在 Kotlin 中可以使用 v.prop 來替代 v.getProp(),儘管 getProp() 是Java中的方法。 類似的,也可以直接向屬性賦值,而不再需要呼叫setter。

class MainActivity : AppCompatActivity() {
    // ……
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.data = weather
        // 等同於
        // binding.setData(weather)
    }
}

在 xml 中繫結監聽器,並在執行事對相應操作進行響應:

<Button
    android:text="@string/next"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="startOtherActivity" />

例如在 MainActivity 中定義的 startOtherActivity 方法:

class MainActivity : AppCompatActivity() {
    // ……
    fun startOtherActivity(view: View) = startActivity<OtherActivity>()
}

本例中使用的效用函式 startActivity 建立一個不帶任何資料引數的 intent,並啟動一個新的 activity,這些方法都來自於 Anko 庫。 若需要新增引數,則呼叫 startActivity(“KEY” to “VALUE”).

請注意,與其在 xml 中宣告 lambda 表示式,不如直接使用程式碼繫結相關動作:

<Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onClick="@{() -> presenter.onSaveClick(task)}" />
// 用 Kotlin 程式碼寫的相同邏輯
button.setOnClickListener { presenter.onSaveClick(task) }

最後一行中 button 由 id 使用 Kotlin Android 擴充套件外掛所引用。 使用該外掛作為替代方案,既允許在程式碼中保持繫結邏輯,同時又具有簡潔的語法。

DBFlow

DBFlow 是一個用於簡化資料庫互動的SQLite開源庫。 它非常之依賴於註解處理。

使用 kapt 配置 Kotlin 依賴:

apply plugin: 'kotlin-kapt'

dependencies {
    kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version"
}

若您的專案中已在使用 DBFlow,可以安全地將在專案中引入 Kotlin。 並且逐步地將程式碼轉換為 Kotlin(確保每次編譯通過)。 轉換後的程式碼與 Java 並無明顯差異。 例如,對錶的宣告和在 Java 中僅有小小的區別,屬性宣告時必須顯示的指定預設值:

@Table(name="users", database = AppDatabase::class)
class User : BaseModel() {

    @PrimaryKey(autoincrement = true)
    @Column(name = "id")
    var id: Long = 0

    @Column
    var name: String? = null
}

對於 DBFlow 而言,除了將已經有功能程式碼轉換為 Kotlin,還能享受到 Kotlin 的特別支援。 例如,將表宣告為資料類:

@Table(database = KotlinDatabase::class)
data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null)

DBFlow 定義了一系列符合 Kotlin 語言習慣的擴充套件功能,這些都可以通過依賴新增:

dependencies {
    compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version"
}

該擴充套件可以通過類似 C# 中的 LINQ 語法方式編寫查詢語句,使用 lambda 表示式可以編寫更簡單的非同步計算程式碼。

Auto-Parcel

Auto-Parcel 使用 @AutoValue 的註解為類檔案自動生成 Parcelable 對應方法和值。

同樣的,gradle 檔案中也需要使用 kapt 作為註解處理器來處理 Kotlin 檔案:

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    kapt "frankiesardo:auto-parcel:$latest-version"
}

對 Kotlin 類檔案新增 @AutoValue 註解。 下方的示例展示轉換後的 Address 類以及自動生成相應的 Parceable 實現

@AutoValue
abstract class Address : Parcelable {
    abstract fun coordinates(): DoubleArray
    abstract fun cityName(): String

    companion object {
        fun create(coordinates: DoubleArray, cityName: String): Address {
            return builder().coordinates(coordinates).cityName(cityName).build()
        }

        fun builder(): Builder = `$AutoValue_Address`.Builder()
    }

    @AutoValue.Builder
    interface Builder {
        fun coordinates(x: DoubleArray): Builder
        fun cityName(x: String): Builder
        fun build(): Address
    }
}

由於 Kotlin 中沒有 static 方法,因此相應的方法會在 companion object中生成。 如果仍然需要從 Java 中呼叫這些方法,需要新增@JvmStatic註解。

如果呼叫 Java 的類或方法恰好在 Kotlin 中是保留字,可以使用反引號()作為轉義字元,比如呼叫上例中生成類的$AutoValue_Address`。

以上所有經過轉換的程式碼與原生 Java 程式碼非常相似