2011-01-18 8 views
12

私はREST-fulエンドポイントとJSON経由でサーバーと通信するアンドロイドクライアントを持っています。このため、ハッシュに変換する前に、完全なサーバー応答を取得する必要があります。しかし、私は上のOutOfMemory例外を除いて、クライアントからフィールドでのクラッシュの報告を見ています、コードはほとんどの部分のために働くAndroid:メモリ不足でストリームをストリングに変換する

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(); 
} 

:私は(インターネットのどこかにあります)があることを行うための場所でこのコードを持っていますライン:

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 

私の質問:サーバーからのデータの小さな塊を送るとは別に、この問題を解決する方法はありますか?

ありがとうございます!

+2

私は一度にメモリ内の全応答を持つ必要性を見ていませんよ。あなたはそれについて詳述できますか? –

+1

私はorg.json.JSONObjectクラスを使用していましたが、これは標準でAndroidに付属しており、ストリーミングAPIはありません。しかし、それは私が調査するストリームAPIを持っていることが判明しました... – esilver

答えて

3

、答えはノーですが、チューニング、あなたがメモリ不足に原因となる条件を確かにすることができます。特に、ストリームよりも先に文字列長を送信すると、正しい配列サイズのStringBuilderをその中に作成することができます。配列を作成後にサイズ変更することはできません。したがって、StringBuilderで配列の容量が使い果たされた場合、実装は新しい配列(サイズが大きすぎるのを避けるために通常はサイズの2倍)を割り当ててから古い配列の内容をコピーする必要があります。 StringBuilderのサイズを変更するには、サイズXのストリームを考慮してください。これはX-1の容量になりました。ほとんどの場合、X * 3のメモリが必要です。サイズ変更が回避されるようにStringBuilderのサイズを設定すると、より大きなストリームをメモリにスクイーズできるようになります。

あなたがやりたいことがもう一つは、チューニングするために、サーバー・プロセスが使用できるメモリの量です。サーバープロセスを起動するときは、-Xmx1024mのようなスイッチを使用してください。もちろん

、メモリに保持されるストリーム全体を必要としないためにあなたのアルゴリズムを修正するためにはるかに良いでしょう。同量のハードウェアでより多くのクライアントを処理することができます。

+0

ええ、私はあなたの "いいえ"答えはここで最も正しいです...私はより大きな初期のStringBufferを事前に割り当てようとします、そして、私はストリーミングパーサー、Jacksonを使うことに目を向けます。 – esilver

+0

@ Constantinサーバープロセスを起動するときに-Xmx1024mのようなスイッチを使用する方法は? – flexdroid

1

このような問題にはさまざまな方法があります。 1つの方法は、ストリーム全体をメモリに入れる必要のないハッシュ関数を使用することです。つまり、一度に1文字または1ブロックの文字をフィードします。もう一つは、レスポンスのサイズを減らすことです。

ストリーム全体が必要な場合は、readLine()の使用を避け、バッファリングされた入力ストリームでread()を呼び出し、読み込みから得た文字を文字列ビルダーに追加します。これにより、作成したり破棄する文字列の数が大幅に削減されます。 (上記のコードへの単純な最適化は、append()呼び出しで改行を取り除くことです - 別の文字列を不必要に作成することになります)また、結果の文字列の長さを知っていれば、構築時に文字列ビルダーの初期容量を設定して、メモリ不足になった場合にすぐにわかるようにします。

あなたはそれを超えて取得したら、あなたはファイルシステムに格納したブロックに文字列を分割開始する必要があります....かなり速い複雑になります。一般的に

+0

私はストリーミングパーサを今見てみましょう - ジャクソンはおそらく助けるでしょう... – esilver

2

Androidは、アプリケーションに割り当てることができる最大メモリ容量に制限があります。ストリームを即座に読むことを検討することができます。応答が非常に大きい場合は、文字列全体を保存しないでください。しかし、ベストプラクティスに従うことを検討すべきです。

データは、sqliteデータベースまたは通常のファイルに保存する必要があります。ユーザーが応答を保存している最中にホームボタンを押すか、電話を受けると、自分が行っていることをやるのはベストプラクティスではありません。中断された状態に戻ることができるように、データベースを使用する方がよいでしょう。次に、メモリ不足について心配する必要もありません。

あなたはAndroidからのRESTサービスと通信するためのベストプラクティスについてこの話を見てきましたか? http://www.youtube.com/watch?v=xHXn3Kg2IQE?8m50s(8:50および11:20)。ベストプラクティスをクリアし、なぜデータベースを使用せずにRESTデータを取得してはならないのかを強く推奨します。

要するに、sqliteデータベースまたはファイルに保存することを検討してください。データが非常に大きい場合は、格納する前に圧縮することを検討することもできます。

+1

トークのリンクをありがとう - 私は見ます。私の問題は実際にはデータを保存することではなく、実際にその多くのRESTデータを一度に解析しているわけではありません(私の最大の見解では20kbです)。 OutOfMemoryErrorがどこで起こったのかがすべて解析された後でなければ、SQLiteデータベース(私が行う)に格納することはできません。 – esilver

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){} 
} 
0

あなたは、文字列にストリームを変換するための方法で構築を試みたことがありますか?これはApache Commonsライブラリ(org.apache.commons.io.IOUtils)の一部です。

次に、あなたのコードは、この1行のようになります。

文字列の合計= IOUtils.toString(InputStreamの);

それはここで見つけることができますのドキュメント:http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

のApache CommonsのIOライブラリは、ここからダウンロードできます。http://commons.apache.org/io/download_io.cgi