【问题标题】:JSONArray throwing out of memory exception in androidJSONArray在android中抛出内存异常
【发布时间】:2014-02-10 07:27:03
【问题描述】:

在我的应用程序中,我在一天结束时将一些数据同步到应用程序服务器。为此,我将所有数据包装为 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"}]

以下是我的代码的主要 sn-ps:

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

【问题讨论】:

  • 你的 JSON 中有这个:"picture":"...base64encoded string......" ...我要疯了猜猜然后说……你的内存快用完了,因为你吸入的图片比你的内存要多。
  • 既然问题是传入了很多图片,你为什么不把一个大的同步过程分成更易于管理的块(比如五个请求,每个请求 10 张图片,或者你想要的) ) - 将 json 构造为不超过 10 个(左右)条目。
  • @sunil :这就是我试图对文件执行的操作。我将 JSONArray 值存储在文件中,然后我目前正在将此文件的小块发送到我的服务器(这段代码有效很好)。但是在将文件发送到服务器的过程之前(对于 50 张图片的情况),我在尝试将 JSONArray 值写入文件时遇到了 OOM:/
  • @BrianRoach:是的,我将图片编码为 base64 编码字符串(这对于每张图片来说都是一个非常大的编码字符串)。你是对的,我的内存不足(但是当我在清单中将堆设置为“大”,一切正常,但正如我所说,我不想使用这种方法)。我只需要将 JSONArray 中的值(工作正常)存储到文件中。是有没有办法做到这一点?

标签: java android string out-of-memory arrays


【解决方案1】:

这就是问题所在。您正在尝试将整个数据集加载到内存中。而且你的内存不足了。

Android 的 JSON 类(和其他一些 JSON 库)旨在获取 Java 对象(在内存中),将其序列化为对象解析树(例如 JSONObjectJSONArray)(在内存中),然后转换那棵树到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();

以上内容未经测试,但它应该可以工作(或者至少让你朝着正确的方向前进)。

【讨论】:

  • @Basher51 目前对您来说最简单的是第三个 - 您可以在循环中迭代时将 JSON 流式传输到输出文件。
  • 是的,即使我这么认为。我需要将对象存储在文件中(文件到服务器部分的工作正如我之前所说的那样)。
  • 好的,一定会试一试,让你知道。谢谢伙计!:)
  • 我实施了你的建议,它奏效了!我在下面提供了我的答案。非常感谢,如果没有你的指导,我会花很多时间。真的很感激 :)
  • @Basher51 很高兴我能帮上忙!
【解决方案2】:

首先,非常感谢 Brian Roach 对我的帮助。他的意见帮助我解决了问题。我正在分享我的答案。

我想解决什么问题? - 在我的项目中,我有一些用户数据(姓名、年龄、图片时间)和每个用户数据的一些相应图片。在 EOD 时,我需要将所有这些数据同步到应用服务器。但是当我尝试同步很多时图片(大约 50kb 中的 50 张)我遇到了 OOM(内存不足)问题。最初,我尝试使用传统的 JSONArray 方法上传所有数据,但很快我发现我遇到了 OOM。我将其归因于当我试图访问 JSONArray 时堆满了(它有很多值,为什么不呢?毕竟我是用 base64encoding 对图片进行编码的,相信我里面有很多字符串数据!)

Brian 的输入建议我将所有数据逐个写入一个文件。因此,在整个过程完成后,我得到一个包含所有数据(姓名、年龄、图片时间、base64 编码图片等)的文件它,然后我将此文件流式传输到服务器。

以下是代码 sn-p,它从应用程序数据库中获取用户数据,从 sd 卡中获取相应图片,遍历所有记录,使用 Jackson Json 库创建 JSONObjects 的 JSONArray(您需要将其包含在您的 libs 文件夹中,如果你使用这个代码)并将它们存储到一个文件中。然后这个文件被流式传输到服务器(这个 sn-p 不包括在内)。希望这对某人有帮助!


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多