Bundle 工具解决 TransactionTooLargeException
记录 TransactionTooLargeException 解决的过程
异常原因及解决方法
TransactionTooLargeException
一般是 Bundle
过大导致,常见于应用界面返回后台时,调用 onSaveInstanceState
时。减少Bundle
大小即可解决。
工具
- 使用如下工具,可把
Bundle
展开并计算大小,来查找哪一项引起的Bundle
过大。
object BundleUtils {
fun Bundle.calculateSize(): BundleSize {
val totalSize = calculateSizeInParcel(this)
val childrenSize = calculateChildrenSize(1)
return BundleSize("Bundle@${Integer.toHexString(hashCode())}", "", totalSize, 0, childrenSize)
}
private fun Bundle.calculateChildrenSize(depth: Int): List<BundleSize> {
val copyBundle = Bundle(this)
val results = mutableListOf<BundleSize>()
try {
var tempSize = calculateSizeInParcel(copyBundle)
for (key in copyBundle.keySet()) {
val (value, childrenSize) = get(key)?.let {
if (it is Bundle && !isEmpty) {
Pair("Bundle@${Integer.toHexString(hashCode())}", it.calculateChildrenSize(depth + 1))
} else Pair(it.toString(), emptyList())
} ?: Pair("", emptyList())
remove(key)
val newBundleSize = calculateSizeInParcel(this)
val valueSize = tempSize - newBundleSize
tempSize = newBundleSize
results.add(BundleSize(key, value, valueSize, depth, childrenSize))
}
} finally {
putAll(copyBundle)
}
return results
}
private fun calculateSizeInParcel(bundle: Bundle): Int {
val parcel = Parcel.obtain()
try {
parcel.writeBundle(bundle)
return parcel.dataSize()
} finally {
parcel.recycle()
}
}
}
data class BundleSize(
val key: String,
val value: String,
val totalSize: Int,
val depth: Int,
val children: List<BundleSize>
) {
override fun toString(): String {
val result = StringBuilder()
if (depth > 0) {
result.append("\n*$depth")
for (i in 1..depth) {
result.append(" ")
}
result.append("$key = $value")
result.append("\n* ")
for (i in 1..depth) {
result.append(" ")
}
}
result.append(String.format("$key is [%,d B] in Parcel.", totalSize))
if (children.isNotEmpty()) {
result.append(" It contains ${children.size} keys.")
for (size in children) {
result.append("$size")
}
}
return result.toString()
}
}
- 可以在
Application
内设置设置全局的onSaveInstanceState
监听,即可监控所有Activity
和Fragment
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
register()
}
}
object OnSaveInstanceStateListener : Application.ActivityLifecycleCallbacks {
fun Application.register() {
registerActivityLifecycleCallbacks(OnSaveInstanceStateListener)
}
private val listener = object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentSaveInstanceState(fm: FragmentManager, f: Fragment, outState: Bundle) {
Log.i(f.javaClass.simpleName, "onSaveInstanceState write: ${outState.calculateSize()}")
f.arguments?.apply {
Log.i(f.javaClass.simpleName, "\n arguments = ${calculateSize()}")
}
}
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
Log.i(activity.javaClass.simpleName, "onSaveInstanceState write: ${outState.calculateSize()}")
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (activity is FragmentActivity) {
activity.supportFragmentManager.registerFragmentLifecycleCallbacks(listener, true)
}
}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {}
override fun onActivityDestroyed(activity: Activity) {}
}
应用
- 使用
Jetpack
的Navigator
从FragmentA
跳转到FragmentB
,应用返回后台。获取到如下日志
MyNavigationActivity onSaveInstanceState write: Bundle@eac7f5a is [892 B] in Parcel. It contains 3 keys.
*1 android:viewHierarchyState = Bundle@eac7f5a
* android:viewHierarchyState is [440 B] in Parcel. It contains 1 keys.
*2 android:views = {16908290=android.view.AbsSavedState$1@2171e1, 2131230774=android.view.AbsSavedState$1@2171e1, 2131230790=android.view.AbsSavedState$1@2171e1, 2131230930=android.view.AbsSavedState$1@2171e1}
* android:views is [372 B] in Parcel.
*1 android:lastAutofillId = 1073741823
* android:lastAutofillId is [60 B] in Parcel.
*1 android:fragments = android.app.FragmentManagerState@7dbd58b
* android:fragments is [388 B] in Parcel.
FragmentA onSaveInstanceState write: Bundle@afb9fb2 is [320 B] in Parcel. It contains 2 keys.
*1 android:support:fragments = Bundle@afb9fb2
* android:support:fragments is [64 B] in Parcel.
*1 androidx.lifecycle.BundlableSavedStateRegistry.key = Bundle@afb9fb2
* androidx.lifecycle.BundlableSavedStateRegistry.key is [252 B] in Parcel. It contains 1 keys.
*2 androidx.lifecycle.internal.SavedStateHandlesProvider = Bundle@d776b03
* androidx.lifecycle.internal.SavedStateHandlesProvider is [128 B] in Parcel.
FragmentB onSaveInstanceState write: Bundle@e1cc8b9 is [320 B] in Parcel. It contains 2 keys.
*1 android:support:fragments = Bundle@e1cc8b9
* android:support:fragments is [64 B] in Parcel.
*1 androidx.lifecycle.BundlableSavedStateRegistry.key = Bundle@e1cc8b9
* androidx.lifecycle.BundlableSavedStateRegistry.key is [252 B] in Parcel. It contains 1 keys.
*2 androidx.lifecycle.internal.SavedStateHandlesProvider = Bundle@f8004fe
* androidx.lifecycle.internal.SavedStateHandlesProvider is [128 B] in Parcel.
FragmentB arguments = Bundle@57bb45f is [44 B] in Parcel. It contains 1 keys.
*1 key = value
* key is [40 B] in Parcel.
NavHostFragment onSaveInstanceState write: Bundle@383775 is [5,160 B] in Parcel. It contains 4 keys.
*1 android:support:fragments = Bundle@383775
* android:support:fragments is [3,244 B] in Parcel. It contains 3 keys.
*2 fragment_5258d6ff-ab9b-4abc-8701-07dca5976459 = Bundle@253730a
* fragment_5258d6ff-ab9b-4abc-8701-07dca5976459 is [1,060 B] in Parcel. It contains 1 keys.
*3 state = FragmentState{com.example.myjetpackdemo.navigation.FragmentB (5258d6ff-ab9b-4abc-8701-07dca5976459)}: id=0x7f0800d2
* state is [956 B] in Parcel.
*2 state = androidx.fragment.app.FragmentManagerState@e51377b
* state is [1,092 B] in Parcel.
*2 fragment_0511d8f5-ea81-4f42-97b1-d43aa535bb3e = Bundle@253730a
* fragment_0511d8f5-ea81-4f42-97b1-d43aa535bb3e is [1,028 B] in Parcel. It contains 1 keys.
*3 state = FragmentState{com.example.myjetpackdemo.navigation.FragmentA (0511d8f5-ea81-4f42-97b1-d43aa535bb3e)}: id=0x7f0800d2 removing
* state is [916 B] in Parcel.
*1 androidx.lifecycle.BundlableSavedStateRegistry.key = Bundle@383775
* androidx.lifecycle.BundlableSavedStateRegistry.key is [244 B] in Parcel. It contains 1 keys.
*2 androidx.lifecycle.internal.SavedStateHandlesProvider = Bundle@b9e4998
* androidx.lifecycle.internal.SavedStateHandlesProvider is [128 B] in Parcel.
*1 android-support-nav:fragment:defaultHost = true
* android-support-nav:fragment:defaultHost is [96 B] in Parcel.
*1 android-support-nav:fragment:navControllerState = Bundle@383775
* android-support-nav:fragment:navControllerState is [1,572 B] in Parcel. It contains 1 keys.
*2 android-support-nav:controller:backStack = [Landroid.os.Parcelable;@61d5f1
* android-support-nav:controller:backStack is [1,456 B] in Parcel.
-
通过抓取到的日志,可以看到
NavHostFragment
通过android:support:fragments
存储了栈内的所有Fragment
信息。在android-support-nav:controller:backStack
又存储了一次Frament
信息。注意:这些
Fragment
信息包含了切换时的传参,即如果切换Fragment
使用的传参过大,即使没有超过限制,也可能在这种翻倍的情况下导致异常崩溃。