【问题标题】:Correct way to create and use Android map markers created from bitmap?创建和使用从位图创建的 Android 地图标记的正确方法?
【发布时间】:2014-01-28 23:41:33
【问题描述】:

从位图创建地图标记的正确方法是什么?

我正在使用 Google Maps Android v1,现在正在迁移到 v2。
我的地图包含多个从位图创建的标记。
每个标记由位图图像和上面的一些文本组成,每个标记可能不同。所以我必须修改内存中的每个位图。
我像这样加载标记图像:

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;
}

然后用画布在其上应用自定义文本:

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);
}

标记图像是 MDPI 设备上约 5KB 大小的多色 PNG 文件。

在某些设备上不时使用Google Maps Android v1(难以重现)我在解码位图图像时得到java.lang.OutOfMemoryError。我有一种感觉,我做错了什么...... 而且我想确定使用Google Maps Android v2 我不会遇到同样的问题。

【问题讨论】:

    标签: android android-maps android-maps-v2


    【解决方案1】:

    几天前我在切换到 v2 时遇到了同样的问题。 我认为最重要的是尽量减少将图像加载到内存中,因此只要您需要,就可以将标记原始位图保存在内存中。

    这是一个代码示例,展示了我是如何做到的:

    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);
        }
    }
    

    当然,如果您的不同标记数量有限,这很有效。只需在您的活动开始时创建工厂,并在停止时将其销毁,您将只有两个位图在内存中,并避免加载/释放大量位图。

    【讨论】:

    • 目前我有 20 个不同的标记。而且还会有更多的时间。我根据当前可见的地图投影使用动态标记加载。所以我不知道将标记位图缓存在内存中是否是一件好事,因为其中一些可能会显示一次并且再也不会出现......
    • 好吧,我认为缓存 50 个标记比加载 200 倍要好,考虑到 Android 中的 Bitmap 垃圾回收是多么奇怪。
    • 但是如果我在内存中缓存 20 个“原始”位图,那么我仍然需要为每个标记制作一个位图的副本,那么那些缓存的位图是不是有点多余?还是将它加载到内存中并复制它会更好?
    • 我的意见是尽量创建/回收尽可能少的位图。这就是为什么我有一个保存在工厂中的原始缩放位图的副本,以及另一个相同大小的位图,每次我需要从这个原件渲染一个新的标记时,我都会从原件重新写入我需要的添加一些文字。我只“复制”这个位图,就“创建一个新的位图”而言,因为然后我逐个像素地复制它,避免任何新的位图分配。
    • 有没有办法实际测试哪种方式更好?
    猜你喜欢
    • 1970-01-01
    • 2020-06-02
    • 1970-01-01
    • 2012-01-09
    • 1970-01-01
    • 1970-01-01
    • 2013-01-26
    相关资源
    最近更新 更多