背景
本文的背景,是因为我在开发高德地图时,需要自定义高德比例尺位置和样式;但结果查看了AMap Flutter插件和AMap SDK源码后,发现AMap无法添加自定义MyMethodCallHandler的实现类!
why?
源码
在Flutter中,高德地图的每个地图视图都是通过AMapPlatformView
类生成且管理的,源码如下
java
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 [email protected]
* @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();
}
}
分析
通过上面的源码可以发现如下几点:
- 在它onMethodCall回调方法中,判断了方法名是否在
myMethodCallHandlerMap
中存在
java
@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();
}
}
myMethodCallHandlerMap
是一个final
属性,这也就意味着,它在构造函数中已经实例化了,且不能更改值,只能修改内容,它通过initMyMethodCallHandlerMap
注册所有的Hnadler
java
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);
}
}
- 通过
initMyMethodCallHandlerMap
方法可以看出,它只注册了mapController
、markersController
、polylinesController
、polygonsController
四类Handler
,且没有提供可以自定义注册的方法,所以我也无能为力了
java
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环境中必须通过PathClassLoader
、DexClassLoader
来加载类,具体加载方法,可以参考其他大神的博客,我这里不做过多追述,因为我不推荐
2. 通过PlatformViewsController
来获取视图(推荐
)
推荐这个方法是因为它可以i通过mapId直接获取,且不用过多介入到内存和进程中的交互中
- 创建一个自定义
MethodChannel
java
var methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method_channel_id")
methodChannel.setMethodCallHandler { call, result ->
run {
//....
}
}
AMapPlatformView
继承PlatformView
,所以可以通过FlutterEngine.getPlatformViewsController().getPlatformViewById(viewId)
方式获取,获取到的是TextureMapView
视图,TextureMapView.getMap()
可以获取AMap
类,从而实现对当前地图视图进行任何操作,
kotlin
class MainActivity : FlutterActivity() {
var handerControllerMap = HashMap<String, IMyMethodCallHander>();
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
IFlutterFactory.engine=flutterEngine;
}
}
java
/**
* 当前缩放级别下,地图上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());
}
}
viewId
其实就是mapId
,可以通过AMapController.mapId
获取,而AMapController
可以通过AMapWidget
的onMapCreated
的回调方法获取
dart
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');
}