Android巨大的JSONObject toString会出现OutOfMemoryError

时间:2016-06-28 10:23:51

标签: android json sqlite out-of-memory tostring

我需要从SQLite数据库中读取大量数据并创建格式正确的JSON。

我目前正在通过一个名为RequestPayload的对象实现这一目标,该对象包含一些ArrayList,其中我将从SQLite中读取的数据放入其中。

RequestPayload类有parseJson()方法返回JSONObject,我最终调用toString()方法获取最终写在文件上的JSON字符串

问题

现在,当我在SQLite中获得“少量”数据时,一切都很顺利。当我获得大量数据时,会发生什么:

06-28 09:55:34.121 10857-6799/it.example.sampler E/AndroidRuntime: FATAL EXCEPTION: Thread-195240
    Process: it.example.sampler, PID: 10857
    Theme: themes:{com.cyanogenmod.trebuchet=overlay:system, com.android.settings=overlay:system, default=overlay:system, iconPack:system, fontPkg:system, com.android.systemui=overlay:system, com.android.systemui.navbar=overlay:system}
    java.lang.OutOfMemoryError: Failed to allocate a 52962812 byte allocation with 16764752 free bytes and 41MB until OOM
      at java.lang.StringFactory.newStringFromChars(Native Method)
      at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:629)
      at java.lang.StringBuilder.toString(StringBuilder.java:663)
      at org.json.JSONStringer.toString(JSONStringer.java:430)
      at org.json.JSONObject.toString(JSONObject.java:690)
      at it.example.sampler.controllers.network.RequestBodyEncoder.serialise(RequestBodyEncoder.java:69)
      at it.example.sampler.controllers.network.RequestBodyEncoder.createPacket(RequestBodyEncoder.java:50)
      at it.example.sampler.services.FileStoreRunnable.run(FileStoreRunnable.java:57)
      at java.lang.Thread.run(Thread.java:818)

06-28 09:55:34.509 10857-10857/it.example.sampler E/WindowManager: android.view.WindowLeaked: Activity it.example.sampler.ui.StartSamplingActivity has leaked window com.android.internal.policy.PhoneWindow$DecorView{74710d V.E...... R......D 0,0-1026,494} that was originally added here
    at android.view.ViewRootImpl.<init>(ViewRootImpl.java:372)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:299)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:86)
    at android.app.Dialog.show(Dialog.java:319)
    at it.example.sampler.ui.StartSamplingActivity.executeStop(StartSamplingActivity.java:305)
    at it.example.sampler.ui.StartSamplingActivity.onClickedButton(StartSamplingActivity.java:256)
    at it.example.sampler.ui.StartSamplingActivity$3.onClick(StartSamplingActivity.java:190)
    at android.view.View.performClick(View.java:5204)
    at android.view.View$PerformClick.run(View.java:21158)
    at android.os.Handler.handleCallback(Handler.java:739)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:148)
    at android.app.ActivityThread.main(ActivityThread.java:5461)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)

守则

错误行来自此方法:

private String serialise() throws JSONException {
    // Serialise
    String sampleString = mPayload.parseJSON().toString(); // THIS LINE

    Logger.get().d(LOG_TAG, "Serialised payload: -> " + sampleString);
    return sampleString;
}

如果我正确地获得了LogCat,那么错误就是在执行toString()方法期间,看起来JSON对象太大了。

我该如何处理这种情况?

更新 回答@YashJain评论询问JSONObject的大小。

抽样后,持续2个小时,我有一个JSON包含:

  • 12 JSONArray包含155.432 float s
  • 2 JSONArray包含8.393 float s
  • 1 JSONArray包含8.393自定义对象,每个对象包含5 float个(和一个字符串)
  • 1 包含155.432 int s
  • 的二维数组

就字节而言,因为Float是由4个字节组成的(我希望我已经正确地完成了微积分)我已经大概了:

(12 * 155432 * 4) + (2 * 8393 * 4) + (1 * 8393 * 5) + (1 * 155432 * 4) ~= 8.191.573 bytes

更新 我被问及使用过的压缩算法。我通过ZLIBDeflater Java类使用DeflaterOutputStream

因此流程是:

  • 样本数据 - &gt;将它们存储在SQLite
  • SQLite读取 - &gt;在内存中构建RequestPayload对象
  • RequestPayload对象转换为JSONObject(使用自定义解析器)。
  • JSONObject转换为字符串(使用JSONObject toString()方法)
  • 压缩字符串字节(使用getBytes()) - &gt;在Base64中编码
  • 将最终的Base64字符串发送到服务器

修改 为了解决“可能的重复标志”,我在研究过程中没有看到这个问题,因为它使用了largeHeap指令(我没有使用)。也没有接受的答案,唯一的答案实际上并没有提供实际的解决方案。

3 个答案:

答案 0 :(得分:1)

很抱歉没有提供更多反馈,我一直忙于其他工作,这在我的优先队列中排在最后。

无论如何,实际上对我有用的是使用Google GSON库。

我的序列化方法变得简单:

private String serialise() {
    // Serialise
    return mPayload.toStringFromGSON();
}

班级toStringFromGSON()中的方法Payload是:

public String toStringFromGSON() {
    GsonBuilder gsonBuilder = new GsonBuilder();
    Gson gson = gsonBuilder.create();

    return gson.toJson(this);
}

无论如何,我认为这只能算是临时补丁。事实上,我注意到应用程序使用的内存在此阶段会急剧增加,因此我认为这个问题只是推迟了,并且最终会在负载较重的情况下发生..

答案 1 :(得分:0)

你的Qn非常好。 您是否尝试过AsyncTask或许它可以通过Thread处理。

private String serialise() throws JSONException {
    String sampleString = "";

    new AsyncTask<Void, Void, Void>() {
        @Override
        protected Void doInBackground( Void... voids ) {
            // Serialise
            sampleString = mPayload.parseJSON().toString(); // THIS LINE.
            return null;
        }
    }.execute();

    Logger.get().d(LOG_TAG, "Serialised payload: -> " + sampleString);
    return sampleString;
}

我认为你有很好的知识。 对不起,如果它可能不起作用。但它确实在我的情况下。请检查一下。

答案 2 :(得分:0)

你会遇到巨大的Jsons转换问题,当转换为String时,系统会将整个内存量分配给一个唯一的变量,因此你会有内存溢出。

处理这种情况的最好方法是做一些Json Streaming,它将部分读取Json并解析他。

我认为重新发明轮子是没有意义的,所以我建议使用Json解析器库,比如Gson或Jackson,他们会自动在POJO中转换你的json,你可以将它们存储在SQLite数据库中,你可以像ORMLite一样使用一些数据库辅助。

这样,您可以收到一个巨大的JSON,直接将他解析为POJO对象,并将POJO直接插入数据库,将内存管理留给库。

在这个site中,您可以解析JSON并以GSON或Jackson格式检索整个类,之后,您只需要添加ORMLite注释并创建数据库助手,它真的是真的容易。