Получение строки JSON с потоковой передачей GSON с помощью ByteArrayOutputStream возвращает OutOfMemoryError - PullRequest
0 голосов
/ 09 января 2019

Это мой код:

@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, но, похоже, он потребляет больше памяти.

Я не могу воспроизвести сбой, но этот отчет о сбое является флагманом среди всех сбоев в моей фабрике.

Мне нужно больше улучшений для моего кода, но я делаю все возможное. Кто-то должен получить верх.

Привет.

Ответы [ 2 ]

0 голосов
/ 10 января 2019

Я определенно не эксперт по 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
}

Это потребует немного больше усилий для внесения некоторых серьезных изменений в структуру общих настроек, но это сэкономит память и не приведет к проблемам с нехваткой памяти, вызванным промежуточными строками.

Надеюсь, это поможет.

0 голосов
/ 09 января 2019

Вам нужно закрыть ByteArrayOutputStream попробуйте это после того, как вы сделали с out instance

if (out != null) {
        try {
            out.close();
        } catch (Exception ex) {

        }
    }
...