Android HttpClient OOM на 4G/LTE (HTC Thunderbolt)

Я получил несколько сообщений от пользователей о сбоях при попытке использовать мое приложение на 4G/LTE от Verizon.

Глядя на трассировку стека, похоже, что реализация Android HttpClient.execute() выдает OOM. Это происходит только на устройствах 4G/LTE, особенно на HTC Thunderbolt, и только на 4G/LTE. Wi-Fi, 3G, UMTS в порядке. Также отлично работает на Sprint WiMax 4G.

Два вопроса:

  • Как лучше всего привлечь к этому внимание разработчиков Android? Есть ли варианты получше, чем отчеты по http://code.google.com/p/android/issues?

  • Любые идеи о том, как я могу обойти это? У меня самого нет устройства 4G, и я не могу добиться этого в эмуляторе, поэтому мне нужно сделать некоторые обоснованные предположения. Я могу попытаться поймать OOM в своем коде и попытаться очистить и принудительно выполнить GC, но я не уверен, что это хорошая идея. Комментарии или другие предложения?

Вот что делает мой код:

    HttpParams params = this.getHttpParams(); // returns params
    ClientConnectionManager cm = new ThreadSafeClientConnManager(params, this.getHttpSchemeRegistry() );
    DefaultHttpClient httpClient = new DefaultHttpClient( cm, params );

    HttpResponse response = null;
    request = new HttpGet( url );

    try {

        response = httpClient.execute(request); // <-- OOM on 4G/LTE. OK otherwise
        int statusCode = response.getStatusLine().getStatusCode();
        Log.i("fetcher", "execute returned, http status " + statusCode );

    ...

Вот трассировка стека сбоя:

E/dalvikvm-heap(11639): Недостаточно памяти при выделении 2055696 байт. I/dalvikvm(11639): "Thread-16" prio=5 tid=9 RUNNABLE I/dalvikvm(11639): | group="main" sCount=0 dsCount=0 s=N obj=0x48563070 self=0x3c4340 I/dalvikvm(11639): | sysTid=11682 nice=0 sched=0/0 cgrp=дескриптор по умолчанию=3948760 I/dalvikvm(11639): | schedstat=( 208709711 74005130 214 )

I/dalvikvm(11639): в org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:~79) I/dalvikvm(11639): в org.apache.http.impl.io.SocketInputBuffer.( SocketInputBuffer.java:93) I/dalvikvm(11639): на org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83) I/dalvikvm(11639): на org.apache.http.impl.conn. DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170) I/dalvikvm(11639): в org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106) I/dalvikvm(11639): в org.apache.http. impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129) I/dalvikvm(11639): at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173) I/dalvikvm(11639): at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) I/dalvikvm(1163 9): в org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) I/dalvikvm(11639): в org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java: 348) I/dalvikvm(11639): на org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) I/dalvikvm(11639): на org.apache.http.impl.client.AbstractHttpClient. выполнить(AbstractHttpClient.java:487) I/dalvikvm(11639): в org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) I/dalvikvm(11639): в com.myapplication.Fetcher. trySourceFetch(Fetcher.java:205) I/dalvikvm(11639): at com.myapplication.Fetcher.run(Fetcher.java:298) I/dalvikvm(11639): at java.lang.Thread.run(Thread.java: 1102) I/dalvikvm(11639): E/dalvikvm(11639): Недостаточно памяти: Размер кучи = 24171 КБ, Выделено = 23142 КБ, Размер растрового изображения = 59 КБ, Предел = 21884 КБ E/dalvikvm(11639): Дополнительная информация: Размер = 24327 КБ , Разрешенный размер = 24519 КБ B, Trimmed = 348 КБ W/dalvikvm (11639): threadid = 9: поток завершается с необработанным исключением (группа = 0x40025b38)


person psychotik    schedule 18.03.2011    source источник
comment
Просто подтверждение того, что я отслеживаю эту же проблему. Появляется только на htc_mecha (thunderbolt) на verizon_wwe. Выпуск впервые появился 17 марта 2011 года.   -  person DougW    schedule 21.03.2011
comment
Я пошел и купил HTC Thunderbolt, чтобы диагностировать эту проблему. То, что CommonsWare говорит ниже, верно. Установка буфера вручную на 8 КБ устраняет сбои. Не знаю, почему HTC решила это изменить. Надеюсь, им понравится пополнение запасов телефонов count++.   -  person DougW    schedule 25.03.2011
comment
Круто, я тоже столкнулся с этой проблемой. Спасибо за подтверждение.   -  person Victor    schedule 25.03.2011
comment
это также происходит на устройстве LG P920   -  person petey    schedule 26.11.2012


Ответы (3)


Глядя на трассировку стека, похоже, что реализация Android HttpClient.execute() выдает OOM.

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

Как лучше всего привлечь к этому внимание разработчиков Android? Есть ли варианты получше, чем отчеты по http://code.google.com/p/android/issues?

Вероятность того, что это чистая ошибка Android, мала, но не равна нулю.

Вот некоторые другие возможности, в произвольном порядке:

  1. Нет проблем с execute() как таковым, но у вас просто не хватает памяти, а трассировка стека, с которой вы столкнулись, просто демонстрирует, что execute() нагружает вашу кучу.

  2. Проблема заключается в некоторых модификациях, которые HTC внесла в Android для Thunderbolt, которые, возможно, вступают в силу только в сети LTE.

  3. Проблема каким-то образом вызвана самой сетью Verizon LTE (например, некоторые их прокси-серверы отправляют обратно нелепую информацию, из-за которой у HttpClient возникают проблемы).

Любые идеи о том, как я могу обойти это?

Во-первых, я бы использовал существующие инструменты (например, сброс HPROF и проверку с помощью Eclipse MAT), чтобы убедиться, что у вас нет утечки памяти в целом, из-за которой комбинация Thunderbolt / LTE просто спотыкается.

Далее я рекомендую вам придумать какой-нибудь способ последовательного воспроизведения ошибки. Это может быть ваше существующее приложение с рядом шагов, которые нужно выполнить, или это может быть специальное приложение (например, зарегистрируйте URL-адрес, который запускает OOM, а затем создайте крошечное приложение, которое просто выполняет этот запрос HttpClient). Я бы хотел, чтобы в DeviceAnywhere был Thunderbolt, но это не похоже на него. Я прощупаю и посмотрю, смогу ли я получить помощь на этом фронте.

Что касается решения этой проблемы, в качестве временной меры вы можете определить, что вы используете Thunderbolt через данные android.os.Build и, возможно, что вы используете LTE через ConnectivityManager (я предполагаю, что LTE будет отображаться как WiMAX, но это просто предположение) и предупреждать пользователей о проблемах с этим комбо.

Кроме того, вы можете попробовать немного изменить использование HttpClient и посмотреть, повлияет ли это, например:

  • Если вы поддерживаете только API уровня 8 или выше, вы можете попробовать AndroidHttpClient в качестве замены.
  • Отключите многопоточный доступ (вообще или специфичный для Thunderbolt) и избавьтесь от ThreadSafeClientConnManager

Мне жаль, что у меня нет для вас "волшебного ответа".


ОБНОВЛЕНИЕ

Теперь, когда у меня есть полная трассировка стека, просмотр исходного кода... несколько проясняет ситуацию.

Проблема, похоже, в том, что:

HttpConnectionParams.getSocketBufferSize(params);

возвращает это значение размером 2 МБ или около того, которое запускает OOM. Это ужасно большой буфер, особенно для движка Dalvik GC, который может быть фрагментирован (да, это слово снова).

params вот HttpParams. Кажется, вы сами их создаете через getHttpParams(). Например, AndroidHttpClient устанавливает значение 8192:

HttpConnectionParams.setSocketBufferSize(params, 8192);

Если вы сами устанавливаете размер буфера сокета, попробуйте уменьшить его. Если нет, попробуйте установить его на 8192 и посмотрите, поможет ли это.

person CommonsWare    schedule 18.03.2011
comment
Ах, ошибка вырезания-вставки. Полный стек теперь там. Спасибо. Это хорошие предложения - я попробую кое-что из того, что вы рекомендовали, и опубликую обновление, если найду что-нибудь новое. - person psychotik; 19.03.2011
comment
Кстати, я думал о № 1, но из того, что я могу сказать в журналах, отправленных пользователями, они не делают ничего особенного в LTE, чего не произошло бы в 3G/WiFi. Таким образом, хотя есть изменение, что это связано с состоянием памяти, вызванным моим приложением, это кажется маловероятным, поскольку это происходит только при использовании LTE. - person psychotik; 19.03.2011
comment
@psychotik: я обновил ответ на основе дальнейших исследований, основанных на вашей измененной трассировке стека. - person CommonsWare; 19.03.2011
comment
@CommonsWare - спасибо, звучит многообещающе. Кстати, не могли бы вы указать мне, где вы нашли этот код. Я хочу покопаться и понять, почему я мог не видеть этого на других телефонах/типах подключения. Я предполагаю, что вы смотрите на стандартный исходный код Android (android.git.kernel.org) или где-нибудь еще? - person psychotik; 19.03.2011
comment
@psychotik: Кстати, не могли бы вы указать мне, где вы нашли этот код. -- используйте поиск кода Google (google.com/codesearch). Введите имя класса в строку поиска и android.git.kernel.org в поле «Пакет». Это отлично подходит для такого рода проблем. Хорошей новостью является то, что все номера строк совпадают с последними данными в репозитории, так что никаких догадок не было. Я просто начал с фактической точки краха и пошел в обратном направлении, пытаясь выяснить, откуда берется размер буфера. - person CommonsWare; 19.03.2011
comment
@psychotik: посмотрите, почему я, возможно, не видел этого на других телефонах / типах подключения - ну, это странно. Предполагая, что вы не устанавливаете его на 2055696, и поскольку я не вижу доказательств того, что обычно это 2055696, я могу предположить, что HTC каким-то образом устанавливает его по умолчанию на 2055696 через взломанную версию HttpConnectionParams. - person CommonsWare; 19.03.2011
comment
@CommonsWare выглядит так, как будто ваше предложение было правильным — возможно, HTC испортила эти значения по умолчанию. Мне было бы неприятно разобраться в этом без вашей помощи выше, так что еще раз спасибо. - person psychotik; 21.03.2011
comment
@psychotik Вы смогли проверить это на Thunderbolt? Удалось ли вам исправить это в вашем, или это помогло просто установить размер буфера? - person Eric Nordvik; 21.03.2011
comment
@psychotik @cant0na – В нашем приложении возникла та же проблема. Я пошел и купил HTC Thunderbolt для тестирования. Установка размера буфера вручную действительно решает проблему. - person DougW; 25.03.2011
comment
Была такая же проблема, подтвердил, что это исправлено. Спасибо! - person Darrell; 15.07.2011
comment
У меня HTC Thunderbolt. Я сделал этот вызов в коде, HttpConnectionParams.getSocketBufferSize(httpClient.getParams()), но он вернул -1, а не 2055696. Что происходит? - person Kai; 07.10.2011

вот исправление: https://review.source.android.com/22852

тем временем URLConnection невосприимчив. эта проблема есть только у HttpClient.

если вы разработчик, желающий протестировать такой сбой, вы можете использовать «adb shell setprop», чтобы установить, скажем, «net.tcp.buffersize.wifi», чтобы максимальные размеры буфера сокета для чтения/записи были огромными, когда ваш устройство подключено к Wi-Fi. что-то вроде следующего было бы настоящим стресс-тестом:

adb shell setprop net.tcp.buffersize.wifi 4096,80999999,80999999,4096,80999999,80999999

именно такое изменение конфигурации вызывает ошибку HttpClient. я не знаю, каковы точные значения для Thunderbolt, но кто-то с устройством может узнать, используя «adb shell getprop | grep buffersize».

person Elliott Hughes    schedule 14.05.2011

Возможно, это поможет:

// Set the timeout in milliseconds until a connection is established.
int timeoutConnection = 5000;

// Set the default socket timeout (SO_TIMEOUT) 
// in milliseconds which is the timeout for waiting for data.
int timeoutSocket = 4000;

// set timeout parameters for HttpClient 
HttpParams httpParameters = new BasicHttpParams();
HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection);
HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket);
HttpConnectionParams.setSocketBufferSize(httpParameters, 8192);//setting setSocketBufferSize

DefaultHttpClient httpClient = new DefaultHttpClient();
httpClient.setParams(httpParameters);
person wormhit    schedule 08.06.2011