Moosphan/Android-Daily-Interview

2019-09-20:Intent传输数据的大小有限制吗?如何解决?

Moosphan opened this issue · 10 comments

2019-09-20:Intent传输数据的大小有限制吗?如何解决?

有限制,1M左右,不同机型大小不同,要问怎么解决,那就是不用(* ̄rǒ ̄)

chsmy commented

Intent传输数据的大小受Binder的限制,上限是1M。不过这个1M并不是安全的上限,Binder可能在处理别的工作,安全上限是多少这个在不同的机型上也不一样。

传 512K 以下的数据的数据可以正常传递。
传 512K~1024K 的数据有可能会出错,闪退。
传 1M以上的数据会报错:TransactionTooLargeException
考虑到 Intent 还包括要启动的 Activity 等信息,实际可以传的数据略小于 512K

解决办法

  1. 减少传输数据量
  2. Intent通过绑定一个Bundle来传输,这个可以超过1M,不过也不能过大
  3. 通过内存共享,使用静态变量或者使用EventBus等类似的通信工具
  4. 通过文件共享

有,限制大约是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 中也会带有其他相关的必要信息,所以实际使用中比这个数字要小很多。

解决方式:

  1. 降低传递数据的大小,或考虑其他方式,见2;
  2. IPC: 将大数据缓存到文件,或者存入数据库,或者图片使用 id 等;使用 Socket;
  3. 非 IPC:可以考虑共享内存,EventBus 等

flying-pigeon 匿名内存

gs666 commented

平时我们在 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的方式,利用共享内存