我有一个Android客户端,通过REST-ful端点和JSON与服务器通信。因此,我需要在将其转换为Hash之前检索完整的服务器响应。我有这个代码(在互联网上找到的地方):
private static String convertStreamToString(InputStream is) {
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
StringBuilder sb = new StringBuilder();
String line = null;
try {
while ((line = reader.readLine()) != null) {
sb.append(line + "\n");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return sb.toString();
}
代码在很大程度上起作用,但是我看到来自具有OutOfMemory异常的客户的字段中的崩溃报告:
while ((line = reader.readLine()) != null) {
完整堆栈跟踪是:
java.lang.RuntimeException: An error occured while executing doInBackground()
at android.os.AsyncTask$3.done(AsyncTask.java:200)
at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
at java.util.concurrent.FutureTask.run(FutureTask.java:137)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1068)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:561)
at java.lang.Thread.run(Thread.java:1102)
Caused by: java.lang.OutOfMemoryError
at java.lang.String.(String.java:468)
at java.lang.AbstractStringBuilder.toString(AbstractStringBuilder.java:659)
at java.lang.StringBuilder.toString(StringBuilder.java:664)
at java.io.BufferedReader.readLine(BufferedReader.java:448)
at com.appspot.myapp.util.RestClient.convertStreamToString(RestClient.java:303)
at com.appspot.myapp.util.RestClient.executeRequest(RestClient.java:281)
at com.appspot.myapp.util.RestClient.Execute(RestClient.java:178)
at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1178)
at com.appspot.myapp.$LoadProfilesTask.doInBackground(GridViewActivity.java:1)
at android.os.AsyncTask$2.call(AsyncTask.java:185)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
... 4 more
我的问题:除了从服务器发送较小的数据块之外,还有什么方法可以解决这个问题吗?
谢谢!
答案 0 :(得分:3)
一般来说,答案是否定的,但你当然可以调整导致内存不足的条件。特别是,如果您在流之前发送字符串长度,您将能够在其中创建一个具有正确数组大小的StringBuilder。数组在创建后无法调整大小,因此如果在StringBuilder中耗尽数组容量,实现必须分配一个新数组(通常是两倍大小以避免过多调整大小),然后复制旧数组内容。考虑大小为X的流,为了调整刚好恰好是X-1容量的StringBuilder,你需要几乎X * 3的内存量。调整StringBuilder的大小以避免调整大小将允许您将更大的流压缩到内存中。
您可能想要做的另一件事是调整服务器进程可用的内存量。启动服务器进程时,请使用-Xmx1024m之类的开关。
当然,将您的算法修改为不要求将整个流保存在内存中会好得多。它将使您能够使用相同数量的硬件处理更多客户端。
答案 1 :(得分:2)
Android限制了您可以分配给应用程序的最大内存量。您可以考虑快速读取流 ,如果响应非常大,则不要将整个事物保存到String中。但你应该考虑遵循最佳实践。
您应该将数据存储在sqlite数据库或常规文件中。当您正在保存响应时,用户可能会按下主页按钮或接听电话,这不是最佳做法。最好使用数据库,以便您可以回到被中断的状态。那么你也不必担心内存不足。
您是否看过有关与Android通信的最佳实践的讨论? http://www.youtube.com/watch?v=xHXn3Kg2IQE?8m50s(8:50和11:20)。强烈建议清除最佳实践以及为什么不使用数据库就不应检索REST数据。
简而言之,请考虑保存到sqlite数据库或文件。如果数据非常大,您可以考虑在存储之前对其进行压缩。
答案 2 :(得分:1)
解决这些问题的方法有很多种。一种方法是使用不需要整个流存储在内存中的散列函数,即一次为它提供一个字符或一个字符块。另一个是减少响应的大小。
如果你不能这样做并且你需要整个流,那么我将避免使用readLine()并在缓冲的输入流上调用read()并将你从read中获得的字符追加到字符串构建器。这将减少您正在创建和放弃的字符串数量。 (对上面代码的简单优化是在append()调用中取出换行符 - 你也在那里不必要地创建另一个字符串。)另外,如果你知道结果字符串有多长,你还可以在构造时设置字符串构建器的初始容量,以便您立即知道是否内存不足。
一旦你超越了,你就必须开始将字符串拆分成存储在文件系统上的块....复杂得快。
答案 3 :(得分:1)
也许这段代码有助于避免使用StringBuilder和内存不足错误:
private String convertStreamToString(InputStream is) {
ByteArrayOutputStream oas = new ByteArrayOutputStream();
copyStream(is, oas);
String t = oas.toString();
try {
oas.close();
oas = null;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return t;
}
private void copyStream(InputStream is, OutputStream os)
{
final int buffer_size = 1024;
try
{
byte[] bytes=new byte[buffer_size];
for(;;)
{
int count=is.read(bytes, 0, buffer_size);
if(count==-1)
break;
os.write(bytes, 0, count);
}
}
catch(Exception ex){}
}
答案 4 :(得分:0)
您是否尝试过内置方法将流转换为字符串?它是Apache Commons库的一部分(org.apache.commons.io.IOUtils)。
那么你的代码就是这一行:
String total = IOUtils.toString(inputStream);
可以从这里下载Apache Commons IO库:http://commons.apache.org/io/download_io.cgi