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

Получих някои доклади от потребители за сривове, когато се опитах да използвам приложението си в 4G/LTE на Verizon.

Разглеждайки проследяването на стека, изглежда, че изпълнението на HttpClient.execute() на Android хвърля OOM. Това се случва само на 4G/LTE устройства, по-специално HTC Thunderbolt, и само когато сте на 4G/LTE. WiFi, 3G, UMTS са ОК. Също така работи добре на WiMax 4G нещата на Sprint работят добре.

Два въпроса:

  • Кой е най-добрият начин да привлечете вниманието на разработчиците на 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): в org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173) I/dalvikvm(11639): в org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) I/dalvikvm(11639): в 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.execute(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): в com.myapplication.Fetcher.run(Fetcher. java:298) I/dalvikvm(11639): в java.lang.Thread.run(Thread.java:1102) I/dalvikvm(11639): E/dalvikvm(11639): Няма памет: Heap Size=24171KB, Разпределено =23142KB, Размер на растерна карта=59KB, Лимит=21884KB E/dalvikvm(11639): Допълнителна информация: Footprint=24327KB, Allowed Footprint=24519KB, Trimmed=348KB 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 казва по-долу, е правилно. Настройването на буфера ръчно на 8k разрешава сривовете. Не знам защо HTC реши да промени това. Надяваме се да се насладят на броя на презареждането на телефона++.   -  person DougW    schedule 25.03.2011
comment
Страхотно, аз също изпитвах този проблем. Благодаря за потвърждението.   -  person Victor    schedule 25.03.2011
comment
това се случва и на устройство LG P920   -  person petey    schedule 26.11.2012


Отговори (3)


Разглеждайки проследяването на стека, изглежда, че изпълнението на HttpClient.execute() на Android хвърля OOM.

Това не е посочено от проследяването на стека, което имате за проблема. Разбира се, вие не предоставихте цялото проследяване на стека по проблема.

Кой е най-добрият начин да привлечете вниманието на разработчиците на Android за това? Някакви по-добри опции от докладване на http://code.google.com/p/android/issues?

Шансовете това да е чисто Android грешка са малки, макар и не нулеви.

Ето някои други възможности, без определен ред:

  1. Няма проблем с execute() сам по себе си, но че просто ви изчерпва паметта и следите на стека, които сте срещнали, просто показват, че execute() натоварва вашата купчина.

  2. Проблемът е в някои модификации, които HTC направи на Android за Thunderbolt, вероятно влизащи в сила само когато сте в LTE мрежата.

  3. Проблемът по някакъв начин е причинен от самата LTE мрежа на Verizon (напр. някакъв техен прокси сървър изпраща обратно невероятна информация, която кара 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);

връща тази стойност от 2MB или нещо такова, която задейства 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 Code Search (google.com/codesearch). Поставете име на клас в лентата за търсене и android.git.kernel.org в полето Package. Отличен е за такъв проблем. Добрата новина е, че всички номера на редове съответстват на най-новите неща в репото, така че наистина нямаше предположения. Току-що започнах от действителната точка на срив и се върнах назад, опитвайки се да разбера откъде идва размерът на буфера. - 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", така че максималните размери на буфера на сокет за четене/запис да са огромни, когато вашият устройството е на wifi. нещо като следното би било истински стрес тест:

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