Eric Sweeten
Lurker
My game was complete and ready to launch until I realized it didn't work on older phones (namely, anything before API level 23 or so had issues). I needed to do some memory management.
So, I started recycling bitmaps that weren't currently being used (and weren't going to be used in the immediate future, except through the click of a menu option). That allowed the game to run on API's as low as 19. 17 and 18 sometimes works and sometimes doesn't, which doesn't make any sense to me. It should either run the whole time or not at all. It occasionally has an OutOfMemory error with all the "GC_" messages. I'm guessing there must be a memory leak.
Any ideas? The following shows how I manage my memory:
Here's the Async bitmap loader class:
The first method here is where I load a set of specific sprites before I need them, and the following method is where I scrap them:
This is the scrampBitmaps method in Tools:
So, I started recycling bitmaps that weren't currently being used (and weren't going to be used in the immediate future, except through the click of a menu option). That allowed the game to run on API's as low as 19. 17 and 18 sometimes works and sometimes doesn't, which doesn't make any sense to me. It should either run the whole time or not at all. It occasionally has an OutOfMemory error with all the "GC_" messages. I'm guessing there must be a memory leak.
Any ideas? The following shows how I manage my memory:
Here's the Async bitmap loader class:
Code:
public static class BitmapDecodeTask extends AsyncTask<Void, Void, Bitmap>
{
//the reason to use a weak reference is to protect from memory leak issues.
private static WeakReference<Context> mContextReference;
private static WeakReference<Tools> mToolsReference;
private int bm;
public BitmapDecodeTask(final int bm)
{
this.bm = bm;
}
@Override
protected Bitmap doInBackground(Void... params)
{
Context context = mContextReference.get();
Tools tools = mToolsReference.get();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), bm, options);
final int width = tools.getRelX(options.outWidth);
final int height = tools.getRelY(options.outHeight);
return Bitmap.createScaledBitmap(decodeSampledBitmapFromResource(context.getResources(), bm, width, height), width, height, false);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
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;
}
return inSampleSize;
}
private 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);
}
public static void setContext(final Context context)
{
mContextReference = new WeakReference<>(context);
}
public static void setTools(final Tools tools)
{
mToolsReference = new WeakReference<>(tools);
}
}
The first method here is where I load a set of specific sprites before I need them, and the following method is where I scrap them:
Code:
public static void loadBitmapsHillaryBody()
{
if (spritesHillaryBodyWalkRight != null) return;
try
{
Bitmap[] sprites = new Bitmap[Num.I_8];
Bitmap bitmap = new Tools.BitmapDecodeTask(R.drawable.hillary_body_walk_right).execute().get();
for (int i = Num.I_0; i < Num.I_8; i++)
{
sprites[i] = Bitmap.createBitmap(bitmap, i * bitmap.getWidth() / Num.I_8, Num.I_0, bitmap.getWidth() / Num.I_8, bitmap.getHeight());
}
spritesHillaryBodyWalkRight = sprites;
sprites = Tools.flipSprites(sprites);
spritesHillaryBodyWalkLeft = sprites;
sprites = new Bitmap[Num.I_5];
bitmap = new Tools.BitmapDecodeTask(R.drawable.hillary_body_turn_left).execute().get();
for (int i = Num.I_0; i < Num.I_5; i++)
{
sprites[i] = Bitmap.createBitmap(bitmap, i * bitmap.getWidth() / Num.I_5, Num.I_0, bitmap.getWidth() / Num.I_5, bitmap.getHeight());
}
spritesHillaryBodyTurnLeft = sprites;
sprites = Tools.flipSprites(sprites);
spritesHillaryBodyTurnRight = sprites;
}
catch (final ExecutionException exc)
{
exc.printStackTrace();
}
catch (final InterruptedException exc)
{
exc.printStackTrace();
}
}
public static void scrapBitmaps()
{
Tools.scrapBitmaps(spritesHillaryBodyTurnLeft);
spritesHillaryBodyTurnLeft = null;
Tools.scrapBitmaps(spritesHillaryBodyTurnRight);
spritesHillaryBodyTurnRight = null;
Tools.scrapBitmaps(spritesHillaryBodyWalkLeft);
spritesHillaryBodyWalkLeft = null;
Tools.scrapBitmaps(spritesHillaryBodyWalkRight);
spritesHillaryBodyWalkRight = null;
}
This is the scrampBitmaps method in Tools:
Code:
public static void scrapBitmaps(Bitmap[] bitmaps)
{
if (bitmaps != null)
{
for (int i = Num.I_0; i < bitmaps.length; i++)
{
bitmaps[i].recycle();
}
}
}