使用 Kotlin 構(gòu)建 Android 應(yīng)用 | Kotlin 遷移指南 (上篇)

今年五月份的 Google I/O 上,我們正式向全球宣布 Kotlin-first 的這一重要理念,Kotlin 將成為 Android 開發(fā)者的首選語言。接下來的幾周我們將會為大家連載關(guān)于 Kotlin 遷移指南的系列文章,包含 Kotlin 的優(yōu)勢和介紹 (上篇)、遷移到 Kotlin (中篇),以及使用 Kotlin 的常見問題 (下篇),幫助開發(fā)者們順利遷移并開始使用 Kotlin 構(gòu)建 Android 應(yīng)用。

了解 Kotlin ,以及使用它的優(yōu)勢

Kotlin 是一種現(xiàn)代的靜態(tài)設(shè)置類型編程語言,可以提高開發(fā)者的工作效率,并提升開發(fā)者的工作愉悅度。

優(yōu)勢 1: 可與 Java 互操作

與 Android SDK 和 Java 程序語言庫兼容,Kotlin 代碼中可以方便調(diào)用 Java 庫 (Android Studio 的 Lint 檢查亦能與 Kotlin 代碼互操作)。

優(yōu)勢 2: 與 IDE 工具兼容

Kotlin 語言由 IntelliJ 的開發(fā)團隊設(shè)計,可與 IntelliJ (以及 Android Studio) 完美搭配使用,Android Studio 為 Kotlin 提供了一流的支持,比如,您可通過內(nèi)置工具來將 Java 代碼轉(zhuǎn)換成 Kotlin 代碼。或者借助 “Show Kotlin Bytecode” 工具,您可以在學(xué)習(xí) Kotlin 時查看等效的 Java 代碼。

優(yōu)勢 3: 空安全檢測

默認情況下,Kotlin 可避免空指針異常發(fā)生。而且可以在開發(fā)時而不是運行時發(fā)現(xiàn)和避免錯誤。

fun foo(p: int) { ... }
foo(null) // 編譯器報錯

var o: String? = ...
println(o.toLowerCase()) // 編譯器報錯

△ 上面兩個例子都會觸發(fā)編譯器報錯,從而避免了在運行時出現(xiàn)崩潰

優(yōu)勢 4: 更簡潔的代碼
Kotlin 有著更簡潔明了的語法,可減少樣板代碼的使用。

// Java 語言類代碼
public class User {

    private String firstName;
    private String lastName;
    
    public User(String firstName, String lastName) {...}
    public String getFirstName() {...}
    public void setFirstName(String firstName) {...}
    public String getLastName() {...}
    public void setLastName(String lastName) {...}
}

比如上例中的數(shù)據(jù)類代碼,有字段以及對應(yīng)的 getter 和 setter 方法,雖然都是常規(guī)內(nèi)容,但不免繁瑣,而且大量的樣本代碼也會占用開發(fā)者的精力。我們來看看同樣的類用 Kotlin 如何編寫:

// Kotlin 語言,同樣的類代碼
class User(
    var firstName: String?,
    var lastName: String?
)

Kotlin 還支持?jǐn)U展方法,可以給現(xiàn)有的類附加新的方法 (而不需要修改類的原始代碼)。比如我們想計算字符串內(nèi)某個字符出現(xiàn)的次數(shù),通常我們這么做:

// 定義方法
fun howMany(string: String, char: Char): Int {
    var count = 0
    val lowerCaseLetter = char.toLowerCase()
    for (i in 0 until string.length) {
        if (lowerCaseLetter == string[i].toLowerCase()) count++
    }
    return count
}

// 計算“Elephant”里有幾個“e”
val string = "Elephant"
howMany(string, 'e')

有了擴展方法,我們可以直接把 howMany 這個方法添加至 String 類:

// 擴展方法
fun String.howMany(char: Char): Int {
    var count = 0
    val lowerCaseLetter = char.toLowerCase()
    for (i in 0 until length) {
        if (lowerCaseLetter == this[i].toLowerCase()) count++
    }
    return count
}

// 執(zhí)行
val string = "Elephant"
string.howMany('e')

如此一來,我們就直接 “問” string “你里面有幾個‘e’字符” 就可以了,這更簡潔、自然,可讀性也大幅提升。

Kotlin 還支持指定/默認參數(shù),這讓開發(fā)者在編寫方法時,不需要為不同參數(shù)的版本另寫一個方法,而是直接在同一個方法里,通過 “?” 標(biāo)出可空參數(shù),通過 “=” 給出參數(shù)的默認值即可。

// View.java
public View(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, 0);
}
public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    // ...
}

// 和上述內(nèi)容等效的 Kotlin 代碼
class View(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0, defStyleRes: Int = 0) {
    // ...
}

△ 使用 Kotlin 僅需要定義一個構(gòu)造函數(shù)即可

優(yōu)勢 5: 語言特性帶來的進階功能
Kotlin 也在持續(xù)為開發(fā)者帶來更多高級的語言特性,協(xié)程就是一個突出的例子。

Kotlin 里的協(xié)程可以理解為從語言級別實現(xiàn)了異步或非阻塞編程,并在 Kotlin 1.3 中開始提供,在 Android 上使用協(xié)程可以避免下面的問題:

  • 通過主 (界面) 線程進行調(diào)用時可以確保安全 (比如在主線程中異步訪問數(shù)據(jù)庫)
  • 避免在主線程上運行耗時較長的任務(wù) (如圖像或網(wǎng)絡(luò)操作) 時發(fā)生阻塞

比如下面這個例子,使用協(xié)程時不會對主線程造成阻塞,并可提高可讀性:

// 使用回調(diào)
fun getData() {
    get("developer.android.google.cn") { result ->
        show(result)
    }
}

// 使用協(xié)程
suspend fun getData() {
    val result = get("developer.android.google.cn")
    show(result)
}

suspend fun get(url: String) {...}

使用 Kotlin 構(gòu)建 Android 應(yīng)用

△ Kotlin 推進的時間表

使用 Kotlin 更快速地編寫更棒的 Android 應(yīng)用,自兩年前 Android 平臺開始支持使用 Kotlin 語言后,我們一直在努力豐富使用 Kotlin 構(gòu)建的體驗和開發(fā)效率的提升。我們?yōu)?Android 開發(fā)者提供了 Android KTX、Android Studio 的支持以及大量的學(xué)習(xí)資源等。

Android KTX
自從兩年前 Android 平臺開始支持 Kotlin 后,我們一直在努力解決 Kotlin 的兼容性問題并豐富其功能,更進一步為大家?guī)砹嗽S多工具來進一步提高開發(fā)效率,比如 Android KTX。它是一組適用于 Android 開發(fā)的 Kotlin 擴展功能,對多種常用的 Android 開發(fā)流程提供簡化的封裝 API。

適用于動畫、圖形、文本等諸多領(lǐng)域。下面來看幾個例子:

KTX: 動畫
AnimatorKt 能讓開發(fā)者在動畫的各個階段執(zhí)行自己的操作。比如以前需要在動畫結(jié)束時執(zhí)行操作需要這么做:

// Animator API
fun addListener(listener: Animator.AnimatorListener!)

// 應(yīng)用代碼
val animator = ObjectAnimator.ofFloat(...)
anim.addListener(object : AnimatorListenerAdapter() {
    override fun onAnimationEnd(animation: Animator?) {
        println("end!")
    }
})

而在 AnimatorKt 里,只需使用 doOnEnd 即可,代碼被精簡成了一行:

// AnimatorKt
inline fun Animator.doOnEnd(
    crossinline action: (animator: Animator) -> Unit
)

// 應(yīng)用代碼
val animator = ObjectAnimator.ofFloat(...)
anim.doOnEnd { println("end!") }

大家可以參看如下代碼了解 AnimatorKt 是如何幫大家精簡代碼的:


inline fun Animator.doOnEnd(crossinline action: (animator: Animator) -> Unit) =
    addListener(onEnd = action)

inline fun Animator.addListener(
    crossinline onEnd: (animator: Animator) -> Unit = {},
    crossinline onStart: (animator: Animator) -> Unit = {},
    crossinline onCancel: (animator: Animator) -> Unit = {},
    crossinline onRepeat: (animator: Animator) -> Unit = {}
): Animator.AnimatorListener {
    val listener = object : Animator.AnimatorListener {
        override fun onAnimationRepeat(animator: Animator) = onRepeat(animator)
        override fun onAnimationEnd(animator: Animator) = onEnd(animator)
        override fun onAnimationCancel(animator: Animator) = onCancel(animator)
        override fun onAnimationStart(animator: Animator) = onStart(animator)
    }
    addListener(listener)
    return listener
}

KTX: Drawables 轉(zhuǎn)化為位圖
將可繪制對象轉(zhuǎn)化為位圖是不少開發(fā)者在處理 UI 時的常用操作,在以前需要如此操作:

// 位圖 API
fun createBitmap(width: Int, height: Int, config: Bitmap.Config): Bitmap

// Canvas API
fun draw(canvas: Canvas)

// 應(yīng)用代碼
val (oldLeft, oldTop, oldRight, oldBottom) = bounds
drawable.setBounds(0, 0, width, height)
val bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888)
drawable.draw(Canvas(bitmap))
drawable.setBounds(oldLeft, oldTop, oldRight, oldBottom)

但如果使用 DrawableKt,只需要如下操作即可,應(yīng)用代碼再次被壓縮成了一行:

// DrawableKt
fun toBitmap(
    width: Int = intrinsicWidth,
    height: Int = intrinsicHeight,
    config: Config? = null
): Bitmap

// 應(yīng)用代碼
d.toBitmap(width, height)

DrawableKt 實際上是使用擴展方法,將開發(fā)者需要做的操作封裝了起來,從而節(jié)省了大量重復(fù)工作的時間:


fun Drawable.toBitmap(
    @Px width: Int = intrinsicWidth,
    @Px height: Int = intrinsicHeight,
    config: Config? = null
): Bitmap {
    if (this is BitmapDrawable) {
        if (config == null || bitmap.config == config) {
           if (width == intrinsicWidth && height == intrinsicHeight) {
                return bitmap
            }
            return Bitmap.createScaledBitmap(bitmap, width, height, true)
        }
    }

    val (oldLeft, oldTop, oldRight, oldBottom) = bounds

    val bitmap = Bitmap.createBitmap(width, height, config ?: Config.ARGB_8888)
    setBounds(0, 0, width, height)
    draw(Canvas(bitmap))

    setBounds(oldLeft, oldTop, oldRight, oldBottom)
    return bitmap
}

Kotlin x Jetpack

在推薦開發(fā)者使用 Kotlin 構(gòu)建應(yīng)用的同時,Android 團隊自己也在大規(guī)模的使用 Kotlin,比如下面要跟大家介紹的在 Jetpack 庫中的 Kotlin 特性的使用:

Jetpack 與協(xié)程

在 Jetpack 的下述組件庫里使用了協(xié)程的特性:

  • Room: suspend 函數(shù)
  • WorkManager: CoroutineWorker
  • Lifecycles: 協(xié)程作用域 (coroutine scope)
  • ViewModel: 協(xié)程作用域
  • LiveData: 協(xié)程構(gòu)建器 (coroutine builder)

Jetpack Compose


在上周舉辦的 Android Dev Summit 2019 大會上,我們發(fā)布了 Jetpack Compose 的開發(fā)者預(yù)覽版。Jetpack Compose 可以幫助開發(fā)者簡化并加速 Android 上的 UI 開發(fā)——使用更少的代碼、強大的工具和非常直觀的 Kotlin API,使您的應(yīng)用栩栩如生。

我們?yōu)殚_發(fā)者們準(zhǔn)備了一些 Jetpack Compose 相關(guān)的教程,幫助您更直觀的體驗和了解它的優(yōu)勢: https://developer.android.google.cn/jetpack/compose/tutorial

請持續(xù)關(guān)注我們接下來時間發(fā)布的與 Kotlin 遷移指南相關(guān)的文章。

如果您對在 Android 開發(fā)中使用 Kotlin 有任何疑問或者想法,歡迎在評論區(qū)和我們分享。

點擊這里即刻使用 Kotlin 打造精彩 Android 應(yīng)用

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容