我们知道可以通过 Intent
和 bundle
在 activity
或 fragment
间进行通信,但是 Intent
传递数据时,如果数据太大,可能会出现异常。
1. Intent 传递不同大小数据时的问题
Activity间通过Intent传递数据的大小限制 – 具体数据博客
- 传
512K
以下的数据的数据可以正常传递。 - 传
512K~1024K
的数据会出错,闪退。 - 传
1024K
以上的数据会报错:TransactionTooLargeException
- 考虑到
Intent
还包括要启动的Activity
等信息,实际可以传的数据略小于512K
2. 问题分析
2.1 从 crash log 看起
TransactionTooLargeException 分析:
在 Intent
中传入一个 Parcelable
对象, 例如传入一个 bitmap
对象。Bitmap
实现了 Parcelable
接口,并且可以通过 getByteCount()
得知所占内存大小(代码在链接里可以找到)。sendBroadcast
时,报出如下信息
V/ActivityManager: Broadcast: Intent { act=intent_bi flg=0x10 (has extras) } ordered=false userid=0 callerApp=ProcessRecord{27aeaaf5 31217:com.rustfisher.basic4/u0a113}
E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
W/BroadcastQueue: Failure sending broadcast Intent { act=intent_bi flg=0x10 (has extras) }
android.os.TransactionTooLargeException
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(Binder.java:504)
at android.app.ApplicationThreadProxy.scheduleRegisteredReceiver(ApplicationThreadNative.java:1170)
at com.android.server.am.BroadcastQueue.performReceiveLocked(BroadcastQueue.java:576)
at com.android.server.am.BroadcastQueue.deliverToRegisteredReceiverLocked(BroadcastQueue.java:848)
at com.android.server.am.BroadcastQueue.processNextBroadcast(BroadcastQueue.java:917)
at com.android.server.am.BroadcastQueue$BroadcastHandler.handleMessage(BroadcastQueue.java:254)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:194)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
查看异常类 TransactionTooLargeException
,它继承了 RemoteExceptio
:
package android.os;
public class TransactionTooLargeException extends RemoteException {
public TransactionTooLargeException() {
super();
}
public TransactionTooLargeException(String msg) {
super(msg);
}
}
追踪到 Binder
,它的 transactNative
方法会报出 RemoteException
:
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
由此可以看出,抛出异常与 Binder
有关。
2.2 Intent
携带信息的大小受 Binder
限制
Intent
携带信息的大小其实是受 Binder
限制。本文标题也可以改为 Binder
传递数据大小限制。
数据以 Parcel
对象的形式存放在 Binder
传递缓存中。如果数据或返回值比传递 buffer
大,则此次传递调用失败并抛出 TransactionTooLargeException
异常。
Binder
传递缓存有一个限定大小,通常是 1Mb
。但同一个进程中所有的传输共享缓存空间。
多个地方在进行传输时,即时它们各自传输的数据不超出大小限制,TransactionTooLargeException
异常也可能会被抛出。
在使用 Intent
传递数据时,1Mb
并不是安全上限。因为 Binder
中可能正在处理其它的传输工作。
不同的机型和系统版本,这个上限值也可能会不同。
在其它地方,例如 onSaveInstanceState(@NonNull Bundle outState)
,也可能会遇到与 Binder
有关的类似问题。
2.3 为什么 Binder
要限制传输数据的大小
个人推测,作为一种 IPC
的方式,Binder
并不是为传输大量数据而设计。传输大量数据,可以考虑URL
之类的方法( 只传递索引,不传递具体的数据。或者其他类似的做法)。
3. 从 Intent 传递数据的原理分析
通过 intent
的 bundle
的源码可以看到它们都是实现了 Parcelable
,其实就是通过序列化来实现通信的。提到 Parcelable
就不得不提 Serializable
,这里引用一段网上的总结:
介绍Parcelable不得不先提一下Serializable接口,Serializable是Java为我们提供的一个标准化的序列化接口,那什么是序列化呢? —- 简单来说就是将对象转换为可以传输的二进制流(二进制序列)的过程,这样我们就可以通过序列化,转化为可以在网络传输或者保存到本地的流(序列),从而进行传输数据 ,那反序列化就是从二进制流(序列)转化为对象的过程.
Parcelable
是Android为我们提供的序列化的接口,Parcelable
相对于Serializable
的使用相对复杂一些,但Parcelable
的效率相对Serializable
也高很多,这一直是 Google工程师引以为傲的,有时间的可以看一下Parcelable
和Serializable
的效率对比Parcelable
vsSerializable
号称快10倍的效率
Parcelable
的底层使用了 Parcel
机制。传递实际上是使用了 binder
机制,binder
机制会将 Parcel
序列化的数据写入到一个共享内存中,读取时也是 binder
从共享内存中读出字节流,然后 Parcel
反序列化后使用。这就是 Intent
或 Bundle
能够在 activity
或者跨进程通信的原理。
关于 Parcelable
和 Serializable
的区别,同样引用一段网上的总结:
Parcelable
和 Serializable
都是实现序列化并且都可以用于 Intent
间传递数据, Serializable
是 Java
的实现方式,可能会频繁的 IO
操作,所以消耗比较大,但是实现方式简单 Parcelable
是 Android
提供的方式, 效率比较高,但是实现起来复杂一些 , 二者的选取规则是:
- 内存序列化上选择Parcelable
- 存储到设备或者网络传输上选择Serializable(当然Parcelable也可以但是稍显复杂)
当我们用Intent传输过大数据时,一般logcat会报出 TransactionTooLargeException
错误,谷歌官方对这个错误的描述如下:
The Binder transaction failed because it was too large.
During a remote procedure call, the arguments and the return value of the call are transferred as
Parcel objects stored in the Binder transaction buffer. If the arguments or the return value are too
large to fit in the transaction buffer, then the call will fail and TransactionTooLargeException
will be thrown.
The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all
transactions in progress for the process. Consequently this exception can be thrown when there
are many transactions in progress even when most of the individual transactions are of moderate size.
There are two possible outcomes when a remote procedure call throws TransactionTooLargeException.
Either the client was unable to send its request to the service (most likely if the arguments were
too large to fit in the transaction buffer), or the service was unable to send its response back to
the client (most likely if the return value was too large to fit in the transaction buffer). It is
not possible to tell which of these outcomes actually occurred. The client should assume that a
partial failure occurred.
The key to avoiding TransactionTooLargeException is to keep all transactions relatively small.
Try to minimize the amount of memory needed to create a Parcel for the arguments and the return
value of the remote procedure call. Avoid transferring huge arrays of strings or large bitmaps.
If possible, try to break up big requests into smaller pieces.
If you are implementing a service, it may help to impose size or complexity contraints on the
queries that clients can perform. For example, if the result set could become large, then don't
allow the client to request more than a few records at a time. Alternately, instead of returning
all of the available data all at once, return the essential information first and make the client
ask for additional information later as needed.
简单来说,我们上面提到了 Parcel
机制使用了一个共享内存,这个共享内存就叫 Binder transaction buffer
,这块内存有一个大小限制,目前是 1MB
,而且共用的,当超过了这个大小就会报错。
也就是说不仅仅是一次性传递大数据会出问题,当同时传递很多数据,尽管每个都不超过1MB
,但是总大小超过 1MB
也会出错
4. 解决方法
- 限制传递数据量
- 改变数据传输方式(参见Activity之间传递数据的方式)
- 静态static
- 单例
- Application
- 持久化
- 使用容器的单例模式
参考链接
- TransactionTooLargeException 官网详细解读
- Activity之间使用intent传递大量数据带来问题总结
- Android中Intent/Bundle的通信原理及大小限制(Parcelable原理及与Serializable的区别
- Activity间通过Intent传递数据的大小限制 – 具体数据
- Android remote method data limit
- https://developer.android.com/reference/android/os/TransactionTooLargeException
- Android中Intent/Bundle的通信原理及大小限制(Parcelable原理及与Serializable的区别)
- https://stackoverflow.com/questions/12819617/issue-passing-large-data-to-second-activity