public class FragmentTestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment_test);
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.constraint_layout);
if (fragment == null) {
Bundle bundle = new Bundle();
byte[] imgBytes = new byte[1024 * 1024 * 20];
bundle.putByteArray("img_bytes", imgBytes);
fragment = new SimpleFragment();
fragment.setArguments(bundle);
fm.beginTransaction().add(R.id.constraint_layout, fragment).commitNow();
}
}
}
大家可以看下上述代码,通过bundle传递了20M到fragment。
问题为:
- 上述代码该Activity启动时会崩溃吗?
- 点击home,切换到桌面时会崩溃吗?
- 如果上述发生崩溃,为什么,尝试从源码中进行分析;
- 除了携带较大的数据,还有携带什么样的数据会发生崩溃?
更多问答 >>
-
2020-06-09 23:17
-
每日一问 | 曾经的记忆中“onSaveInstanceState 会在系统意外杀死 Activity 时调用”,正确吗?
2020-07-12 23:49 -
每日一问 | Android P 上,需要配置 network_security_config ,才能抓包,正确吗?
2020-06-29 21:26 -
2020-07-08 23:05
-
每日一问 | RecyclerView的多级缓存机制,每级缓存到底起到什么样的作用?
2020-07-19 23:56 -
2020-06-07 20:55
-
每日一问 在 Java 支持了 switch(字符串)之后,只是语法糖而已?
2020-06-01 00:55 -
2020-05-25 23:08
-
每日一问 | 我们常说的dalvik虚拟机是基于寄存器的,而jvm是基于栈,到底指的是什么?
2020-05-20 21:29 -
2020-05-21 01:15
测试题目给出的代码,会发现:
这个Activity是能正常启动的,但是当它变得不可见时(打开了其他Activity或按下HOME键),就抛TransactionTooLargeException了:从堆栈信息中可以看出,异常的源头在PendingTransactionActions内部类StopInfo的
run
方法里面,它里面的代码是这样的:嗯,可以断定这个TransactionTooLargeException就是在调用ActivityTaskManagerService的
在上一个问题中,我们了解到:当Activity变得不可见时(activityStopped
方法时抛出的,这就对应了上一个问题:Fragment 是如何被存储与恢复的?onSaveInstanceState
和onStop
方法回调后),在应用进程这边会通过ActivityTaskManagerService的activityStopped
方法(Binder通讯),把刚刚在onSaveInstanceState
中保存了数据的Bundle对象(这个Bundle里面包含了已添加到该Activity的Fragment的所有信息(FragmentState),当然也包括Arguments),传到系统服务进程那边:(继续用一下这张图):activityStopped
方法也里没看到相关逻辑啊。堆栈信息的后半段:可以看出真正抛出异常的地方是在BinderProxy的
链接:android_util_Binder.cpptransactNative
里面,但这个是native方法,AS跟踪不到,只能打开网站看了:
这个android_os_BinderProxy_transact
方法的倒数第二行,调用了signalExceptionForError
方法,并将err
(error code)传了进去:emmm,如果error code是FAILED_TRANSACTION的话,并且(
还有,决定抛出TransactionTooLargeException的重要因素是canThrowRemoteException
在上个方法中写死了是true)parcelSize
(parcelSize
就是在java层传进来的Parcel对象的大小)大于200 * 1024(byte)
(即200k)的话,就会抛出TransactionTooLargeException了。error code = *FAILED_TRANSACTION*
,而从那段FIXME注释中可以知道,error code
为FAILED_TRANSACTION会有多种原因(不一定是数据量太大),比如事务格式不对、引用已关闭的文件描述符等。error code
是在刚刚的android_os_BinderProxy_transact
中调用transact
方法获取的:err
的值都是取waitForResponse
方法的返回值,也就是说,只要waitForResponse
方法返回值是FAILED_TRANSACTION,并且传输的Parcel size > 200k就会抛异常了,那现在来看下waitForResponse
方法:signalExceptionForError
方法中的那个FIXME提到的吗?那在什么情况下,cmd
的值会是BR_FAILED_REPLY呢?Binder驱动源码:binder.c(打开后页内查找BR_FAILED_REPLY就有了)
不过这一部分的代码超长,400多行(不愧是驱动),里面一共有26个case是会返回BR_FAILED_REPLY(也就是抛TransactionTooLargeException的原因有26个。。。)的,我试着把这26个原因解释一下(不保证准确无误。。。):t
的内存失败;tcomplete
的内存失败;除此还有两个拷贝数据失败的case。
结合题目给出的代码,猜测最有可能就是以上这5种情况之一。至于题目第四点,“除了传输数据量大的Bundle,还有哪种情况也会出这个错?”
这个具体场景还没找到。睡觉了。真·大佬
大佬,我这边测试代码: Bundle bundle = new Bundle(); byte[] data = new byte[1010 * 1024 ...查看更多
大佬,我这边测试代码: Bundle bundle = new Bundle(); byte[] data = new byte[1010 * 1024]; bundle.putByteArray("data", data); 没有出现崩溃,超过1010*1024开始崩溃,感觉跟源码里的200*1024不一样啊, 同样试了启动Activity用Intent传数组,超过500*1024就开始崩,对这个跨进程传递数据大小限制有点蒙了。。 (测试机是华为Mate10pro,系统10.0)
TransactionTooLargeException的文档注释里面有介绍到,Binder的transaction buffer大小限制在了1M(1024*1024)(源码地址:http://and ...查看更多
TransactionTooLargeException的文档注释里面有介绍到,Binder的transaction buffer大小限制在了1M(1024*1024)(源码地址:http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/java/android/os/TransactionTooLargeException.java)
卧槽!!我刚刚测试了startActivity,传了520*1024,居然连log都没有,app也马上死掉了!!直接回到了桌面!!!等我!
大概知道怎么回事了,直接原因也是在startActivity过程中发生了TransactionTooLargeException,也就是startActivity失败了,第一次失败,ActivityS ...查看更多
大概知道怎么回事了,直接原因也是在startActivity过程中发生了TransactionTooLargeException,也就是startActivity失败了,第一次失败,ActivityStackSupervisor会尝试重新启动进程,进行第二次尝试,但如果第二次同样失败了,就会强行把进程kill掉(总不可能无休止的重试吧)。大概就是这样,等我总结一下!(不过我也是蒙了,为什么activityStopped的时候,传520k就没事,startActivity就有事。。。。)
辛苦小缘大佬啦~~
哈哈哈哈哈,我好像知道为什么了
启动崩溃是为什么哪?
昨天 @lkycumt@126.com 同学发现了两个问题:
代码逻辑跟题目的一样,byte数组长度改为1010*1024,按下HOME键后无异常,但是,当长度改成1020*1024时,就崩溃了,这是为什么呢?TransactionTooLargeException这个类的文档注释有说明:当前Binder事务的缓冲区大小只有1Mb,并且是进程内共享的。当Activity的onSaveInstanceState
方法回调时,(不手动put任何数据)除了会保存我们自己添加的Fragment状态,还有一个lifecycle的ReportFragment,和View的一些临时状态(比如Toolbar的话,它会保存menu是否打开,打开了哪个menu等),这当然也会占用一部分内存,Fragmentarguments
存1010k没事,1020k却死了,说明除此arguments
之外,其他(put到outState
里的)东西占用的内存 > 4 bytes < 14 bytes。嗯,既然说限制为1Mb,那为什么android_util_Binder.cpp的源码里是判断
能走到判断parcelSize
> 200k 就抛TransactionTooLargeException了呢?parcelSize
大小这里,有一个前提是:IBinder的transact
方法必须返回FAILED_TRANSACTION(返回FAILED_TRANSACTION的26个原因我们昨天分析过),如果能成功开辟内存并拷贝数据,就算parcelSize
超过了200k,也不会走到这个分支,也就是不会报错了。不过,至于为什么是200k,不是300不是100不是500,这。。这个,我也不知道。。。行,那意思就是,只要确保在同一时间内,传输的数据总量不超过1Mb就平安无事了对吧?
从现在的情况来看,是的。Activity Stop后会通过Binder把
那为什么,同样是长度为600*1024的byte数组,我在savedState
传到系统服务进程对吧?startActivity
也是要通过Binder把Intent传到AMS那边没错吧?onSaveInstanceState
时,将它put进outState
中就没事,而当我把它扔进Intent的extra
里面,并作为startActivity
的参数传进去的时候,进程就暴毙了呢?这数据都没到1Mb啊!甚至连crash dialog都没有弹,也看不到任何有用的log还有,接着上一个场景,为什么当我传1024*1024的byte数组,就能在logcat里面看到对应的异常堆栈信息,而且手机上也show了那个crash dialog了呢? 同样是调用startActivity
方法啊!!!不知道大家在编写AIDL文件的时候,有没有用到过
在对应(自动生成)的Proxy类中会看到,用oneway
关键字,用这个关键字修饰的方法,在调用时,是不会阻塞的,也就是变成了异步执行。如果用它来修饰接口,那这个接口里面的所有方法,都会变成异步执行(当然了,前提是方法不能有返回值,必须为void)。oneway
修饰的方法,在调用IBinder的transact
方法时,最后一个参数flags
传的是IBinder.FLAG_ONEWAY,而不是0,看图:这是我们的AIDL文件:oneway
修饰。自动生成的Proxy类的实现:testOneway
方法里面,调用transact
方法时最后一个参数传的是IBinder.FLAG_ONEWAY,而testNormal
传的是0,跟刚刚说的一致。我为什么会突然说这些看似毫不相干的东西呢?
因为我看到了一篇文章: Binder通讯能传输多大的数据文章上面也分析了Binder驱动源码,发现:在Binder驱动中,除了会记录buffer_size
(Binder事务缓冲区剩余空间),还会记录一个free_async_space
(异步事务(ONEWAY)的剩余空间):free_async_space
的初始大小就是buffer_size
的一半。再看回之前的那个binder_transaction
方法:!reply
很明显就是 "不用回复" 的意思了,说的就是方法返回值为void。flags & TF_ONE_WAY
,就是检查flags
是否加入了TF_ONE_WAY这个标识。现在来想一下:在AIDL接口中,如果方法用oneway
修饰的话,这里肯定会是true了。来看下这个binder_alloc_buf
方法做了什么:binder_transaction
方法就会返回BR_FAILED_REPLY
了!那么最后会在android_util_Binder.cpp中抛出TransactionTooLargeException!!!这说明了什么??!!!说明了在调用Binder的transact
方法时,如果最后一个参数传的是FLAG_ONEWAY(AIDL中用oneway修饰),那么本次传输的数据大小限制,将会比传0(普通方式,不用oneway修饰)小1/2!!!如果普通方式最大限制为1Mb的话,那ONEWAY方式超过512k就会报TransactionTooLargeException了!!!!现在看回上面的问题,为什么同样大小的数据,调用不同的Binder方法,一个会报错一个不会呢?那我们现在就只需求证startActivity
和activityStopped
方法有没有用oneway修饰就知道了:startActivity
还是activityStopped
,都能成功传输过去的。有同学可能会有疑问:既然startActivity
能传1Mb,那为什么上面的例子传600k会死掉?别急,看过SDK28源码的同学会知道,28之后,AMS在startActivity
时,会通过ApplicationThread的scheduleTransaction
方法将目标Activity的相关信息传回到应用进程这边,随后就会调用大家熟知的ActivityThread.handleLaunchActivity
方法。还没看过的同学也不要紧,请关注 Fragment 是如何被存储与恢复的? 这个问题,后续会更新关于数据恢复(关系到startActivity)的细节分析。那现在我们重点看IApplicationThread的scheduleTransaction
了:oneway
!真相大白了:在onSaveInstanceState
方法中能put将近(为什么用将近,因为其他地方还会加一些数据进去,也会一部分占用空间)1Mb的数据,是因为activityStopped
没有用oneway修饰。而startActivity
传将近1Mb的数据会死掉是因为startActivity
的后半段(将目标Activity信息传回来)调用的ApplicationThread.scheduleTransaction
是oneway方法,所以能传输的最大值只有普通方法的一半,也就是512k。那低版本的呢?低版本不会有
SDK26(8.0)之前,ActivityManager和ApplicationThread的父类都不是AIDL生成的,而是手动实现的Binder。与scheduleTransaction
方法啊,那它们会怎样?scheduleTransaction
方法对应的就是scheduleLaunchActivity
,这个scheduleLaunchActivity
的内部实现,在调用IBinder的transact
方法时,最后一个参数flags
传的也是IBinder.FLAG_ONEWAY(对应oneway关键字)。篇幅原因就不把每个版本的代码贴出来了,用一个表来总结吧:可以看到,除了android10,其他版本的
activityStopped
都是oneway的,也就是onSaveInstanceState
中不能put > 512k的数据。好,接着下一个问题:"为什么传600*1024的byte数组,app死掉了也没有log 而传1024*1024就有"
有同学可能已经知道了,在android10上,startActivity
时能成功把数据传到系统服务进程AMS中,而在AMS那边却不能传回应用进程这边(因为scheduleTransaction是oneway),所以报错的进程是系统服务进程而不是我们自己的应用进程!!把Logcat窗口的Filters选项改成No Filters,再次让它崩溃,就能看到对应log了:为什么传1024*1024就能看到log?"
哈哈哈,相信同学们都已经知道了,就是因为在应用进程这边就已经报错了啊,都没成功传过去呢。大佬太拼了,咱白天搞也行~O(∩_∩)O
更新啦
一个字,牛!!
膜拜大佬
我是弟弟
大佬~ 牛
看了小缘大佬的分析,对着源码捋了一遍,还是有所收获的:
【分析源码版本是28】 分析应用切到手机桌面或者打开其他页面会通过 ActivityStack 做的几件事情:我activity里面是一个fragment,fragment里面是webview;我在activity的onSaveInstanceState 只简单的保存了一个对象,对象里面只有几个简单的字符串,但是上线后发现,遇见了 android.os.TransactionTooLargeException:data parcel size 20978084 bytes 这个异常,用户每次切后台时候,这个对象保存,就遇见了个这个问题,我这边也无法复现,请问onSaveInstanceState我只存了我保存的字符串,数据量都特别小,是什么导致了这个异常。
onSaveInstanceState方法回调的时候,不仅你的Activity会收到,对应的Fragment,还有View也会收到,可以检查下这些可疑的类。还有,发生TransactionTooLar ...查看更多
onSaveInstanceState方法回调的时候,不仅你的Activity会收到,对应的Fragment,还有View也会收到,可以检查下这些可疑的类。还有,发生TransactionTooLargeException,不一定就是onSaveInstanceState时候出的错,还可能发生在startActivity,startService等所有会通过Binder传输数据的场景,可以逐一排查下。
为啥我试了没有闪退,只有一条打印啊。。
JavaBinder: !!! FAILED BINDER TRANSACTION !!!注意看进程号,是不是已经挂了
如果targetSdkVersion < 24,是不会抛异常的
进程还在,target是29
噢,知道了,SDK22的代码,catch了异常,但是没有往外throw,所以不会崩溃,只有native层的那个log。看这里[android_util_Binder.cpp](http://andro ...查看更多
噢,知道了,SDK22的代码,catch了异常,但是没有往外throw,所以不会崩溃,只有native层的那个log。看这里[android_util_Binder.cpp](http://androidxref.com/5.1.0_r1/xref/frameworks/base/core/jni/android_util_Binder.cpp#683) 第683行。还有这里:[ActivityThread.java](http://androidxref.com/5.1.0_r1/xref/frameworks/base/core/java/android/app/ActivityThread.java#3296) 第3296行
既然高版本系统判断了 N以下不崩溃,那么N以下的系统大概率 默认肯定就是不崩溃的。 jiafeng的手机版本是22
Activity启动时不会崩溃,但点击home切换到桌面时会崩溃。
原因是切到桌面时调用了Activity的onSaveInstanceState()方法,进而FragmentManager对fragment的状态进行了保存,在这一步出了异常(parcel数据太大)期待大佬回答第一个问题为何不会崩溃以及第四个问题1不会崩溃是因为Activity向Fragment传递数据,不需要夸进程啊