2019-09-20:Intent传输数据的大小有限制吗?如何解决?
Moosphan opened this issue · 10 comments
有限制,1M左右,不同机型大小不同,要问怎么解决,那就是不用(* ̄rǒ ̄)
Intent传输数据的大小受Binder的限制,上限是1M。不过这个1M并不是安全的上限,Binder可能在处理别的工作,安全上限是多少这个在不同的机型上也不一样。
传 512K 以下的数据的数据可以正常传递。
传 512K~1024K 的数据有可能会出错,闪退。
传 1M以上的数据会报错:TransactionTooLargeException
考虑到 Intent 还包括要启动的 Activity 等信息,实际可以传的数据略小于 512K
解决办法
- 减少传输数据量
- Intent通过绑定一个Bundle来传输,这个可以超过1M,不过也不能过大
- 通过内存共享,使用静态变量或者使用EventBus等类似的通信工具
- 通过文件共享
有,限制大约是1M左右,这个限制是binder底层的限制,因为binder本来就没有设计成传输大量数据的
解决办法:别直接传,比如通过MemoryFile开辟一片共享内存,然后传递FileDescriptor,接收端用这个fd读
Intent 中的 Bundle 是使用 Binder 机制进行数据传送的, 数据会写到内核空间, Binder 缓冲区域;
Binder 的缓冲区是有大小限制的, 有些 ROM 是 1M, 有些 ROM 是 2M;
这个限制定义在 frameworks/native/libs/binder/processState.cpp 类中, 如果超过这个限制, 系统就会报错;
#define BINDER_VM_SIZE ((1*1024*1024) - (4096 *2)) ;
因为 Binder 本身就是为了进程间频繁-灵活的通信所设计的, 并不是为了拷贝大量数据;
如果非 ipc 就很简单了, static 变量, eventBus 之类的都可以;
如果是 ipc, 一定要一次性传大文件, 可以用 file 或者 socket;
我以前回答过类似的问题
参见 #19
有限制,一般是 1M,ROM厂商可以修改这个值,但是实际传输上要低于 1M ,因为 intent 一般还要承担其他责任,会占用一部分空间。
非 IPC
EventBus,静态变量都能解决。
IPC
使用文件,Sokect等手段
先说结论:
有大小限制
再说原因:
Intent 是消息传递对象,用于各组件间通信。各组件以及个程序间通信都用到了进程间通信。因此 Intent 的数据传递是基于 Binder 的,Intent 中的数据会存储在 Bundle 中,然后 IPC 过程中会将各个数据以 Parcel 的形式存储在 Binder 的事物缓冲区(Binder transaction buffer)进程传递,而 Binder 的事物缓冲区有个固定的大小,大小在 1M 附近。因为这 1M 大小是当前进程共享的,Intent 中也会带有其他相关的必要信息,所以实际使用中比这个数字要小很多。
解决方式:
- 降低传递数据的大小,或考虑其他方式,见2;
- IPC: 将大数据缓存到文件,或者存入数据库,或者图片使用 id 等;使用 Socket;
- 非 IPC:可以考虑共享内存,EventBus 等
flying-pigeon 匿名内存
平时我们在 Android 组件之间传递数据一般使用Intent都能解决,但是在传递的数据较大时(比如一个size>1000的列表),Intent就不能用了,如果非要用的话就会崩溃:TransactionTooLargeException。Intent 无法传递大数据是因为其内部使用了 Binder 通信机制,Binder 事务缓冲区限制了传递数据的大小。Binder 事务缓冲区的大小限定在 1MB,但是这个尺寸是共享的,也就是并不是传递 1MB 以下的数据就绝对安全,要视当前的环境而定。
不要挑战 Intent 传递数据大小的极限,对于大数据,例如长字符串、Bitmap 等,不要考虑 Intent 传递数据的方案。下面介绍几种解决方案。
使用单例
代码如下,比较简单,在此就不再多介绍了,只需要在传递时 set,在获取时 get。
public class MusicListHolder {
private ArrayList musicInfoList;
public ArrayList getMusicInfoList() {
return musicInfoList;
}
public void setMusicInfoList(ArrayList musicInfoList) {
this.musicInfoList = musicInfoList;
}
private static final MusicListHolder holder = new MusicListHolder();
public static MusicListHolder getInstance() {
return holder;
}
}
注意事项:这种方法不可用于多进程,因为不同的进程获取到的单例并非同一个单例,也就是获取不到数据。
使用EventBus
EventBus 是一个 Android 端优化的 Publish/subscribe 消息总线,简化了应用程序内各个组件间、组件与后台线程间的通信。在《阿里巴巴Android开发手册》中也有推荐:“Activity 间的数据通信,对于数据量比较大的,避免使用 Intent + Parcelable 的方式,可以考虑 EventBus 等替代方案,以免造成 TransactionTooLargeException。”。具体使用可参见:EventBus
使用 Application
将数据保存在Application中,用的时候取出来。这样整个应用都能够读写这个数据。使用很方便,在此就不多讲了。但是使用时是有一些问题的要注意的。
有时候因为内存不足等原因,我们的应用会被系统强制杀死,此时再次点击进入应用时,系统会直接进入被杀死前的那个界面。但是此时Application却是新创建的,我们也就无法拿到之前存取的数据,如果不加以判断,则会导致空对象的问题。
使用建议:
使用时一定要做好非空判断
如果数据为空,可以考虑逻辑上让应用直接返回到最初的Activity。
持久化数据
将数据保存在文件里。常见的手段有sqlite、shared preference、file等。
优点:
应用中所有地方都可以访问
不会轻易丢失
缺点:
操作麻烦
效率低下
读取易出错
Intent的上限是1M
传递521K的数据为正常
传递512K-1024K的数据可能会报错
传递大于1024K的数据就会报错
解决方法
减少传递数量
传递数据使用绑定Bundle 这个可以超过1M 但也不能太大
使用EventBus传递
数据保存 文件保存方式
通过putBinder的方式,利用共享内存