前言

Flutter 作为目前通用的业界跨平台解决方案,开拓了一套全新的设计理念,通过自研的 UI 框架,反对高效构建多端平台上的利用,同时放弃着原生利用一样的高性能。在Flutter我的项目开发过程中,对插件的开发和复用可能进步开发效率,升高工程的耦合度。Flutter开发者能够引入对应插件就能够为我的项目疾速集成相干能力,从而专一于具体业务性能的实现。而在Flutter我的项目开发过程中面对通用业务逻辑拆分、或者须要对原生能力封装等场景时,开发者须要开发新的组件。

为缩小开发者同时开发Android和iOS利用的老本,晋升开发效率,升高集成地图SDK的门槛,腾讯位置服务团队也打算于业务实际中基于原生地图SDK能力封装一套地图Flutter插件,反对Flutter开发者跨平台调用地图SDK接口。笔者在2019年实习期间,曾基于过后的最新版本4.2.4的Android地图SDK,将地图SDK中一些罕用的根底的地图操作性能封装,构建了一套Android端的地图SDK Flutter插件。

现如今,地图SDK曾经迭代到了4.4.0版本,笔者也将地图Flutter插件进行了一次相干版本升级。本篇文章将介绍地图Flutter插件我的项目的构建、地图实例的加载以及demo示例出现。对于地图根底操作的性能封装细节将在后续文章中进行具体解说阐明。

地图Flutter插件我的项目的构建

地图Flutter插件我的项目构造

地图Flutter插件我的项目构架的整体构造如下图所示:

android/ios目录:原生代码。对应为Android/iOS Flutter插件目录。
lib目录:Dart 代码。Flutter开发者将会应用这里的Flutter插件实现的接口。
example目录:地图SDK的demo程序。用于验证Flutter插件的可用性的应用示例。

地图Flutter插件依赖配置项

Android端的Flutter插件配置项与官网对于Android地图SDK的配置阐明相似,须要配置android目录下的两个文件:build.gradle、AndroidManifest.xml。
其中Android端的Flutter插件的包名为com.tencent.tencentmap,AndroidManifest.xml文件配置如下:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"  package="com.tencent.tencentmap">    <!-- 腾讯地图 sdk 要求的权限(开始) -->    <!-- 拜访网络获取地图服务 -->    <uses-permission android:name="android.permission.INTERNET" />    <!-- 查看网络可用性 -->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <!-- 拜访WiFi状态 -->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <!-- 须要内部存储写权限用于保留地图缓存 -->    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    <!-- 获取 device id 分别设施 -->    <uses-permission android:name="android.permission.READ_PHONE_STATE" />    <!-- 获取日志读取权限,帮忙咱们进步地图 sdk 稳定性 -->    <uses-permission android:name="android.permission.READ_LOGS" />    <!-- 腾讯地图 sdk 要求的权限(完结) -->    <!-- 腾讯定位 sdk 要求的权限  (开始) -->    <!-- 通过GPS失去准确地位 -->    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />    <!-- 通过网络失去粗略地位 -->    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />    <!-- 拜访网络. 某些地位信息须要从网络服务器获取 -->    <uses-permission android:name="android.permission.INTERNET" />    <!-- 拜访WiFi状态. 须要WiFi信息用于网络定位 -->    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />    <!-- 批改WiFi状态. 发动WiFi扫描, 须要WiFi信息用于网络定位 -->    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />    <!-- 拜访网络状态, 检测网络的可用性. 须要网络运营商相干信息用于网络定位 -->    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />    <!-- 拜访网络的变动, 须要某些信息用于网络定位 -->    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />    <!-- 拜访手机以后状态, 须要device id用于网络定位 -->    <uses-permission android:name="android.permission.READ_PHONE_STATE" />    <!-- 腾讯定位 sdk 要求的权限 (完结) -->    <application>        <!-- 如果您key确认无误,却仍然受权没有通过,请查看您的key的白名单配置 -->        <meta-data            android:name="TencentMapSDK"            android:value="Your key"/>    </application></manifest>

本文应用的Android端地图SDK版本为4.4.0。同时,本文Flutter插件的实现语言是基于Kotlin实现。build.gradle的依赖配置项如下:

dependencies {    implementation 'com.android.support:appcompat-v7:27.1.1'    implementation 'com.tencent.map:tencent-map-vector-sdk:4.4.0'    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"    compile "org.jetbrains.kotlin:kotlin-script-runtime:1.2.71"}

地图Flutter插件加载地图实例

Flutter插件在下层UI Dart端与底层Native SDK端之间起到了一层桥接的作用。
Flutter端与Native端之间通信的流程如下图所示:

Flutter 跟Native代码能够通过 MethodChannel 进行通信。客户端通过 MethodChannel 将办法调用和参数产生给服务端,服务端也通过 MethodChannel 接管相干的数据。
因而,在Flutter插件开发中,MethodChannel与EventChannel是两个不可避免用到的类。
用比拟艰深的语言来解释这两个类的性能:

MethodChannel的作用是传递办法调用,例如在flutter端调用native端的办法或native端调用flutter端的办法。MethodChannel次要用于办法调用。

EventChannel的作用是发送音讯,当native层想告诉flutter层一些音讯的时候,native层发送音讯,Flutter接管音讯。EventChannel通常用于数据流通信。

后续文章将具体解说MethodChannel与EventChannel在地图SDK插件中的应用。
言归正传,本文重点要解说应用PlatformView对地图实例进行加载的流程。
PlatformView的应用形式是与MethodChannel的应用形式相似的,具体的加载地图实例流程如下:

(1)Native端创立TencentMapView

TencentMapView继承自PlatformView。
PlatformView为Flutter 1.0版本中的通用组件,辨别为Android和iOS。在Android平台上叫做 AndroidView组件,在iOS平台,叫UIKitView组件。
因而利用PlatformView构建加载Native SDK中的地图实例并在PlatformView中保护地图实例的生命周期。
TencentMapView中也退出了MethodChannel与EventChannel的注册逻辑,次要用于地图的接口进行双端交互,对于这两局部的阐明将在后续文章中进行具体介绍。
Android端的TencentMapView实现如下:

class TencentMapView(context: Context, private val id: Int, private val activityState: AtomicInteger, tencentMapOptions: TencentMapOptions) : PlatformView, Application.ActivityLifecycleCallbacks{    // 加载构建地图实例    private val mapView = MapView(context, tencentMapOptions)    private val registrarActivityHashCode: Int = TencentmapPlugin.registrar.activity().hashCode()        // 保护地图实例生命周期    fun setup(){        when(activityState.get()){            STOPPED -> {                mapView.onStop()            }            RESUMED -> {                mapView.onResume()            }            CREATED -> {                mapView.onStart()            }            DESTROYED -> {                mapView.onDestroy()            }        }        // flutter端调用地图native SDK相干性能的MethodChannel        val mapChannel = MethodChannel(registrar.messenger(), "$mapChannelName$id")        mapChannel.setMethodCallHandler { methodCall, result ->            MAP_METHOD_HANDLER[methodCall.method]                    ?.with(mapView.map)                    ?.onMethodCall(methodCall, result) ?: result.notImplemented()        }                // native SDK告诉flutter层相干音讯的EventChannel        val mapEventChannel = EventChannel(registrar.messenger(), "$mapChannelName$id")    }}

(2)在插件Native层的入口文件TencentmapPlugin.kt中注册刚写好的TencentMapView实例tencentMapView:

@JvmStatic    fun registerWith(registrar: PluginRegistry.Registrar){    //将TencentMapView实例注册到插件中    registrar.platformViewRegistry().registerViewFactory("com.tencentmap/map", tencentMapView)    }

(3)在Flutter端的dart代码应用AndroidView,将AndroidView嵌入到TencentMapView中:

class TencentMapView extends StatelessWidget{  const TencentMapView({    this.onTencentMapViewCreated,});  final MapCreatedCallback onTencentMapViewCreated;  @override  Widget build(BuildContext context) {    if (defaultTargetPlatform == TargetPlatform.android) {      return AndroidView(          viewType: 'com.tencentmap/map',          onPlatformViewCreated: _onViewCreated,          creationParams: {          },          creationParamsCodec: const StandardMessageCodec(),      );    }  }}

这里要留神的一点是,在Android端和Flutter端注册的viewType中的字符串值必须保持一致,用于惟一标识。在本文中的标识字符串为'com.tencentmap/map',将Flutter端的AndroidView与Native端的TencentMapView建设了关联。

Flutter插件对应Demo示例出现

Demo示例

demo UI采纳了Flutter自反对的Material Design格调的一套UI组件。
Flutter demo调用地图SDK展现地图实例的界面如图所示:

demo中还实现了地图根底操作的相干功能性接口,例如相干覆盖物的绘制等,示例如下图所示:

版本升级过程中遇到的小坑

在理论版本升级过程中,原有我的项目的demo运行起来是白屏,控制台打印出如下信息:

[VERBOSE-2:ui_dart_state.cc(157)] Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.If you're running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first.If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding.#0      defaultBinaryMessenger.<anonymous closure> (package:flutter/src/services/binary_messenger.dart:76:7)#1      defaultBinaryMessenger (package:flutter/src/services/binary_messenger.dart:89:4)#2      MethodChannel.binaryMessenger (package:flutter/src/services/platform_channel.dart:140:62)#3      MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:314:35)#4      MethodChannel.invokeMapMethod (package:flutter/src/services/platfo<…>

依据控制台的输入信息,通过查阅相干材料后找到了起因:该问题由Flutter版本升级导致的重大更改引起的:https://groups.google.com/g/f...
具体解决办法为:在main.dart文件中的main办法中,须要在runApp()前显式调用如下代码:

WidgetsFlutterBinding.ensureInitialized();

总结

本文次要介绍了腾讯地图SDK Flutter插件我的项目的构建、地图实例加载、demo出现,对地图根底功能性接口的封装细节,将会在后续文章继续解说。