Android string concat выдаёт ошибку памяти - PullRequest
0 голосов
/ 31 января 2012

Я работаю над проектом, где я загружаю пакеты данных через Интернет и анализирую их в своем приложении. Каждый пакет имеет свою собственную структуру, и в начале я получаю только текстовые данные. Но в какой-то момент я начинаю получать двоичные пакеты с изображениями и тому подобным материалом, а иногда и на некоторых устройствах, когда двоичный файл слишком большой, я получаю эту ошибку. На самом деле это никогда не происходит во время тестирования, но я получаю некоторые отчеты от пользователей. Вот собственно, как я это делаю:

чтение ответа:

InputStream response = new BufferedInputStream(connection.getInputStream());

        int bytesRead = -1;
        byte[] buffer = new byte[30 * 1024];
        while ((bytesRead = response.read(buffer)) > 0 && stopThread) {
            byte[] buffer2 = new byte[bytesRead];
            System.arraycopy(buffer, 0, buffer2, 0, bytesRead);
            handleDataFromSync(buffer2);
        }

и синтаксический анализ данных следующим образом:

public void handleDataFromSync(byte[] buffer) {
    RPCPacket packet;
    String responseBody;

    while(!stopThread) return;
    try {
        responseBody = new String(buffer, "UTF-8");
        StringBuilder tmp = new StringBuilder(responseBody);
        totalBytesReceived += responseBody.length();

        if (tmpBuffer != null) {
            tmpBuffer = tmpBuffer.append(tmp);
            tmp = tmpBuffer;
        }
        int bufferLen = tmp.length();
        int lastLoc = 0;
        boolean gotPacket;
        boolean gotField;
        String thisPart = "";

        try {
            do {
                gotPacket = false;
                gotField = true;
                int needsSize = packetFieldSizes[tmpCurrentField - 1];
                if (tmpCurrentField == packetFieldSizes.length) {
                    needsSize = payloadSize;
                }
                if (needsSize > bufferLen - lastLoc) {
                    gotField = false;
                    String proba = tmp.substring(lastLoc);
                    tmpBuffer =  new StringBuilder(proba);
                    break;
                }
                thisPart = tmp.substring(lastLoc, lastLoc + needsSize);
                lastLoc += needsSize;
                if (gotField) {

                    switch (tmpCurrentField) {

                    case 1: {
                        long intVal = Long.parseLong(thisPart);
                        objectIdentificator = (int) intVal;
                        break;
                    }
                    case 2: {
                        long intVal = Long.parseLong(thisPart);
                        if (intVal == 0) {
                            isBad = true;
                            break;
                        }
                        pType = (short) intVal;
                        break;
                    }
                    case 3: {
                        long intVal = Long.parseLong(thisPart);
                        if (intVal == 0) {
                            isBad = true;
                            break;
                        }
                        operationType = (short) intVal;
                        break;
                    }
                    case 4: {
                        objectOId = thisPart;
                        break;
                    }
                    case 5: {
                        long intVal = Long.parseLong(thisPart);
                        if (intVal == 0) {
                            isBad = true;
                            break;
                        }
                        id = (int) intVal;
                        break;
                    }
                    case 6: {
                        long intVal = Long.parseLong(thisPart);
                        payloadSize = (int) intVal;
                        dataSize = (int) intVal;
                        break;
                    }
                    case 7: {
                        hashH = thisPart;
                        break;
                    }
                    case 8: {
                        long intVal = Long.parseLong(thisPart);
                        if (intVal == 0) {
                            isBad = true;
                            break;
                        }
                        dataType = (short) intVal;
                        break;
                    }
                    case 9: {
                        if (payloadSize != 0) {
                            byte[] tmpData = Base64.decode(thisPart);
                            first = tmpData;
                        }
                        break;
                    }
                    }

                    if (tmpCurrentField >= packetFieldSizes.length)
                        gotPacket = true;

                    if (gotPacket) {
                        Log.d("", "Gotpacket!");
                        packet = new RPCPacket(objectIdentificator,
                                RPCPacketType.getPacketTypeByValue(pType),
                                RPCOperationType.getByValue(operationType),
                                objectOId, id, dataSize, hashH,
                                RPCPacketDataType.getByValue(dataType),
                                first);
                        parseRPCPacket(packet);

                        myProgress++;
                        update();
                        Log.e("","myProgress : "+myProgress);
                        Log.e("","TOTAL PACKETS : "+RPCCommunicator.totalPackets);

                        // release temp fields
                        objectIdentificator = 0;
                        pType = 0;
                        operationType = 0;

                        objectOId = null;

                        id = 0;
                        dataSize = 0;

                        hashH = null;

                        dataType = 0;

                        first = null;

                        tmpCurrentField = 1;
                        payloadSize = 0;

                    } else {
                        tmpCurrentField++;
                    }
                }

                // you baad bad buffer
                assert (lastLoc <= bufferLen);

                if (isBad)
                    break;

            } while (true);

        } catch (IOException e) {
            e.printStackTrace();
            RPCCommunicator.writeLogs(e.toString(), "Synchronization" ,"handleDataFromSync");

        } finally {
            thisPart = null;
            tmp = null;
        }
    } catch (Exception e) {
        e.printStackTrace();
        RPCCommunicator.writeLogs(e.toString(), "Synchronization","handleDataFromSync");
    }

}

поэтому в этой строке выдается ошибка: tmpBuffer = tmpBuffer.concat(tmp); только если изображение слишком велико и несколько раз объединяет строку, чтобы получить весь пакет. Я читаю ответ в 30 КБ, но я могу получать изображения с 300, 400 КБ и т. Д.

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

Заранее спасибо!

Ответы [ 2 ]

3 голосов
/ 31 января 2012

Точно так же, как сказал Дженс, просто используйте StringBuilder (http://developer.android.com/reference/java/lang/StringBuilder.html) или StringBuffer (http://developer.android.com/reference/java/lang/StringBuffer.html)), если вам нужны синхронизированные вызовы из разных потоков.

В вашем текущем коде каждый развы присоединяете что-то к String, создается новый объект, поэтому могут возникнуть проблемы с памятью. При использовании StringBuilder используется только один объект.

1 голос
/ 31 января 2012

Итак, для начала, то, как вы читаете свой поток, выделяет слишком много памяти, особенно эту часть:

byte[] buffer2 = new byte[bytesRead];
System.arraycopy(buffer, 0, buffer2, 0, bytesRead);
handleDataFromSync(buffer2);

Подумайте о пересмотре API с handleDataFromSync(byte[] buffer) на handleDataFromSync(byte[] buffer, int start, int count) и делайте так, когда читаете ваш поток:

while ((bytesRead = response.read(buffer)) > 0 && stopThread) {
    handleDataFromSync(buffer, 0, bytesRead);
}

Вы можете создать строку из этой строки, используя new String(buffer, offset, count, "UTF-8") в handleDataFromSync (простой сброс байтов в строку таким образом не гарантирует получение правильно декодированной строки, если вы включите любой символ , который будет использовать более одного октета в UTF-8, например, ÅÄÖ или другой мусор).

Обработка строк в вашем разборе слишком расплывчата, чтобы судить - вы измерили / напечатали, сколько tmpBuffer растет при разборе, например?

В вашем случае я бы подумал об использовании InputStreamReader и попытался бы вообще пересмотреть синтаксический анализ - есть ли какая-то причина, по которой вы должны читать куски по 30 КБ?

( FYI , assert(..) практически отключен в Android, отметьте и сгенерируйте исключение, если вы хотите защититься от чего-либо).

...