Martynas Jurkus
Martynas Jurkus

Reputation: 9301

Correct way to create and use Android map markers created from bitmap?

What would be the correct way of creating map markers from bitmap?

I'm using Google Maps Android v1 and moving to v2 now.
My map contains multiple markers that are created from bitmap.
Each marker consists of bitmap image and some text on it that could be different for each marker. So I have to modify each bitmap in memory.
I load marker images like this:

private Bitmap getMarker(final String name) {
    Bitmap result = BitmapFactory.decodeFile(context.getFilesDir() + "/markers/" + name + ".png");

    if (result == null) {
        // must make a mutable copy as by default resource is returned immutable
        result = decodeResource(context.getResources(), R.drawable.default_marker).copy(Config.ARGB_8888, true);
    } else {
        result = result.copy(Config.ARGB_8888, true);
    }

    return result;
}

And then apply custom text on it with canvas:

private void applyText(Bitmap marker, Paint paint, String text) {
    Canvas canvas = new Canvas(marker);
    canvas.drawText(text, calculateTextX(marker, paint, text), calculateTextY(marker, paint, text), paint);
}

Marker images are ~5KB size multicolor PNG files on MDPI devices.

With Google Maps Android v1 from time to time on some devices (hard to reproduce) I got java.lang.OutOfMemoryError while decoding Bitmap image. And I have a feeling that I'm doing something wrong... And I would like to know for sure that with Google Maps Android v2 I wont get the same problem.

Upvotes: 2

Views: 4275

Answers (1)

Thibault D.
Thibault D.

Reputation: 10005

I have had the same issue a few days ago when switching to v2. I think the most important thing is to try to load the images in memory as less as possible, and therefore to keep in memory your marker original Bitmap as long as you need it.

Here is a sample of code showing how I did it:

public class MyMarkerFactoryFactory {

    private Context ctx;
    private Bitmap cachedMarkerBase; // Cached bitmap
    private Bitmap currentMarker; // Working copy
    private final List<Marker> markers = new ArrayList<Marker>();

    public MyMarkerFactoryFactory(Context ctx, String markerName, int markerWidth, int markerHeight) {
        this.ctx = ctx;

        Bitmap src = BitmapFactory.decodeFile(ctx.getFilesDir() + "/markers/" + markerName + ".png");
        int srcBitmapWidth = src.getWidth();
        int srcBitmapHeight = src.getHeight();
        if (markerWidth != srcBitmapWidth && markerHeight != srcBitmapHeight) {
            // The rendering bitmap will depend on provided width and height
            // In my case it's because the bitmap does not have the same pixel size
            // depending on the display pixel density. So I've declared the size I
            // I want in "dp" somewhere else and fetch it from ctx.getDimen
            // createScaledBitmap will return the same bitmap if you are scaling
            // to the same size, so it's good to test the size before you rescale
            // otherwise you are likely to recycle() the bitmap you wanted to use.
            cachedMarkerBase = Bitmap.createScaledBitmap(src, markerWidth, markerHeight, false);
            src.recycle();
        } else {
            cachedMarkerBase = src;
        }
        currentMarker = cachedMarkerBase.copy(cachedMarkerBase.getConfig(), true);
    }

    public void onDestroy() {
        clearOverlays();
        if (cachedMarkerBase != null) {
            cachedMarkerBase.recycle();
            cachedMarkerBase = null;
        }
        if (currentMarker != null) {
            currentMarker.recycle();
            currentMarker = null;
        }
    }

    public void clearOverlays() {
        for (Marker marker : markers) {
            marker.remove();
        }
        markers.clear();
    }

    public void addOverlay(GoogleMap map, LatLng position, String myText) {
        drawMarkerWith(myText);
        BitmapDescriptor bitmapDescriptor = BitmapDescriptorFactory.fromBitmap(currentMarker);
        markers.add(map.addMarker(new MarkerOptions().anchor(0.5f, 0.5f).icon(bitmapDescriptor).position(position)));
    }

    private void drawMarkerWith(String myText) {
        // Copy image from cache
        for (int i = 0; i < cachedMarkerBase.getWidth(); i++) {
            for (int j = 0; j < cachedMarkerBase.getHeight(); j++) {
                currentMarker.setPixel(i, j, cachedMarkerBase.getPixel(i, j));
            }
        }
        int x = currentMarker.getWidth() / 2, y = currentMarker.getHeight() / 2;

        Paint paintForText = new Paint()
        paintForText.setTextSize(7f); // TODO

        // Draw text
        Canvas canvas = new Canvas(currentMarker);
        int x = 0; // TODO
        int y = 0; // TODO
        canvas.drawText(myText, x, y, paintForText);
    }
}

This works well, of course, if you have a limited number of different markers. Just create the factory when your activity starts, and destroy it when it stops, and you'll only have two bitmaps in memory, and avoid loading/releasing a lot of bitmaps.

Upvotes: 2

Related Questions