Activity 通过 Intent 传参的一般写法:
class ListActivity : Activity() {
fun toDetail(id: Int, name: String) {
Intent(this, DetailActivity::class.java).apply {
putExtra(DetailActivity.EXTRA_ID, id)
putExtra(DetailActivity.EXTRA_NAME, name)
}.apply { startActivity(this) }
}
}
class DetailActivity : Activity() {
companion object {
const val EXTRA_ID = "EXTRA_ID"
const val EXTRA_NAME = "EXTRA_NAME"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val (id: Int, name: String) = intent.run {
getIntExtra(EXTRA_ID, -1) to getStringExtra(EXTRA_NAME)!!
}
}
}
在目标 Activity 中构建 Intent 工具方法的写法:
class ListActivity : Activity() {
fun toDetail(id: Int, name: String) {
DetailActivity.createIntent(this, id, name).apply {
startActivity(this)
}
}
}
class DetailActivity : Activity() {
companion object {
private const val EXTRA_ID = "EXTRA_ID"
private const val EXTRA_NAME = "EXTRA_NAME"
private fun Intent.id() = getIntExtra(EXTRA_ID, -1)
private fun Intent.name() = getStringExtra(EXTRA_NAME)!!
fun createIntent(context: Context, id: Int, name: String): Intent {
return Intent(context, DetailActivity::class.java).apply {
putExtra(EXTRA_ID, id)
putExtra(EXTRA_NAME, name)
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val id = intent.id()
val name = intent.name()
}
}
Splitties 写法:
class ListActivity : Activity() {
fun toDetail(id: Int, name: String) {
start<DetailActivity> {
putExtras(DetailActivity.ExtraSpec) {
this.id = id
this.name = name
}
}
// start(DetailActivity) { _, extra ->
// extra.id = id
// extra.name = name
// } // call with ActivityIntentSpec
}
}
class DetailActivity : Activity() {
// companion object : ActivityIntentSpec<DetailActivity, ExtraSpec> by activitySpec(ExtraSpec)
object ExtraSpec : BundleSpec() {
var id: Int by bundle()
var name: String by bundle()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val (id, name) = withExtras(ExtraSpec) {
id to name
}
}
}
BundleSpec 的优点有:1. 提供类型约束 2. 省略 EXTRA_KEY
接下来看看 BundleSpec 的实现原理
先从神奇的 by bundle() 代理方法看起
fun <T : Any> BundleSpec.bundle() = BundleDelegate as ReadWriteProperty<BundleSpec, T>
private object BundleDelegate : ReadWriteProperty<BundleSpec, Any> {
override operator fun getValue(thisRef: BundleSpec, property: KProperty<*>): Any {
val key = property.name
return checkNotNull(thisRef.bundle[key]) { "Property $key could not be read" }
}
override operator fun setValue(thisRef: BundleSpec, property: KProperty<*>, value: Any) {
thisRef.put(property.name, value)
}
}
可以看出 BundleSpec 内定义的参数字段,最终会以 property.name 为键,与 BundleSpec 包裹的 bundle 相关联
那么 BundleSpec 又是如何包裹 bundle 对象呢?就得从入口方法 putExtras(BundleSpec) 和出口方法 withExtras(BundleSpec) 找答案了
inline fun <Spec : BundleSpec> Intent.putExtras(
spec: Spec,
crossinline block: Spec.() -> Unit
) {
replaceExtras((extras ?: Bundle()).apply { with(spec, block) })
}
inline fun <Spec : BundleSpec, R> Bundle.with(
spec: Spec,
crossinline block: Spec.() -> R
): R {
return try {
spec.currentBundle = this
spec.block()
} finally {
spec.currentBundle = null
}
}
inline fun <Spec : BundleSpec, R> Intent.withExtras(
spec: Spec,
crossinline block: Spec.() -> R
): R {
return try {
spec.isReadOnly = true
spec.currentBundle = extras ?: Bundle()
spec.block()
} finally {
spec.currentBundle = null
spec.isReadOnly = false
}
}
open class BundleSpec {
internal var currentBundle: Bundle?
// ...
internal var isReadOnly: Boolean
// ...
}
可以看出 BundleSpec 中留有 currentBundle 字段,被 internal 约束,仅可由库代码访问,在赋值和取值的出入口方法中,先与上下文中 Intent.bundle 相关联(hook),提供了代理底层支持,然后配置访问,最后再解除关联,保证不泄露地安全地单次使用