@SR1s
2018-02-22T23:49:53.000000Z
字数 3097
阅读 4174
Kotlin
android-ktx
在Google宣布将Kotlin作为Android开发的第一语言之后,Android官方发布了android-ktx
项目。这个项目利用Kotlin语言灵活的语法特性,给Android API增加了一系列的简洁好用的拓展函数,让开发者用起来更顺手、代码更加简单紧凑,同时,这个项目也在Github
上开源。
社区开发者也纷纷为这个项目添砖加瓦做贡献。有一个给Intent
添加拓展函数的讨论很有意思。
题主glidor认为Intent
在Android API里很常见,但用起来不顺手。受Kotlin标准库里类似buildString
拓展函数的启发,他提议,ktx
里添加一个类似的buildIntent
的拓展函数。
用法大致如下:
val intent = buildIntent {
action = "some action"
data = "http://example.com".toUri()
putExtra("name", value)
}
实现的话也不难:
inline fun buildIntent(initializer: Intent.() -> Unit): Intent {
return Intent().apply(initializer)
}
一般使用Intent还会指定要启动的Activity,这个场景,这个哥们给了这样的一个思路:
// 用法
val intent = buildIntent<MyActivity>(context) {
putExtra("name", value)
}
// 实现
inline fun <reified T : Activity> buildIntent(context: Context, initializer: Intent.() -> Unit): Intent {
return Intent(context, T::class.java).apply(initializer)
}
确实,相比之前的使用方式,这种方式使得代码更加简练,使用起来也顺手很多,可读性也提高了不少。
后来,这个哥们想着不如更近一步,构造完intent之后,直接启动Activity好了,于是有了下面这个用例:
// 用法
activity.startActivity<MyActivity> {
putExtra("name", "value")
}
// 实现
inline fun <reified T : Activity> Activity.startActivity(initializer: Intent.() -> Unit) {
startActivity(
Intent(this, T::class.java).apply(initializer)
)
}
然后有另一个哥们dovahkiin98,觉得一直调用putExtra
也很繁琐,针对这个情况,提出了使用key - value
可变参数的版本:
// 用法
activity.startActivity<MyActivity>(
"foo" to "bar",
"theAnswer" to 42
)
// 实现
inline fun <reified T : Activity> Activity.startActivity(vararg args: Pair<String, Any>) {
val intent = Intent(this, T::class.java)
intent.putExtras(bundleOf(*args))
startActivity(intent)
}
这时候,又有个叫peikedai的哥们跳出来,反对上面这种用法。倒不是说这个的实现有问题,他觉得这种用法不方便。Intent
和Bundle
有点像Map<String, Any?>
,往里面存数据需要指定数据的key
,业务在维护数据之外,还要维护这个key
的字符串,这个就让人蛋疼了。他提议,将参数存储在Parcelable
对象里进行传递:
// 用法
activity.startActivity<MyActivity>(Client("Alice", 15))
val parameter = activity.getParameter<Client>()
@Parcelize
data class Client(val name: String, val age: Int): Parcelable
// 实现
inline fun <reified T : Activity> Activity.startActivity(parameter: Parcelable) {
val intent = Intent(this, T::class.java).apply {
val name = getExtraName(T::class.java)
putExtra(name, parameter)
}
startActivity(intent)
}
inline fun <reified T : Parcelable> Activity.getParameter(): T {
val name = getExtraName(this::class.java)
return intent.getParcelableExtra<T>(name)
}
fun getExtraName(target: Class<out Activity>): String {
return "${target.canonicalName}.Parameter"
}
这种方式更符合面向对象的思想,在Java里也能这么用,这也是笔者一直以来的用法,只不过在Java里这种用法太过繁琐:实现Parcelable
要写相当多的代码,在简单的场合,还不如使用key - value
的方式来得方便。Kotlin的Android Plugin会自动生成Parcelable
的实现,这使得使用这种方式更加方便了。
对于key - value
可变参数的版本,题主glidor提了三点意见反对:
1. 相比原有的putExtra
调用,可变参数的版本创建了额外的对象:每个key - value
对分别创建一个对象,这些对象凑成的数组对象,还有bundleOf
方法内部创建的Bundle
对象,这些都会带来额外的开销。
2. 同样的,这种方式丧失了一定的灵活性。buildIntent
的方式,本质上是一个代码块,可以在代码块里做运算和逻辑判断,而key - value
可变参数的版本则不行。
3. 这个解决方案是类型不安全的,只有基础类型、Parcelabel
、Serializable
这些能传递,而实现上的参数类型声明却是Any
,即允许任意类型。
dovahkiin98对第一点没做反驳,他认为,这些拓展函数是可以共存的,仅仅需要传递key - value
对时,可以用key - value
可变参数版本,如果需要对初始化过程做更多的控制,可以用提供了初始化器的版本。至于类型不安全,他指出ktx
里的bundleOf
拓展方法就是这么实现的:允许传入任意类型的数据,bundleOf
内部会做类型检查,遇到不合适的类型直接抛异常。
至此,讨论就暂告一段落了。从上述的讨论,可以看到,在Kotlin
强大的语言特性支持之下,能够帮助开发者以更简洁高效的方式来编码,同时也应该注意到,Kotlin
的语言特性虽然支持我们自定义更简洁的API接口,但也可能在不经意间引入了性能问题,这点需要开发者在编码的过程中留意和权衡。
后面官方会以哪种方式实现buildIntent
和startActivity
目前还不得而知,如果觉得上面的方案有用,大家也可以先行在项目里用着。倘若有其他想法,不妨留言里一起探讨:-)