今年五月份的 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 代碼互操作)。
- Kotlin 互操作指南:https://developer.android.google.cn/kotlin/interop
優(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 更快速地編寫更棒的 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)用
