【问题标题】:Android HttpClient OOM on 4G/LTE (HTC Thunderbolt)4G/LTE 上的 Android HttpClient OOM (HTC Thunderbolt)
【发布时间】:2011-07-18 11:57:59
【问题描述】:

当我尝试在 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):“线程 16”prio=5 tid=9 可运行 I/dalvikvm(11639):| group="main" sCount=0 dsCount=0 s=N 对象=0x48563070 自我=0x3c4340 我/dalvikvm(11639):|系统时间=11682 nice=0 sched=0/0 cgrp=default 句柄=3948760 I/dalvikvm(11639):| schedstat=(208709711 74005130 214)

I/dalvikvm(11639):在 org.apache.http.impl.io.AbstractSessionInputBuffer.init(AbstractSessionInputBuffer.java:~79) 我/dalvikvm(11639):在 org.apache.http.impl.io.SocketInputBuffer.(SocketInputBuffer.java:93) 我/dalvikvm(11639):在 org.apache.http.impl.SocketHttpClientConnection.createSessionInputBuffer(SocketHttpClientConnection.java:83) 我/dalvikvm(11639):在 org.apache.http.impl.conn.DefaultClientConnection.createSessionInputBuffer(DefaultClientConnection.java:170) 我/dalvikvm(11639):在 org.apache.http.impl.SocketHttpClientConnection.bind(SocketHttpClientConnection.java:106) 我/dalvikvm(11639):在 org.apache.http.impl.conn.DefaultClientConnection.openCompleted(DefaultClientConnection.java:129) 我/dalvikvm(11639):在 org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:173) 我/dalvikvm(11639):在 org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:164) 我/dalvikvm(11639):在 org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:119) 我/dalvikvm(11639):在 org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:348) 我/dalvikvm(11639):在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:555) 我/dalvikvm(11639):在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:487) 我/dalvikvm(11639):在 org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:465) 我/dalvikvm(11639):在 com.myapplication.Fetcher.trySourceFetch(Fetcher.java:205) 我/dalvikvm(11639):在 com.myapplication.Fetcher.run(Fetcher.java:298) 我/dalvikvm(11639):在 java.lang.Thread.run(Thread.java:1102) 我/dalvikvm(11639):E/dalvikvm(11639): 内存不足:堆大小=24171KB, 已分配=23142KB,位图大小=59KB, 限制=21884KB E/dalvikvm(11639):额外 信息:足迹=24327KB,允许 占用空间=24519KB,修剪=348KB W/dalvikvm(11639): threadid=9: 线程 以未捕获的异常退出 (组=0x40025b38)

【问题讨论】:

  • 确认我正在跟踪同样的问题。仅出现在 verizon_wwe 上的 htc_mecha (thunderbolt) 上。问题于 2011 年 3 月 17 日首次出现。
  • 我去买了一个 HTC Thunderbolt 来诊断这个问题。 CommonsWare 下面说的是正确的。手动将缓冲区设置为 8k 可以解决崩溃问题。不知道为什么 HTC 决定改变这一点。希望他们喜欢手机补货计数++。
  • 太棒了,我也遇到了这个问题。感谢您的确认。
  • 这也发生在 LG P920 设备上

标签: android httpclient 4g htc-android htc-thunderbolt


【解决方案1】:

查看堆栈跟踪,看起来 Android 的 HttpClient.execute() 实现正在引发 OOM。

您在该问题上的堆栈跟踪并未表明这一点。当然,您没有提供有关该问题的完整堆栈跟踪。

什么是引起 Android 开发者注意的最佳方式?有比报告http://code.google.com/p/android/issues 更好的选择吗?

这是一个纯 Android 错误的可能性很小,尽管不是零。

以下是其他一些可能性,排名不分先后:

  1. execute() 本身没有问题,但您只是内存不足,而您遇到的堆栈跟踪只是表明 execute() 正在给您的堆施加压力。

  2. 问题出在 HTC 针对 Thunderbolt 对 Android 进行的一些修改中,可能仅在 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 级别 8 或更高版本,您可以将 AndroidHttpClient 作为直接替代品使用
  • 禁用多线程访问(一般或特定于 Thunderbolt)并摆脱 ThreadSafeClientConnManager

很抱歉,我在这里没有“灵丹妙药”的答案。


更新

现在我有了完整的堆栈跟踪,查看源代码……有点启发性。

问题似乎是:

HttpConnectionParams.getSocketBufferSize(params);

正在返回触发 OOM 的 2MB 左右的值。这是一个非常大的缓冲区,特别是对于 Dalvik GC 引擎,它可能会变得碎片化(是的,又是那个词)。

params 这里是HttpParams。您似乎是通过getHttpParams() 自己创建的。例如,AndroidHttpClient 将其设置为 8192:

HttpConnectionParams.setSocketBufferSize(params, 8192);

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

【讨论】:

  • @psychotik:我根据进一步的研究更新了答案,其本身基于您修改后的堆栈跟踪。
  • @psychotik:“顺便说一句,你能告诉我你找到这段代码的地方吗?” -- 使用谷歌代码搜索 (google.com/codesearch)。在搜索栏中输入一个类名,在 Package 字段中输入android.git.kernel.org。它非常适合此类问题。好消息是所有行号都与 repo 中的最新内容相匹配,所以真的没有猜测。我只是从实际的崩溃点开始,然后倒退,试图找出缓冲区大小的来源。
  • @psychotik:“看看为什么我在其他手机/连接类型上看不到这个”——嗯,这很奇怪。假设您没有将其设置为 2055696,并且由于我没有看到任何证据表明它通常是 2055696,我最好的猜测是 HTC 通过HttpConnectionParams 的黑客版本以某种方式将其默认设置为 2055696。
  • @psychotik @cant0na - 我们的应用也遇到了同样的问题。我去买了一个 HTC Thunderbolt 来测试。手动设置缓冲区大小确实可以解决问题。
  • 我有一个 HTC ThunderBolt。我在代码中调用了 HttpConnectionParams.getSocketBufferSize(httpClient.getParams()),但它返回了 -1,但不是 2055696。这是怎么回事?
【解决方案2】:

这里是修复: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);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-12
      • 2013-01-22
      相关资源
      最近更新 更多