【问题标题】:Android: Error Parsing Large Json on Limited Memory (Specific) DeviceAndroid:在有限内存(特定)设备上解析大型 Json 时出错
【发布时间】:2016-11-07 21:28:30
【问题描述】:

使用 OkHttpClient 在 TC75(8mb 的 RAM)上解析 36972 行 JSON 响应时出现 java.lang.OutOfMemoryError。当我尝试调用 response.body().string()

时出现问题
E/dalvikvm-heap: Out of memory on a 26525298-byte allocation.

我试图将数据保存在 StringBuilder 和流式 JsonReader 中,如 [in this answer here][1]

OkHttpClient client = new OkHttpClient();
Request request = new Request.Builder().url(...).build();

try {
    Response response = client.newCall(request).execute();
    JsonReader jsonReader = new JsonReader(new StringReader(response.body().string()));

    ... (loads of JsonReader parsing)
} catch (IOException | ParseException e) {
    e.printStackTrace();
}

在 Samsung S6 Active(3gb RAM)上运行时,应用程序可以轻松解析此 JSON。如何避免这种有限 RAM 的内存不足错误?有没有可能?

堆栈跟踪:

E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2
   Process: com.at.project, PID: 4409
   java.lang.RuntimeException: An error occured while executing doInBackground()
      at android.os.AsyncTask$3.done(AsyncTask.java:300)
      at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:355)
      at java.util.concurrent.FutureTask.setException(FutureTask.java:222)
      at java.util.concurrent.FutureTask.run(FutureTask.java:242)
      at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231)
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
      at java.lang.Thread.run(Thread.java:841)
   Caused by: java.lang.OutOfMemoryError
      at java.lang.String.<init>(String.java:255)
      at java.lang.String.<init>(String.java:228)
      at com.squareup.okhttp.ResponseBody.string(ResponseBody.java:83)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.getOEMPartPricingJson(LoginNewActivity.java:1496)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.downloadAnyNewJson(LoginNewActivity.java:860)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.checkForatDprojectdates(LoginNewActivity.java:746)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.checkForUpdates(LoginNewActivity.java:659)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.doInBackground(LoginNewActivity.java:486)
      at com.at.project.activities.LoginNewActivity$UserLoginTask.doInBackground(LoginNewActivity.java:465)
      at android.os.AsyncTask$2.call(AsyncTask.java:288)
      at java.util.concurrent.FutureTask.run(FutureTask.java:237)
      at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:231) 
      at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112) 
      at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587) 
      at java.lang.Thread.run(Thread.java:841)    

【问题讨论】:

  • 请发布与您的问题中的代码相关联的完整 Java 堆栈跟踪。我可以看到OutOfMemoryError 用于response.body().string() 场景。您暗示您的 JsonReader 代码不起作用,但您没有说明该代码存在什么问题。
  • JsonReader 代码与 StringBuilder 代码存在相同的问题,即在 response.body().string() 调用时内存不足。这是堆栈跟踪。我将使用堆栈跟踪编辑原始帖子。
  • 您的堆栈跟踪显示您在 ResponseBody 上调用了 string()。您问题中的源代码ResponseBody 上调用string()。您问题中的源代码在ResponseBody 上调用charStream()。请发布完整的 Java 堆栈跟踪与您的问题中的代码相关联,显示您对 JsonReadercharStream() 的使用
  • 抱歉,charStream() 调用是一个错字。这会引发错误,因为它会在 StringReader 的构造函数中返回 Stream。
  • 对于这种大小的 JSON 文件,您会收到带有 string()OutOfMemoryError,我并不感到非常惊讶。您正在尝试将整个 JSON 文件一次性读入内存,这在大多数设备上都不起作用。直接使用charStream() 应该会更好。因此,您应该考虑详细解释“无济于事”的含义。

标签: android json


【解决方案1】:

流解析器被设计为在从数据流中解析出 XML 或 json 之类的元素时返回它们。它们通常允许解析总内存使用量较低的大型文档,或者在您开始对正在解析的文件做一些有用的事情之前减少缓冲时间。

Jackson Streaming 解析器是用 java 编写的。它可能会满足您的需求。我敢肯定还有其他流式 json 解析器。

对于内存不足的系统,您可能无法将所有数据吸收到 String 或 StringBuilder 中。相反,您将把流交给流解析器并在元素出现时处理回调。您需要丢弃未使用和不必要的元素,而不是尝试构建完整 json 文件的解析树。

【讨论】:

  • JsonReader 是一个流解析器,这是 OP 在问题代码中显示的内容。
  • 链接代码似乎显示所有元素都保存到树中。对于低内存系统,您必须丢弃所有不使用的元素。我想我应该说得更清楚。
  • Evaitl,你说的被保存到树上是什么意思?我不确定在我的代码中会是什么样子。对于它的价值,只需访问 JsonReader jsonReader = new JsonReader(new StringReader(response.body().string())); 行中的流,设备就会耗尽内存
  • 我认为代码已更改。大多数解析器会创建一个完整的解析树,其中包含正在解析的任何内容。对于带有流式解析器的低内存系统,您注册回调,当您感兴趣的元素出现在解析流中时调用这些回调。你用这些元素做你需要的东西,然后扔掉其他所有东西。有一个示例 Parse JSON Response with Gson 将 okhttp 响应流传递给 json 解析器,而不是尝试吸收整个响应并将字符串传递给解析器。听起来你需要这样做。
  • 谢谢evaitl,我会尽快尝试的。
【解决方案2】:

内存错误与变量可以拥有的最大内存量有关。

我推荐使用JSON to POJO helper,它将数据直接存储在具有更大内存堆的Objects中。

使用GSONJackson 库非常简单。

您还可以使用 HTTP Helper 更轻松地执行请求,其中大多数已经使用 GSON 和 Jackson 进行反序列化。

我使用Retrofit,但Volley 也是一个不错的选择。

我知道这不能解决您的错误,但我认为实现这些库比您自己优化内存使用和 http 请求要容易得多。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-06-19
    • 1970-01-01
    相关资源
    最近更新 更多