如何避免Android的TransactionTooLargeException?LruCache方案可行吗?
问题背景
你在Android应用里遇到了跨Activity/Fragment、向Service传递大数据时的android.os.TransactionTooLargeException问题——当前用Parcelable存到Intent/Bundle里,数据超过1M就会崩溃。现在打算把Parcelable对象存入LruCache,只在Parcel里传递uuid,替换原有的读写逻辑,想知道这个方案的潜在问题,以及其他可行的解决办法。
原Parcelable实现代码:
@ApiSerializable class DataItem ( @SerializedName("uuid") var uuid: String = "", @SerializedName("image") val mainImage: Image?, //another parcelable type @SerializedName("entities") var entities: List<EntityInfo>?, //...... // a lot of data //...... //...... ) : BaseDataItem(), IData { override fun uuid(): String { return uuid } //...... constructor(parcel: Parcel) : this( parcel.readString(), //uuid //... //... parcel.readParcelable(Image::class.java.classLoader), mutableListOf<EntityInfo>().apply { parcel.readTypedList(this, EntityInfo.CREATOR) } ) { } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(uuid ?: "") //...... //...... parcel.writeParcelable(mainImage, flags) parcel.writeTypedList(entities) } override fun describeContents(): Int { return 0 } companion object CREATOR : Parcelable.Creator<DataItem> { override fun createFromParcel(parcel: Parcel): DataItem { return DataItem(parcel) } override fun newArray(size: Int): Array<DataItem?> { return arrayOfNulls(size) } } }
拟采用的LruCache方案代码:
// having the cache somewhere val dataCache = LruCache<String, IData>(200) fun init (copyData: DataItem) { // do copy over from the copyData } constructor(parcel: Parcel) : this() { uuid = parcel.readString(), //uuid val _thisCopy = dataCache.get(uuid) init(_thisCopy) } override fun writeToParcel(parcel: Parcel, flags: Int) { parcel.writeString(uuid ?: "") dataCache.put(uuid, this) }
LruCache方案的潜在问题
这个思路确实能绕过TransactionTooLargeException,但踩坑点不少:
- 内存泄漏风险:LruCache用的是强引用缓存,如果
DataItem里持有Context、View或者其他和组件生命周期绑定的对象,就算原页面/组件销毁了,缓存还攥着这些引用,GC根本收不掉,分分钟引发内存泄漏,严重的话会导致OOM。 - 进程重启数据丢失:Android系统在内存紧张时会杀掉后台进程,要是接收组件(比如目标Activity/Service)启动前进程被重启了,LruCache里的缓存数据会全部清空,这时候通过uuid拿不到数据,直接空指针或者业务逻辑崩溃。
- 缓存有效性问题:
- 要是同一个uuid对应的
DataItem被更新了,但缓存里的旧数据没及时清理,接收方拿到的就是过期数据,业务逻辑会出问题; - LruCache有容量限制(你设的200条),超过容量旧数据会被自动移除,如果接收方还没来得及获取,就直接拿不到对应数据了。
- 要是同一个uuid对应的
- 组件生命周期不匹配:比如发送方把数据存进缓存后,接收方还没来得及读取,发送方就因为页面销毁主动清理了缓存,接收方同样拿不到数据。
- 线程安全隐患:虽然LruCache的
put/get方法是同步的,但如果DataItem本身不是线程安全的,多线程环境下读写(比如后台Service读、前台Activity写)可能会出现数据不一致的问题。
其他可行的解决建议
根据不同场景,给你几个替代方案:
- 本地文件存储:把
DataItem序列化为JSON或者Protobuf格式,写入本地文件(可以用uuid当文件名),然后在Intent里传递文件路径或者uuid。接收方拿到后读取文件反序列化即可。注意要处理IO异常,还要记得在使用完后清理文件,或者定期删除过期文件,避免占用过多存储。 - 弱引用内存容器:如果只是临时在内存中传递,不需要持久化,可以用
WeakHashMap<String, IData>代替LruCache。WeakHashMap里的对象没有其他强引用时会被GC自动回收,能减少内存泄漏风险,但同样解决不了进程重启丢失数据的问题,适合短期跨组件传递的场景。 - ViewModel+Flow/LiveData:如果是同一个进程内的组件传递,可以借助ViewModel持有数据,用SharedFlow或者LiveData来分发。比如发送方把数据存入ViewModel的Flow,接收方在自己的ViewModel里订阅这个Flow,就能拿到数据,完全绕开Intent/Bundle的大小限制。
- 优化Parcelable数据大小:先看看
DataItem里的内容能不能精简,比如图片只传递url而不是Bitmap,大字符串可以压缩后再传递(接收方解压),把数据控制在1M以内,这是最直接的方案,不需要额外的缓存或存储逻辑。 - ContentProvider(跨进程场景):如果是跨进程传递数据,ContentProvider可以用来安全共享数据,但实现起来比较繁琐,适合复杂的跨进程数据交互场景。
内容的提问来源于stack exchange,提问作者lannyf
相关产品推荐
相关产品推荐

