I was testing my app on Android Nougat, and it crashed when I try to move from one Activity to another. I saw this in the log: java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 700848 bytes
.
Actually, the platform has been printing warning log about this for a while, but let's be honest, who has time to read all the logs? Nougat (API 24) throws TransactionTooLargeException
s as RuntimeException
s, so we cannot ignore that any more.
onSaveInstanceState
Turns out I was saving a big list of search results during onSaveInstanceState
to persist them over rotation. Time to move that to a Loader!
But what is the limit?
But now I'm curious: What is the limit? I wrote a sample app to find out.
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent(this, AnotherActivity.class); startActivity(intent); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); int length = 1000; Uri uri = getIntent().getData(); if (uri != null) { try { length = Integer.parseInt(uri.getLastPathSegment()); } catch (NumberFormatException e) { } } byte[] data = new byte[length]; outState.putByteArray("data", data); } }
To try different sizes, I start the app like this:
adb shell am start \ -a android.intent.action.View \ -n com.sqisland.tutorial.transcation_too_large/.MainActivity \ -d 700000
This launches MainActivity
, which immediately goes to AnotherActivity
. When that happens, the system calls onSaveInstanceState
, which tries to stash away a byte array of the length specified in the adb command, retrieved by getIntent().getData()
. This way, I can try different numbers without recompiling and redeploying the app.
I did a binary search on a Nougat emulator and my Nexus 9. The limit is slightly different, but it hovers around 500000. That is not a small number, but not too hard to exceed if you try to store data rather than state.
Inline coding questions will not be answsered. Instead, ask on StackOverflow and put the link in the comment.
If I recall correctly, that limit in a bundle is global (shared across all apps), it will depend on other apps running at the same time (let's say, a service in background using bundles too). I need to dig through Android source again.
ReplyDeleteIt is more complicated than a fixed size limit. Each process has a fixed buffer for receiving incoming transactions, whose size is 1MB. So technically you could say the maximum size is 1MB, but that assumes there are no other IPCs happening. In practice, you should never have a size anywhere close to that, or you run the risk of occasionally failing when there are a number of other concurrent IPCs in progress.
ReplyDeleteFor the specific case of saved state, you also want to keep this small because whatever you provide here is data the system needs to hold on to in the system process for as long as the user can ever navigate back to that activity (even if the activity's process is killed).
So I would recommend keeping the saved state well less than 100K, and really less than 50K, and in most cases much less than even that.
You're sending a List object through an intent right? I have a crazy feeling that if you wrap that List in a POJO and send that object instead it would all magically work again :)
ReplyDeleteI came up to this problem of yours a while ago and wrote a post about it - http://nemanjakovacevic.net/blog/english/2015/03/24/yet-another-post-on-serializable-vs-parcelable/
Interesting blog post, Nemanja. I ran into this same issue in an app I'm working on with lots of nested objects and lists of objects. I converted all the objects to implement Serializable and it magically works again. Additionally, there wasn't a noticeable speed change either. Like you, I'm definitely rethinking Parcelable vs Serializable.
ReplyDelete