【问题标题】:Android MapView overlay, caching bitmap on drawAndroid MapView覆盖,绘制时缓存位图
【发布时间】:2012-11-26 23:41:09
【问题描述】:

我有一个用于在我的MapView 上绘制路径的叠加层,但我注意到它每秒会不必要地重绘大约十次。 由于在draw 方法中我绘制了路径的每一段,这很容易成为效率问题。

出于这个原因,我决定缓存叠加层的内容,并在必要时重新绘制它,即路径发生变化、地图中心移动或缩放级别发生变化时。

现在,draw 方法的参数之一是要使用的 Canvas。我知道如何在上面绘制缓存的位图,问题是我不知道如何在位图上缓存画布的内容。
我无法实例化新的画布,也无法调用setBitmap,因为HardwareCanvas 中的画布如果调用该方法会抛出UnsupportedOperationException

所以,回顾一下,我有一个画布和一个位图,我如何将画布的内容复制到位图?

编辑
为了清楚起见,这是我的绘制方法,我不手动调用它,但即使地图根本没有移动,它仍然会被重复调用

public void draw(Canvas canvas, MapView map, boolean shadow) {
    if (shadow) {  
        // this overlay doesn't need to draw shadows  
        return;
    }
    if (paths.isEmpty()) {
        // nothing to draw
        return;
    }
    center = map.getMapCenter();
    zoomLevel = map.getZoomLevel();
    map.getDrawingRect(bounds);
    projection = map.getProjection();
    maxLevel = map.getMaxZoomLevel();
    for (MapPath mp : paths) {
        // adjust path width to current zoom
        adjustedWidth = mp.getWidth() * zoomLevel / maxLevel;
        if (adjustedWidth < MIN_WIDTH_TO_DRAW) {
            // path is too thin, avoid drawing it
            continue;
        }
        paint.setStrokeWidth(adjustedWidth);
        paint.setColor(mp.getColor());
        state = PathState.FIRST_POINT;
        path.reset();
        for (PathPoint pp : mp.getPoints()) {
            if (!pp.shoudAppearAtZoomLevel(zoomLevel)) {
                // do not draw this point at this zoom level
                continue;
            }
            // project a geopoint to a pixel
            projection.toPixels(pp.getGeoPoint(), point);
            inside = isInsideBounds(point, map);
            switch (state) {
            case FIRST_POINT:
                // move to starting point
                firstX = point.x;
                firstY = point.y;
                path.moveTo(firstX, firstY);
                break;
            case WAS_INSIDE:
                // segment is completely or partially on map
                path.lineTo(point.x, point.y);
                break;
            case WAS_OUTSIDE:
                if (inside) {
                    // segment is partially on map
                    path.lineTo(point.x, point.y);
                } else {
                    // segment is completely off map
                    path.moveTo(point.x, point.y);
                }
                break;
            }
            // update state
            state = inside ? PathState.WAS_INSIDE : PathState.WAS_OUTSIDE;
        }
        // workaround to avoid canvas becoming too big when path is mostly off screen
        path.moveTo(firstX, firstY);
        // draw this path to canvas
        canvas.drawPath(path, paint);
    }
    super.draw(canvas, map, shadow);
}

【问题讨论】:

    标签: android caching overlay android-mapview android-canvas


    【解决方案1】:

    您无法将位图显示到Mapviewcanvas 正在绘制的位置。

    方法应该如下:

    • 首先您创建自己的(空的和透明的)位图,其大小与MapView 画布相同
    • 然后为您的位图创建您赢得的画布(此画布是您用来绘制位图的绘图工具)并使用它绘制路径。
    • 最后,您将位图(已绘制路径)绘制到 MapView 画布上。

    但是,您提到的性能/效率问题可能是由于您现有解决方案的设计不正确。在中型设备中,我可以在大约 3 毫秒内绘制具有 10.000 个点的路径,而无需使用位图(并且有一些很好的理由不使用它们)。

    在我对这篇文章的回答中,有一些关于如何处理它的提示:Overlay.draw() calls many times。还要检查@shkschneider 在同一篇文章中的答案。

    --已编辑--

    仅通过查看代码,我无法弄清楚您收到此警告的原因......但是让它变得比需要的复杂得多。

    按以下方式组织你的代码:

    draw()methos 只检查是否有缩放变化(如果是,则询问要重建的路径)以及地图是否移动(如果是偏移路径)并最终绘制路径。

    @Override
    public void draw(Canvas canvas, MapView mapview, boolean shadow) {
        super.draw(canvas, mapview, shadow);
        if(shadow) return;
        if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
    
        Projection projection = mapview.getProjection();
        int lonSpanNew = projection.fromPixels(0,mapview.getHeight()/2).getLongitudeE6() - 
                projection.fromPixels(mapview.getWidth(),mapview.getHeight()/2).getLongitudeE6();
        if(lonSpanNew != pathInitialLonSpan)
            pathBuild();
        else{ //check if path need to be offset
            projection.toPixels(mp.getPoints().get(0), p1);
            if(p1.x != pathInitialPoint.x || p1.y != pathInitialPoint.y){
                path.offset(p1.x - pathInitialPoint.x, p1.y - pathInitialPoint.y);
                pathInitialPoint.x = p1.x;
                pathInitialPoint.y = p1.y;
            }
    
        }
        canvas.drawPath(path, paint); 
    }
    

    路径构建

    每次缩放更改时都必须构建路径。缩放变化检测是使用pathInitialLonSpan 完成的,因为getZoomLevel() 不会与地图缩放动画同步。

    private void pathBuild(){
        path.rewind(); 
        if(mp.getPoints() == null || mp.getPoints().size() < 2) return;
    
        Projection projection = mapView.getProjection();
        pathInitialLonSpan = projection.fromPixels(0,mapView.getHeight()/2).getLongitudeE6() - 
                projection.fromPixels(mapView.getWidth(),mapView.getHeight()/2).getLongitudeE6();
    
        projection.toPixels(mp.getPoints().get(0), pathInitialPoint);
        path.moveTo(pathInitialPoint.x,pathInitialPoint.y); 
    
        for(int i=1; i<mp.getPoints().size(); i++){
            projection.toPixels(mp.getPoints().get(i), p1);
            int distance2 = (pPrev.x - p1.x) * (pPrev.x - p1.x) + (pPrev.y - p1.y) * (pPrev.y - p1.y); 
            if(distance2 > 9){
                path.lineTo(p1.x,p1.y);
                pPrev.set(p1.x, p1.y);
            }
        }
    

    某些对象(即 p1、pPrev 等)是在类级别定义的,以避免在每次方法运行时创建新对象。

    Note: 我已经更改了变量名称以适合您使用的名称。我希望我没有犯任何错误,但你应该能够弄清楚。

    问候。

    【讨论】:

    • 我的地图没有任何减速,我很确定我的解决方案设计做得很好(我遵循了许多最佳实践,还包括了一些优化)它只是我认为缓存结果路径而不是每秒无限次重新计算它会更有效......但是您是否建议避免使用位图?我能问你为什么吗?
    • 我建议您重新检查最佳实践...首先,仅当地图移动/缩放或根据您的请求(不是每秒 10 次)时才会绘制叠加层,其次您可以将路线缓存在Path(虽然它在绘制时比位图慢一点,但它使用的内存要少得多,并且需要重新计算缩放变化,而不是地图移动)。
    • 使用位图的缺点是: 1 - 它们需要大量操作系统内存(如果使用 ARGB8888,则宽度 x 高度 x 2 字节)并且位图的内存在 3.0 之前的设备的堆内存之外维护,而不是GC有效收集,导致内存不足异常(只是goole)。 2 - 您需要在每次地图移动时重新计算位图(这不需要使用Path 对象),因为您需要在其中包含进入屏幕的路径的新部分并删除存在屏幕的部分。跨度>
    • 我并不是说它不能完成(我自己做了它来与使用Path 的解决方案进行比较),但对于一般路线和地图利用率来说,它在内存利用率(位图大小)和性能(更多重绘),并增加代码复杂度。
    • 感谢您的详细解释。我不知道为什么我的平局如此频繁地被调用,我将不得不对此进行调查。我曾经按照您的建议缓存路径,但我遇到了一个问题:在高缩放级别上,调用 canvas.drawPath 时,生成的位图会太大,以至于 android 拒绝绘制它并打印警告。你怎么处理这个?我不得不放弃这个解决方案并制作另一个只绘制屏幕上实际路径部分的解决方案,但是这样我不能在地图滚动时偏移路径
    猜你喜欢
    • 2010-12-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 2012-09-07
    • 1970-01-01
    相关资源
    最近更新 更多