Bundle 工具解决 TransactionTooLargeException

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 监听,即可监控所有 ActivityFragment
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) {}
}

应用

  • 使用 JetpackNavigatorFragmentA 跳转到 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 使用的传参过大,即使没有超过限制,也可能在这种翻倍的情况下导致异常崩溃。

参考

toolargetool 库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值