2011-01-18 26 views
12

Tôi có một ứng dụng khách Android giao tiếp với máy chủ thông qua thiết bị đầu cuối REST-ful và JSON. Bởi vì điều này, tôi có nhu cầu truy xuất phản hồi máy chủ đầy đủ trước khi chuyển đổi nó thành một Hash. Tôi có mã này tại chỗ để làm điều đó (tìm thấy trên một nơi nào đó internet):Android: Chuyển đổi luồng thành chuỗi mà không cần hết bộ nhớ

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

Mã này hoạt động đối với hầu hết các phần, tuy nhiên tôi nhìn thấy những báo cáo về tai nạn trong lĩnh vực này từ các khách hàng với một ngoại lệ OutOfMemory trên dòng:

while ((line = reader.readLine()) != null) { 

các vết đống đầy đủ là:

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 

câu hỏi của tôi: là có cách nào để giải quyết vấn đề này ngoài việc gửi những phần nhỏ hơn của dữ liệu từ máy chủ?

Cảm ơn!

+2

Tôi không nhìn thấy sự cần thiết phải có toàn bộ phản ứng trong bộ nhớ cùng một lúc. Bạn có thể giải thích về điều đó không? –

+1

Tôi đang sử dụng lớp org.json.JSONObject, chuẩn này có tiêu chuẩn với Android và thiếu API truyền trực tuyến. Nhưng, nó chỉ ra rằng Jackson có một API streaming mà tôi sẽ điều tra ... – esilver

Trả lời

3

Nói chung, câu trả lời là không, nhưng bạn chắc chắn có thể điều chỉnh các điều kiện khiến bạn hết bộ nhớ. Đặc biệt, nếu bạn gửi độ dài chuỗi trước luồng của mình, bạn sẽ có thể tạo một StringBuilder với kích thước mảng chính xác bên trong nó. Mảng không thể thay đổi kích cỡ sau khi tạo, vì vậy nếu bạn hết dung lượng mảng trong StringBuilder, việc triển khai phải phân bổ một mảng mới (thường gấp đôi kích thước để tránh quá nhiều kích thước) và sau đó sao chép nội dung mảng cũ. Hãy xem xét dòng kích thước X, để thay đổi kích thước StringBuilder mà chỉ xảy ra là một dung lượng X-1, bạn cần gần như số lượng bộ nhớ X * 3. Định kích thước StringBuilder như vậy mà thay đổi kích thước được tránh sẽ cho phép bạn ép dòng lớn hơn vào bộ nhớ.

Một điều bạn có thể muốn làm là điều chỉnh lượng bộ nhớ khả dụng cho quy trình máy chủ của bạn. Sử dụng công tắc như -Xmx1024m khi khởi chạy quá trình máy chủ.

Tất nhiên, tốt hơn hết là sửa đổi thuật toán của bạn để không yêu cầu toàn bộ luồng được lưu trữ trong bộ nhớ. Nó sẽ cho phép bạn xử lý nhiều khách hàng hơn với cùng một lượng phần cứng.

+0

Vâng, tôi nghĩ rằng bạn "không "câu trả lời là đúng nhất ở đây ... Tôi sẽ thử phân bổ trước một StringBuffer ban đầu lớn hơn, và tôi sẽ xem xét sử dụng trình phân tích cú pháp phát trực tiếp, Jackson. – esilver

+0

@Konstantin cách sử dụng công tắc như -Xmx1024m khi khởi chạy quá trình máy chủ? – flexdroid

1

Có nhiều cách khác nhau để giải quyết các loại vấn đề này. Một cách là sử dụng hàm băm không yêu cầu toàn bộ luồng trong bộ nhớ, tức là bạn cho nó một ký tự hoặc khối ký tự cùng một lúc. Một cách khác là giảm kích thước của phản hồi.

Nếu bạn không thể làm điều đó và bạn cần toàn bộ luồng, thì tôi sẽ tránh sử dụng readLine() và chỉ cần đọc read() trên luồng đầu vào được đệm và nối thêm ký tự bạn nhận được từ đọc vào trình tạo chuỗi. Điều này sẽ làm giảm số lượng chuỗi bạn đang tạo và loại bỏ khá đáng kể. (Một tối ưu hóa đơn giản cho đoạn mã trên là đưa ra dòng mới trong hàm append() - bạn cũng đang tạo một chuỗi khác không cần thiết.) Ngoài ra, nếu bạn có bất kỳ ý tưởng nào về chuỗi kết quả, bạn cũng có thể đặt công suất ban đầu của trình tạo chuỗi khi xây dựng để bạn biết ngay nếu bạn không còn bộ nhớ.

Khi bạn vượt ra ngoài, bạn phải bắt đầu tách chuỗi thành các khối mà bạn lưu trữ trên hệ thống tập tin .... trở nên phức tạp khá nhanh.

+0

Tôi sẽ xem xét một trình phân tích cú pháp trực tuyến ngay bây giờ - Jackson - có lẽ điều đó sẽ giúp ... – esilver

2

Android có hạn chế về số lượng bộ nhớ tối đa bạn có thể phân bổ cho ứng dụng của mình. Bạn có thể xem xét đọc luồng khi đang bay và không lưu toàn bộ nội dung vào chuỗi nếu phản hồi rất lớn. Nhưng bạn nên cân nhắc thực hành tốt nhất.

Bạn nên lưu trữ dữ liệu trong cơ sở dữ liệu sqlite hoặc vào một tệp thông thường.Cách tốt nhất là không nên làm những gì bạn đang làm khi người dùng có thể nhấn nút home hoặc nhận cuộc gọi điện thoại khi bạn đang ở giữa lưu phản hồi. Tốt hơn là sử dụng cơ sở dữ liệu để bạn có thể quay trở lại trạng thái bị gián đoạn. Sau đó, bạn cũng không phải lo lắng về việc hết bộ nhớ.

Bạn đã xem cuộc nói chuyện này về các phương pháp hay nhất để giao tiếp với các dịch vụ REST từ Android phải không? http://www.youtube.com/watch?v=xHXn3Kg2IQE?8m50s (8:50 và 11:20). Rất khuyến khích để xóa các thực tiễn tốt nhất và tại sao người ta không nên truy xuất dữ liệu REST mà không cần sử dụng cơ sở dữ liệu.

Tóm lại, hãy xem xét lưu vào cơ sở dữ liệu sqlite hoặc tệp. Nếu dữ liệu là rất lớn, bạn có lẽ có thể xem xét nén nó trước khi bạn lưu trữ nó.

+1

Cảm ơn bạn đã liên kết đến buổi nói chuyện - Tôi sẽ xem. Vấn đề của tôi không thực sự là tiết kiệm dữ liệu và tôi không thực sự phân tích cú pháp nhiều dữ liệu REST cùng một lúc (20kb trên chế độ xem lớn nhất của tôi). Lưu trữ vào một cơ sở dữ liệu SQLite (mà tôi làm) sẽ chỉ có thể được sau khi toàn bộ điều đã được phân tích cú pháp, đó là nơi OutOfMemoryError xảy ra. – esilver

1

Có lẽ mã này giúp tránh sử dụng StringBuilder và out-of-bộ nhớ lỗi:

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

Bạn đã cố gắng xây dựng trong phương pháp để chuyển đổi một dòng vào một chuỗi? Nó là một phần của thư viện Apache Commons (org.apache.commons.io.IOUtils).

Sau đó, mã của bạn sẽ là này một dòng: tổng

String = IOUtils.toString (InputStream);

Các tài liệu cho nó có thể được tìm thấy ở đây: http://commons.apache.org/io/api-1.4/org/apache/commons/io/IOUtils.html#toString%28java.io.InputStream%29

Thư viện Apache Commons IO có thể được tải về từ đây: http://commons.apache.org/io/download_io.cgi

Các vấn đề liên quan