Skip to content

Commit

Permalink
fix: stop memory leak in CoordTileProvider (#1929)
Browse files Browse the repository at this point in the history
Bitmaps served by this provider cause memory leaks after the map is destroyed. Narrowing the scope to a field allows it to be garbage collected and does not affect the provider/activity's ability to serve and render the custom tiles.
  • Loading branch information
stanfchristina authored Dec 19, 2024
1 parent 66db0fa commit 55c6297
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
Expand Down Expand Up @@ -59,49 +60,48 @@ private static class CoordTileProvider implements TileProvider {

private final float scaleFactor;

private final Bitmap borderTile;

public CoordTileProvider(Context context) {
/* Scale factor based on density, with a 0.6 multiplier to increase tile generation
* speed */
scaleFactor = context.getResources().getDisplayMetrics().density * 0.6f;
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStyle(Paint.Style.STROKE);
borderTile = Bitmap.createBitmap((int) (TILE_SIZE_DP * scaleFactor),
(int) (TILE_SIZE_DP * scaleFactor), android.graphics.Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(borderTile);
canvas.drawRect(0, 0, TILE_SIZE_DP * scaleFactor, TILE_SIZE_DP * scaleFactor,
borderPaint);
}

@Override
public Tile getTile(int x, int y, int zoom) {
Bitmap coordTile = drawTileCoords(x, y, zoom);
Bitmap coordTile = createTile(x, y, zoom);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
coordTile.compress(Bitmap.CompressFormat.PNG, 0, stream);
byte[] bitmapData = stream.toByteArray();
return new Tile((int) (TILE_SIZE_DP * scaleFactor),
(int) (TILE_SIZE_DP * scaleFactor), bitmapData);
}

private Bitmap drawTileCoords(int x, int y, int zoom) {
// Synchronize copying the bitmap to avoid a race condition in some devices.
Bitmap copy = null;
synchronized (borderTile) {
copy = borderTile.copy(android.graphics.Bitmap.Config.ARGB_8888, true);
}
Canvas canvas = new Canvas(copy);
private Bitmap createTile(int x, int y, int zoom) {
Bitmap tile =
Bitmap.createBitmap(
(int) (TILE_SIZE_DP * scaleFactor),
(int) (TILE_SIZE_DP * scaleFactor),
Config.ARGB_8888);
Canvas canvas = new Canvas(tile);

// Draw the tile borders.
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(0, 0, TILE_SIZE_DP * scaleFactor,
TILE_SIZE_DP * scaleFactor, borderPaint);

// Draw the tile position text.
String tileCoords = "(" + x + ", " + y + ")";
String zoomLevel = "zoom = " + zoom;
/* Paint is not thread safe. */
Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(18 * scaleFactor);
canvas.drawText(tileCoords, TILE_SIZE_DP * scaleFactor / 2,
TILE_SIZE_DP * scaleFactor / 2, mTextPaint);
canvas.drawText(zoomLevel, TILE_SIZE_DP * scaleFactor / 2,
TILE_SIZE_DP * scaleFactor * 2 / 3, mTextPaint);
return copy;

return tile;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Bundle;
Expand Down Expand Up @@ -58,49 +59,48 @@ private static class CoordTileProvider implements TileProvider {

private final float mScaleFactor;

private final Bitmap mBorderTile;

public CoordTileProvider(Context context) {
/* Scale factor based on density, with a 0.6 multiplier to increase tile generation
* speed */
mScaleFactor = context.getResources().getDisplayMetrics().density * 0.6f;
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStyle(Paint.Style.STROKE);
mBorderTile = Bitmap.createBitmap((int) (TILE_SIZE_DP * mScaleFactor),
(int) (TILE_SIZE_DP * mScaleFactor), android.graphics.Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mBorderTile);
canvas.drawRect(0, 0, TILE_SIZE_DP * mScaleFactor, TILE_SIZE_DP * mScaleFactor,
borderPaint);
}

@Override
public Tile getTile(int x, int y, int zoom) {
Bitmap coordTile = drawTileCoords(x, y, zoom);
Bitmap coordTile = createTile(x, y, zoom);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
coordTile.compress(Bitmap.CompressFormat.PNG, 0, stream);
byte[] bitmapData = stream.toByteArray();
return new Tile((int) (TILE_SIZE_DP * mScaleFactor),
(int) (TILE_SIZE_DP * mScaleFactor), bitmapData);
}

private Bitmap drawTileCoords(int x, int y, int zoom) {
// Synchronize copying the bitmap to avoid a race condition in some devices.
Bitmap copy = null;
synchronized (mBorderTile) {
copy = mBorderTile.copy(android.graphics.Bitmap.Config.ARGB_8888, true);
}
Canvas canvas = new Canvas(copy);
private Bitmap createTile(int x, int y, int zoom) {
Bitmap tile =
Bitmap.createBitmap(
(int) (TILE_SIZE_DP * mScaleFactor),
(int) (TILE_SIZE_DP * mScaleFactor),
Config.ARGB_8888);
Canvas canvas = new Canvas(tile);

// Draw the tile borders.
Paint borderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
borderPaint.setStyle(Paint.Style.STROKE);
canvas.drawRect(0, 0, TILE_SIZE_DP * mScaleFactor,
TILE_SIZE_DP * mScaleFactor, borderPaint);

// Draw the tile position text.
String tileCoords = "(" + x + ", " + y + ")";
String zoomLevel = "zoom = " + zoom;
/* Paint is not thread safe. */
Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(18 * mScaleFactor);
canvas.drawText(tileCoords, TILE_SIZE_DP * mScaleFactor / 2,
TILE_SIZE_DP * mScaleFactor / 2, mTextPaint);
canvas.drawText(zoomLevel, TILE_SIZE_DP * mScaleFactor / 2,
TILE_SIZE_DP * mScaleFactor * 2 / 3, mTextPaint);
return copy;

return tile;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,32 +48,38 @@ class TileCoordinateDemoActivity : AppCompatActivity(), OnMapReadyCallback {

private class CoordTileProvider(context: Context) : TileProvider {
private val scaleFactor: Float
private val borderTile: Bitmap
override fun getTile(x: Int, y: Int, zoom: Int): Tile {
val coordTile = drawTileCoords(x, y, zoom)
val coordTile = createTile(x, y, zoom)
val stream = ByteArrayOutputStream()
coordTile!!.compress(Bitmap.CompressFormat.PNG, 0, stream)
val bitmapData = stream.toByteArray()
return Tile((TILE_SIZE_DP * scaleFactor).toInt(),
(TILE_SIZE_DP * scaleFactor).toInt(), bitmapData)
}

private fun drawTileCoords(x: Int, y: Int, zoom: Int): Bitmap? {
// Synchronize copying the bitmap to avoid a race condition in some devices.
var copy: Bitmap? = null
synchronized(borderTile) { copy = borderTile.copy(Bitmap.Config.ARGB_8888, true) }
val canvas = Canvas(copy!!)
private fun createTile(x: Int, y: Int, zoom: Int): Bitmap? {
val tile = Bitmap.createBitmap((TILE_SIZE_DP * scaleFactor).toInt(),
(TILE_SIZE_DP * scaleFactor).toInt(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(tile)

// Draw the tile borders.
val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
borderPaint.style = Paint.Style.STROKE
canvas.drawRect(0f, 0f, TILE_SIZE_DP * scaleFactor, TILE_SIZE_DP * scaleFactor,
borderPaint)

// Draw the tile position text.
val tileCoords = "($x, $y)"
val zoomLevel = "zoom = $zoom"
/* Paint is not thread safe. */
val mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mTextPaint.textAlign = Paint.Align.CENTER
mTextPaint.textSize = 18 * scaleFactor
canvas.drawText(tileCoords, TILE_SIZE_DP * scaleFactor / 2,
TILE_SIZE_DP * scaleFactor / 2, mTextPaint)
canvas.drawText(zoomLevel, TILE_SIZE_DP * scaleFactor / 2,
TILE_SIZE_DP * scaleFactor * 2 / 3, mTextPaint)
return copy

return tile
}

companion object {
Expand All @@ -84,13 +90,6 @@ class TileCoordinateDemoActivity : AppCompatActivity(), OnMapReadyCallback {
/* Scale factor based on density, with a 0.6 multiplier to increase tile generation
* speed */
scaleFactor = context.resources.displayMetrics.density * 0.6f
val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)
borderPaint.style = Paint.Style.STROKE
borderTile = Bitmap.createBitmap((TILE_SIZE_DP * scaleFactor).toInt(),
(TILE_SIZE_DP * scaleFactor).toInt(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(borderTile)
canvas.drawRect(0f, 0f, TILE_SIZE_DP * scaleFactor, TILE_SIZE_DP * scaleFactor,
borderPaint)
}
}
}

0 comments on commit 55c6297

Please sign in to comment.