2

I'm trying to second an array of 1000000 ints from one Activity to another. It works fine with smaller numbers, but when I try 1000000, startActivity does nothing, and causes this to show in logcat:

E/JavaBinder(2239): !!! FAILED BINDER TRANSACTION !!!

Why?

Here's some code demonstrating the issue:

MainActivity.java

package com.example.a;
import android.os.Bundle;
import android.view.View;
import android.app.Activity;
import android.content.Intent;
public class MainActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
  }
  public void startSecond(View v) {
    startActivity(new Intent(this, SecondActivity.class).putExtra(
        "a", new int[1000000]));
  }
}

SecondActivity.java

package com.example.a;
import android.os.Bundle;
import android.app.Activity;
public class SecondActivity extends Activity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
  }
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:layout_marginLeft="56dp"
        android:layout_marginTop="31dp"
        android:onClick="startSecond"
        android:text="click clack" />
</RelativeLayout>

activity_second.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="17" />
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.example.a.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name="com.example.a.SecondActivity"></activity>
    </application>
</manifest>
Dog
  • 7,707
  • 8
  • 40
  • 74
  • 1
    http://stackoverflow.com/questions/3528735/failed-binder-transaction .. specifically the answer that tells you the size is limited. – Brian Roach Aug 14 '13 at 23:28
  • how does this question have -1 and that other one has +26? – Dog Aug 14 '13 at 23:33
  • 4
    I'm going to guess that because 3 years ago it wasn't a question that had already been answered on SO and therefore wasn't the number one result from google if you search for that error ... – Brian Roach Aug 14 '13 at 23:38
  • 2
    If you really need to do something like that use a singleton pattern with a static field to share such a large amount of data. Alternatively you can store the data in a file, a sqlite database or a persistence storage and share the URI between the activities. – type-a1pha Aug 14 '13 at 23:46
  • @BrianRoach: That question is incomprehensible; The only way you can tell is that it's related to this is that he had one of the same symptoms (log message). Now that I read CommonsWare's answer, I know that Binder (whatever that is) is somehow related to `Intent`, and that Binder has a 1MB limit. The top answer of that question doesn't help because it's talking about `Bundle`, not `Intent`. Sorry for not magically stumbling upon that question and guessing that it answers mine. Notice how that page doesn't mention the word `Intent` once? I'm trying hard not to straight up call you an idiot. – Dog Aug 14 '13 at 23:48
  • I don't think calling people names is an appropriate way to get help here. In any case, you don't want to copy an array of 1 million `int`s as that is a huge waste of memory. Just put it in a `static` variable and share it that way (see type-a1pha's comment). – David Wasser Aug 15 '13 at 13:10

1 Answers1

7

Why?

Because you are limited to 1MB per Binder transaction, and Binder underlies the Intent system. The size of your Intent, including all extras, needs to be under 1MB.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • 2
    And an array of 1,000,000 ints is well over 1MB. – McAden Aug 14 '13 at 23:27
  • Just to make this a more concise answer, how much MB is an `Array` of 1,000,000 `ints`? – tolgap Aug 14 '13 at 23:31
  • 1
    @tolgap: 1 int = 4 byte, so +- 3.8MB. Give or take a few bytes. That's just int allocation though, not including any references. My allocation knowledge isn't very extensive, but I believe there might be some overhead between memory addresses as well. All together you should look in the direction of 4MB. – Jeroen Vannevel Aug 14 '13 at 23:32
  • What's a "Binder transaction"? Why is the `Intent` size limited? Can I turn this off? – Dog Aug 14 '13 at 23:51
  • @Dog: Binder is an inter-process communication (IPC) mechanism used at the heart of Android. The `Intent` size is limited because device RAM is limited, and IPC tends to make several copies of transferred buffers. Also, the larger the buffers, the slower the IPC, due to CPU time in making those copies. You cannot "turn this off", except perhaps by building your own ROM mod and installing it on whatever devices you can. "The top answer of that question doesn't help because it's talking about Bundle, not Intent." -- `Intent` extras are implemented via a `Bundle`. – CommonsWare Aug 14 '13 at 23:56
  • "The Intent size is limited because device RAM is limited" I have 2GB RAM on my phone, not 1MB or N*1MB for some small N which you say is >1. I checked and both activities are running in the same process, so why would it even use IPC in the first place? Is there some other mechanism than `Intent`s to transfer my data? The last thing I tried was convert all my `Activity`s into `View`s, and have their parent view swap between different sub `View`s. But this isn't very straightforward. – Dog Aug 15 '13 at 00:07
  • I also tried using static variables to pass data between the parent `Activity` and its child, but this isn't general. For example, I ran into the issue that if you accidentally press a button that creates a child `Activity` twice too fast, two of that `Activity` are spawned, and then they both share the same static variables. I gave up on that approach after hitting that road block, thinking I will hit much more issues such as this. – Dog Aug 15 '13 at 00:10
  • @Dog: "I have 2GB RAM on my phone" -- Android was designed around much smaller phones. "I checked and both activities are running in the same process, so why would it even use IPC in the first place?" -- all `startActivity()` calls go through a central OS process for dispatch. "Is there some other mechanism than Intents to transfer my data?" -- this was covered in the comments to your question, with the most typical solution being a static data member. "then they both share the same static variables" -- use a more flexible static data structure. – CommonsWare Aug 15 '13 at 00:12
  • I suppose I could make a map of tokens to parameters which would normally be in the `Intent`, then pass the token in an `Intent` destined to the child `Activity`. I'm not sure this is problem free though, for example the map would make it more likely to accidentally introduce memory leaks. Perhaps I shouldn't be using `Activity`s. All I want to do really is change the screen to show a different component when you press a certain button. Sorry if I'm bending the topic. – Dog Aug 15 '13 at 00:43
  • @Dog: "All I want to do really is change the screen to show a different component when you press a certain button" -- that wouldn't seem to involve 100,000 integers. To draw an analogy, would you be passing 100,000 integers in query parameters on a link to a new Web page? Assuming that 100,000 integers is sensible (and I'm not convinced of that), that should be treated as a central data model to be modified, not something that gets passed around from activity to activity. All that being said, modify the UI of the existing activity on the button press, such as running a `FragmentTransaction`. – CommonsWare Aug 15 '13 at 00:49
  • @CommonsWare: In my actual code, in the main activity theres multiple large graphs of data to select from. When you select one, it opens a different UI component which is an editor that edits the graph. It's not hard to exceed 1MB. Yep, the current approach I'm trying is to swap out `MainActivity`'s `View` with another (actually it's not that simple, I have them both all the time and set one to `GONE` and one to `VISIBLE`). The only remaining problem is that I need to manually save all the state of the sub `View` to survive rotating the screen etc. – Dog Aug 15 '13 at 01:06
  • @Dog: "theres multiple large graphs of data to select from" -- which means that this should be a central data model that your UI can access. Presumably, that data model's official location is some persistent store (database, "the cloud", etc.), perhaps with a static data member serving as a cache. – CommonsWare Aug 15 '13 at 11:43