最近在某些项目上出现了 TransactionTooLargeException,查看崩溃日志后返现并没有给出确切的错误定位。

# 产生原因

TransactionTooLargeException 原因分析:在应用层与各种 SystemService 交互过程中,调用参数和返回值会通过 Parcel 对象存储在 Binder Transaction 缓冲区中以进行传输,如果参数或返回值太大超过了 Binder`` Transaction 缓冲区的限制大小,那么调用将会失败抛出 TransactionTooLargeException 。在日常开发中, TransactionTooLargeException 通常是由于都是 传输的数据过大页面被杀死时缓存的数据过大 导致的。

# 定位问题

我们使用 toolargetool 来定位问题

dependencies {
    ...
    implementation 'com.gu.android:toolargetool:0.3.0'
}

Application.onCreate 方法中初始化

TooLargeTool.startLogging(this);

使用 adb 命令打开专用监听窗口

adb logcat -s TooLargeTool

出现组件正在向事务写入大量数据和缓冲区的时候就会出现日志

# 常见的原因

  1. Fragment.setArguments 传输的 Bundle 过大

    我们应特别注意通过 Fragment.setArguments 所传递的 Bundle 的数据大小,如果数据量较大,应该选择使用其他方式进行传输。

    public class MemoryBigData<T> extends ZeroBaseDto{
        private static final ConcurrentHashMap<String, Object> CACHE_MAP = new ConcurrentHashMap<>();
        public static <T> MemoryBigData<T> of(T value) {
            return of(UUID.randomUUID().toString(), value);
        }
        /**
         * 通过 tag 存储 MemoryBigData
         * @param tag
         * @param value
         * @return
         * @param <T>
         */
        public static <T> MemoryBigData<T> of(String tag, T value) {
            MemoryBigData<T> bigData = new MemoryBigData<>(tag);
            bigData.set(value);
            return bigData;
        }
        private final String tag;
        /**
         * 通过 tag 构造 MemoryBigData
         * @param tag
         */
        private MemoryBigData(String tag) {
            this.tag = tag;
        }
        public void set(T t) {
            CACHE_MAP.put(tag, t);
        }
        /**
         * 通过 tag 获取 MemoryBigData
         * @return
         */
        public T get() {
            return (T) CACHE_MAP.get(tag);
        }
        /**
         * 通过 tag 获取 MemoryBigData 并删除
         * @return
         */
        public T getAndRemove() {
            return (T) CACHE_MAP.remove(tag);
        }
        /**
         * 判断是否存在 tag 对应的数据
         * @return
         */
        public boolean isPresent() {
            return CACHE_MAP.containsKey(tag);
        }
    }

    存储:

    Bundle args = new Bundle();
    args.putSerializable(MODELS,MemoryBigData.of(models));

    获取:

    MemoryBigData<List<TestDetectionEditViewModel>> bigData = (MemoryBigData) arguments.getSerializable(MODELS);
    models = bigData.getAndRemove();

    也可以使用 bigData.get() ,不过这样数据在内存会越存越多,最终导致内存溢出。

  2. 页面回收恢复后,重复创建 Fragment 导致重复的缓存数据
    通过 FragmentManager.beginTransaction.add.commit 添加 Framgnet 时,但页面回收恢复后, FragmentManager 仍然会保留所有之前的 Fragment ,所以:
    方式一:重复使用缓存的 Fragment,不再重复创建
    方式二:在页面回收恢复后,先删除之前的所有 Fragment

  3. ViewPager.setOffscreenPageLimit 太大导致 Fragment 被杀死时缓存的数据过大

    如果页面数据很大,我们也应该格外注意 setOffscreenPageLimit,如果 OffscreenPageLimit 的数量过大,应该选择使用本地持久化等方式对页面数据进行缓存。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Logan 微信支付

微信支付

Logan 支付宝

支付宝

Logan 贝宝

贝宝