Safe Args
Safe Args是官方提供的一個(gè)gradle插件來(lái)生成一些代碼幫助在fragment之間傳值亲善,顧名思義就是要保證值傳遞的安全性,因?yàn)閎undle傳值時(shí)是通過(guò)key-value的形式逗柴,在兩個(gè)頁(yè)面之間傳值很難形式固定的約束蛹头,下個(gè)頁(yè)面接受值的時(shí)候總需要做各種判斷,防止值不存在或沒傳遞。使用Safe Args之后就不會(huì)存在該問(wèn)題了渣蜗。但是Safe Args還是基于bundle傳值屠尊,所以還是建議一次傳遞小量的數(shù)據(jù)。
Safe Args的使用方式
在navigation graph文件中聲明action和argument
例如, 在第一個(gè)fragment中創(chuàng)建action
<action
android:id="@+id/next_action"
app:destination="@id/navigation_detail"
/>
在第二個(gè)fragment創(chuàng)建argument
<argument
android:name="testString"
app:argType="string"
app:nullable="true"
android:defaultValue="@null"
/>
android:defaultValue為必須聲明耕拷,否則編譯時(shí)會(huì)報(bào)錯(cuò)
跳轉(zhuǎn)頁(yè)面時(shí)使用:
val directions = HomeFragmentDirections.nextAction("testString")
findNavController().navigate(directions)
接受值:
val safeArgs: DetailFragmentArgs by navArgs()
text_home.text = safeArgs.testString
Safe Arg原理
在項(xiàng)目中添加了Safe Arg插件之后讼昆,在navigation graph添加了action和argument之后,下次編譯的時(shí)候就會(huì)生成對(duì)應(yīng)的輔助類骚烧。先看下Directions:
class HomeFragmentDirections private constructor() {
private data class NextAction(
val testString: String? = null
) : NavDirections {
override fun getActionId(): Int = R.id.next_action
override fun getArguments(): Bundle {
val result = Bundle()
result.putString("testString", this.testString)
return result
}
}
companion object {
fun nextAction(testString: String? = null): NavDirections = NextAction(testString)
}
}
Directions中定義了一個(gè)對(duì)應(yīng)action名字的類名浸赫,綁定了對(duì)應(yīng)的action Id, 賦值了聲明的Argument, 然后通過(guò)靜態(tài)方法將NavDirections實(shí)例化返回了出去赃绊。結(jié)構(gòu)比較簡(jiǎn)單既峡,一看就懂。
Args
data class DetailFragmentArgs(
val testString: String? = null
) : NavArgs {
fun toBundle(): Bundle {
val result = Bundle()
result.putString("testString", this.testString)
return result
}
companion object {
@JvmStatic
fun fromBundle(bundle: Bundle): DetailFragmentArgs {
bundle.setClassLoader(DetailFragmentArgs::class.java.classLoader)
val __testString : String?
if (bundle.containsKey("testString")) {
__testString = bundle.getString("testString")
} else {
__testString = null
}
return DetailFragmentArgs(__testString)
}
}
}
Args輔助類就更加簡(jiǎn)單了碧查,提供了fromBundle和toBundle兩個(gè)方法运敢,最終還是通過(guò)Bundle傳遞過(guò)去的。
在fromBundle中調(diào)用了setClassLoader的方法忠售,這個(gè)是什么時(shí)候用的呢传惠?
看下接受值的時(shí)候獲取args的地方:
val safeArgs: DetailFragmentArgs by navArgs()
是通過(guò)navArgs方法獲取到的,看下navArg方法的源碼:
@MainThread
inline fun <reified Args : NavArgs> Fragment.navArgs() = NavArgsLazy(Args::class) {
arguments ?: throw IllegalStateException("Fragment $this has null arguments")
}
一個(gè)Fragment的內(nèi)聯(lián)擴(kuò)展方法档痪,最終獲取到了NavArgsLazy類中的value, NavArgsLazy是Kotlin的lazy的一個(gè)子類,里面的泛型是NavArgs涉枫。DetailFragmentArgs的父類就是NavArgs. NavArgsLazy類的實(shí)現(xiàn):
internal val methodSignature = arrayOf(Bundle::class.java)
internal val methodMap = ArrayMap<KClass<out NavArgs>, Method>()
class NavArgsLazy<Args : NavArgs>(
private val navArgsClass: KClass<Args>,
private val argumentProducer: () -> Bundle
) : Lazy<Args> {
private var cached: Args? = null
override val value: Args
get() {
var args = cached
if (args == null) {
val arguments = argumentProducer()
val method: Method = methodMap[navArgsClass]
?: navArgsClass.java.getMethod("fromBundle", *methodSignature).also { method ->
// Save a reference to the method
methodMap[navArgsClass] = method
}
@Suppress("UNCHECKED_CAST")
args = method.invoke(null, arguments) as Args
cached = args
}
return args
}
override fun isInitialized() = cached != null
}
可以看到是在Lazy懶加載的基礎(chǔ)上添加了一層緩存,使用cached緩存了一次腐螟,如果緩存有則直接返回愿汰。
如果沒有緩存則從arguments中生成。先從argumentProducer獲取fragment的arguments對(duì)象乐纸,然后通過(guò)反射fromBundle方法衬廷,生成對(duì)應(yīng)的實(shí)例。又緩存了一次NavArgs的Method汽绢,為了提高性能吗跋。