问题
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
来源:https://stackoverflow.com/questions/17996888/rotating-the-phone-causes-memory-out-of-memory