8

I have a memory leak in this file, I cannot find where exactly, but I think is the image around --> (Bitmap bm = BitmapFactory.decodeFile(filename)), I have tried many different ways but I can't get it to work.

package prod.vegs;

//All imports here but not need to write them all now :-)


public class ProductForm extends Activity {

private static int TAKE_PICTURE = 1;
private static int SELECT_PICTURE = 2;

//JSON Response node names
private static String KEY_SUCCESS = "success";
private static String ERROR_MSG = "error_msg";
private static String KEY_TYPES = "subtypes";
private static String TYPE_NAME = "name";
private static String TYPE_ID = "id_type";
private static String PRODUCT_ID = "id_product";

private JSONObject json;
private JSONParser jsonParser;
private String barcodeStr;
private String filename;
private int code;
private ProgressDialog dialog;
private TypeClass[] items;
private TypeClass[] sub_items;

//Declare assets objects
Spinner type;
Spinner subtype;
TextView errorMsg;
TextView description;
TextView name;
Button camera;
Button gallery;
Intent intent;
ImageView preview;
Bundle bundle;
LinearLayout errorMsgContainer;

Context context;

public void onCreate(Bundle icicle) {
    super.onCreate(icicle);
    setContentView(R.layout.product_form);
    context = this;

    Bundle b = getIntent().getExtras();
    barcodeStr = b.getString("barcode");

    jsonParser = new JSONParser();
    dialog = new ProgressDialog(this);
    dialog.setMessage(getString(R.string.loading));
    dialog.setTitle(getString(R.string.progress));
    dialog.setCancelable(true);

    //Set assets
    name = (TextView) findViewById(R.id.productName);
    description = (TextView) findViewById(R.id.productDescription);
    errorMsg = (TextView) findViewById(R.id.error_msg);
    errorMsgContainer = (LinearLayout) findViewById(R.id.error_msg_container);
    type = (Spinner) findViewById(R.id.productParentType);
    subtype = (Spinner) findViewById(R.id.productType);
    camera = (Button) findViewById(R.id.productCamera);
    gallery = (Button) findViewById(R.id.productGallery);
    preview = (ImageView) findViewById(R.id.productPreview);
    filename = Environment.getExternalStorageDirectory() + String.format(getString(R.string.api_product_form_picture_file), barcodeStr);

    Boolean fromScanner = b.getBoolean("scanner");
    if (fromScanner == true) {

        AlertDialog.Builder alertbox = new AlertDialog.Builder(this);
        alertbox.setMessage(getString(R.string.insert_product));
        alertbox.setPositiveButton(getString(R.string.yes), 
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface arg_1, int arg_num) {
                    final Functions function = new Functions();
                    List<NameValuePair> params = new ArrayList<NameValuePair>();
                    String url = String.format(getString(R.string.api_product_form_types_url), getString(R.string.api_url));
                    json = function.loadJSONUrl(url, params);
                    if(json != null){
                        try {
                            if (json.getString(KEY_SUCCESS) != null) {
                                String res = json.getString(KEY_SUCCESS);
                                if(Integer.parseInt(res) == 1){

                                    JSONArray types = json.getJSONArray(KEY_TYPES);
                                    items = convertJSONArray(types);

                                    SpinAdapter listViewArrayAdapter = new SpinAdapter(context, android.R.layout.simple_spinner_item, items);
                                    listViewArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                                    type.setAdapter(listViewArrayAdapter);
                                    type.setOnItemSelectedListener(new OnItemSelectedListener(){
                                        public void onItemSelected(AdapterView<?> parent, View v, int pos, long id) {
                                            try {
                                                String url = String.format(getString(R.string.api_subtypes_id_url), getString(R.string.api_url), ((TypeClass) type.getSelectedItem()).getId());
                                                List<NameValuePair> params = new ArrayList<NameValuePair>();
                                                JSONObject json_subtypes = function.loadJSONUrl(url, params);
                                                if (json_subtypes.getString(KEY_SUCCESS) != null) {
                                                    JSONArray subtypes = json_subtypes.getJSONArray(KEY_TYPES);
                                                    sub_items = convertJSONArray(subtypes);
                                                    SpinAdapter subTypeAdapter = new SpinAdapter(context, android.R.layout.simple_spinner_item, sub_items);
                                                    subTypeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
                                                    subtype.setAdapter(subTypeAdapter);
                                                    subtype.setPrompt("Selecciona la cateogría");
                                                }
                                            } catch (Exception e) {
                                                e.printStackTrace();
                                            }
                                        }

                                        public void onNothingSelected(AdapterView<?> args) {
                                            //Auto-generated method stub
                                        }
                                    });
                                    type.setPrompt("Selecciona la cateogría");

                                    //camera action
                                    camera.setOnClickListener(new View.OnClickListener() {
                                        public void onClick(View view) {
                                            intent =  new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                                            int timeMili = (int) (System.currentTimeMillis());
                                            filename = Environment.getExternalStorageDirectory() + "/" + timeMili + ".jpg";
                                            Uri output = Uri.fromFile(new File(filename));
                                            intent.putExtra(MediaStore.EXTRA_OUTPUT, output);
                                            code = TAKE_PICTURE;
                                            startActivityForResult(intent, code);   
                                        }
                                    });

                                    //gallery action
                                    gallery.setOnClickListener(new View.OnClickListener() {
                                        public void onClick(View view) {
                                            intent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI);
                                            code = SELECT_PICTURE;
                                            startActivityForResult(intent, code);
                                        }
                                    });

                                    //button of the form
                                    Button button = (Button) findViewById(R.id.button);
                                    button.setOnClickListener(new View.OnClickListener() {
                                        public void onClick(View view) {
                                            if (!NetworkHelper.CheckNetworkStatus(view.getContext())) {
                                                return;
                                            }
                                            bundle = new Bundle();
                                            bundle.putString("barcode", barcodeStr.toString());
                                            bundle.putString("name", name.getText().toString());
                                            bundle.putString("description", description.getText().toString());
                                            bundle.putString("type_id", ((TypeClass) subtype.getSelectedItem()).getId());

                                            if (_checkFormValues()) {
                                                new SendDataJSON().execute(view);
                                            } else {
                                                Toast.makeText( view.getContext(), getString(R.string.error_form_incomplete), Toast.LENGTH_LONG).show();
                                            }
                                        }
                                    });

                                }  else {
                                    errorMsg.setText(json.getString(ERROR_MSG));
                                    errorMsgContainer.setVisibility(LinearLayout.VISIBLE);
                                }
                            }
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    } else {

                    }
                }
        }).setNegativeButton("No", 
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface arg0, int arg1) {
                    Intent myIntent = new Intent(ProductForm.this, CaptureActivity.class);
                    startActivity(myIntent);
                    finish();
                }
        }).show();

    } else {
        finish();
    }  

}

class SendDataJSON extends AsyncTask<View, Void, View>{

    @Override
    protected View doInBackground(View... views) {

        String url = String.format(getString(R.string.api_product_form_url),getString(R.string.api_url)); 
        HttpPost httpPost = new HttpPost(url);

        try {
            // Add your data
            MultipartEntity entity = new MultipartEntity();

            File photo = new File(filename);
            if (photo.exists()) {
                //create the compressed image to send                   
                //create the file to send the image
                File sd = Environment.getExternalStorageDirectory();
                File data = Environment.getDataDirectory();
                if (!sd.canWrite()) { sd = data; }
                String destinationFolderPath = sd + "/" + getString(R.string.app_dir) + "/";
                String destinationImageName= "photo_" + bundle.getString("barcode") + ".jpg";

                //create the folder to store it
                File destinationFolder = new File(destinationFolderPath);
                if (!destinationFolder.exists()) {
                    destinationFolder.mkdirs();
                }

                File destination = new File(destinationFolder, destinationImageName);
                FileOutputStream out = new FileOutputStream(destination);

                Bitmap bm = BitmapFactory.decodeFile(filename);                 
                int width = bm.getWidth();
                int height = bm.getHeight();
                int max_value = 1024;
                int max = Math.max(width,height);
                if (max > max_value) {
                    width = width * max_value / max;
                    height = height * max_value / max;
                }

                //Make the new image with the new size values
                try {
                    Bitmap bm2 = Bitmap.createScaledBitmap(bm, width, height, true);
                    //Compress the image
                    bm2.compress(CompressFormat.JPEG, 75, out);                     
                    out.flush();
                    out.close();                        
                    destination = new File(destinationFolder, destinationImageName);                        
                    FileBody filePhoto = new FileBody(destination);
                    entity.addPart("image", filePhoto);
                } catch (Exception e) {
                    Log.w(ProductForm.class.getSimpleName(), e);
                }

            }
            SharedPreferences userSettings = getSharedPreferences("UserPreferences", Context.MODE_PRIVATE); 
            Charset chars = Charset.forName("UTF-8");
            entity.addPart("barcode", new StringBody(bundle.getString("barcode"),chars));
            entity.addPart("name", new StringBody(bundle.getString("name"),chars));
            entity.addPart("description", new StringBody(bundle.getString("description"),chars));
            entity.addPart("id_type", new StringBody(bundle.getString("type_id")));
            entity.addPart("uid",new StringBody(userSettings.getString("uid", ""),chars));
            httpPost.setEntity(entity);
            HttpClient httpclient = new DefaultHttpClient();
            httpclient.execute(httpPost);

        } catch (IOException e) {
            //
        }

        return views[0];
    }

    @Override
    protected void onPreExecute() {
        dialog.setMax(100);
        dialog.setProgress(0);
        dialog.show();
    }

    @Override
    protected void onPostExecute(View view) {
        //redirect to the product page          
        setContentView(R.layout.product_barcode);           
        String url = String.format(getString(R.string.api_product_barcode_url), getString(R.string.api_url), bundle.getString("barcode"));  
        new LoadJSONBarcode().execute(url);
    }
}

//Send data to server and receive respond
private class LoadJSONBarcode extends AsyncTask<String, Void, JSONObject>{

    @Override
    protected JSONObject doInBackground(String... urls) {
        List<NameValuePair> params = new ArrayList<NameValuePair>();
        json = new JSONObject();
        json = jsonParser.getJSONFromUrl(urls[0], params);
        return json;
    }

    @Override
    protected void onPreExecute() {
        dialog.setMax(100);
        dialog.setProgress(0);
        dialog.show();
    }

    @Override
    protected void onPostExecute(JSONObject json) {

        if (json != null) {
            try {

                if (json.getString(KEY_SUCCESS) != null) {
                    String res = json.getString(KEY_SUCCESS);
                    if(Integer.parseInt(res) == 1){
                        View view = findViewById(R.id.productBarcodeXML);                       
                        Intent myIntent = new Intent(view.getContext(), Product.class);
                        Bundle b = new Bundle();
                        b.putString("id", json.getString(PRODUCT_ID));
                        myIntent.putExtras(b);
                        view.getContext().startActivity(myIntent);
                    } else {
                        errorMsg.setText(json.getString(ERROR_MSG));
                        errorMsgContainer.setVisibility(LinearLayout.VISIBLE);
                    }
                    dialog.dismiss();
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == TAKE_PICTURE) {
        if (data != null) {
            if (data.hasExtra("data")) { 
                preview.setImageBitmap((Bitmap) data.getParcelableExtra("data"));
                preview.setVisibility(ImageView.VISIBLE);
            }
        } else {
            preview.setImageBitmap(BitmapFactory.decodeFile(filename));
            preview.setVisibility(ImageView.VISIBLE);
            new MediaScannerConnectionClient() {
                private MediaScannerConnection msc = null; {
                    msc = new MediaScannerConnection(getApplicationContext(), this); msc.connect();
                }
                public void onMediaScannerConnected() { 
                    msc.scanFile(filename, null);
                }
                public void onScanCompleted(String path, Uri uri) { 
                    msc.disconnect();
                } 
            };              
        }
    } else if (requestCode == SELECT_PICTURE){
        if (data != null){ 
            Uri selectedImage = data.getData();
            InputStream is;
            String[] filePathColumn = {MediaStore.Images.Media.DATA};
            Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
            cursor.moveToFirst();

            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            filename = cursor.getString(columnIndex);
            cursor.close();

            try {
                is = getContentResolver().openInputStream(selectedImage);
                BufferedInputStream bis = new BufferedInputStream(is);
                Bitmap bitmap = BitmapFactory.decodeStream(bis);            
                preview.setImageBitmap(bitmap);     
                preview.setVisibility(ImageView.VISIBLE);
            } catch (FileNotFoundException e) {

            }
        }
    } 
}

private TypeClass[] convertJSONArray(JSONArray jsonArray){
    int len = jsonArray.length();
    TypeClass[] t = new TypeClass[len];
    if (jsonArray != null) { 
        for (int i=0;i<len;i++){ 
            try {
                JSONObject o = jsonArray.getJSONObject(i);
                t[i] = new TypeClass();
                t[i].setName(o.getString(TYPE_NAME));
                t[i].setId(o.getString(TYPE_ID));
            } catch (JSONException e) {
                e.printStackTrace();
            }
       } 
    } 
    return t;
}

protected boolean _checkFormValues() {

    boolean result = true;

    if (name.getText().length() == 0) {
        name.requestFocus();
        result = false;
    }
    if (((TypeClass) subtype.getSelectedItem()).getId() == null){
        subtype.requestFocus();
        result = false;
    }
    return result;
}

}

Error Log

11-07 23:55:26.914: E/AndroidRuntime(15457): FATAL EXCEPTION: AsyncTask #3
11-07 23:55:26.914: E/AndroidRuntime(15457): java.lang.RuntimeException: An error occured while executing doInBackground()
11-07 23:55:26.914: E/AndroidRuntime(15457):    at android.os.AsyncTask$3.done(AsyncTask.java:278)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.lang.Thread.run(Thread.java:864)
11-07 23:55:26.914: E/AndroidRuntime(15457): Caused by: java.lang.OutOfMemoryError: (Heap Size=35491KB, Allocated=27993KB)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at android.graphics.BitmapFactory.nativeDecodeFile(Native Method)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:373)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:443)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at prod.vegs.ProductForm$SendDataJSON.doInBackground(ProductForm.java:272)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at prod.vegs.ProductForm$SendDataJSON.doInBackground(ProductForm.java:1)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at android.os.AsyncTask$2.call(AsyncTask.java:264)
11-07 23:55:26.914: E/AndroidRuntime(15457):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
11-07 23:55:26.914: E/AndroidRuntime(15457):    ... 4 more
VMAtm
  • 27,943
  • 17
  • 79
  • 125
jonaypelluz
  • 368
  • 4
  • 13
  • 3
    Well, how large can the images be? If you load a 5 Megapixel photo, and then resize it to 1k*1k while keeping the old one in memory, you may end up with 24MB taken by only that one image. – Jave Nov 12 '12 at 22:02
  • http://stackoverflow.com/a/12819091/726863 – Lalit Poptani Nov 19 '12 at 05:13
  • There is a lot of code on your post, can you reduce it to the most relevant, OR consider removing irrelevant code. – Siddharth Nov 19 '12 at 09:54

5 Answers5

10

Bitmaps are very big memory consumers. Having two loaded into memory could be the big issue. You should consider using BitmapFactory.Options when you decode a new bitmap. Also, you don't need bm2. Instead, replace that line with this:

bm = Bitmap.createScaledBitmap(bm, width, height, true);

Finally, if you have no other options, you can increase your app's heap size using the Application attribute android:largeHeap="true" in your AndroidManifest.xml. This option should not be needed - and should only be considered for extremely graphic-intensive applications.

EDIT

Another link you may find helpful for optimizing Bitmap usage: http://developer.android.com/training/tv/optimizing-layouts-tv.html#HandleLargeBitmaps

Phil
  • 35,852
  • 23
  • 123
  • 164
  • To expand on Phil's comment, you'll want to set the `inJustDecodeBounds` option when you load the first `Bitmap`: http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html#inJustDecodeBounds – twaddington Nov 16 '12 at 00:51
  • I have tryed and it doesn't work, but it is just in some models that it doesn't work because it goes ok in Nexus HTC hero, etc. Now I am testing it with HTC One S and it eats all the memory, but so far your answer is the more useful one. Thanks, I will keep trying until I fix and I will update the question. – jonaypelluz Nov 19 '12 at 10:14
4

Bitmaps use up a lot of memory is clear. You need to take care of the following to use bitmaps effectively

  • Remember that your png's in drawables are automatically resized based on screen size by android. And that takes up a lot of memory. Android does not resize images if it finds images of its needed size / scale in the correct drawable folder. So to start with copy all ldpi files to hdpi too. This will reduce your memory utilization by 40% atleast. I know how this sounds, but this is true, profile your app using debuggable=true in the manifest, and heap utilization using ddms. Make the change and run exactly the same scenario, you will notice the 40% reduction.
  • When you read your bitmap, scale it down. Dont use just CreateBitmap, since it will read the entire file and utilize a lot more memory. Instead use BitmapOptions and scale it down. To scale it down you first need to set your options, and then use this options as a parameter to your CreateBitmap call. Here is a nice link. Search more on stackoverflow you will find some more interesting responses.
  • If you have any files in your drawables that are 1024X512, scale them down. OR Create new files that are clear but smaller in size. Use these files for mdpi, delete ldpi. Use the 1024X512 for your hdpi folder.
  • Explore the possibility of using smaller files, sort by size and play around with it a bit. The graphical view on eclipse for xml files is really neat, and relatively bug free. Use it.
  • Edited : Dont forget to null your bitmap for garbage collection. This is most important.
Community
  • 1
  • 1
Siddharth
  • 9,349
  • 16
  • 86
  • 148
  • I have been using DDMS and now I know where the memory leaks is. When I chose the image from the folder or I take, it is in that moment that I loose most of the memory, with Phil answer and yours I am trying to fix it, thanks! – jonaypelluz Nov 19 '12 at 10:17
  • Added one more fix that u need to make. – Siddharth Nov 19 '12 at 10:20
2

A few general hints:

  • As @Phil suggested, Bitmap objects tend to eat a lot of memory in Android. You should always use SoftReferences to hold bitmaps so that the OS can free the memory when needed.
  • You should also use the recycle() method to throw away transformation bitmaps (ie, your bm2 variable) when you are finished with them.
  • As paranoid as it sounds, setting Bitmaps to NULL when you are finished with them is a good practice to hint to the garbage collector that they can be collected. However, as a general rule calling the gc manually in Android either makes matters worse or has no effect.
  • And finally, profile your application using ddms!
Community
  • 1
  • 1
Nik Reiman
  • 39,067
  • 29
  • 104
  • 160
0

Can't say for sure, as I'm not the Java developer, but in .NET BitMap calss is IDisposable and is recommended to be recycled as soon as it is not in use.
May be you should free your memory after BitMap loading?

VMAtm
  • 27,943
  • 17
  • 79
  • 125
0

Yes A memory cache offers fast access to bitmaps at the cost of taking up valuable application memory. If LruCache can't resolve the problem you can try this:ImageManager ,in the class ImageManager, it has a method recycleBitmaps.

Jamesprite
  • 15
  • 7
  • Using LRUCache is only recommended if he is reusing the bitmaps. Here the case is not that. Just using the bitmap is causing a crash. – Siddharth Nov 19 '12 at 10:21
  • @jonaypelluz Maybe, you can try this [ImageManager](http://blog.pseudoblue.com/2010/08/15/android-bitmaps-and-memory-leaks/) in the class ImageManager, it has a method recycleBitmaps. – Jamesprite Nov 19 '12 at 10:36
  • I have seen LruCache and I was going to implement it in another app, but I think I will have a go with it to check it solves the problem in this one. thanks! – jonaypelluz Nov 19 '12 at 11:32