android - 在后台获取 GoogleMap 快照

标签 android google-maps rx-java android-mapview

我试图在后台获取 GoogleMap(来自 MapView)的快照,通过回调返回它并在 ImageView 中显示它。在这样做的同时,我试图让快照部分尽可能独立于布局(因此,尽量避免将 MapView 添加到布局中)。我尝试过以编程方式创建具有给定大小的 MapView,在其上添加一些路径和引脚,获取快照并返回它(全部使用 RxJava 订阅)但我收到以下错误并且应用程序正在关闭甚至没有“应用停止工作”对话框。

FATAL EXCEPTION: androidmapsapi-Snapshot Process:
com.package.name.debug, PID: 2159
    java.lang.IllegalArgumentException: width and height must be > 0
    at android.graphics.Bitmap.createBitmap(Bitmap.java:1013)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:980)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:930)
    at android.graphics.Bitmap.createBitmap(Bitmap.java:891)
    at com.google.maps.api.android.lib6.impl.ec.b(:com.google.android.gms.DynamiteModulesB@11975940:4)
    at com.google.maps.api.android.lib6.impl.bf.run(:com.google.android.gms.DynamiteModulesB@11975940:4)
    at java.lang.Thread.run(Thread.java:764)

获取快照的代码如下

fun bitmapFor(mapData: MapData, specs: Specs): Single<Bitmap?> {
    val mapView = MapView(context)
    mapView.layoutParams = ViewGroup.LayoutParams(specs.mapWidth, specs.mapHeight)

    return mapView.getMap()
            .subscribeOn(AndroidSchedulers.mainThread())
            .map { googleMap ->
                setupMap(googleMap, mapData) // Adds paths and pins
                googleMap
            }
            .flatMap { googleMap ->
                googleMap.snapshot(mapData)
            }
}

private fun MapView.getMap(): Single<GoogleMap> {
    return Single.create { subscriber ->

        val handler = Handler(Looper.getMainLooper())
        handler.post {
            onCreate(null)
            onStart()
            onResume()

            getMapAsync { googleMap ->
                subscriber.onSuccess(googleMap)
            }
        }
    }
}

private fun GoogleMap.snapshot(mapData: MapData): Single<Bitmap> {
    return Single.create { subscriber ->
        snapshot { bitmap ->
            saveMap(mapData.hashCode().toString(), bitmap)
            subscriber.onSuccess(bitmap)
        }
    }
}

在 UI 类中,我正在做

viewModel.mapProvider
        .bitmapFor(map, viewModel.mapData, Specs(map.measuredWidth, map.measuredHeight))
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(this::onMap, this::onError) // onMap set's the image bitmap

最佳答案

请查看更新!

似乎无法在不可见的 MapView 背景上获取 Google map 快照,因为本地图不可见时,它甚至没有开始加载。您可以为不可见的 MapVew 设置正确的大小(例如,基于 this 文章),但它似乎是带有“Google” Logo 的空黑条。可能最简单的解决方法 - 仍然显示 MapView,但禁用其上的控件和手势:

`public void onMapReady(GoogleMap googleMap) {
    mGoogleMap = googleMap;
    mGoogleMap.getUiSettings().setAllGesturesEnabled(false);
    ...
}`

或通过 Static Map API 下载 map 图像,并在下载的位图上本地绘制您需要的任何内容。

更新:

通过将其设置为 Lite Mode,可以在不可见的 MapView 背景上获取 Google map 快照。关于创作:

...
GoogleMapOptions options = new GoogleMapOptions()
        .compassEnabled(false)
        .mapToolbarEnabled(false)
        .camera(CameraPosition.fromLatLngZoom(KYIV,15))
        .liteMode(true);
mMapView = new MapView(this, options);
...

并且有必要通过measure()layout()onMapReady()中确定MapView的大小> 启动 map 图像加载的调用 - 在 map 图像加载后,可以在 onMapLoaded() 回调中获取它:

mMapView.setDrawingCacheEnabled(true);
mMapView.measure(View.MeasureSpec.makeMeasureSpec(mMapWidth, View.MeasureSpec.EXACTLY),
        View.MeasureSpec.makeMeasureSpec(mMapHeight, View.MeasureSpec.EXACTLY));
mMapView.layout(0, 0, mMapWidth, mMapHeight);
mMapView.buildDrawingCache(true);
Bitmap b = Bitmap.createBitmap(mMapView.getDrawingCache());  // <- that is bitmap with map image
mMapView.setDrawingCacheEnabled(false);

必须在 getMapAsyhc() 之前调用 MapView 对象上的 onCreate()otherwise no map will appear .

完整源代码:

public class MainActivity extends AppCompatActivity {

    private static final String MAP_VIEW_BUNDLE_KEY = "MapViewBundleKey";
    static final LatLng KYIV = new LatLng(50.450311, 30.523730);

    private ImageView mImageView;

    private MapView mMapView;
    // dimensions of map image
    private int mMapWidth = 600;
    private int mMapHeight = 800;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mImageView = (ImageView) findViewById(R.id.image_view);

        Bundle mapViewBundle = null;
        if (savedInstanceState != null) {
            mapViewBundle = savedInstanceState.getBundle(MAP_VIEW_BUNDLE_KEY);
        }

        GoogleMapOptions options = new GoogleMapOptions()
                .compassEnabled(false)
                .mapToolbarEnabled(false)
                .camera(CameraPosition.fromLatLngZoom(KYIV,15))
                .liteMode(true);
        mMapView = new MapView(this, options);
        mMapView.onCreate(mapViewBundle);

        mMapView.getMapAsync(new OnMapReadyCallback() {
            @Override
            public void onMapReady(GoogleMap googleMap) {

                // form your card (add markers, path etc.) here: 
                googleMap.addMarker(new MarkerOptions()
                        .position(KYIV)
                        .title("Kyiv"));

                // set map size in pixels and initiate image loading
                mMapView.measure(View.MeasureSpec.makeMeasureSpec(mMapWidth, View.MeasureSpec.EXACTLY),
                        View.MeasureSpec.makeMeasureSpec(mMapHeight, View.MeasureSpec.EXACTLY));
                mMapView.layout(0, 0, mMapWidth, mMapHeight);

                googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() {
                    @Override
                    public void onMapLoaded() {
                        mMapView.setDrawingCacheEnabled(true);
                        mMapView.measure(View.MeasureSpec.makeMeasureSpec(mMapWidth, View.MeasureSpec.EXACTLY),
                                View.MeasureSpec.makeMeasureSpec(mMapHeight, View.MeasureSpec.EXACTLY));
                        mMapView.layout(0, 0, mMapWidth, mMapHeight);
                        mMapView.buildDrawingCache(true);
                        Bitmap b = Bitmap.createBitmap(mMapView.getDrawingCache());
                        mMapView.setDrawingCacheEnabled(false);
                        mImageView.setImageBitmap(b);

                    }
                });

            }
        });
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        Bundle mapViewBundle = outState.getBundle(MAP_VIEW_BUNDLE_KEY);
        if (mapViewBundle == null) {
            mapViewBundle = new Bundle();
            outState.putBundle(MAP_VIEW_BUNDLE_KEY, mapViewBundle);
        }
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="{PACKAGE_NAME}.MainActivity">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        />

</RelativeLayout>

在 map 上获取带有标记的屏幕截图:

MapView screenshot example

如果您希望在精简模式 map 上显示“我的位置”点并使用默认位置源,您需要调用 onResume()onPause() :

...
@Override
protected void onResume() {
    super.onResume();
    if (mMapView != null) {
        mMapView.onResume();
    }
}

@Override
protected void onPause() {
    if (mMapView != null) {
        mMapView.onPause();
    }
    super.onPause();
}
...

因为位置源只会更新between these calls .

关于android - 在后台获取 GoogleMap 快照,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48621401/

相关文章:

android - RxJava - 获取列表,对其项目进行操作,再次分组到列表中。 toList() 等待 onCompleted()

android - 单击微调器android时如何保持全屏?

android - 在执行一段代码之前确保两个线程都执行完

google-maps - 使用地理编码 api 有多少免费请求?

body 标签的 CSS 奇怪地影响了谷歌地图 Canvas

rx-java - flatMapCompletable 不调用给定的 Action

android - 添加 FacebookSDK 时 openssl 不被识别为内部或外部命令

android - 如何在包含 RealmList 作为属性的 Realm 中深度复制对象,并且不让 RealmList 对象引用同一对象

java - 是否可以将 BitmapDescriptor 转换为位图?

android - 如何在 Android 上对 RxJava Completable.error 进行单元测试