Intent putExtra方法的最大长度是多少?(强制关闭)

37

我需要帮助调试我的应用程序。首先:在模拟器和一些其他设备上,我的应用程序运行良好。但在我的设备上,却出现了一个强制关闭(没有强制关闭消息)。

如果应用程序的活动被更改,就会发生这种“崩溃”。

下面是MainActivity类的一些代码。它只是通过Webview从网页读取HTML内容。不过,不可能使用HttpRequest来实现这一点,因为我无法模拟POST请求。

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");

}
所以,在 onConsoleMessage() 方法中,我只是将 HTML 代码传递给另一个 Activity 类,该类读取、解析并显示内容。

问题在于此时应该加载 ReadDataActivity 类的时候,应用程序会直接关闭并返回到主屏幕,而没有任何消息或用户对话框。

可能传递给 ReadDataActivity 的 HTML 代码太大了吗?我还尝试将 HTML 代码作为字符串添加到 HashMap 中,但问题仍然存在。

有什么想法可以调试这个问题吗?也许我应该尝试创建一个 Parcelable 对象?

在模拟器中一切都正常工作。


2
不要使用意图/包裹来处理大数据。将其保存到文件中并发送文件链接。 - zapl
1
这个回答解决了你的问题吗?Intent.putExtras大小限制? - Andrey Petrov
如果URI ArrayList存在大于Intent限制的大尺寸,则解决方案是什么? - Abdur Rehman
9个回答

68

根据我的经验(一段时间前),您可以在Bundle中传递多达1MB的数据进行IPC。如果在给定时间发生了大量事务,则此限制可能会降低。更多信息请参见此处

为了解决这个问题,我建议您将内容保存在临时文件中,并将临时文件的路径/URI传递给第二个活动。然后在第二个活动中,从文件中读取内容,执行所需操作,最后删除该文件。

如果您认为处理文件很麻烦,也可以将Shared_Preferences用于此任务。


谢谢,这对我有用。顺便说一下,我在果冻豆(Android 4.3)中遇到了这个问题。 - Alex
4
如果在Activity之间传输数据,将写入文件太慢。我认为这不是一个好主意。把这些大量的数据存储在Application类中,并且随时访问会更好。在photup应用程序中,Chris Banes将包含ArrayList的PhotoUploadController存储在Application类中。https://github.com/chrisbanes/photup/blob/master/client/src/uk/co/senab/photup/PhotupApplication.java - tasomaniac
1MB 不正确,实际数量远远不止这个数字,请查看我的回答:https://dev59.com/S2cs5IYBdhLWcg3w5H9a#37054839 - Rolf ツ
我没有这样做,因为这似乎无关紧要,据我所知,虚拟机堆大小与系统级别的(本地)序列化无关。 - Rolf ツ
我实际上在多个设备和 Android 版本上进行了测试,所有这些设备上的值相等或几乎相等,这不可能是巧合。但我认为你应该自己尝试一下 ;) - Rolf ツ
显示剩余3条评论

20

你是否也考虑了堆大小?你测试的设备的堆大小设置是多少? - waqaslam
1
我并没有这样做,因为这应该无关紧要,据我所知,虚拟机堆大小与系统级别(本地)序列化无关。 - Rolf ツ
是的,largeHeap=true 在这里没有帮助。另外,我可以确认,当数据字符串达到585KB时,我超过了.putExtra的限制。 - Starwave

4

我发现通过读写文件的方式会降低性能。然后我看到了这个解决方案:。所以我正在使用这个解决方案:

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();
    }

}

然后在MainActivity中,我按照以下方式调用它:
ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
extras.putExtra("extra", new byte[1024 * 1024]);
extras.putExtra("other", "hello world");

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

并且在DetailActivity中

ExtendedDataHolder extras = ExtendedDataHolder.getInstance();
if (extras.hasExtra("other")) {
    String other = (String) extras.getExtra("other");
}

1
  1. 如果您在多个活动中使用它,可能会导致数据损坏,如果它们之间使用相同的键。
  2. 它不适用于两个应用程序(IPC)之间。
  3. 在应用程序被杀死或低内存发生时,数据将不会被保留。
- chakrapani

4

Jelly Bean中,Intent的大小限制仍然相当低,略低于1MB(约90K),因此即使您的应用程序仅针对最新的Android版本,您也应始终注意数据长度。


4

1MB的固定大小不仅限于意图。 与意图一样,内容提供程序、Messenger以及所有系统服务(如电话、振动器等)都使用Binder提供的IPC基础结构。 而且,活动生命周期回调也使用此基础结构。

1MB是系统中在特定时刻执行的所有binder事务的总限制。

如果在发送意图时发生了大量事务,即使额外数据不大,也可能会失败。
http://codetheory.in/an-overview-of-android-binder-framework/


2
有点晚了,但我刚遇到了同样的问题。在我的情况下,将数据写入文件在性能上并不是很合理,但我在寻找答案时遇到了这个:

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

使用单例对我来说更好,因为没有必要进行磁盘 IO。如果数据不需要持久化,性能更好。

以下是一个示例实现:

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; }
}

然后您可以在一个活动中设置使用这个。
DataResult.getInstance().setData(data);

在另一个活动中获取它的方法如下:
List<SomeObject> data = DataResult.getInstance().getData();

3
这种方法在进程间传递时是行不通的,例如当向服务发送意图时。 - zyamys
3
当您的应用程序被回收以释放内存(即进入后台/不可见状态)并被终止时,它将无法工作。当您的应用程序重新创建时,静态值将不会被填充。 - DustinB

2

使用 static String 变量是不错的选择。如果用户需要在不同的 HTML 片段之间前后切换,您还可以像这样使用 LruCache

    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);

接下来,在ReadDataActivity方面

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

这个想法来自这里

2
Binder事务缓冲区有一个固定的有限大小-1Mb。 但问题在于,正在进行的进程中所有事务共享缓冲区。 因此,请尽可能使您的意图数据保持尽可能小。

1
一种传递大量数据的替代方案是使用静态字段。在您的情况下,将此行添加到ReadDataActivity类中。
public static String msg;

然后,您可以在MainActivity类中使用静态字段msg,如下所示。
ReadDataActivity.msg = cmsg.message().substring(5); 

最后,不需要额外的输入,直接启动您的活动。
Intent intent = new Intent(MainActivity.this, ReadDataActivity.class);                  
startActivity(intent);

1
请直接在问题中添加您的答案。您可以使用链接作为参考材料,但仅链接到帖子不是答案。 - Nander Speerstra

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接