【移动端】Flutter 获取Android AMap实例

文章讲述了作者在使用AMapFlutter开发过程中遇到的问题,即无法为高德地图添加自定义的MyMethodCallHandler实现类。作者分析了AMapPlatformView的源码,发现其限制了自定义Handler的注册,并提出了通过自定义MethodChannel和PlatformViewsController来绕过这一限制的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

本文的背景,是因为我在开发高德地图时,需要自定义高德比例尺位置和样式;但结果查看了AMap Flutter插件和AMap SDK源码后,发现AMap无法添加自定义MyMethodCallHandler的实现类!

why?

源码

在Flutter中,高德地图的每个地图视图都是通过AMapPlatformView类生成且管理的,源码如下

package com.amap.flutter.map;

import android.content.Context;
import android.os.Bundle;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;

import com.amap.api.maps.AMap;
import com.amap.api.maps.AMapOptions;
import com.amap.api.maps.TextureMapView;
import com.amap.flutter.map.core.MapController;
import com.amap.flutter.map.overlays.marker.MarkersController;
import com.amap.flutter.map.overlays.polygon.PolygonsController;
import com.amap.flutter.map.overlays.polyline.PolylinesController;
import com.amap.flutter.map.utils.LogUtil;

import java.util.HashMap;
import java.util.Map;

import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding;
import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.platform.PlatformView;


/**
 * @author whm
 * @date 2020/10/27 5:49 PM
 * @mail hongming.whm@alibaba-inc.com
 * @since
 */
public class AMapPlatformView
        implements
        DefaultLifecycleObserver,
        ActivityPluginBinding.OnSaveInstanceStateListener,
        MethodChannel.MethodCallHandler,
        PlatformView {
    private static final String CLASS_NAME = "AMapPlatformView";
    private final MethodChannel methodChannel;

    private MapController mapController;
    private MarkersController markersController;
    private PolylinesController polylinesController;
    private PolygonsController polygonsController;

    private TextureMapView mapView;

    private boolean disposed = false;

    private final Map<String, MyMethodCallHandler> myMethodCallHandlerMap;

    AMapPlatformView(int id,
                     Context context,
                     BinaryMessenger binaryMessenger,
                     LifecycleProvider lifecycleProvider,
                     AMapOptions options) {

        methodChannel = new MethodChannel(binaryMessenger, "amap_flutter_map_" + id);
        methodChannel.setMethodCallHandler(this);
        myMethodCallHandlerMap = new HashMap<String, MyMethodCallHandler>(8);

        try {
            mapView = new TextureMapView(context, options);
            AMap amap = mapView.getMap();
            mapController = new MapController(methodChannel, mapView);
            markersController = new MarkersController(methodChannel, amap);
            polylinesController = new PolylinesController(methodChannel, amap);
            polygonsController = new PolygonsController(methodChannel, amap);
            initMyMethodCallHandlerMap();
            lifecycleProvider.getLifecycle().addObserver(this);
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "<init>", e);
        }
    }

    private void initMyMethodCallHandlerMap() {
        String[] methodIdArray = mapController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, mapController);
            }
        }

        methodIdArray = markersController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, markersController);
            }
        }

        methodIdArray = polylinesController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, polylinesController);
            }
        }

        methodIdArray = polygonsController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, polygonsController);
            }
        }
    }


    public MapController getMapController() {
        return mapController;
    }

    public MarkersController getMarkersController() {
        return markersController;
    }

    public PolylinesController getPolylinesController() {
        return polylinesController;
    }

    public PolygonsController getPolygonsController() {
        return polygonsController;
    }


    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        LogUtil.i(CLASS_NAME, "onMethodCall==>" + call.method + ", arguments==> " + call.arguments);
        String methodId = call.method;
        if (myMethodCallHandlerMap.containsKey(methodId)) {
            myMethodCallHandlerMap.get(methodId).doMethodCall(call, result);
        } else {
            LogUtil.w(CLASS_NAME, "onMethodCall, the methodId: " + call.method + ", not implemented");
            result.notImplemented();
        }
    }


    @Override
    public void onCreate(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onCreate==>");
        try {
            if (disposed) {
                return;
            }
            if (null != mapView) {
                mapView.onCreate(null);
            }
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onCreate", e);
        }
    }

    @Override
    public void onStart(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onStart==>");
    }

    @Override
    public void onResume(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onResume==>");
        try {
            if (disposed) {
                return;
            }
            if (null != mapView) {
                mapView.onResume();
            }
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onResume", e);
        }
    }

    @Override
    public void onPause(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onPause==>");
        try {
            if (disposed) {
                return;
            }
            mapView.onPause();
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onPause", e);
        }
    }

    @Override
    public void onStop(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onStop==>");
    }

    @Override
    public void onDestroy(@NonNull LifecycleOwner owner) {
        LogUtil.i(CLASS_NAME, "onDestroy==>");
        try {
            if (disposed) {
                return;
            }
            destroyMapViewIfNecessary();
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onDestroy", e);
        }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle bundle) {
        LogUtil.i(CLASS_NAME, "onDestroy==>");
        try {
            if (disposed) {
                return;
            }
            mapView.onSaveInstanceState(bundle);
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onSaveInstanceState", e);
        }
    }

    @Override
    public void onRestoreInstanceState(@Nullable Bundle bundle) {
        LogUtil.i(CLASS_NAME, "onDestroy==>");
        try {
            if (disposed) {
                return;
            }
            mapView.onCreate(bundle);
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "onRestoreInstanceState", e);
        }
    }


    @Override
    public View getView() {
        LogUtil.i(CLASS_NAME, "getView==>");
        return mapView;
    }

    @Override
    public void dispose() {
        LogUtil.i(CLASS_NAME, "dispose==>");
        try {
            if (disposed) {
                return;
            }
            methodChannel.setMethodCallHandler(null);
            destroyMapViewIfNecessary();
            disposed = true;
        } catch (Throwable e) {
            LogUtil.e(CLASS_NAME, "dispose", e);
        }
    }

    private void destroyMapViewIfNecessary() {
        if (mapView == null) {
            return;
        }
        mapView.onDestroy();
    }


}

分析

通过上面的源码可以发现如下几点:

  1. 在它onMethodCall回调方法中,判断了方法名是否在myMethodCallHandlerMap中存在
@Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
        LogUtil.i(CLASS_NAME, "onMethodCall==>" + call.method + ", arguments==> " + call.arguments);
        String methodId = call.method;
        if (myMethodCallHandlerMap.containsKey(methodId)) {
            myMethodCallHandlerMap.get(methodId).doMethodCall(call, result);
        } else {
            LogUtil.w(CLASS_NAME, "onMethodCall, the methodId: " + call.method + ", not implemented");
            result.notImplemented();
        }
    }
  1. myMethodCallHandlerMap 是一个final属性,这也就意味着,它在构造函数中已经实例化了,且不能更改值,只能修改内容,它通过initMyMethodCallHandlerMap注册所有的Hnadler
private final Map<String, MyMethodCallHandler> myMethodCallHandlerMap;

AMapPlatformView(int id,
                 Context context,
                 BinaryMessenger binaryMessenger,
                 LifecycleProvider lifecycleProvider,
                 AMapOptions options) {

    methodChannel = new MethodChannel(binaryMessenger, "amap_flutter_map_" + id);
    methodChannel.setMethodCallHandler(this);
    myMethodCallHandlerMap = new HashMap<String, MyMethodCallHandler>(8);

    try {
        mapView = new TextureMapView(context, options);
        AMap amap = mapView.getMap();
        mapController = new MapController(methodChannel, mapView);
        markersController = new MarkersController(methodChannel, amap);
        polylinesController = new PolylinesController(methodChannel, amap);
        polygonsController = new PolygonsController(methodChannel, amap);
        initMyMethodCallHandlerMap();
        lifecycleProvider.getLifecycle().addObserver(this);
    } catch (Throwable e) {
        LogUtil.e(CLASS_NAME, "<init>", e);
    }
}
  1. 通过initMyMethodCallHandlerMap方法可以看出,它只注册了mapControllermarkersControllerpolylinesControllerpolygonsController四类Handler,且没有提供可以自定义注册的方法,所以我也无能为力了
private void initMyMethodCallHandlerMap() {
        String[] methodIdArray = mapController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, mapController);
            }
        }

        methodIdArray = markersController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, markersController);
            }
        }

        methodIdArray = polylinesController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, polylinesController);
            }
        }

        methodIdArray = polygonsController.getRegisterMethodIdArray();
        if (null != methodIdArray && methodIdArray.length > 0) {
            for (String methodId : methodIdArray) {
                myMethodCallHandlerMap.put(methodId, polygonsController);
            }
        }
    }

解决思路

1. 通过修改initMyMethodCallHandlerMap属性值(不推荐

通过上面分析可以想到,通过修改initMyMethodCallHandlerMap属性值,自定义handler,但前提时需要拿到对应的AMapPlatformView实例,通常的方法可以通过ServiceLoader来获取,但在Android中ServiceLoader是不用生效的,Android环境中必须通过PathClassLoaderDexClassLoader来加载类,具体加载方法,可以参考其他大神的博客,我这里不做过多追述,因为我不推荐

2. 通过PlatformViewsController来获取视图(推荐

推荐这个方法是因为它可以i通过mapId直接获取,且不用过多介入到内存和进程中的交互中

  1. 创建一个自定义MethodChannel
var methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method_channel_id")
        methodChannel.setMethodCallHandler { call, result ->
            run {
                //....
            }
        }
  1. AMapPlatformView继承PlatformView,所以可以通过FlutterEngine.getPlatformViewsController().getPlatformViewById(viewId)方式获取,获取到的是TextureMapView视图,TextureMapView.getMap()可以获取AMap类,从而实现对当前地图视图进行任何操作,
class MainActivity : FlutterActivity() {

    var handerControllerMap = HashMap<String, IMyMethodCallHander>();


    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        IFlutterFactory.engine=flutterEngine;
    }
}

/**
     * 当前缩放级别下,地图上1像素点对应的长度,单位米。
     * @param call
     * @param result
     */
    public void getScalePerPixel(MethodCall call, MethodChannel.Result result) {
        Object mapId = call.argument("mapId");
        if (null == call || null == mapId) {
            return;
        }

        try {
            View view = IFlutterFactory.engine.getPlatformViewsController().getPlatformViewById(Integer.parseInt(mapId.toString()));

            if (view != null) {
                result.success(((TextureMapView) view).getMap().getScalePerPixel());
            } else {
                result.error("ScaleControllerExecption", "获取比例尺数据失败!", "");
            }

        } catch (Exception e) {
            result.error("ScaleControllerExecption", e.getMessage(), e.getLocalizedMessage());
        }


    }
  1. viewId 其实就是mapId,可以通过AMapController.mapId获取,而AMapController可以通过AMapWidgetonMapCreated的回调方法获取
void onMapCreated(AMapController controller) {

	//连接自定义的MethodChannel
 	MethodChannel _channel=MethodChannel("method_channel_id");
	//将mapId传给android端
    double? scale=await _channel.invokeMethod<double>("scale#get",<String,dynamic>{
    "mapId":_mapController.mapId
    });

    print('-----scale---$scale');
}
Flutter 中,你可以通过 Android Gradle 插件 (Android.bp) 或者 Dart 的 platform_channel 来获取 Android SDK 版本号。以下是两种常见方法: **1. 使用 Android Gradle 插件:** 在 `build.gradle` 文件中的 `defaultConfig` 部分查找 `targetSdkVersion` 属性,它的值就是 Android SDK 的版本号。例如: ```groovy defaultConfig { // ... targetSdkVersion 30 // 这里就是 SDK 版本号 } ``` **2. 使用 Dart 的 platform_channel:** 如果你需要在运行时动态获取,可以创建一个 Android 应用模块,并使用 `MethodChannel` 来与 dart 主程打交道。首先,在 Android 库中创建一个 `MainActivity.kt` 并初始化 MethodChannel: ```kotlin import io.flutter.embedding.android.FlutterActivity import io.flutter.plugin.common.MethodChannel class MainActivity: FlutterActivity() { override fun configureFlutterEngine(flutterEngine: FlutterEngine) { val methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "getSDKVersion") methodChannel.setMethodCallHandler { call -> val result = if (call.method == "getSDKVersion") call.result.success(ApplicationInfo.currentApplication().targetSdkVersion.toString()) else call.result.notFound() } } // ... } ``` 然后,在 dart 代码中,通过 `MethodChannel` 调用该方法: ```dart import 'package:flutter/services.dart'; Future<int> getAndroidSDKVersion() async { final channel = MethodChannel('getSDKVersion'); try { return int.parse(await channel.invokeMethod('getSDKVersion')); } on PlatformException catch (_) { throw Exception('Failed to fetch Android SDK version.'); } } // 使用示例 int sdkVersion = await getAndroidSDKVersion(); print('Current SDK Version: $sdkVersion'); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Space Chars

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值