温馨提示:这篇文章已超过439天没有更新,请注意相关的内容是否还可用!
摘要:本文将介绍如何在Android和iOS平台上接入Flutter高德地图,包括定位和地图功能。通过简单的步骤,开发者可以实现在Flutter应用中集成高德地图服务,包括用户定位和地图展示。这将为用户提供便捷的位置服务和导航体验,加速移动应用的开发进程。
定位
Android
参考链接
官方文档
创建应用
创建Key
输入Key的名称
服务平台选择Android平台
接下里获取SHA1
PackageName输入项目的包名
打开Android目录,并生成签名文件
如果存在签名文件,如下图
若没有签名文件,则需要Create New 创建新的签名文件
填写密码,别名、文件位置等信息
创建完后,会发现目录下有签名文件,可以移到app目录下
配置签名文件
signingConfigs { release { //keystore中key的别名 keyAlias 'key0' //keystore中key的密码 keyPassword '123456' //keystore的文件路径,可以是绝对路径也可以是相对路径 storeFile file('./deman_mobo.jks') //keystore的密码l storePassword '123456' } } buildTypes { release { // TODO: Add your own signing config for the release build. // Signing with the debug keys for now, so `flutter run --release` works. signingConfig signingConfigs.release minifyEnabled true //删除无用代码 shrinkResources true //删除无用资源 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { signingConfig signingConfigs.release debuggable true } }
获取签名文件的SHA1
keytool -list -v -keystore ./gaode_key.jks
引入依赖
dependencies { implementation('com.amap.api:location:5.6.0') }
Flutter 配置项目
amap_flutter_location: ^3.0.0 permission_handler: ^11.3.0
申明权限
配置服务
测试代码
import 'dart:async'; import 'dart:io'; import 'package:amap_flutter_location/amap_flutter_location.dart'; import 'package:amap_flutter_location/amap_location_option.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { final AMapFlutterLocation flutterLocation = AMapFlutterLocation(); final AMapLocationOption aMapLocationOption = AMapLocationOption( needAddress: true, geoLanguage: GeoLanguage.DEFAULT, onceLocation: false, locationMode: AMapLocationMode.Hight_Accuracy, locationInterval: 2000, pausesLocationUpdatesAutomatically: false, desiredAccuracy: DesiredAccuracy.Best, desiredLocationAccuracyAuthorizationMode: AMapLocationAccuracyAuthorizationMode.FullAccuracy, distanceFilter: -1, ); late final StreamSubscription subscription; late int count = 0; @override void initState() { AMapFlutterLocation.updatePrivacyShow(true, true); AMapFlutterLocation.updatePrivacyAgree(true); requestPermission(); AMapFlutterLocation.setApiKey( "e51a737b3742762791f3c89f4dc61e6d", "cb341ecb2fb63ff6965c62a009979f29", ); if (Platform.isIOS) { requestAccuracyAuthorization(); } subscription = flutterLocation.onLocationChanged().listen((event) { print(event.toString()); }); super.initState(); } @override void dispose() { subscription.cancel(); flutterLocation.destroy(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( onPressed: () { flutterLocation.setLocationOption(aMapLocationOption); Timer.periodic(const Duration(seconds: 1), (timer) { count++; print("定位序列号$count"); flutterLocation.startLocation(); }); }, child: Text("开始定位"), ), ElevatedButton( onPressed: () { flutterLocation.stopLocation(); }, child: Text("停止定位"), ), ], ), ), ); } /// 动态申请定位权限 void requestPermission() async { bool hasLocationWhenInUsePermission = await requestIosLocationWhenInUserPermission(); if (hasLocationWhenInUsePermission) { bool hasLocationAlwaysWhenInUsePermission = await requestIosLocationAlwaysWhenInUserPermission(); if (hasLocationAlwaysWhenInUsePermission) { } else {} } else {} } /// 申请定位权限 Future requestLocationPermission() async { var status = await Permission.location.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.location.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationPermission() async { var status = await Permission.location.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.location.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationWhenInUserPermission() async { var status = await Permission.locationWhenInUse.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.locationWhenInUse.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationAlwaysWhenInUserPermission() async { var status = await Permission.locationAlways.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.locationAlways.request(); print("Permission.locationAlways - $status"); if (status == PermissionStatus.granted) { return true; } else { return false; } } } void requestAccuracyAuthorization() async { AMapAccuracyAuthorization currentAccuracyAuthorization = await flutterLocation.getSystemAccuracyAuthorization(); if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) { print("精确定位类型"); } else if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) { print("模糊定位类型"); } else { print("未知定位类型"); } } }
运行结果
IOS
创建Key
Bundle ID为包名
配置Podfile
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ '$(inherited)', 'PERMISSION_LOCATION=1', ]
配置info.plist
权限参考官方文档
下面是按照后台定位的标准来配置的,就是APP在后台,也能定位。
NSLocationTemporaryUsageDescriptionDictionary AnotherUsageDescription This app needs accurate location so it can show you relevant results. ExampleUsageDescription This app needs accurate location so it can verify that you are in a supported region. NSLocationAlwaysUsageDescription can I has location always? NSLocationWhenInUseUsageDescription need location when in use? NSLocationAlwaysAndWhenInUseUsageDescription always and when in use! NSLocationUsageDescription older devices need location.
权限对应
拒绝了上面的这个权限,那就没办法后台定位了
其余几个权限申明也加上,点我查看参考链接
Background Mode
勾上location updates
测试代码
import 'dart:async'; import 'dart:io'; import 'package:amap_flutter_location/amap_flutter_location.dart'; import 'package:amap_flutter_location/amap_location_option.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { final AMapFlutterLocation flutterLocation = AMapFlutterLocation(); final AMapLocationOption aMapLocationOption = AMapLocationOption( needAddress: true, geoLanguage: GeoLanguage.DEFAULT, onceLocation: false, locationMode: AMapLocationMode.Hight_Accuracy, locationInterval: 2000, pausesLocationUpdatesAutomatically: false, desiredAccuracy: DesiredAccuracy.Best, desiredLocationAccuracyAuthorizationMode: AMapLocationAccuracyAuthorizationMode.FullAccuracy, distanceFilter: -1, ); late final StreamSubscription subscription; late int count = 0; @override void initState() { AMapFlutterLocation.updatePrivacyShow(true, true); AMapFlutterLocation.updatePrivacyAgree(true); requestPermission(); AMapFlutterLocation.setApiKey( "e51a737b3742762791f3c89f4dc61e6d", "cb341ecb2fb63ff6965c62a009979f29", ); if (Platform.isIOS) { requestAccuracyAuthorization(); } subscription = flutterLocation.onLocationChanged().listen((event) { print(event.toString()); }); super.initState(); } @override void dispose() { subscription.cancel(); flutterLocation.destroy(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( onPressed: () { flutterLocation.setLocationOption(aMapLocationOption); Timer.periodic(const Duration(seconds: 1), (timer) { count++; print("定位序列号$count"); flutterLocation.startLocation(); }); }, child: Text("开始定位"), ), ElevatedButton( onPressed: () { flutterLocation.stopLocation(); }, child: Text("停止定位"), ), ], ), ), ); } /// 动态申请定位权限 void requestPermission() async { bool hasLocationWhenInUsePermission = await requestIosLocationWhenInUserPermission(); if (hasLocationWhenInUsePermission) { bool hasLocationAlwaysWhenInUsePermission = await requestIosLocationAlwaysWhenInUserPermission(); if (hasLocationAlwaysWhenInUsePermission) { } else {} } else {} } /// 申请定位权限 Future requestLocationPermission() async { var status = await Permission.location.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.location.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationPermission() async { var status = await Permission.location.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.location.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationWhenInUserPermission() async { var status = await Permission.locationWhenInUse.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.locationWhenInUse.request(); if (status == PermissionStatus.granted) { return true; } else { return false; } } } Future requestIosLocationAlwaysWhenInUserPermission() async { var status = await Permission.locationAlways.status; if (status == PermissionStatus.granted) { return true; } else { status = await Permission.locationAlways.request(); print("Permission.locationAlways - $status"); if (status == PermissionStatus.granted) { return true; } else { return false; } } } void requestAccuracyAuthorization() async { AMapAccuracyAuthorization currentAccuracyAuthorization = await flutterLocation.getSystemAccuracyAuthorization(); if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationFullAccuracy) { print("精确定位类型"); } else if (currentAccuracyAuthorization == AMapAccuracyAuthorization.AMapAccuracyAuthorizationReducedAccuracy) { print("模糊定位类型"); } else { print("未知定位类型"); } } }
运行结果
地图
官方文档
引入插件
amap_flutter_map插件地址
amap_flutter_base插件地址
Android
引入依赖
implementation('com.amap.api:3dmap:latest.integration')
测试代码(显示地图)
import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:amap_flutter_base/amap_flutter_base.dart'; import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { late AMapController _mapController; late AMapWidget mapWidget; @override void initState() { mapWidget = AMapWidget( apiKey: const AMapApiKey( iosKey: "cb341ecb2fb63ff6965c62a009979f29", androidKey: "e51a737b3742762791f3c89f4dc61e6d", ), privacyStatement: const AMapPrivacyStatement( hasContains: true, hasShow: true, hasAgree: true, ), onMapCreated: onMapCreated, ); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: Stack( children: [ mapWidget, Positioned( right: 10, bottom: 10, child: FutureBuilder( future: getApprovalNumber(), builder: (ctx, snapshot) { return Column( children: [ Text("${snapshot.data}"), ], ); }, ), ), ], ), ), ); } void onMapCreated(AMapController controller) { CameraUpdate cameraUpdate = CameraUpdate.newCameraPosition( const CameraPosition( target: LatLng(30, 121.473658), zoom: 10, tilt: 30, bearing: 0, ), ); controller.moveCamera(cameraUpdate); setState(() { _mapController = controller; }); } Future getApprovalNumber() async { // 普通地图审图号 String? mapContentApprovalNumber = await _mapController.getMapContentApprovalNumber(); // // 卫星地图审图号 // String? satelliteImageApprovalNumber = // await _mapController.getSatelliteImageApprovalNumber(); return mapContentApprovalNumber; } }
运行结果
地图标点
地图标点参考文档
测试代码(地图标点)
import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:amap_flutter_base/amap_flutter_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:ui' as ui; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { late AMapController _mapController; late Map markerMap; late double nowLatitude; late double nowLongitude; late AMapApiKey aMapApiKey; late AMapPrivacyStatement aMapPrivacyStatement; @override void initState() { markerMap = {}; nowLatitude = 30; nowLongitude = 121.473658; aMapApiKey = const AMapApiKey( iosKey: "cb341ecb2fb63ff6965c62a009979f29", androidKey: "e51a737b3742762791f3c89f4dc61e6d", ); aMapPrivacyStatement = const AMapPrivacyStatement( hasContains: true, hasShow: true, hasAgree: true, ); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: Stack( children: [ AMapWidget( apiKey: aMapApiKey, privacyStatement: aMapPrivacyStatement, onMapCreated: onMapCreated, markers: Set.of(markerMap.values), ), Positioned( right: 10, bottom: 10, child: FutureBuilder( future: getApprovalNumber(), builder: (ctx, snapshot) { return Column( children: [ Text("${snapshot.data}"), ], ); }, ), ), ], ), ), ); } void onMapCreated(AMapController controller) { CameraUpdate cameraUpdate = CameraUpdate.newCameraPosition( CameraPosition( target: LatLng(nowLatitude, nowLongitude), zoom: 10, tilt: 30, bearing: 0, ), ); controller.moveCamera(cameraUpdate); setState(() { _mapController = controller; }); getMarker( nowLatitude, nowLongitude, image: "assets/images/my_position.png", title: "我", ); } Future getApprovalNumber() async { // 普通地图审图号 String? mapContentApprovalNumber = await _mapController.getMapContentApprovalNumber(); // // 卫星地图审图号 // String? satelliteImageApprovalNumber = // await _mapController.getSatelliteImageApprovalNumber(); return mapContentApprovalNumber; } Future getMarker( double latitude, double longitude, { String? image, String? title, String? snippet, }) async { LatLng position = LatLng(latitude, longitude); Marker marker = Marker( onTap: (s) { print(s); }, infoWindow: InfoWindow( title: title, snippet: snippet, ), position: position, icon: image != null ? await getBitmapDescriptorFromAssetBytes(image, 100, 100) : BitmapDescriptor.defaultMarker, ); markerMap[marker.id] = marker; setState(() {}); } Future getBitmapDescriptorFromAssetBytes( String path, double width, double height, ) async { var imageFile = await rootBundle.load(path); var pictureRecorder = ui.PictureRecorder(); var canvas = Canvas(pictureRecorder); var imageUint8List = imageFile.buffer.asUint8List(); var codec = await ui.instantiateImageCodec(imageUint8List); var imageFI = await codec.getNextFrame(); paintImage( canvas: canvas, rect: Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), image: imageFI.image, filterQuality: FilterQuality.medium, ); var image = await pictureRecorder .endRecording() .toImage(width.toInt(), height.toInt()); var data = await image.toByteData(format: ui.ImageByteFormat.png); return BitmapDescriptor.fromBytes(data!.buffer.asUint8List()); } }
运行结果
Ios
配置pod
按照上面的步骤,用Xcode打开ios目录
pod 'AMapLocation' pod 'AMap3DMap'
最好加上,不加可能会在打包时出错
加上后pod install
还有pod repo update也要试试(遇到一些bug时)
以及
测试代码(同Android)
import 'package:amap_flutter_map/amap_flutter_map.dart'; import 'package:amap_flutter_base/amap_flutter_base.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:ui' as ui; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter Demo', home: HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State createState() => _HomePageState(); } class _HomePageState extends State { late AMapController _mapController; late Map markerMap; late double nowLatitude; late double nowLongitude; late AMapApiKey aMapApiKey; late AMapPrivacyStatement aMapPrivacyStatement; @override void initState() { markerMap = {}; nowLatitude = 30; nowLongitude = 121.473658; aMapApiKey = const AMapApiKey( iosKey: "cb341ecb2fb63ff6965c62a009979f29", androidKey: "e51a737b3742762791f3c89f4dc61e6d", ); aMapPrivacyStatement = const AMapPrivacyStatement( hasContains: true, hasShow: true, hasAgree: true, ); super.initState(); } @override void dispose() { super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: SizedBox( height: MediaQuery.of(context).size.height, width: MediaQuery.of(context).size.width, child: Stack( children: [ AMapWidget( apiKey: aMapApiKey, privacyStatement: aMapPrivacyStatement, onMapCreated: onMapCreated, markers: Set.of(markerMap.values), ), Positioned( right: 10, bottom: 10, child: FutureBuilder( future: getApprovalNumber(), builder: (ctx, snapshot) { return Column( children: [ Text("${snapshot.data}"), ], ); }, ), ), ], ), ), ); } void onMapCreated(AMapController controller) { CameraUpdate cameraUpdate = CameraUpdate.newCameraPosition( CameraPosition( target: LatLng(nowLatitude, nowLongitude), zoom: 10, tilt: 30, bearing: 0, ), ); controller.moveCamera(cameraUpdate); setState(() { _mapController = controller; }); getMarker( nowLatitude, nowLongitude, image: "assets/images/my_position.png", title: "我", ); } Future getApprovalNumber() async { // 普通地图审图号 String? mapContentApprovalNumber = await _mapController.getMapContentApprovalNumber(); // // 卫星地图审图号 // String? satelliteImageApprovalNumber = // await _mapController.getSatelliteImageApprovalNumber(); return mapContentApprovalNumber; } Future getMarker( double latitude, double longitude, { String? image, String? title, String? snippet, }) async { LatLng position = LatLng(latitude, longitude); Marker marker = Marker( onTap: (s) { print(s); }, infoWindow: InfoWindow( title: title, snippet: snippet, ), position: position, icon: image != null ? await getBitmapDescriptorFromAssetBytes(image, 100, 100) : BitmapDescriptor.defaultMarker, ); markerMap[marker.id] = marker; setState(() {}); } Future getBitmapDescriptorFromAssetBytes( String path, double width, double height, ) async { var imageFile = await rootBundle.load(path); var pictureRecorder = ui.PictureRecorder(); var canvas = Canvas(pictureRecorder); var imageUint8List = imageFile.buffer.asUint8List(); var codec = await ui.instantiateImageCodec(imageUint8List); var imageFI = await codec.getNextFrame(); paintImage( canvas: canvas, rect: Rect.fromLTWH(0, 0, width.toDouble(), height.toDouble()), image: imageFI.image, filterQuality: FilterQuality.medium, ); var image = await pictureRecorder .endRecording() .toImage(width.toInt(), height.toInt()); var data = await image.toByteData(format: ui.ImageByteFormat.png); return BitmapDescriptor.fromBytes(data!.buffer.asUint8List()); } }
运行结果
打包也没问题就行了
发布apk
出现的闪退问题
再全部配置完后,发现一个bug,执行
flutter build apk --no-tree-shake-icons --obfuscate --split-debug-info=./
安装完apk后,点开地图会直接闪退
并出现下面
F/obo2.deman_mob(25374): java_vm_ext.cc:570] JNI DETECTED ERROR IN APPLICATION: java_class == null F/obo2.deman_mob(25374): java_vm_ext.cc:570] in call to GetStaticMethodID F/obo2.deman_mob(25374): java_vm_ext.cc:570] from java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader, java.lang.Class) F/obo2.deman_mob(25374): thread.cc:2560] No pending exception expected: java.lang.ClassNotFoundException: com.autonavi.base.amap.mapcore.ClassTools F/obo2.deman_mob(25374): thread.cc:2560] at java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader, java.lang.Class) (Runtime.java:-2) F/obo2.deman_mob(25374): thread.cc:2560] at java.lang.String java.lang.Runtime.nativeLoad(java.lang.String, java.lang.ClassLoader) (Runtime.java:1115) F/obo2.deman_mob(25374): thread.cc:2560] at void java.lang.Runtime.loadLibrary0(java.lang.ClassLoader, java.lang.Class, java.lang.String) (Runtime.java:1069) F/obo2.deman_mob(25374): thread.cc:2560] at void java.lang.Runtime.loadLibrary0(java.lang.Class, java.lang.String) (Runtime.java:1007) F/obo2.deman_mob(25374): thread.cc:2560] at void java.lang.System.loadLibrary(java.lang.String) (System.java:1668) F/obo2.deman_mob(25374): thread.cc:2560] at boolean o5.a.k(android.content.Context) (:-1) F/obo2.deman_mob(25374): thread.cc:2560] at void z3.v7.N1(int, javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig) (:-1) F/obo2.deman_mob(25374): thread.cc:2560] at void z3.v7.F0(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig) (:-1) F/obo2.deman_mob(25374): thread.cc:2560] at void d5.c.onSurfaceCreated(javax.microedition.khronos.opengles.GL10, javax.microedition.khronos.egl.EGLConfig) (:-1) F/obo2.deman_mob(25374): thread.cc:2560] at void z3.p8$i.q() (:-1) F/obo2.deman_mob(25374): thread.cc:2560] at void z3.p8$i.run() (:-1)
去修改build.gradle
参考文章即可修复bug
这样就不会闪退了
还没有评论,来说两句吧...