37

I need some help with debugging my application. First of all: In emulator and on some other devices my app is running fine. On my device I got a force close (without a force close message).

The "crash" happens if the Activity of the app is changed.

Here is some code of the MainActivity class. It just reads html content from a web page over webview. And no, it is NOT possible to do this over HttpRequest because I was not able to simulate the post request.

public class MainActivity extends Activity {

    public final static String EXTRA_HTML = "com.example.com.test.HTML";

    private WebView mWebView;
    private ProgressDialog mDialog;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);  
        mWebView = (WebView) findViewById(R.id.webView1);
        CookieSyncManager.createInstance(this);
        CookieManager cookieManager = CookieManager.getInstance();
        cookieManager.removeAllCookie();
        mWebView.setBackgroundColor(0);
        mWebView.setWebChromeClient(new WebChromeClient() {
            public boolean onConsoleMessage(ConsoleMessage cmsg) {
                if (cmsg.message().startsWith("MAGIC")) {
                    mDialog.cancel();
                    /*HashMap<String, String> message = new HashMap<String, String>();*/
                    String msg = cmsg.message().substring(5);
                    Intent intent = new Intent(MainActivity.this,
                        ReadDataActivity.class);
                    /*message.put("message", msg);*/
                    /*intent.putExtra(EXTRA_HTML, message);*/
                                    intent.putExtra(EXTRA_HTML, msg);
                    startActivity(intent);
                }
                return false;
            }
        });
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setPluginState(PluginState.OFF);
        mWebView.getSettings().setLoadsImagesAutomatically(false);
        mWebView.getSettings().setBlockNetworkImage(true);
        mWebView.getSettings().setAppCacheEnabled(true);
        mWebView.getSettings().setSavePassword(true);
        mWebView.getSettings()
                .setCacheMode(WebSettings.LOAD_NORMAL);
        mWebView.setWebViewClient(new WebViewClient() {

            public void onPageFinished(WebView view, String address) {
                if (address.indexOf("mySession") != -1) {
                    view.loadUrl("javascript:console.log('MAGIC'+document.getElementsByTagName('html')[0].innerHTML);");
                }
});

                mWebView.loadUrl("http://www.myurl.de");

}

So, in the onConsoleMessage() method I just pass the html code to another Activity class which read, parse and display the content.

The problem is now that at this point when the ReadDataActivity class should be loaded the application just close and go back to the home screen without any message or user dialog.

Is it possible that the html code which is passed as a string to the ReadDataActivity is to big? I also try to add the html code as a string in a HashMap but the problem is the same.

Some ideas what I can do to debug the problem? Maybe I should try to create a Parcelable object?

In the emulator everything is working fine.

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
sk2212
  • 1,688
  • 4
  • 23
  • 43
  • 2
    Don't use intent / parcels for big data. Save it to a file and send a link to the file. – zapl Sep 19 '12 at 14:30
  • 1
    Does this answer your question? [Intent.putExtras size limit?](https://stackoverflow.com/questions/9384619/intent-putextras-size-limit) – Andrey Petrov Jun 05 '20 at 07:05
  • if uri arraylist exist in large size that exceed the limit of intent then what is the solution? – Abdur Rehman Jul 15 '20 at 13:06

9 Answers9

68

As per my experience (sometime ago), you are able to parcel up to 1MB of data in a Bundle for IPC. This limit can be reduced if a lot of transactions are happening at a given time. Further information here.

In order to overcome this issue, I would suggest you to save your content on a temp file and pass the path/URI of your temp file to your second activity. Then in your second activity, read the contents out from file, perform your desired operations and finally delete that file.

If you want, you may also incorporate Shared_Preferences for this task - if you think handling files is cumbersome.

waqaslam
  • 67,549
  • 16
  • 165
  • 178
  • Thanks, this worked for me. FYI I experienced this issue in Jelly Bean (Android 4.3) – Alex Oct 02 '13 at 00:17
  • 4
    If the data is transferred between Activities it would be too slow to write to a file. I don't think it is a good idea. Storing this big data in Application class and access everywhere would be better I think. In the photup application, Chris Banes stores PhotoUploadController which contains an ArrayList in Application class. https://github.com/chrisbanes/photup/blob/master/client/src/uk/co/senab/photup/PhotupApplication.java – tasomaniac Jun 10 '14 at 06:31
  • 1MB is not correct, the real amount is not even close to that number, please see my answer: http://stackoverflow.com/a/37054839/1052697 – Rolf ツ May 05 '16 at 15:49
  • @Rolfツ did you also put heap size under consideration? what was heap size setting for the device you performed tests? – waqaslam May 05 '16 at 17:50
  • I did not as it shouldn't matter, the VM heap size has to my knowledge nothing to do with system level (native) serialization. – Rolf ツ May 05 '16 at 18:25
  • @Rolfツ I'm not really convinced on your findings as I believe I had sent somewhere around 970KB through Intent at the time of writing this answer. Perhaps It could be device dependent. – waqaslam May 05 '16 at 20:17
  • I actually tested on multiple devices and Android versions, it can't be coincidence that on all those devices the value is equal or almost equal. But I think you should just try it yourself ;) – Rolf ツ May 05 '16 at 20:20
  • @waqaslam Because of your doubts I did some further testing mostly on physical devices (e.g. OnePlus One, Huawei P1, Nexus 7) and they all report the same results. I also tested API 22, you can find the updates in my blog post: https://www.neotechsoftware.com/blog/android-intent-size-limit – Rolf ツ May 05 '16 at 21:49
20

I did some research on the maximum amount of data you can transfer using an Intent. And it seems that the limit is nowhere near 1MB or 90KB, it's more like 500KB (tested on API 10, 16, 19 and 23).

I wrote a blog post about this topic, you can find it here: http://web.archive.org/web/20200217153215/http://neotechsoftware.com/blog/android-intent-size-limit

Rolf ツ
  • 8,611
  • 6
  • 47
  • 72
  • did you also put heap size under consideration? what was heap size setting for the device you performed tests? – waqaslam May 05 '16 at 17:50
  • 1
    I did not as it shouldn't matter, the VM heap size has to my knowledge nothing to do with system level (native) serialization. – Rolf ツ May 05 '16 at 18:26
  • Yes, `largeHeap=true` doesn't help here. Also, can confirm, that with 585KB data string I reached over the `.putExtra` limit. – Starwave Nov 16 '21 at 13:16
4

The size limit of Intent is still pretty low in Jelly Bean, which is somewhat lower than 1MB (around 90K), so you should always be cautious about your data length, even if your application targets only latest Android versions.

NullNoname
  • 2,056
  • 1
  • 15
  • 12
4

The fixed size of 1MB is not only limited to intents. As Intents, Content Providers, Messenger, all system services like Telephone, Vibrator etc. utilize IPC infrastructure provider by Binder. Moreover the activity lifecycle callbacks also use this infrastructure.

1MB is the overall limit on all the binder transactions executed in the system at a particular moment.

In case there are lot of transactions happening when the intent is sent,it may fail even though extra data is not large.
http://codetheory.in/an-overview-of-android-binder-framework/

Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Shinoo Goyal
  • 601
  • 8
  • 10
4

I have seen that by writing and reading from a file consists of less performance . Then I have seen this solution : . So I am using this solution :

public class ExtendedDataHolder {

    private static ExtendedDataHolder ourInstance = new ExtendedDataHolder();

    private final Map<String, Object> extras = new HashMap<>();

    private ExtendedDataHolder() {
    }

    public static ExtendedDataHolder getInstance() {
        return ourInstance;
    }

    public void putExtra(String name, Object object) {
        extras.put(name, object);
    }

    public Object getExtra(String name) {
        return extras.get(name);
    }

    public boolean hasExtra(String name) {
        return extras.containsKey(name);
    }

    public void clear() {
        extras.clear();
    }

}

Then in MainActivity I have called it like the following :

ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");

startActivity(new Intent(MainActivity.this, DetailActivity.class));

and in DetailActivity

ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
    String other = (String) extras.getExtra("other");
}
Christopher Marlowe
  • 2,098
  • 6
  • 38
  • 68
  • 1
    1)if you use it with multiple Activities, it may lead to data corruption, in case same keys are used across them. 2) It doesn't work b/w two applications (IPC). 3) Data will not be retained, app kill cases, when low memory occured. – chakrapani May 26 '20 at 15:40
2

A little late to the game, but I just ran up against the same issue. Writing the data to file didn't really make sense performance-wise in my case, but I came across this in my search for answers:

http://developer.android.com/guide/faq/framework.html#3

Using a singleton is better for me as there's no need for disk IO. Better performance if the data doesn't need to be persisted.

Here's an example implementation:

public class DataResult {

    private static DataResult instance;
    private List<SomeObject> data = null;

    protected DataResult() {

    }

    public static DataResult getInstance() { 
        if (instance == null) {
            instance = new DataResult();
        }
        return instance;
    }

    public List<SomeObject> getData() { return data; }
    public void setData(List<SomeObject> data) { this.data = data; }
}

Then you can set using this in one activity:

DataResult.getInstance().setData(data);

And get it in the other activity like this:

List<SomeObject> data = DataResult.getInstance().getData();
monkeybuffer
  • 450
  • 5
  • 10
  • 2
    This is not going to work across processes, e.g. when sending an Intent to a Service. – zyamys Jan 23 '16 at 04:51
  • 2
    And it won't work when your app is reclaimed for memory (ie, goes into background/not-visible state) and is killed. When your app is recreated, the static values will not be populated. – DustinB Apr 25 '17 at 19:41
2

The Binder transaction buffer has a limited fixed size - 1Mb. But the problem is that buffer shared by all transactions in progress for the process.

So try to keep your intent's data as small as possible every time.

Anatolii Shuba
  • 4,614
  • 1
  • 16
  • 17
2

The use of static String variable is good. If there is a need for the user to go back & forth between different pieces of HTML, you can also use LruCache like this:

    static LruCache<String, String> mMemoryCache;
    final int kiloByte = 1024;
    .
    .
    final int maxMemoryKB = (int) (Runtime.getRuntime().maxMemory() / kiloByte);
    // then choose how much you want to allocate for cache
    final int cacheSizeKB = maxMemoryKB / 8;
    .
    .
    mMemoryCache = new LruCache<String, String>(cacheSizeKB) {
        //@Override
        protected int sizeOf(String key, String value) {   
            try {
                byte[] bytesUtf8 = value.getBytes("UTF-8");
                return bytesUtf8.length / kiloByte;
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return -1;
        }
    };
    .
    .
    String cacheKey = generateUniqueString(key);
    if (mMemoryCache.get(key) == null) {
        mMemoryCache.put(cacheKey, yourContent);
    }

    Intent intent = new Intent(getApplicationContext(), ReadDataActivity.class);
    intent.putExtra(EXTRA_HTML, cacheKey);
    startActivity(intent);

Then on the ReadDataActivity side

Intent intent = getIntent();
String cacheKey = intent.getStringExtra(EXTRA_HTML);
String contentString = MainActivity.mMemoryCache.get(cacheKey);
doSomethingWith(contentString);

This idea came from here.

Community
  • 1
  • 1
rockhammer
  • 957
  • 2
  • 13
  • 38
1

An alternative solution for passing large data between activities is to use a static field. In your case add this line to ReadDataActivity class

public static String msg;

Then you can use the static field msg within MainActivity class as follows

ReadDataActivity.msg = cmsg.message().substring(5); 

And finally start your activity without extra put

Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);                  
startActivity(intent);
Zakaria
  • 11
  • 2
  • 1
    Please add your answer to this question _directly_ in your question. You can use links as reference material, but just linking to a post is not an answer. – Nander Speerstra Jul 25 '16 at 09:25