[英]JSONArray throwing out of memory exception in android
在我的應用程序中,我將一天結束時的一些數據同步到應用程序服務器。為此,我將所有數據包裝為JSONObjects的JSONArray。數據主要包括約50張圖片,每張圖片的大小約為50kb(以及一些文本數據) )。所有這些圖片均使用base64編碼進行編碼。當上傳的圖片(以及一些文本數據)數量很少時,一切正常,但是當我上傳大量圖片時,比如說大約50張,那么我在日志中看到所有數據都正確地形成了JSONArray,但是當我嘗試使用'array.toString()'方法顯示JSONArray時,我遇到了內存不足的異常。我相信這是由於堆變滿了(但是,當我嘗試使清單中的android:largeHeap =“ true”一切正常,但是我想避免使用這種方法,因為這不是一個好習慣。)我的意圖是將這個JSONArray值寫入文件,然后中斷將此文件分成小塊,然后發送到服務器。 請指導我將JSONAray值寫入文件的最佳方法,這不會導致OOM問題。謝謝!
以下是JSONArray的格式:
[{"pid":"000027058451111","popup_time":"2014-01-13 23:36:01","picture":"...base64encoded string......","punching_time":"Absent","status":"Absent"},{"pid":"000027058451111","popup_time":"2014-01-13 23:36:21","picture":"...base64encoded string......","punching_time":"Absent","status":"Absent"}]
以下是我的代碼的主要摘要:
JSONObject aux;
JSONArray array = new JSONArray();
.
.
// Looping through each record in the cursor
for (int i = 0; i < count; i++) {
aux = new JSONObject();
try {
aux.put("pid", c.getString(c.getColumnIndex("pid")));
aux.put("status", c.getString(c.getColumnIndex("status")));
aux.put("pop_time", c.getString(c.getColumnIndex("pop_time")));
aux.put("punching_time", c.getString(c.getColumnIndex("punching_time")));
aux.put("picture", c.getString(c.getColumnIndex("image_str"))); // stores base64encoded picture
} catch (Exception e) {
e.printStackTrace();
}
array.put(aux); // Inserting individual objects into the array , works perfectly fine,no error here
c.moveToNext(); // Moving the cursor to the next record
}
Log.d("Log", "length of json array - "+array.length()); // shows me the total no of JSONObjects in the JSONArray,works fine no error
// HAD GOT OOM HERE
//Log.d("Log", "JSONArray is - " + array.toString());
if (array.length() != 0){
try {
String responseCode = writeToFile(array); //Writing the JSONArray value to file,which will then send file to server.
if(responseCode.equals("200"))
Log.d("Log","Data sent successfully from app to app server");
else
Log.d("Log","Data NOT sent successfully from app to app server");
} catch (Exception e) {
e.printStackTrace();
}
}
.
.
private String writeToFile(JSONArray data) {
Log.d("Log", "Inside writeToFile");
File externalStorageDir = new File(Environment.getExternalStorageDirectory().getPath(), "Pictures/File");
if (!externalStorageDir.exists()) {
externalStorageDir.mkdirs();
}
String responseCode = "";
File dataFile = new File(externalStorageDir, "File");
/* FileWriter writer;
String responseCode = "";
try {
writer = new FileWriter(dataFile);
writer.append(data);
writer.flush();
writer.close();
responseCode = sendFileToServer(dataFile.getPath(), AppConstants.url_app_server); // Sends the file to server,worked fine for few pictures
} catch (IOException e) {
e.printStackTrace();
}*/
try {
FileWriter file = new FileWriter("storage/sdcard0/Pictures/File/File");
file.write(data.toString()); // GOT OOM here.
file.flush();
file.close();
Log.d("Log","data written from JSONArray to file");
responseCode = sendFileToServer(dataFile.getPath(), AppConstants.url_app_server); // Sends the file to server,worked fine for few pictures
} catch (IOException e) {
e.printStackTrace();
}
return responseCode;
}
public String sendFileToServer(String filename, String targetUrl) {
.
.
// Sends the file to server,worked fine for few pictures
.
.
return response;
}
這是問題。 您正在嘗試將整個數據集加載到內存中。 而且您的內存不足。
Android的JSON類(和其他一些JSON庫)旨在獲取Java對象(在內存中),將其序列化為對象的解析樹(例如JSONObject
, JSONArray
)(在內存中),然后將該樹轉換為String
(在內存中)內存)並將其寫出。
特別是在您的情況下(目前),它將解析樹轉換為String
會耗盡內存。 該String
實際上將此時所需的內存量加倍。
為了解決您的問題,您有幾種選擇,我將提供3:
完全不使用JSON。 重構只需將文件和信息發送到服務器即可。
重構事物,以便您一次只將X圖像讀入內存並具有多個輸出文件。 X是一些圖像。 請注意,如果您的圖片大小相差很大/無法預測,這仍然是有問題的。
切換為使用Jackson作為JSON庫。 它支持流操作,您可以在創建數組中的每個對象時將JSON流到輸出文件。
編輯添加:對於您的代碼,使用Jackson看起來像這樣:
// Before you get here, have created your `File` object
JsonFactory jsonfactory = new JsonFactory();
JsonGenerator jsonGenerator =
jsonfactory.createJsonGenerator(file, JsonEncoding.UTF8);
jsonGenerator.writeStartArray();
// Note: I don't know what `c` is, but if it's a cursor of some sort it
// should have a "hasNext()" or similar you should be using instead of
// this for loop
for (int i = 0; i < count; i++) {
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("pid", c.getString(c.getColumnIndex("pid")));
jsonGenerator.writeStringField("status", c.getString(c.getColumnIndex("status")));
jsonGenerator.writeStringField("pop_time", c.getString(c.getColumnIndex("pop_time")));
jsonGenerator.writeStringField("punching_time", c.getString(c.getColumnIndex("punching_time")));
// stores base64encoded picture
jsonGenerator.writeStringField("picture", c.getString(c.getColumnIndex("image_str")));
jsonGenerator.writeEndObject();
c.moveToNext(); // Moving the cursor to the next record
}
jsonGenerator.writeEndArray();
jsonGenerator.close();
上面的代碼未經測試,但是應該可以工作(或者至少可以使您朝正確的方向前進)。
首先,感謝十億美元的布萊恩·羅奇(Brian Roach)為我提供的幫助,他的投入幫助我解決了這個問題,我正在分享我的答案。
我想解決什么? -在我的項目中,我有一些用戶數據(名稱,年齡,圖片時間)和每個用戶數據的對應圖片。在EOD時,我需要將所有這些數據同步到應用服務器,但是當我嘗試大量同步時的圖片(例如約50個50kb的圖片)我遇到了OOM(內存不足)問題。最初,我試圖使用傳統的JSONArray方法上傳所有數據,但很快我發現我遇到了OOM。到當我嘗試訪問JSONArray時堆變滿(它有大量的值,為什么不呢?畢竟我是通過base64encoding對圖片進行編碼的,這使我相信里面有很多字符串數據!)
來自Brian的輸入建議我將所有數據一一寫入到文件中。因此,在整個過程完成后,我得到了一個包含所有數據(名稱,年齡,picture_time,base64編碼圖片等)的文件。然后我將此文件流式傳輸到服務器。
以下是從App數據庫中獲取用戶數據,從SD卡中獲取相應圖片,循環瀏覽所有記錄,使用Jackson Json Library創建JSONObjects JSONArray的代碼段(您需要將其包含在libs文件夾中,您應該使用此代碼段)代碼)並將其存儲到文件中。然后將該文件流式傳輸到服務器(不包含此代碼段)。希望對您有所幫助!
// Sync the values in DB to the server
Log.d("SyncData", "Opening db to read files");
SQLiteDatabase db = context.openOrCreateDatabase("data_monitor", Context.MODE_PRIVATE, null);
db.execSQL("CREATE TABLE IF NOT EXISTS user_data(device_id VARCHAR,name VARCHAR,age VARCHAR,picture_time VARCHAR);");
Cursor c = db.rawQuery("SELECT * FROM user_data", null);
int count = c.getCount();
if (count > 0) {
File file = new File(Environment.getExternalStorageDirectory().getPath(), "Pictures/UserFile/UserFile");
JsonFactory jsonfactory = new JsonFactory();
JsonGenerator jsonGenerator = null;
try {
jsonGenerator = jsonfactory.createJsonGenerator(file, JsonEncoding.UTF8);
jsonGenerator.writeStartObject();
jsonGenerator.writeArrayFieldStart("user_data"); //Name for the JSONArray
} catch (IOException e3) {
e3.printStackTrace();
}
c.moveToFirst();
// Looping through each record in the cursor
for (int i = 0; i < count; i++) {
try {
jsonGenerator.writeStartObject(); //Start of inner object '{'
jsonGenerator.writeStringField("device_id", c.getString(c.getColumnIndex("device_id")));
jsonGenerator.writeStringField("name", c.getString(c.getColumnIndex("name")));
jsonGenerator.writeStringField("age", c.getString(c.getColumnIndex("age")));
jsonGenerator.writeStringField("picture_time", c.getString(c.getColumnIndex("picture_time")));
} catch (Exception e) {
e.printStackTrace();
}
// creating a fourth column for the input of corresponding image from the sd card
Log.d("SyncData", "Name of image - " + c.getString(c.getColumnIndex("picture_time")));
image = c.getString(c.getColumnIndex("picture_time")).replaceAll("[^\\d]", ""); //Removing everything except digits
Log.d("SyncData", "imagename - " + image);
File f = new File(Environment.getExternalStorageDirectory().getPath(), "Pictures/UserPic/" + image + ".jpg");
Log.d("SyncData", "------------size of " + image + ".jpg" + "= " + f.length());
String image_str;
if (!f.exists() || f.length() == 0) {
Log.d("SyncData", "Image has either size of 0 or does not exist");
try {
jsonGenerator.writeStringField("picture", "Error Loading Image");
} catch (Exception e) {
e.printStackTrace();
}
} else {
try {
// Reusing bitmaps to avoid Out Of Memory
Log.d("SyncData", "Image exists,encoding underway...");
if (bitmap_reuse == 0) { //ps : bitmap reuse was initialized to 0 at the start of the code,not included in this snippet
// Create bitmap to be re-used, based on the size of one of the bitmaps
mBitmapOptions = new BitmapFactory.Options();
mBitmapOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(f.getPath(), mBitmapOptions);
mCurrentBitmap = Bitmap.createBitmap(mBitmapOptions.outWidth, mBitmapOptions.outHeight, Bitmap.Config.ARGB_8888);
mBitmapOptions.inJustDecodeBounds = false;
mBitmapOptions.inBitmap = mCurrentBitmap;
mBitmapOptions.inSampleSize = 1;
BitmapFactory.decodeFile(f.getPath(), mBitmapOptions);
bitmap_reuse = 1;
}
BitmapFactory.Options bitmapOptions = null;
// Re-use the bitmap by using BitmapOptions.inBitmap
bitmapOptions = mBitmapOptions;
bitmapOptions.inBitmap = mCurrentBitmap;
mCurrentBitmap = BitmapFactory.decodeFile(f.getPath(), mBitmapOptions);
if (mCurrentBitmap != null) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
mCurrentBitmap.compress(Bitmap.CompressFormat.JPEG, 35, stream);
Log.d("SyncData", "------------size of " + "bitmap_compress" + "= " + mCurrentBitmap.getByteCount());
} catch (Exception e) {
e.printStackTrace();
}
byte[] byte_arr = stream.toByteArray();
Log.d("SyncData", "------------size of " + "image_str" + "= " + byte_arr.length);
stream.close();
stream = null;
image_str = Base64.encodeToString(byte_arr, Base64.DEFAULT);
jsonGenerator.writeStringField("picture", image_str);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
try {
jsonGenerator.writeEndObject(); //End of inner object '}'
} catch (JsonGenerationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
c.moveToNext(); // Moving the cursor to the next record
}
try {
jsonGenerator.writeEndArray(); //close the array ']'
//jsonGenerator.writeStringField("file_size", "0"); // If need be, place another object here.
jsonGenerator.writeEndObject();
jsonGenerator.flush();
jsonGenerator.close();
} catch (JsonGenerationException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}
c.close();
db.close();
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.