通过ByteArrayOutputStream通过流GSON获取JSON字符串将获得OutOfMemoryError

时间:2019-01-09 11:38:13

标签: android crash gson out-of-memory outputstream

这是我的代码:

@Nullable
private static String streamBlaModelsIntoJsonString(List<BlaModel> blaModels) {
    try {
        Gson gson = new Gson();
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        JsonWriter writer = new JsonWriter(new OutputStreamWriter(out, "UTF-8"));
        writer.setIndent("  ");
        writer.beginArray();
        for (BlaModel blaModel : blaModels) {
            gson.toJson(blaModel, BlaModel.class, writer);
        }
        writer.endArray();
        writer.close();

        return out.toString("UTF-8");
    } catch (IOException e) {
        e.printStackTrace();
    }

    return null;
}

这是Fabric的崩溃报告:

Fatal Exception: java.lang.OutOfMemoryError: Failed to allocate a 1219838 byte allocation with 265632 free bytes and 255KB until OOM
   at java.lang.StringFactory.newStringFromBytes(StringFactory.java:176)
   at java.lang.StringFactory.newStringFromBytes(StringFactory.java:59)
   at java.io.ByteArrayOutputStream.toString(ByteArrayOutputStream.java:232)
   at com.example.magnificentapp.util.SharedPrefsUtils.streamBlaModelsIntoJsonString(SharedPrefsUtils.java:230)
   at com.example.magnificentapp.util.SharedPrefsUtils.saveBlaList(SharedPrefsUtils.java:207)
   at com.example.magnificentapp.BlaListActivity.goToDetail(BlaListActivity.java:462)
   at com.example.magnificentapp.presentation.view.activity.BlaListActivity.onItemClick(BlaListActivity.java:605)
   at com.example.magnificentapp.adapter.viewholder.BlaViewHolder$2.onClick(BlaViewHolder.kt:34)
   at android.view.View.performClick(View.java:6261)
   at android.view.View$PerformClick.run(View.java:23748)
   at android.os.Handler.handleCallback(Handler.java:751)
   at android.os.Handler.dispatchMessage(Handler.java:95)
   at android.os.Looper.loop(Looper.java:154)
   at android.app.ActivityThread.main(ActivityThread.java:6776)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)

崩溃发生在行return out.toString("UTF-8");上。编写此代码是为了解决应用程序中的OutOfMemory崩溃问题,但与以前相比,我得到的错误仍然更多。

我试图一个又一个地流式传输BlaModels并附加一个StringBuilder,但这似乎消耗了更多的内存。

我无法重现崩溃,但是此崩溃报告是我的Fabric中所有崩溃中的旗舰。

我的代码需要进一步提高,但是我正在尽力而为。有人要加油。

致谢。

2 个答案:

答案 0 :(得分:0)

您需要关闭 ByteArrayOutputStream ,并在完成 out 实例

后尝试此操作
if (out != null) {
        try {
            out.close();
        } catch (Exception ex) {

        }
    }

答案 1 :(得分:0)

我绝对不是Android专家,但是明显的例外是告诉您它不能处理大字符串。 即使对于台式机/企业Java,处理大字符串也是有问题的,如果在没有充分理由的情况下使用,通常会认为它是反模式。 我猜测,您至少有两种处理方式:

  • 进行真正的流传输并将真实的JSON写到其他地方,而不缓冲到中间字符串中;
  • 进行某种惰性映射,并分别存储每个列表元素(我偷看了堆栈跟踪,发现SharedPrefsUtils最有可能指示使用键值存储)。

如果您对第一种方法满意,并希望写入存储,那么一切都非常简单:

// Gson instances can be reused and you can save time and memory just having it as a static field
private static final Gson gson = new Gson();
// Let speed it up by caching the reference to the type adapter
private static final TypeAdapter<BlaModel> blaModelTypeAdapter = gson.getAdapter(BlaModel.class);

private static void stream(final Iterable<? extends BlaModel> blaModels, final Writer writer) {
    @SuppressWarnings("resource")
    final JsonWriter jsonWriter = new JsonWriter(writer);
    jsonWriter.beginArray();
    for ( final BlaModel blaModel : blaModels ) {
        blaModelTypeAdapter.write(jsonWriter, blaModel);
    }
    jsonWriter.endArray();
}

如果仍然必须使用字符串(据我所知,共享首选项编辑器无法使用流),则可以懒惰地将列表元素一一转换,然后让调用者代码确定何时和做什么。 我不确定它是否可以在一个事务中保存大量数据,但是如果不能,则可能(再次,我不是Android专家)可能希望更频繁地提交首选项或编写JSON文档存储到存储中,然后将路径作为值(一种符号链接)。

private static Iterable<String> mapBlaModels(final Iterable<BlaModel> blaModels) {
    return new Iterable<String>() {
        @Override
        public Iterator<String> iterator() {
            return new Iterator<String>() {
                private final Iterator<? extends BlaModel> iterator = blaModels.iterator();

                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                @Override
                public String next() {
                    final BlaModel blaModel = iterator.next();
                    return blaModelTypeAdapter.toJson(blaModel);
                }

                @Override
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    };
}

或者,如果您使用的是Google Guava:

private static Iterable<String> mapBlaModels(final Iterable<? extends BlaModel> blaModels) {
    // Or even Iterables.transform(blaModels, blaModelTypeAdapter::toJson); when using RetroLambda
    return Iterables.transform(blaModels, new Function<BlaModel, String>() {
        @Override
        public String apply(@Nullable final BlaModel blaModel) {
            return blaModelTypeAdapter.toJson(blaModel);
        }
    });
}

然后

final Iterable<BlaModel> blaModels = ImmutableList.of(new BlaModel(), new BlaModel(), new BlaModel());
final Iterable<String> jsonDocuments = mapBlaModels(blaModels);
final Iterator<String> jsonDocumentIterator = jsonDocuments.iterator();
for ( int i = 0; jsonDocumentIterator.hasNext(); i++ ) {
    final String jsonDocument = jsonDocumentIterator.next();
    // TODO manage the document index and the JSON document
    // Say, something like putString("blaModel:" + i, jsonDocument), etc
}

这需要付出更多的努力才能对共享的首选项结构进行一些重大更改,但这将节省内存,并且不会遇到由中间字符串引起的内存不足问题。

希望这会有所帮助。