Android中发送数据比较常用的方式是使用Bundle,但每次使用fragment的时候要写一些模板代码,既然kotlin中支持延迟初始化对象,使用自定义代理的方式是否可以实现不写模板代码也可以获取到值呢?
在mvi框架封装这篇文章中使用到了自定义路由跳转,苦思冥想后发现少了点什么,嗯就是参数传递应当简化,本篇文章和上篇文章无关,可以看做独立的篇章,本篇文章主要是简化Bundle的参数传递和接受。
RouterBundle 是对 builder的封装,由于这个类的来源是路由框架,所有也包含的activity跳转传递相关的代码,比如activityResult这个可以忽略不记,RouterBundle 使用的是kotlin的构建者模式,RouterBundle.Builder是用来设置存放在 Builder中的数据,如果觉得不够可自行拓展,通过bundle()方法将Bundle对象暴露出去。
class RouterBundle private constructor(private val builder: Builder) {
companion object {
inline fun build(block: Builder.() -> Unit = {}) =
Builder().apply(block).build()
}
/** 数据 **/
fun bundle() = builder.bundle
/** 是否有回调 **/
fun activityResult() = builder.registerForActivityResult
class Builder {
// 用于android序列化数据
val bundle = Bundle()
// 如果指定该参数则表示有回调
var registerForActivityResult: ActivityResultLauncher<Intent>? = null
/** 外部无需在调用该函数 **/
fun build() = RouterBundle(this)
fun setString(key: String, value: String) =
bundle.putString(key, value)
fun setStringArray(key: String, value: Array<out String>) =
bundle.putStringArray(key, value)
fun setBool(key: String, value: Boolean) =
bundle.putBoolean(key, value)
fun setFloat(key: String, value: Float) =
bundle.putFloat(key, value)
fun setLong(key: String, value: Long) =
bundle.putLong(key, value)
fun setDouble(key: String, value: Double) =
bundle.putDouble(key, value)
fun setSerializable(key: String, value: Serializable) =
bundle.putSerializable(key, value)
fun setParcelable(key: String, value: Parcelable) =
bundle.putParcelable(key, value)
fun setParcelableArray(key: String, value: Array<out Parcelable>) {
bundle.putParcelableArray(key, value)
}
fun setActivityResult(result: ActivityResultLauncher<Intent>) {
registerForActivityResult = result
}
}
}
拓展fragment,用于封装模板代码,帮助fragment设置Bundle数据,如果activity跳转使用了Bundle参数可以使用params获取Bundle参数(注意: activity/fragment只支持Bundle参数)
object FragmentExt {
/**
* 简化fragment创建实例的数据传递
*/
fun <T: Fragment> T.bundle(block: RouterBundle.Builder.() -> Unit = {}): T {
val request = RouterBundle.build(block)
this.arguments = request.bundle()
return this
}
}
使用kotlin代理的方式,将Bundle数据解析为对应实际类型数据
/**
* 获取 intent bundle的值
*/
class ActivityParamsDelegate<T>(
private val activity: Activity,
private val customKey: String = "",
private val defValue: T? = null,
private val type: KType
) : ReadOnlyProperty<Any?, T> {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val key = customKey.ifEmpty { property.name }
val bundle = activity.intent?.extras ?: return throw NullPointerException("没有找到Bundle对象")
return bundle.get(key, type, defValue)
}
}
/**
* 自定义类型传递
*/
internal inline fun <P, reified R> P?.or(defaultValue: () -> R): R {
return this as? R ?: defaultValue()
}
internal fun <T> Bundle.get(key: String, type: KType, def: T?): T {
val result: Any = when (type) {
typeOf<Byte>() -> getByte(key, def.or { 0.toByte() })
typeOf<Short>() -> getShort(key, def.or { 0.toShort() })
typeOf<Int>() -> getInt(key, def.or { 0 })
typeOf<Float>() -> getFloat(key, def.or { 0f })
typeOf<Double>() -> getDouble(key, def.or { 0.0 })
typeOf<Long>() -> getLong(key, def.or { 0L })
typeOf<Boolean>() -> getBoolean(key, def.or { false })
typeOf<Char>() -> getChar(key, def.or { '0' })
typeOf<CharSequence>() -> getCharSequence(key, def.or { "" })
typeOf<String>() -> getString(key, def.or { "" })
//array type
typeOf<ByteArray>() -> getByteArray(key) ?: def.or { ByteArray(0) }
typeOf<ShortArray>() -> getShortArray(key) ?: def.or { ShortArray(0) }
typeOf<IntArray>() -> getIntArray(key) ?: def.or { IntArray(0) }
typeOf<FloatArray>() -> getFloatArray(key) ?: def.or { FloatArray(0) }
typeOf<DoubleArray>() -> getDoubleArray(key) ?: def.or { DoubleArray(0) }
typeOf<LongArray>() -> getLongArray(key) ?: def.or { LongArray(0) }
typeOf<BooleanArray>() -> getBooleanArray(key) ?: def.or { BooleanArray(0) }
typeOf<CharArray>() -> getCharArray(key) ?: def.or { CharArray(0) }
typeOf<Array<CharSequence>>() -> getCharSequenceArray(key)
?: def.or { emptyArray<CharSequence>() }
typeOf<Array<String>>() -> getStringArray(key) ?: def.or { emptyArray<String>() }
else -> {
/**
* Must check array and list first!!
*/
when {
type.isSubtypeOf(typeOf<List<*>>()) -> {
when {
type.isSubtypeOf(typeOf<ArrayList<String>>()) ->
getStringArrayList(key) ?: def.or { arrayListOf<String>() }
type.isSubtypeOf(typeOf<ArrayList<CharSequence>>()) ->
getCharSequenceArrayList(key) ?: def.or { arrayListOf() }
type.isSubtypeOf(typeOf<ArrayList<Int>>()) ->
getIntegerArrayList(key) ?: def.or { arrayListOf() }
else -> throw Exception("获取参数当前不支持该类型")
}
}
type.isSubtypeOf(typeOf<Parcelable>()) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getParcelable(key, type.javaClass) ?: def.or { type.jvmErasure.createInstance() }
} else {
getParcelable(key) ?: def.or { type.jvmErasure.createInstance() }
}
}
type.isSubtypeOf(typeOf<Serializable>()) -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val cls = type.javaClass as Class<Serializable>
getSerializable(key, cls) ?: def.or { type.jvmErasure.createInstance() }
} else {
getSerializable(key) ?: def.or { type.jvmErasure.createInstance() }
}
}
else -> throw Exception("获取参数当前不支持该类型")
}
}
}
return result as T
}
bundle.get 这里使用了activity代理中对应的拓展
class FragmentParamsDelegate<T>(
private val fragment: Fragment,
private val customKey: String = "",
private val defValue: T? = null,
private val type: KType
) : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val key = customKey.ifEmpty { property.name }
val bundle = fragment.arguments ?: return throw NullPointerException("没有找到Bundle对象")
// bundle.get 在ActivityParamsDelegate中,由于这两个类在一个层级,所以是可以访问的
return bundle.get(key, type, defValue)
}
}
进一步简化调用方式,保持activity、fragment相同调用方式
object ParamsExt {
/**
* 获取传递参数
*/
inline fun <reified T> Activity.params(
key: String,
defaultValue: T? = null
) = ActivityParamsDelegate(this, key, defaultValue, typeOf<T>())
/**
* 获取传递的参数
*/
inline fun <reified T> Fragment.params(
key: String,
defaultValue: T? = null
) = FragmentParamsDelegate(this, key, defaultValue, typeOf<T>())
}
// 设置参数
private fun fragmentSuccess() = FragmentXXX().bundle {
setString("title", "Hello")
}
class FragmentXXX: Fragment() {
// 获取参数, activity中使用方式一致
private val title: String by params("title", "")
}
简简单单的小技巧,简化代码量看着也舒服了,不得不说kotlin拓展功能的强大,当然延迟加载功能在移动端开发挺实用的。