Nature of Android memory leaks on orientation change and garbage collection

731 views Asked by At

I have an activity which loads and displays a somewhat large bitmap. Risky business in the first place, but we load it in using a BitmapFactory using the standard method below:

private static Bitmap decodeSampledBitmapFromFile(String filepath, int reqWidth,int orientation) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeFile(filepath,options);
    if(orientation==0){
    options.inSampleSize = calculateInSampleSizeWidth(options, reqWidth);
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    Bitmap d = BitmapFactory.decodeFile(filepath,options);
    return Bitmap.createScaledBitmap(d, reqWidth, d.getHeight()*reqWidth/d.getWidth(), true);
    }
    else{
        options.inSampleSize = calculateInSampleSizeHeight(options,reqWidth);
        options.inJustDecodeBounds = false;
        Bitmap d = BitmapFactory.decodeFile(filepath,options);
        return Bitmap.createScaledBitmap(d, d.getWidth()*reqWidth/d.getHeight(), reqWidth, true);
    }
}
private static int calculateInSampleSizeWidth( BitmapFactory.Options options, int reqWidth) {
    int inSampleSize = 1;   
    if (options.outWidth > reqWidth) {
        final int halfWidth = options.outWidth / 2;
        while ( (halfWidth / inSampleSize) > reqWidth) {
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

The loading of this Bitmap into the ImageView is done in an AsyncTask shown below:

private static class LoadBitmapTask extends AsyncTask<String, Void, Bitmap>{
    private ViewSampleActivity _activity;
    public LoadBitmapTask(ViewSampleActivity activity){
        this._activity = activity;
    }
    public void detach(){
        this._activity = null;
    }
    @Override
    protected Bitmap doInBackground(String... params) {
        try{
            String filename = params[0];
            Display display = _activity.getWindowManager().getDefaultDisplay();
            Point size = new Point();
            display.getSize(size);
            int orientation = getCameraPhotoOrientation(_activity,Uri.fromFile(new File(filename)),filename);
            Bitmap d = null;

            d = decodeSampledBitmapFromFile(filename, (int)(((double)size.x)*0.85),orientation);
            d = rotateBitmapAppropriately(_activity,Uri.fromFile(new File(filename)),filename,d);
            return d;
        }
        catch(Exception e){
            return null;
        }
    }

    @Override
    protected void onPostExecute(Bitmap result){
        try{
            _activity.sampleImageView.setImageBitmap(result);
            _activity.sampleImageView.setVisibility(View.VISIBLE);
            _activity.sampleImageView.getLayoutParams().height = result.getHeight();
            _activity.sampleImageView.getLayoutParams().width = result.getWidth();
            _activity.viewSampleLayout.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
            _activity.hasPicture = true;
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }
}

In order to try to avoid memory leaks, I've overloaded the activity's onStop method to detach the activity from the AsyncTask.

@Override
public void onStop(){
    if(this.loadBitmapTask!=null){
        loadBitmapTask.detach();
    }
    super.onStop();
}

Now, the strange behaviour I'm seeing is that the activity loads just fine on the first run. After an orientation change, the activity will be reloaded correctly. However, after five or so orientation changes, the application will crash with an out of memory error. The Bitmap would seem to be the obvious culprit. I'm not saving a static copy of any ui element or bitmap or anything, so I'm wondering if these out of memory errors are due to a memory leak.

The activity in question is the last of a hierarchy of activities. The root of the hierarchy, the main activity, has an android Handler object with a WeakReference to it, and also a persistent fragment which manages Bluetooth networking threads. The second activity in the hierarchy is a simple ListActivity.

Would the parent activities of the activity in question have any effect on Java's garbage collection of this activity?

1

There are 1 answers

1
JstnPwll On BEST ANSWER

Would Bitmap d in your decodeSampledBitmapFromFile() method be garbage collected? I'm not 100% clear on these concepts, but it seems like creating a scaled copy from d without recycling d itself is suspect.

I also recommend trying to re-attach to your AsyncTask instead of letting it die (and spawning more?!). If you're using FragmentActivity, you can use a non-configuration instance. Store a reference to your task in your activity, and save it as the non-configuration instance. Then attempt to re-attach onCreate of your activity:

private LoadBitmapTask mTask;

@Override
public Object onRetainCustomNonConfigurationInstance() {
    if(mTask != null){
       mTask.detach();
    }
    return mTask;
}

@Override
protected void onCreate(Bundle savedInstanceState) {
    mTask = (LoadBitmapTask) getLastCustomNonConfigurationInstance();

    if (mTask != null) {
        mTask.attach(this);
    }
}

This way you can "pick up where you left off" with the AsyncTask and not have to spawn more.