Rotating the phone causes memory out of memory

2019-06-10 08:36发布

问题:

I have a very simple Activity that draws a drawable on another drawable in a Canvas and in the end shows it on a ImageView. I know that creating bitmaps takes too much memory and scale them down and I know about leaking by keeping reference to context, but can not detect where it happens. by now after 4-5 times rotating I get out of memory error. (I've specified that on code)

Can you help me find where leak happens?

here is my code:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        int height = displaymetrics.heightPixels;
        int width = displaymetrics.widthPixels;


        // image view in main layout to be filled by raw bitmaps combination
        ImageView imageView = (ImageView) findViewById(R.id.image);

        // Get your images from their files
        // raw bitmaps taken from drawable folder
        //Bitmap bottomImage = BitmapFactory.decodeResource(getResources(), R.drawable.arsenal);
        //Bitmap topImage = BitmapFactory.decodeResource(getResources(), R.drawable.setare);

        Log.v("imageView", String.valueOf(imageView.getWidth()) + " " + imageView.getHeight());

        Bitmap bottomImage = decodeSampledBitmapFromResource(getResources(),
                R.drawable.arsenal, width, (int)0.75 * width);

        Bitmap topImage = decodeSampledBitmapFromResource(getResources(),
                R.drawable.setare, 400, 400);

        // a copy of the below bitmap that is mutable.

        Bitmap temp = bottomImage.copy(Bitmap.Config.ARGB_8888, true); /// ?! I get error here! 

        // not necessary, only for testing whether is possible to diractly cache a text view or not
        TextView t = (TextView) findViewById(R.id.text);
        t.setDrawingCacheEnabled(true);

        // canvas for drawing functions
        Canvas comboImage = new Canvas(temp);
        // Then draw the second on top of that
        comboImage.drawBitmap(topImage, 400f, 400f, null);


        // a paint to determine style of what would be drawn in canvas.
        Paint p = new Paint();
        p.setColor(Color.YELLOW);
        p.setStyle(Style.FILL);
        p.setTextSize(70);

        // manually draw a text on canvas
        comboImage.drawText("Ehsan Mirza Razi", 100, 100, p);

        // draw text view directly on canvas. 
        //by now causes out of memory exception 
        //comboImage.drawBitmap(t.getDrawingCache(), 1000f, 200f, new Paint());

        // drawing the temp drawable edited in canvas, on ImageView
        imageView.setImageDrawable(new BitmapDrawable(getResources(), temp));

        // To write the file out to the SDCard:
        OutputStream os = null;
        try {
            os = new FileOutputStream("myNewFileName.png");
            bottomImage.compress(CompressFormat.PNG, 100, os);
            topImage.recycle();
            bottomImage.recycle();
            temp.recycle();
        } catch(IOException e) {
            e.printStackTrace();
        }

    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    public static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            // Calculate ratios of height and width to requested height and width
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // Choose the smallest ratio as inSampleSize value, this will guarantee
            // a final image with both dimensions larger than or equal to the
            // requested height and width.
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
        }
        Log.v("shrink", String.valueOf(inSampleSize));
        return inSampleSize;
    }

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
            int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    @Override
    protected void onDestroy() {
        // TODO Auto-generated method stub
        super.onDestroy();

        //ImageView = null;

    }
}

回答1:

That is happening because You are creating Bitmaps. And whenever You rotate device it will again create without recycling previous bitmaps(Because onCreate() again call when you rotate device). So try with this way-

 public class MainActivity extends Activity {
  Bitmap bottomImage,topImage,temp;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    if(bottomImage!=null){
       bottomImage.recycle();
       bottomImage=null;
     }
      if(topImage!=null){
       topImage.recycle();
       topImage=null;
     }
     if(temp!=null){
       temp.recycle();
       temp=null;
     }

    DisplayMetrics displaymetrics = new DisplayMetrics();
    getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
    int height = displaymetrics.heightPixels;
    int width = displaymetrics.widthPixels;


    // image view in main layout to be filled by raw bitmaps combination
    ImageView imageView = (ImageView) findViewById(R.id.image);

    // Get your images from their files
    // raw bitmaps taken from drawable folder
    //Bitmap bottomImage = BitmapFactory.decodeResource(getResources(), R.drawable.arsenal);
    //Bitmap topImage = BitmapFactory.decodeResource(getResources(), R.drawable.setare);

    Log.v("imageView", String.valueOf(imageView.getWidth()) + " " + imageView.getHeight());

    bottomImage = decodeSampledBitmapFromResource(getResources(),
            R.drawable.arsenal, width, (int)0.75 * width);

    topImage = decodeSampledBitmapFromResource(getResources(),
            R.drawable.setare, 400, 400);

    // a copy of the below bitmap that is mutable.

    temp = bottomImage.copy(Bitmap.Config.ARGB_8888, true); /// ?! I get error here! 

    // not necessary, only for testing whether is possible to diractly cache a text view or not
    TextView t = (TextView) findViewById(R.id.text);
    t.setDrawingCacheEnabled(true);

    // canvas for drawing functions
    Canvas comboImage = new Canvas(temp);
    // Then draw the second on top of that
    comboImage.drawBitmap(topImage, 400f, 400f, null);


    // a paint to determine style of what would be drawn in canvas.
    Paint p = new Paint();
    p.setColor(Color.YELLOW);
    p.setStyle(Style.FILL);
    p.setTextSize(70);

    // manually draw a text on canvas
    comboImage.drawText("Ehsan Mirza Razi", 100, 100, p);

    // draw text view directly on canvas. 
    //by now causes out of memory exception 
    //comboImage.drawBitmap(t.getDrawingCache(), 1000f, 200f, new Paint());

    // drawing the temp drawable edited in canvas, on ImageView
    imageView.setImageDrawable(new BitmapDrawable(getResources(), temp));

    // To write the file out to the SDCard:
    OutputStream os = null;
    try {
        os = new FileOutputStream("myNewFileName.png");
        bottomImage.compress(CompressFormat.PNG, 100, os);
        topImage.recycle();
        bottomImage.recycle();
        temp.recycle();
    } catch(IOException e) {
        e.printStackTrace();
    }

}


@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.activity_main, menu);
    return true;
}

public static int calculateInSampleSize(
        BitmapFactory.Options options, int reqWidth, int reqHeight) {
    // Raw height and width of image
    final int height = options.outHeight;
    final int width = options.outWidth;
    int inSampleSize = 1;

    if (height > reqHeight || width > reqWidth) {

        // Calculate ratios of height and width to requested height and width
        final int heightRatio = Math.round((float) height / (float) reqHeight);
        final int widthRatio = Math.round((float) width / (float) reqWidth);

        // Choose the smallest ratio as inSampleSize value, this will guarantee
        // a final image with both dimensions larger than or equal to the
        // requested height and width.
        inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
    }
    Log.v("shrink", String.valueOf(inSampleSize));
    return inSampleSize;
}

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
        int reqWidth, int reqHeight) {

    // First decode with inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // Calculate inSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}

@Override
protected void onDestroy() {
    // TODO Auto-generated method stub
    super.onDestroy();

    //ImageView = null;

}
}

see this also



回答2:

On each rotation (portrait -> landscape and vice-versa) the activity is recreated and some devices don't have enough memory for that transformations in "crude" mode. You should check the following link since it explains how to handle this in a nice way. (imho)

Handling Android Rotation