Android HttpClient在4G/LTE(HTC Thunderbolt)上出现OOM问题。

21

我的应用程序在使用Verizon的4G/LTE时会遇到崩溃问题,一些用户向我报告了这个问题。

查看堆栈跟踪,似乎是Android的HttpClient.execute()实现引发了OOM。这仅在4G/LTE设备上发生,特别是HTC Thunderbolt,并且仅在4G/LTE时发生。WiFi、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” 优先级=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=default handle=3948760 I/dalvikvm(11639): | schedstat=( 208709711 74005130 214 )

I/dalvikvm(11639): at org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:~79) I/dalvikvm(11639): at org.apache.http.impl.io.SocketInputBuffer.(SocketInputBuffer.java:93) I/dalvikvm(11639): at org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83) I/dalvikvm(11639): at org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170) I/dalvikvm(11639): at org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106) I/dalvikvm(11639): at 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(11639): at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) I/dalvikvm(11639): at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348) I/dalvikvm(11639): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) I/dalvikvm(11639): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487) I/dalvikvm(11639): at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) I/dalvikvm(11639): at 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): Out of memory: Heap Size=24171KB, Allocated=23142KB, Bitmap Size=59KB, Limit=21884KB E/dalvikvm(11639): Extra info: Footprint=24327KB, Allowed Footprint=24519KB, Trimmed=348KB W/dalvikvm(11639): threadid=9: thread exiting with uncaught exception (group=0x40025b38)


1
只是确认我正在追踪同样的问题。该问题仅出现在verizon_wwe上的htc_mecha(雷霆)上。问题首次出现于2011年3月17日。 - DougW
1
我去买了一台HTC Thunderbolt来诊断这个问题。下面CommonsWare所说的是正确的。手动将缓冲区设置为8k可以解决崩溃问题。不确定HTC为什么决定更改它。希望他们享受手机的重新进货计数++。 - DougW
太棒了,我也遇到了这个问题。感谢确认。 - Victor
这也发生在LG P920设备上。 - petey
3个回答

26
看堆栈跟踪,Android的HttpClient.execute()实现似乎抛出了OOM。
这在您报告的问题的堆栈跟踪中并未指明。当然,您没有提供完整的问题堆栈跟踪。
有什么更好的方法能引起Android开发人员的注意吗?除了在http://code.google.com/p/android/issues上报告之外,还有其他更好的选择吗?
这纯粹是Android的错误的可能性很小,但不为零。
以下是一些其他可能性,没有特定的顺序:
1. execute()本身没有问题,只是您的内存不足,您遇到的堆栈跟踪只是证明execute()正在压力测试您的堆。 2. 问题出在HTC为Thunderbolt所做的一些修改上,可能只在LTE网络上生效。 3. 问题由Verizon LTE网络本身引起(例如,他们的某个代理发送回了疯狂的信息,导致HttpClient出现了问题)。
有没有关于如何解决此问题的想法?
首先,我会使用现有工具(例如,倾倒HPROF并使用Eclipse MAT进行检查)来确认您没有一般的内存泄漏,只是Thunderbolt/LTE组合似乎会触发它们。
接下来,我建议您想出一种方法来持续重现这个错误。可以是您现有的应用程序并跟随一系列步骤,也可以是一个专用的应用程序(例如,记录触发OOM的URL,然后创建一个仅执行该HttpClient请求的小型应用程序)。我希望DeviceAnywhere有一个Thunderbolt,但看起来并没有。我会打听一下,看看能否在这方面得到一些帮助。
就解决问题而言,作为临时措施,您可以通过android.os.Build数据检测您是否正在运行Thunderbolt,并可能通过ConnectivityManager检测您是否正在使用LTE(我猜LTE会列为WiMAX,但这只是一个猜测),并警告用户这种组合存在的问题。
除此之外,您可以尝试稍微更改一下HttpClient的使用方式,看看是否有影响,例如:
  • 如果您仅支持API Level 8或更高级别,则可以尝试使用AndroidHttpClient作为替代品。
  • 禁用多线程访问(通常或特定于Thunderbolt)并消除ThreadSafeClientConnManager
很抱歉我在这里没有“万能答案”。

更新

现在我已经获得了完整的堆栈跟踪,浏览源代码是...有些启发性的。

问题似乎在于:

HttpConnectionParams.getSocketBufferSize(params);

返回的是大约2MB的值,这会触发OOM。对于Dalvik GC引擎来说,这是一个非常大的缓冲区,可能会出现碎片化(是的,又是这个词)。
这里的"params"是"HttpParams"。你似乎是通过"getHttpParams()"自己创建的。例如,"AndroidHttpClient"将其设置为8192:
HttpConnectionParams.setSocketBufferSize(params, 8192);

如果您自己设置了套接字缓冲区大小,请尝试减小它。如果没有,请尝试将其设置为8192,看看是否有帮助。

1
@psychotik:我根据进一步的研究更新了答案,这些研究是基于您修改后的堆栈跟踪。 - CommonsWare
1
@psychotik: “顺便问一下,你能告诉我在哪里找到这段代码吗?”--使用Google Code Search(http://www.google.com/codesearch)。在搜索栏中输入类名,并在包字段中输入“android.git.kernel.org”。它非常适用于这种问题。好消息是,所有行号都与存储库中的最新内容匹配,因此没有猜测。我从实际崩溃点开始,向后工作,试图弄清楚缓冲区大小来自何处。 - CommonsWare
3
@psychotik说:“看到我为什么可能在其他手机/连接类型上没有看到这个” - 这就是奇怪的地方。假设你没有将它设置为2055696,并且由于我没有看到通常是2055696的证据,我最好的猜测是HTC通过一个破解版的HttpConnectionParams默认将其设置为2055696。 - CommonsWare
3
我们的应用程序也遇到了同样的问题。我去买了一台HTC Thunderbolt进行测试。手动设置缓冲区大小确实解决了这个问题。 - DougW
1
我有一部HTC ThunderBolt手机。我在代码中调用了HttpConnectionParams.getSocketBufferSize(httpClient.getParams()),但它返回了-1而不是2055696。发生了什么事? - Kai
显示剩余6条评论

4
这里是解决方法: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”找出。


3
也许这会有所帮助:
// 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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接