Подтвердить что ты не робот

Android: конвертировать поток в строку без исчерпания памяти

У меня есть клиент Android, который взаимодействует с сервером через конечные точки REST-ful и JSON. Из-за этого у меня есть необходимость получить полный ответ сервера перед преобразованием его в хэш. У меня есть этот код на месте, чтобы сделать это (где-то в Интернете):

private static String convertStreamToString(InputStream is) {

    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
    StringBuilder sb = new StringBuilder();

    String line = null;
    try {
        while ((line = reader.readLine()) != null) {
            sb.append(line + "\n");
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    return sb.toString();
}

Код работает по большей части, однако я вижу сообщения об авариях в поле от клиентов с исключением OutOfMemory в строке:

    while ((line = reader.readLine()) != null) {

Полная трассировка стека:

java.lang.RuntimeException: An error occured while executing doInBackground()
    at android.os.AsyncTask$3.done(AsyncTask.java:200)
    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
    at java.lang.Thread.run(Thread.java:1102)
Caused by: java.lang.OutOfMemoryError
    at java.lang.String.(String.java:468)
    at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:659)
    at java.lang.StringBuilder.toString(StringBuilder.java:664)
    at java.io.BufferedReader.readLine(BufferedReader.java:448)
    at com.appspot.myapp.util.RestClient.convertStreamToString(RestClient.java:303)
    at com.appspot.myapp.util.RestClient.executeRequest(RestClient.java:281)
    at com.appspot.myapp.util.RestClient.Execute(RestClient.java:178)
    at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1178)
    at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1)
    at android.os.AsyncTask$2.call(AsyncTask.java:185)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
    ... 4 more

Мой вопрос: есть ли способ решить эту проблему, кроме отправки меньших фрагментов данных с сервера?

Спасибо!

4b9b3361

Ответ 1

В общем случае ответ отрицательный, но вы можете точно настроить условия, которые вызывают нехватку памяти. В частности, если вы отправляете длину строки перед вашим потоком, вы сможете создать StringBuilder с правильным размером массива внутри него. Массивы не могут быть изменены после создания, поэтому, если у вас не хватает емкости массива в StringBuilder, реализация должна выделять новый массив (обычно в два раза больше, чтобы избежать слишком большого количества изменений), а затем копировать содержимое старого массива. Рассмотрим поток размером X, чтобы изменить размер StringBuilder, который только что оказался пропускной способностью X-1, вам нужно почти X * 3 объема памяти. Размер StringBuilder, который позволяет избежать изменения размеров, позволит вам сжать большие потоки в память.

Еще одна вещь, которую вы можете захотеть - настроить объем памяти, доступный для вашего серверного процесса. Используйте переключатель типа -Xmx1024m при запуске процесса сервера.

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

Ответ 2

Android имеет ограничения на максимальный объем памяти, который вы можете выделить для своего приложения. Вы могли бы подумать над чтением потока на лету, а не сохранить все это в String, если ответ очень велик. Но вам следует подумать о лучшей практике.

Вы должны хранить данные в базе данных sqlite или в обычном файле. Не лучше делать то, что вы делаете, поскольку пользователь может нажать кнопку "домой" или получить телефонный звонок, когда вы находитесь в середине сохранения ответа. Лучше использовать базу данных, чтобы вы могли вернуться в состояние, в котором вы были прерваны. Тогда вам также не нужно беспокоиться о нехватке памяти.

Вы смотрели этот разговор о лучших методах общения с службами REST с Android? http://www.youtube.com/watch?v=xHXn3Kg2IQE?8m50s (8:50 и 11:20). Настоятельно рекомендуется очистить передовые методы и почему не следует извлекать данные REST без использования базы данных.

Вкратце, рассмотрите возможность сохранения базы данных sqlite или файла. Если данные очень велики, вы, возможно, подумаете об их сжатии, прежде чем хранить его.

Ответ 3

Существуют различные способы решения этих проблем. Одним из способов было бы использовать хеш-функцию, которая не требует, чтобы весь поток находился в памяти, т.е. Вы кормили его символом или блоком символов за раз. Другим является уменьшение размера ответа.

Если вы не можете этого сделать, и вам нужен весь поток, я бы избегал использования readLine() и просто вызывал read() в буферизованном потоке ввода и добавлял символ, который вы получаете от чтения, в построитель строк. Это уменьшит количество строк, которые вы создаете, и отбросьте довольно резко. (Простая оптимизация вышеприведенного кода заключается в том, чтобы вывести новую строку в вызове append() - вы также создаете другую строку без необходимости.) Кроме того, если у вас есть представление о том, как долго будет получена строка, вы также может установить начальную емкость строкового построителя при построении, чтобы вы сразу узнали, если вы потеряете память.

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

Ответ 4

Возможно, этот код помогает избежать ошибок StringBuilder и вне памяти:

private String convertStreamToString(InputStream is) {
    ByteArrayOutputStream oas = new ByteArrayOutputStream();
    copyStream(is, oas);
    String t = oas.toString();
    try {
        oas.close();
        oas = null;
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return t;
}

private void copyStream(InputStream is, OutputStream os)
{
    final int buffer_size = 1024;
    try
    {
        byte[] bytes=new byte[buffer_size];
        for(;;)
        {
          int count=is.read(bytes, 0, buffer_size);
          if(count==-1)
              break;
          os.write(bytes, 0, count);
        }
    }
    catch(Exception ex){}
}

Ответ 5

Вы пробовали встроенный метод для преобразования потока в строку? Это часть библиотеки Apache Commons (org.apache.commons.io.IOUtils).

Тогда ваш код будет такой:

String total = IOUtils.toString(inputStream);

Документацию по этому вопросу можно найти здесь: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Библиотека IO Apache Commons можно скачать здесь: http://commons.apache.org/io/download_io.cgi