Memory leak in Android when trying to send a form

2019-02-21 17:51发布

问题:

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

回答1:

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



回答2:

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.


回答3:

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!


回答4:

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?



回答5:

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.