使用 Kotlin 優化 Intent 數據傳遞
首先,這篇文章可能會涉及到的知識點有:
- 伴生對象
- 對象表達式與對象聲明
- 擴展函數
- 擴展屬性
- 高階函數( lambda 表達式)
- 帶接收者的函數字面值
- 委托屬性
Java 的實現方式
在 Android 開發中, Activity 之間的數據傳遞是不可避免的。初次接觸 Android 編程的時候,許多教程會寫出這樣的代碼:
// 第一個 Activity
Intent intent = new Intent(context, AimActivity.class);
intent.putExtra("msg", message);
startActivity(intent);
// AimActivity
this.message = getIntent().getStringExtra("msg");
這樣寫當然沒有錯,的確達到了傳遞數據的目的,但是,這卻會讓代碼難以維護。稍微有些開發經驗的,都會選擇將 Extra 信息的鍵值抽出變為常量,并且把許多類似的鍵值放在同一個地方,避免出現鍵值的沖突。
為了更好的可讀性,還有一些人會這樣去封裝 Intent :
private static final String MSG_KEY = "key for message";
@Nullable publicstaticStringgetMessage(@NonNull Intent intent){
return intent.getStringExtra(MSG_KEY);
}
publicstaticvoidsetMessage(@NonNull Intent intent, String message){
intent.putExtra(MSG_KEY, message);
}
這樣一來,讀寫 Intent 的確如讀寫類成員屬性一般了,可是,不得不說,這也極大地增加了代碼的編寫量。當然,或者可以通過注解、動態生成代碼的方式來解決這一問題,但這不是本篇文章的解決方案。
Kotlin 的登場
在 Java 的實現方式中,最適合閱讀、最符合面向對象思想的數據傳遞方式當屬最后一種,那,下面就用一些 Kotlin 的特性來實現:
object IntentOptions {
private const val MSG_KEY = "key for message"
funIntent.getMessage(): String? = getStringExtra(MSG_KEY)
funIntent.setMessage(message:String?) {
putExtra(MSG_KEY, message)
}
}
// 數據的存儲與獲取
with(IntentOptions) {
intent.setMessage("message")
message = intent.getMessage()
}
可以看到,利用 Kotlin 提供的擴展函數的特性,我們就可以在 Intent 對象上直接使用 getter/setter 了,這是 Java 語言做不到的。
需要注意的是 with 表達式的使用,這涉及到 擴展函數 的作用域問題。在 Kotlin 中,擴展函數的作用范圍只在其定義范圍內。一般而言,我們把許多擴展函數定義在了頂級作用域中,這樣在整個 App ,被擴展的對象實例都可以使用該函數。但是考慮到,我們不能在 Intent 對象上無限制地添加 getter/setter ,所以利用 object ,讓這些擴展函數只在該對象的作用域內有效。在實際使用中,這個 object 可以是某個 Activity 的伴生對象。
到這里,我們可以更進一步,使用 擴展屬性 ,讓賦值/取值的過程更符合 Kotlin 的語言規范:
object IntentOptions {
private const val MSG_KEY = "key for message"
var Intent.message: String?
get() = getStringExtra(MSG_KEY)
set(message) {
putExtra(MSG_KEY, message)
}
}
// 使用
with(IntentOptions) {
intent.message = "message"
message = intent.message
}
嗯,不論寫那個 object 的過程怎樣,至少,在使用這樣的 Intent 方面,體驗到了讀寫原生對象的便利啊。
Kotlin 中的委托
委托?為什么要用委托?上面的代碼我們使用委托的模式改寫一下:
classIntentExtraStringDelegate(val key: String) {
fungetValue(intent:Intent): String? =
intent.getStringExtra(name)
funsetValue(intent:Intent, value:String?) {
intent.putExtra(name, value)
}
}
object IntentOptions {
private val messageDelegate = IntentExtraStringDelegate("key for message")
var Intent.message: String?
get() = messageDelegate.getValue(this)
set(message) = messageDelegate.setValue(this, message)
}
可以看到,委托就是把 Intent 內容的讀寫操作抽取出來,以便復用。進行了這樣的抽取之后,我們又可以在 Kotlin 的語言特性中找到這樣的實現:
classIntentExtraString(private val key: String) {
operator fungetValue(intent:Intent, property:KProperty<*>): String? =
intent.getStringExtra(key)
operator funsetValue(intent:Intent, property:KProperty<*>, value:String?) {
intent.putExtra(key, value)
}
}
// 使用
object IntentOptions {
var Intent.message by IntentExtraString("key for message")
}
啊哈,這樣一來,相當于僅僅是對 Intent 內的變量進行了一下聲明,就可以在對應的范圍內使用了!而存儲的鍵值,也完全可以省略,直接使用字段名:
classIntentExtraString(private val key: String? = null) {
private val KProperty<*>.extraName: String
get() = this@IntentExtraString.key ?: name
operator fungetValue(intent:Intent, property:KProperty<*>): String? =
intent.getStringExtra(property.extraName)
operator funsetValue(intent:Intent, property:KProperty<*>, value:String?) {
intent.putExtra(property.extraName, value)
}
}
一般而言,在 Activity 可以這樣使用:
classActivity:AppCompatActivity() {
companion object IntentOptions {
var Intent.id by IntentExtraString()
var Intent.name by IntentExtraString()
var Intent.message by IntentExtraString()
}
funtest(intent:Intent) {
intent.id = "1"
intent.name = "pass"
intent.message = "message"
}
}
funtestOutSide(intent:Intent) = with(Activity.IntentOptions) {
val id = intent.id
val name = intent.name
val message = intent.message
}
以上,就是使用 Kotlin 優化 Intent 數據傳輸的基本思路。當然,在這個思路下,還有許多可以封裝的,比如,自定義類型數據的傳輸——可以去看一看原作者的開源庫 android-extras-delegates 。
來自:http://blog.saplf.top/2017/03/19/kotlin-intent-extension/