From 66e345d4ddba0458be1ddb79494701f3a87af2ad Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Mon, 11 Aug 2025 21:00:16 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=8E=BB=E5=A4=84=E6=B2=A1=E7=94=A8?= =?UTF-8?q?=E7=9A=84=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/asr_service.dart | 3 --- lib/data/services/ast_service.dart | 3 --- lib/data/services/speech_impl/azure_asr_service.dart | 12 ------------ lib/data/services/speech_impl/azure_ast_service.dart | 7 ------- .../controllers/meeting_record_controller.dart | 4 ---- .../controllers/speech_test_controller.dart | 3 --- .../yunqiinnovation/agent_service/AgentService.kt | 4 +--- .../yunqiinnovation/azure_speech/AzureAsrHelper.kt | 8 +------- .../azure_speech/AzureSpeechPlugin.kt | 8 -------- 9 files changed, 2 insertions(+), 50 deletions(-) diff --git a/lib/data/services/asr_service.dart b/lib/data/services/asr_service.dart index 827194a1a..4a8b34c2d 100644 --- a/lib/data/services/asr_service.dart +++ b/lib/data/services/asr_service.dart @@ -85,9 +85,6 @@ enum RecognitionEventType { /// 中间识别结果(实时反馈) intermediateResult, - /// 音频 - onAudio, - /// 会话开始 sessionStarted, diff --git a/lib/data/services/ast_service.dart b/lib/data/services/ast_service.dart index 69cd23592..244a7fb37 100644 --- a/lib/data/services/ast_service.dart +++ b/lib/data/services/ast_service.dart @@ -36,9 +36,6 @@ enum RecognitionEventType1 { /// 中间识别结果(实时反馈) intermediateResult, - /// 音频 - onAudio, - /// 会话开始 sessionStarted, diff --git a/lib/data/services/speech_impl/azure_asr_service.dart b/lib/data/services/speech_impl/azure_asr_service.dart index 7c34a204d..5137faee5 100644 --- a/lib/data/services/speech_impl/azure_asr_service.dart +++ b/lib/data/services/speech_impl/azure_asr_service.dart @@ -261,11 +261,6 @@ class AzureAsrService extends GetxService implements AsrService { // 发送到音频数据流 _audioDataStreamController?.add(data); - // 也可以通过识别事件流发送 - _eventStreamController?.add(RecognitionEvent( - type: RecognitionEventType.onAudio, - audio: data, - )); break; } } @@ -322,13 +317,6 @@ class AzureAsrService extends GetxService implements AsrService { detectedLanguage: detectedLanguage, )); break; - case 'onAudio': - final Uint8List data = eventMap['data'] as Uint8List? ?? Uint8List(0); - _eventStreamController?.add(RecognitionEvent( - type: RecognitionEventType.onAudio, - audio: data, - )); - break; case 'sessionStarted': _eventStreamController?.add(RecognitionEvent( type: RecognitionEventType.sessionStarted, diff --git a/lib/data/services/speech_impl/azure_ast_service.dart b/lib/data/services/speech_impl/azure_ast_service.dart index f66945d9f..5b5dc35da 100644 --- a/lib/data/services/speech_impl/azure_ast_service.dart +++ b/lib/data/services/speech_impl/azure_ast_service.dart @@ -97,13 +97,6 @@ class AzureAstService extends GetxService implements AstService { detectedLanguage: detectedLanguage, )); break; - case 'onAudio': - final Uint8List data = eventMap['data'] as Uint8List? ?? Uint8List(0); - _eventStreamController?.add(RecognitionEvent1( - type: RecognitionEventType1.onAudio, - audio: data, - )); - break; case 'sessionStarted': _eventStreamController?.add(RecognitionEvent1( type: RecognitionEventType1.sessionStarted, diff --git a/lib/modules/meeting/controllers/meeting_record_controller.dart b/lib/modules/meeting/controllers/meeting_record_controller.dart index eb177d910..50736fe52 100644 --- a/lib/modules/meeting/controllers/meeting_record_controller.dart +++ b/lib/modules/meeting/controllers/meeting_record_controller.dart @@ -320,10 +320,6 @@ class MeetingRecordController extends GetxController case RecognitionEventType.intermediateResult: intermediateContent.value = event.text; break; - case RecognitionEventType.onAudio: - // 添加新音频数据到波形 - _processAudioData(event.audio!); - break; case RecognitionEventType.finalResult: finalContent.value += _formatTranscript(event); intermediateContent.value = ''; diff --git a/lib/modules/speech_test/controllers/speech_test_controller.dart b/lib/modules/speech_test/controllers/speech_test_controller.dart index 5bcfc83e0..57d54bcad 100644 --- a/lib/modules/speech_test/controllers/speech_test_controller.dart +++ b/lib/modules/speech_test/controllers/speech_test_controller.dart @@ -184,9 +184,6 @@ class SpeechTestController extends GetxController { // 清空中间结果 intermediateResult.value = ''; break; - case RecognitionEventType.onAudio: - // TODO: Handle this case. - break; } }, onError: (error) { isListening.value = false; diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 6bccc7960..0acee79b3 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -591,9 +591,7 @@ object AgentService : CoroutineScope { } - override fun onAudio(data: ByteArray) { - - } + override fun onSessionStopped() { sendEvent("recognition_stopped", mapOf("status" to "stopped")) diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index 048d6d74c..afc9e0409 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -681,13 +681,7 @@ class AzureAsrHelper(private val context: Context) { */ fun onSessionStopped() - /** - * 返回音频 - * - * @param data 识别的音频 - */ - fun onAudio(data: ByteArray) - + /** * 识别取消时调用 * diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt index c936703af..cad5b8251 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureSpeechPlugin.kt @@ -242,14 +242,6 @@ class AzureSpeechPlugin : BleService.Callback, FlutterPlugin { sendAsrEvent(mapOf("type" to "sessionStopped")) } - override fun onAudio(data: ByteArray) { - sendAsrEvent( - mapOf( - "type" to "onAudio", - "data" to data - ) - ) - } override fun onCanceled(reason: String, errorDetails: String) { From 559c51378c3ad34912ac0aa314d9069b89e6ea20 Mon Sep 17 00:00:00 2001 From: tanlongsheng <252620078@qq.com> Date: Mon, 11 Aug 2025 21:56:41 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=8C=89=E9=92=AE?= =?UTF-8?q?=E8=BE=B9=E6=A1=86=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/widgets/intro_overlay_builder.dart | 156 ++++++++++++++++++-- 1 file changed, 141 insertions(+), 15 deletions(-) diff --git a/lib/core/widgets/intro_overlay_builder.dart b/lib/core/widgets/intro_overlay_builder.dart index bab8412b0..920737ae2 100644 --- a/lib/core/widgets/intro_overlay_builder.dart +++ b/lib/core/widgets/intro_overlay_builder.dart @@ -89,22 +89,9 @@ class IntroOverlayBuilder { } static Widget _button(String text, Function() onTap) { - return GestureDetector( + return _AnimatedBorderButton( + text: text, onTap: onTap, - child: Container( - padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20.r), - border: Border.all(color: Colors.white), - ), - child: Text( - text, - style: TextStyle( - fontSize: 14.sp, - color: Colors.white, - ), - ), - ), ); } } @@ -166,3 +153,142 @@ class _AnimatedLottieWidgetState extends State<_AnimatedLottieWidget> ); } } + +// 新增的带边框流动动画的按钮组件 +class _AnimatedBorderButton extends StatefulWidget { + final String text; + final Function() onTap; + + const _AnimatedBorderButton({ + required this.text, + required this.onTap, + }); + + @override + _AnimatedBorderButtonState createState() => _AnimatedBorderButtonState(); +} + +class _AnimatedBorderButtonState extends State<_AnimatedBorderButton> + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: const Duration(seconds: 2), + vsync: this, + ); + _animation = Tween( + begin: 0.0, + end: 1.0, + ).animate(_animationController); + + _animationController.repeat(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onTap, + child: AnimatedBuilder( + animation: _animation, + builder: (context, child) { + return CustomPaint( + painter: _FlowingBorderPainter( + progress: _animation.value, + borderRadius: 20.r, + ), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10.w, vertical: 5.w), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.r), + border: Border.all(color: Colors.transparent), + ), + child: Text( + widget.text, + style: TextStyle( + fontSize: 14.sp, + color: Colors.white, + ), + ), + ), + ); + }, + ), + ); + } +} + +// 自定义绘制器,用于绘制流动的边框 +class _FlowingBorderPainter extends CustomPainter { + final double progress; + final double borderRadius; + + _FlowingBorderPainter({ + required this.progress, + required this.borderRadius, + }); + + @override + void paint(Canvas canvas, Size size) { + final rect = Rect.fromLTWH(0, 0, size.width, size.height); + final rrect = RRect.fromRectAndRadius(rect, Radius.circular(borderRadius)); + + // 计算路径总长度 + final path = Path()..addRRect(rrect); + final pathMetrics = path.computeMetrics().first; + final totalLength = pathMetrics.length; + + // 流动效果:创建一个渐变的虚线效果 + final dashLength = totalLength * 0.3; // 虚线长度 + final startOffset = (progress * totalLength) % totalLength; + + // 绘制流动的边框段 + for (int i = 0; i < 3; i++) { + final offset = (startOffset + i * totalLength / 3) % totalLength; + final endOffset = (offset + dashLength) % totalLength; + + Path extractedPath; + if (endOffset > offset) { + extractedPath = pathMetrics.extractPath(offset, endOffset); + } else { + // 处理跨越路径起点的情况 + final path1 = pathMetrics.extractPath(offset, totalLength); + final path2 = pathMetrics.extractPath(0, endOffset); + extractedPath = Path() + ..addPath(path1, Offset.zero) + ..addPath(path2, Offset.zero); + } + + // 创建渐变效果 + const gradient = LinearGradient( + colors: [ + Colors.green, + Colors.yellow, + Colors.red, + ], + stops: [0.0, 0.5, 1.0], + ); + + final gradientPaint = Paint() + ..shader = gradient.createShader(rect) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5; + + canvas.drawPath(extractedPath, gradientPaint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return true; + } +} From 60ed6dc1b30eb9c4efd23d8dfe05e417406b2bf2 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 12 Aug 2025 10:46:03 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=20=E4=B8=8A=E4=BC=A0=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ios/agent_service/Package.swift | 2 + .../agent_service/AgentServiceImpl.swift | 35 +- .../chat_api/ChatApiService.kt | 2 +- .../chat_api/SystemFunctionHandler.kt | 67 +-- .../chat_api/SystemFunctionHandler.swift | 4 +- .../android/src/main/AndroidManifest.xml | 7 +- .../location_service/LocationService.kt | 418 ++++++--------- .../location_service/LocationServicePlugin.kt | 38 +- .../location_service/NativeLocationService.kt | 499 ++++++++++++++++++ .../location_service/LocationData.swift | 10 +- .../location_service/LocationService.swift | 9 +- 11 files changed, 707 insertions(+), 384 deletions(-) create mode 100644 local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/NativeLocationService.kt diff --git a/local_plugins/agent_service/ios/agent_service/Package.swift b/local_plugins/agent_service/ios/agent_service/Package.swift index cc30a9ad6..83f572fff 100644 --- a/local_plugins/agent_service/ios/agent_service/Package.swift +++ b/local_plugins/agent_service/ios/agent_service/Package.swift @@ -14,6 +14,7 @@ let package = Package( .package(path: "../../../azure_speech/ios/azure_speech"), .package(path: "../../../chat_api/ios/chat_api"), .package(path: "../../../chat_storage/ios/chat_storage"), + .package(path: "../../../location_service/ios/location_service"), .package(path: "../../../music_service/ios/music_service"), .package(path: "../../../music_service/ios/user_portrait") @@ -28,6 +29,7 @@ let package = Package( .product(name: "azure-speech", package: "azure_speech"), .product(name: "chat-api", package: "chat_api"), .product(name: "chat-storage", package: "chat_storage"), + .product(name: "location-service", package: "location_service"), .product(name: "music-service", package: "music_service"), .product(name: "user-portrait", package: "user_portrait"), ], diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index e5ce57159..c05148c00 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -1,6 +1,7 @@ import Foundation import AVFoundation import ble_service +import location_service import speech import azure_speech import os @@ -65,7 +66,7 @@ class AgentServiceImpl: NSObject { private let maxIdleSeconds: TimeInterval = 10 internal var audioPlayer: AudioPlayer? - private var locationInfo:[String: String]? + // private var locationInfo:[String: String]? internal let logger = OSLog(subsystem: "com.yunqiinnovation.agent_service", category: "AgentServiceImpl") @@ -182,7 +183,7 @@ class AgentServiceImpl: NSObject { } //启动定位服务 NotificationCenter.default.post(name: .locationstartEvent, object: nil) - addlocationmonitor() + // addlocationmonitor() if let mcpServer = config["mcpServer"] as? String { self.mcpServer = mcpServer } @@ -204,7 +205,9 @@ class AgentServiceImpl: NSObject { if isInitialized { loadChatHistory() } - + LocationService.shared.getCurrentLocation() { location in + // self.locationInfo = location + } return isInitialized } @@ -841,19 +844,19 @@ private func jsonToString(_ json: [String: Any]) -> String? { } // 添加定位监听 - private func addlocationmonitor(){ - // 通常放在启动后或模块初始化时 - NotificationCenter.default.addObserver( - forName: .updatelocationEvent, - object: nil, - queue: .main - ) { notification in - if let info = notification.userInfo as? [String: String] { - print("📤 地址信息:\(info)") - self.locationInfo = info - } - } - } + // private func addlocationmonitor(){ + // // 通常放在启动后或模块初始化时 + // NotificationCenter.default.addObserver( + // forName: .updatelocationEvent, + // object: nil, + // queue: .main + // ) { notification in + // if let info = notification.userInfo as? [String: String] { + // print("📤 地址信息:\(info)") + // self.locationInfo = info + // } + // } + // } /** * 填充模板字符串,使用正则表达式匹配 {key} 格式的占位符 * 对应 Kotlin 版本的 fillTemplate 方法 diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt index 7d7d50bbe..7d344240f 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt @@ -403,7 +403,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor Log.e("ChatApiService", "openAI对象为null") throw IllegalStateException("OpenAI客户端未初始化") } -// Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") + Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") val flow = openAI!!.chatCompletions(chatCompletionRequest) // Log.d(TAG, "liwei-------------------------开始AI 对话 6") flow diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt index 2af198233..1e4f969d0 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/SystemFunctionHandler.kt @@ -19,7 +19,6 @@ import android.telephony.SmsManager import android.util.Log import androidx.core.content.ContextCompat import com.yunqiinnovation.location_service.LocationService -import androidx.core.content.ContextCompat.RECEIVER_NOT_EXPORTED import org.json.JSONObject import java.text.SimpleDateFormat import java.util.* @@ -1134,7 +1133,7 @@ private class GetCurrentTimeHandler : FunctionHandler { * 获取当前位置处理器 */ private class GetCurrentLocationHandler(private val context: Context?) : FunctionHandler { - + override suspend fun handle(arguments: Map): String { if (context == null) { return JSONObject().apply { @@ -1144,76 +1143,28 @@ private class GetCurrentLocationHandler(private val context: Context?) : Functio }) }.toString() } - - val handler = context.let { SystemFunctionHandler(it) } - - // 检查位置权限 - if (!handler.checkPermissionAndGuide(Manifest.permission.ACCESS_FINE_LOCATION, "位置信息", "获取位置")) { - return JSONObject().apply { - put("error", JSONObject().apply { - put("code", "permission_denied") - put("message", "需要位置权限才能获取当前位置,请在权限管理中开启位置权限") - }) - }.toString() - } - - // 获取缓存的位置数据 - val cachedLocation = LocationService.getCachedLocationData() - - // 异步触发最新定位获取(不等待结果) - LocationService.requestLocationInBackground(object : LocationService.LocationCallback { - override fun onLocationSuccess(latitude: Double, longitude: Double, address: String) { - Log.d(SystemFunctionHandler.TAG, "后台定位成功,缓存已更新: $latitude, $longitude, $address") - } - - override fun onLocationError(errorCode: Int, errorMessage: String) { - Log.w(SystemFunctionHandler.TAG, "后台定位失败: $errorCode - $errorMessage") - } - }) - - // 立即返回缓存数据(如果存在) - if (cachedLocation != null) { - val cacheAge = System.currentTimeMillis() - cachedLocation.timestamp - val cacheAgeMinutes = cacheAge / (60 * 1000) - - return JSONObject().apply { - put("result", JSONObject().apply { - put("latitude", cachedLocation.latitude) - put("longitude", cachedLocation.longitude) - put("address", cachedLocation.address) - put("location", "${cachedLocation.latitude},${cachedLocation.longitude}") - put("source", "cache") - put("timestamp", cachedLocation.timestamp) - put("cacheAgeMinutes", cacheAgeMinutes) - put("note", "正在后台更新位置信息") - }) - }.toString() - } - // 如果没有缓存,则同步获取一次 return try { kotlinx.coroutines.suspendCancellableCoroutine { continuation -> LocationService.requestLocation(object : LocationService.LocationCallback { - override fun onLocationSuccess(latitude: Double, longitude: Double, address: String) { - Log.d(SystemFunctionHandler.TAG, "首次定位成功: $latitude, $longitude, $address") - + override fun onLocationSuccess(latitude: Double, longitude: Double) { + Log.d(SystemFunctionHandler.TAG, "首次定位成功: $latitude, $longitude") + val result = JSONObject().apply { put("result", JSONObject().apply { - put("latitude", latitude) put("longitude", longitude) - put("address", address) - put("source", "gps") + put("latitude", latitude) put("timestamp", System.currentTimeMillis()) - put("note", "首次获取位置信息") + }) }.toString() - + continuation.resumeWith(Result.success(result)) } override fun onLocationError(errorCode: Int, errorMessage: String) { Log.e(SystemFunctionHandler.TAG, "首次定位失败: $errorCode - $errorMessage") - + val result = JSONObject().apply { put("error", JSONObject().apply { put("code", "location_error") @@ -1221,7 +1172,7 @@ private class GetCurrentLocationHandler(private val context: Context?) : Functio put("errorCode", errorCode) }) }.toString() - + continuation.resumeWith(Result.success(result)) } }) diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift index b0c19e4a6..35d553698 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/SystemFunctionHandler.swift @@ -490,9 +490,9 @@ private class GetCurrentLocationHandler: FunctionHandler { let result: [String: Any] = [ "success": true, "data": [ - "latitude": latitude, "longitude": longitude, - "location": "\(latitude),\(longitude)", + "latitude": latitude, + "location": "\(longitude),\(latitude)", "timestamp": Date().timeIntervalSince1970 * 1000 ] ] diff --git a/local_plugins/location_service/android/src/main/AndroidManifest.xml b/local_plugins/location_service/android/src/main/AndroidManifest.xml index 3cbeb31e4..48fb949c7 100644 --- a/local_plugins/location_service/android/src/main/AndroidManifest.xml +++ b/local_plugins/location_service/android/src/main/AndroidManifest.xml @@ -1,7 +1,12 @@ + + + - + + + diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt index 73788b194..fcdc8ce17 100644 --- a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt @@ -17,29 +17,32 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch + object LocationService { private const val TAG = "LocationService" private const val PERMISSION_REQUEST_CODE = 1001 + // 只接受高精度定位 + private const val HIGH_ACCURACY_THRESHOLD = 50.0 // 只接受50米以内的高精度定位 + private const val LOCATION_TIMEOUT_MS = 60000L // 增加到60秒超时,给更多时间获取高精度定位 + private const val CACHE_PREFS_NAME = "location_cache" + private var appcontext: Context? = null private var locationClient: AMapLocationClient? = null private var initialized = false private var permissionRetryHandler: Handler? = null private var permissionRetryRunnable: Runnable? = null - - // 存储当前的定位请求回调(Flutter用) private var currentLocationCallback: MethodChannel.Result? = null - // 存储权限请求回调(Flutter用) private var permissionRequestCallback: MethodChannel.Result? = null - - // 存储服务间调用的回调 private val serviceCallbacks = mutableListOf() + private var isHighAccuracyMode = true + private var cachedLocationData: LocationData? = null + private var locationTimeoutHandler: Handler? = null + private var locationStartTime: Long = 0 - /** - * 定位回调接口,供其他Android服务使用 - */ + // 修正接口,包含address参数 interface LocationCallback { - fun onLocationSuccess(latitude: Double, longitude: Double, address: String) + fun onLocationSuccess(latitude: Double, longitude: Double) fun onLocationError(errorCode: Int, errorMessage: String) } @@ -65,147 +68,164 @@ object LocationService { } fun initialize(context: Context) { - if (initialized) return - appcontext = context - - if (!checkPermissions(context)) { - Log.w(TAG, "没有定位权限,等待用户授权中...") - startPermissionMonitor() + if (initialized) { + Log.d(TAG, "定位服务已初始化") return } - + + appcontext = context.applicationContext + try { AMapLocationClient.updatePrivacyShow(context, true, true) AMapLocationClient.updatePrivacyAgree(context, true) - locationClient = AMapLocationClient(context.applicationContext) - val option = AMapLocationClientOption().apply { - locationMode = AMapLocationClientOption.AMapLocationMode.Hight_Accuracy - isOnceLocation = true // 单次定位 - isNeedAddress = true // 需要地址信息 - httpTimeOut = 20000 // 设置超时时间 - } - + + // 优化后的定位参数配置 + val option = AMapLocationClientOption() + + // 高精度定位模式(融合GPS、网络、传感器) + option.setLocationMode(AMapLocationClientOption.AMapLocationMode.Hight_Accuracy) + + // 设置定位场景 + option.setLocationPurpose(AMapLocationClientOption.AMapLocationPurpose.SignIn) // 改为签到模式,更适合高精度定位 + + // 定位间隔(首次定位建议设置较短间隔) + option.setInterval(2000) // 改为2秒,更快响应 + + // 超时时间 + option.setHttpTimeOut(30000) // 30秒足够 + + // 启用所有辅助定位技术 + option.setGpsFirst(false) // 不强制GPS优先,让SDK智能选择 + option.setWifiScan(true) // 启用WiFi辅助定位 + option.setSensorEnable(true) // 启用传感器辅助 + option.setLocationCacheEnable(true) // 启用位置缓存 + + // 单次定位设置 + option.setOnceLocation(true) // 改为单次定位,避免持续定位的干扰 + option.setOnceLocationLatest(true) // 获取最新的定位结果 + + // 其他设置 + option.setMockEnable(false) // 禁用模拟定位 + option.setNeedAddress(true) // 启用地址解析,有助于定位精度 + locationClient?.setLocationOption(option) - - // 设置定位监听器 locationClient?.setLocationListener { location -> handleLocationResult(location) } - + initialized = true - Log.d(TAG, "定位服务初始化完成") + Log.d(TAG, "定位服务初始化完成 - 优化高精度模式") } catch (e: Exception) { Log.e(TAG, "高德定位初始化失败: ${e.message}", e) } } - // 在LocationService object中添加缓存相关的属性和方法 - private var cachedLocationData: LocationData? = null - private const val CACHE_PREFS_NAME = "location_cache" - /** * 后台异步获取位置信息(不阻塞调用者) * @param callback 定位结果回调 */ - fun requestLocationInBackground(callback: LocationCallback) { + fun requestLocation(callback: LocationCallback) { val ctx = appcontext if (ctx == null) { callback.onLocationError(-1, "定位服务未初始化") return } - + if (!checkPermissions(ctx)) { callback.onLocationError(-2, "没有定位权限") return } - + if (!initialized) { callback.onLocationError(-3, "定位服务未初始化") return } - + + // 优先返回内存中的缓存 + if (cachedLocationData != null) { + callback.onLocationSuccess(cachedLocationData!!.latitude,cachedLocationData!!.longitude) + }else{ + // 添加到回调列表 + serviceCallbacks.add(callback) + } + // 记录定位开始时间 + locationStartTime = System.currentTimeMillis() + // 启动超时计时器 + startLocationTimeout() // 使用协程在后台执行 CoroutineScope(Dispatchers.IO).launch { try { - // 添加到回调列表 - serviceCallbacks.add(callback) - // 如果没有正在进行的定位请求,开始定位 - if (currentLocationCallback == null && serviceCallbacks.size == 1) { - locationClient?.startLocation() - } + locationClient?.startLocation() } catch (e: Exception) { + stopLocationTimeout() callback.onLocationError(-4, "启动后台定位失败: ${e.message}") } } } - - // 修改handleLocationResult方法,添加缓存逻辑 + + // 统一的handleLocationResult方法(整合优化逻辑) private fun handleLocationResult(location: AMapLocation?) { - // 处理Flutter回调 - val flutterCallback = currentLocationCallback - currentLocationCallback = null + if (location == null) { + Log.e(TAG, "定位结果为空") + return + } + + Log.d(TAG, "定位结果: 来源=${location.provider}, 精度=${location.accuracy}米, 坐标=(${location.latitude}, ${location.longitude})") - if (location != null && location.errorCode == 0) { - Log.d(TAG, "定位成功: ${location.latitude}, ${location.longitude}") - - // 缓存位置数据 - val locationData = LocationData( - latitude = location.latitude, - longitude = location.longitude, - address = location.address ?: "未知地址", - timestamp = System.currentTimeMillis() - ) - cachedLocationData = locationData - saveCachedLocationToPrefs(locationData) - - // 通知Flutter - if (flutterCallback != null) { - val result = mapOf( - "user_location" to (location.address ?: "未知地址"), - "user_lat" to location.latitude.toString(), - "user_lng" to location.longitude.toString() - ) - flutterCallback.success(result) - } + // 检查定位来源,只接受GPS定位 + if (location.provider != "gps") { + Log.w(TAG, "拒绝非GPS定位,当前来源: ${location.provider}") + // 继续等待GPS定位,不回调 + return + } + + if (location.errorCode == AMapLocation.LOCATION_SUCCESS) { + val accuracy = location.accuracy + Log.d(TAG, "GPS定位成功: 精度=${accuracy}米") - // 通知其他Android服务 - serviceCallbacks.forEach { callback -> + // 只接受高精度GPS定位 + if (accuracy <= HIGH_ACCURACY_THRESHOLD) { + Log.d(TAG, "接受高精度GPS定位: ${accuracy}米") + + // 停止超时计时器 + stopLocationTimeout() + + // 停止定位 try { - callback.onLocationSuccess( - location.latitude, - location.longitude, - location.address ?: "未知地址" - ) + locationClient?.stopLocation() } catch (e: Exception) { - Log.e(TAG, "服务回调失败: ${e.message}", e) + Log.e(TAG, "停止定位失败: ${e.message}", e) } - } - - } else { - Log.w(TAG, "定位失败: ${location?.errorCode} - ${location?.errorInfo}") - - // 通知Flutter - if (flutterCallback != null) { - flutterCallback.error("LOCATION_ERROR", "定位失败: ${location?.errorInfo ?: "未知错误"}", null) - } - - // 通知其他Android服务 - serviceCallbacks.forEach { callback -> - try { - callback.onLocationError( - location?.errorCode ?: -1, - location?.errorInfo ?: "未知错误" - ) - } catch (e: Exception) { - Log.e(TAG, "服务回调失败: ${e.message}", e) + + // 缓存位置数据 + val locationData = LocationData( + latitude = location.latitude, + longitude = location.longitude, + timestamp = System.currentTimeMillis() + ) + cachedLocationData = locationData + + // 处理成功的定位结果 + serviceCallbacks.forEach { callback -> + try { + callback.onLocationSuccess( + location.latitude, + location.longitude, + ) + } catch (e: Exception) { + Log.e(TAG, "服务回调失败: ${e.message}", e) + } } + // 清空服务回调列表(单次定位) + serviceCallbacks.clear() + } else { + Log.w(TAG, "GPS精度不足(${accuracy}米),继续等待更高精度定位") } + } else { + Log.e(TAG, "GPS定位失败: ${location.errorCode} - ${location.errorInfo}") } - - // 清空服务回调列表(单次定位) - serviceCallbacks.clear() } private fun startPermissionMonitor() { @@ -315,182 +335,56 @@ object LocationService { } } - /** - * 处理权限请求结果(Flutter用) - */ - fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ): Boolean { - if (requestCode != PERMISSION_REQUEST_CODE) { - return false - } - - val callback = permissionRequestCallback - permissionRequestCallback = null - - if (callback == null) { - return true - } - - val ctx = appcontext - if (ctx == null) { - callback.error("NO_CONTEXT", "上下文丢失", null) - return true - } - - val hasPermission = checkPermissions(ctx) - - if (hasPermission) { - // 权限获取成功,重新初始化定位服务 - if (!initialized) { - initialize(ctx) - } - callback.success(mapOf("granted" to true)) - } else { - callback.success(mapOf("granted" to false)) - } - - return true - } /** - * 按需获取当前位置(Flutter用) + * 位置数据类 */ - fun getCurrentLocation(result: MethodChannel.Result) { - val ctx = appcontext - if (ctx == null) { - result.error("NOT_INITIALIZED", "定位服务未初始化", null) - return - } - - if (!checkPermissions(ctx)) { - result.error("NO_PERMISSION", "需要定位权限", null) - return - } - - if (!initialized) { - result.error("NOT_INITIALIZED", "定位服务未初始化", null) - return - } - - if (currentLocationCallback != null) { - result.error("LOCATION_IN_PROGRESS", "正在定位中,请稍后再试", null) - return - } - - // 保存回调,开始定位 - currentLocationCallback = result - locationClient?.startLocation() - } + data class LocationData( + val latitude: Double, + val longitude: Double, + val timestamp: Long + ) - // ========== 以下是供其他Android服务使用的接口 ========== - - /** - * 检查是否有定位权限(供其他服务使用) - */ - fun hasLocationPermission(): Boolean { - val ctx = appcontext ?: return false - return checkPermissions(ctx) - } - - /** - * 获取当前位置(供其他Android服务使用) - * @param callback 定位结果回调 - */ - fun requestLocation(callback: LocationCallback) { - val ctx = appcontext - if (ctx == null) { - callback.onLocationError(-1, "定位服务未初始化") - return - } - - if (!checkPermissions(ctx)) { - callback.onLocationError(-2, "没有定位权限") - return - } - - if (!initialized) { - callback.onLocationError(-3, "定位服务未初始化") - return - } - - // 添加到回调列表 - serviceCallbacks.add(callback) - - // 如果没有正在进行的定位请求,开始定位 - if (currentLocationCallback == null && serviceCallbacks.size == 1) { - locationClient?.startLocation() - } - } - /** - * 同步获取缓存的位置信息(供其他服务使用) - * 注意:这个方法返回的可能是过期的位置信息 + * 启动定位超时计时器 */ - fun getCachedLocationData(): LocationData? { - // 优先返回内存中的缓存 - if (cachedLocationData != null) { - return cachedLocationData - } + private fun startLocationTimeout() { + // 如果已经有超时计时器在运行,先停止它 + stopLocationTimeout() - // 从SharedPreferences中读取缓存 - val ctx = appcontext ?: return null - return loadCachedLocationFromPrefs(ctx) - } - - private fun saveCachedLocationToPrefs(locationData: LocationData) { - val ctx = appcontext ?: return - try { - val prefs = ctx.getSharedPreferences(CACHE_PREFS_NAME, Context.MODE_PRIVATE) - prefs.edit().apply { - putString("latitude", locationData.latitude.toString()) - putString("longitude", locationData.longitude.toString()) - putString("address", locationData.address) - putLong("timestamp", locationData.timestamp) - apply() + locationTimeoutHandler = Handler(Looper.getMainLooper()) + locationTimeoutHandler?.postDelayed({ + Log.w(TAG, "定位超时,停止定位请求") + + // 停止定位 + try { + locationClient?.stopLocation() + } catch (e: Exception) { + Log.e(TAG, "停止定位失败: ${e.message}", e) } - } catch (e: Exception) { - Log.e(TAG, "保存位置缓存失败: ${e.message}", e) - } - } - - private fun loadCachedLocationFromPrefs(context: Context): LocationData? { - return try { - val prefs = context.getSharedPreferences(CACHE_PREFS_NAME, Context.MODE_PRIVATE) - val latitude = prefs.getString("latitude", null)?.toDoubleOrNull() ?: return null - val longitude = prefs.getString("longitude", null)?.toDoubleOrNull() ?: return null - val address = prefs.getString("address", null) ?: return null - val timestamp = prefs.getLong("timestamp", 0L) - if (timestamp == 0L) return null + // 通知所有等待的回调超时错误 + serviceCallbacks.forEach { callback -> + try { + callback.onLocationError(-5, "定位超时,未能在${LOCATION_TIMEOUT_MS/1000}秒内获取到高精度位置") + } catch (e: Exception) { + Log.e(TAG, "超时回调失败: ${e.message}", e) + } + } - LocationData(latitude, longitude, address, timestamp) - } catch (e: Exception) { - Log.e(TAG, "读取位置缓存失败: ${e.message}", e) - null - } + // 清空回调列表 + serviceCallbacks.clear() + + // 清理超时处理器 + locationTimeoutHandler = null + }, LOCATION_TIMEOUT_MS) } /** - * 位置数据类 + * 停止定位超时计时器 */ - data class LocationData( - val latitude: Double, - val longitude: Double, - val address: String, - val timestamp: Long - ) - - fun stop() { - locationClient?.stopLocation() - locationClient?.onDestroy() - locationClient = null - currentLocationCallback = null - permissionRequestCallback = null - serviceCallbacks.clear() - initialized = false - stopPermissionMonitor() + private fun stopLocationTimeout() { + locationTimeoutHandler?.removeCallbacksAndMessages(null) + locationTimeoutHandler = null } } diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt index 3c90cfabc..21bd7184f 100644 --- a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationServicePlugin.kt @@ -12,11 +12,10 @@ import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.PluginRegistry /** LocationServicePlugin */ -class LocationServicePlugin: FlutterPlugin, MethodCallHandler, ActivityAware, PluginRegistry.RequestPermissionsResultListener { +class LocationServicePlugin: FlutterPlugin, MethodCallHandler { private val TAG = "LocationServicePlugin" private lateinit var context: Context private lateinit var channel : MethodChannel - private var activityBinding: ActivityPluginBinding? = null override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { context = flutterPluginBinding.applicationContext @@ -25,43 +24,12 @@ class LocationServicePlugin: FlutterPlugin, MethodCallHandler, ActivityAware, Pl } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - // Log.d(TAG, "Method call: ${call.method}") - when (call.method) { - "location" -> LocationService.getCurrentLocation(result) - "requestPermission" -> LocationService.requestLocationPermission(activityBinding?.activity, result) - "checkPermission" -> LocationService.checkLocationPermission(context, result) - else -> result.notImplemented() - } + Log.d(TAG, "Method call: ${call.method}") + } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { channel.setMethodCallHandler(null) } - override fun onAttachedToActivity(binding: ActivityPluginBinding) { - activityBinding = binding - binding.addRequestPermissionsResultListener(this) - LocationService.initialize(context) - } - - override fun onDetachedFromActivityForConfigChanges() { - onDetachedFromActivity() - } - - override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { - onAttachedToActivity(binding) - } - - override fun onDetachedFromActivity() { - activityBinding?.removeRequestPermissionsResultListener(this) - activityBinding = null - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ): Boolean { - return LocationService.onRequestPermissionsResult(requestCode, permissions, grantResults) - } } diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/NativeLocationService.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/NativeLocationService.kt new file mode 100644 index 000000000..af4064134 --- /dev/null +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/NativeLocationService.kt @@ -0,0 +1,499 @@ +package com.yunqiinnovation.location_service + +import android.Manifest +import android.app.Activity +import android.content.Context +import android.content.pm.PackageManager +import android.location.* +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Log +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.util.* + +object NativeLocationService { + private const val TAG = "NativeLocationService" + private const val PERMISSION_REQUEST_CODE = 1002 + + // 高精度定位阈值 + private const val HIGH_ACCURACY_THRESHOLD = 50.0f // 只接受50米以内的高精度定位 + private const val LOCATION_TIMEOUT_MS = 60000L // 60秒超时 + private const val MIN_TIME_BETWEEN_UPDATES = 5000L // 5秒最小更新间隔 + private const val MIN_DISTANCE_CHANGE_FOR_UPDATES = 10f // 10米最小距离变化 + + private var appContext: Context? = null + private var locationManager: LocationManager? = null + private var initialized = false + private val serviceCallbacks = mutableListOf() + private var cachedLocationData: LocationData? = null + private var locationTimeoutHandler: Handler? = null + private var locationStartTime: Long = 0 + private var gpsLocationListener: LocationListener? = null + private var networkLocationListener: LocationListener? = null + + // 定位回调接口 + interface LocationCallback { + fun onLocationSuccess(latitude: Double, longitude: Double) + fun onLocationError(errorCode: Int, errorMessage: String) + } + + /** + * 初始化原生定位服务 + */ + fun initialize(context: Context) { + if (initialized) { + Log.d(TAG, "原生定位服务已初始化") + return + } + + appContext = context.applicationContext + locationManager = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + + if (locationManager == null) { + Log.e(TAG, "无法获取LocationManager") + return + } + + initialized = true + Log.d(TAG, "原生定位服务初始化完成") + } + + /** + * 检查GPS是否可用 + */ + private fun isGpsEnabled(): Boolean { + return locationManager?.isProviderEnabled(LocationManager.GPS_PROVIDER) ?: false + } + + /** + * 检查网络定位是否可用 + */ + private fun isNetworkEnabled(): Boolean { + return locationManager?.isProviderEnabled(LocationManager.NETWORK_PROVIDER) ?: false + } + + /** + * 获取位置信息(异步) + */ + fun requestLocation(callback: LocationCallback) { + val ctx = appContext + if (ctx == null) { + callback.onLocationError(-1, "定位服务未初始化") + return + } + + if (!checkPermissions(ctx)) { + callback.onLocationError(-2, "没有定位权限") + return + } + + if (!initialized) { + callback.onLocationError(-3, "定位服务未初始化") + return + } + + // 检查定位服务是否可用 + if (!isGpsEnabled() && !isNetworkEnabled()) { + callback.onLocationError(-4, "GPS和网络定位都不可用,请检查设置") + return + } + + // 优先返回缓存的位置 + cachedLocationData?.let { cached -> + val currentTime = System.currentTimeMillis() + // 如果缓存时间在5分钟内,直接返回 + if (currentTime - cached.timestamp < 5 * 60 * 1000) { + callback.onLocationSuccess(cached.latitude, cached.longitude) + return + } + } + + // 添加到回调列表 + serviceCallbacks.add(callback) + + // 记录定位开始时间 + locationStartTime = System.currentTimeMillis() + + // 启动超时计时器 + startLocationTimeout() + + // 使用协程在后台执行定位 + CoroutineScope(Dispatchers.IO).launch { + try { + startLocationUpdates() + } catch (e: Exception) { + stopLocationTimeout() + callback.onLocationError(-5, "启动定位失败: ${e.message}") + } + } + } + + /** + * 开始位置更新 + */ + private fun startLocationUpdates() { + val ctx = appContext ?: return + val locManager = locationManager ?: return + + // 检查权限 + if (ActivityCompat.checkSelfPermission( + ctx, + Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission( + ctx, + Manifest.permission.ACCESS_COARSE_LOCATION + ) != PackageManager.PERMISSION_GRANTED + ) { + Log.e(TAG, "没有定位权限") + return + } + + Log.d(TAG, "=== 启动混合定位模式 ===") + Log.d(TAG, "网络定位可用: ${isNetworkEnabled()}") + Log.d(TAG, "GPS定位可用: ${isGpsEnabled()}") + + // 优先启动网络定位 + if (isNetworkEnabled()) { + Log.d(TAG, "🌐 启动网络定位(优先)") + + networkLocationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + Log.d(TAG, "🌐 网络定位更新: 精度=${location.accuracy}米") + handleLocationResult(location, "NETWORK") + } + + override fun onProviderEnabled(provider: String) { + Log.d(TAG, "网络提供者已启用: $provider") + } + + override fun onProviderDisabled(provider: String) { + Log.d(TAG, "网络提供者已禁用: $provider") + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + Log.d(TAG, "网络状态变化: $provider, status: $status") + } + } + + locManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, + MIN_TIME_BETWEEN_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES, + networkLocationListener!!, + Looper.getMainLooper() + ) + Log.d(TAG, "✅ 网络定位已启动") + } + + // GPS作为补充定位 + if (isGpsEnabled()) { + Log.d(TAG, "🛰️ 启动GPS定位(补充)") + + gpsLocationListener = object : LocationListener { + override fun onLocationChanged(location: Location) { + Log.d(TAG, "🛰️ GPS定位更新: 精度=${location.accuracy}米") + handleLocationResult(location, "GPS") + } + + override fun onProviderEnabled(provider: String) { + Log.d(TAG, "GPS提供者已启用: $provider") + } + + override fun onProviderDisabled(provider: String) { + Log.d(TAG, "GPS提供者已禁用: $provider") + } + + override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) { + when (status) { + LocationProvider.AVAILABLE -> Log.d(TAG, "GPS状态: 可用") + LocationProvider.OUT_OF_SERVICE -> Log.d(TAG, "GPS状态: 服务中断") + LocationProvider.TEMPORARILY_UNAVAILABLE -> Log.d(TAG, "GPS状态: 暂时不可用") + } + } + } + + locManager.requestLocationUpdates( + LocationManager.GPS_PROVIDER, + MIN_TIME_BETWEEN_UPDATES, + MIN_DISTANCE_CHANGE_FOR_UPDATES, + gpsLocationListener!!, + Looper.getMainLooper() + ) + Log.d(TAG, "✅ GPS定位已启动") + } else { + Log.w(TAG, "GPS未启用,仅使用网络定位") + } + + if (!isNetworkEnabled() && !isGpsEnabled()) { + Log.e(TAG, "❌ 所有定位服务都不可用") + serviceCallbacks.forEach { callback -> + callback.onLocationError(-6, "定位服务不可用") + } + } + } + + /** + * 处理定位结果 + */ + // 修改handleLocationResult方法,对网络定位进行更细致的处理 + private fun handleLocationResult(location: Location, provider: String) { + Log.d(TAG, "定位结果: 来源=$provider, 精度=${location.accuracy}米, 坐标=(${location.latitude}, ${location.longitude})") + + // 混合定位策略:优先接受可用的定位结果 + when (provider) { + "NETWORK" -> { + // 网络定位优先策略 + when { + location.accuracy <= 200f -> { + Log.d(TAG, "✅ 接受网络定位: ${location.accuracy}米") + processLocationSuccess(location) + } + location.accuracy <= 1000f -> { + Log.d(TAG, "⚠️ 接受中等精度网络定位: ${location.accuracy}米") + // 短暂等待,看是否有更好的定位 + val elapsedTime = System.currentTimeMillis() - locationStartTime + if (elapsedTime > 5000) { // 5秒后接受 + processLocationSuccess(location) + } else { + Log.d(TAG, "等待更高精度定位中...") + } + } + location.accuracy <= 3000f -> { + Log.w(TAG, "⚠️ 网络定位精度较低: ${location.accuracy}米") + // 10秒后接受低精度定位 + val elapsedTime = System.currentTimeMillis() - locationStartTime + if (elapsedTime > 10000) { + Log.d(TAG, "接受低精度网络定位") + processLocationSuccess(location) + } + } + else -> { + Log.w(TAG, "网络定位精度过低(${location.accuracy}米),继续等待") + } + } + } + "GPS" -> { + // GPS作为高精度补充 + if (location.accuracy <= HIGH_ACCURACY_THRESHOLD) { + Log.d(TAG, "✅ 接受高精度GPS定位: ${location.accuracy}米") + processLocationSuccess(location) + } else if (location.accuracy <= HIGH_ACCURACY_THRESHOLD * 2) { + Log.d(TAG, "✅ 接受GPS定位: ${location.accuracy}米") + processLocationSuccess(location) + } else { + Log.w(TAG, "GPS定位精度不足(${location.accuracy}米),继续等待") + } + } + } + } + + /** + * 处理定位成功 + */ + private fun processLocationSuccess(location: Location) { + // 停止超时计时器 + stopLocationTimeout() + + // 停止定位更新 + stopLocationUpdates() + + // 获取地址信息(可选) + val address = getAddressFromLocation(location) + + // 缓存位置数据 + val locationData = LocationData( + latitude = location.latitude, + longitude = location.longitude, + timestamp = System.currentTimeMillis() + ) + cachedLocationData = locationData + + // 处理成功的定位结果 + serviceCallbacks.forEach { callback -> + try { + callback.onLocationSuccess( + location.latitude, + location.longitude + ) + } catch (e: Exception) { + Log.e(TAG, "服务回调失败: ${e.message}", e) + } + } + + // 清空服务回调列表 + serviceCallbacks.clear() + } + + /** + * 停止定位更新 + */ + private fun stopLocationUpdates() { + val locManager = locationManager ?: return + + gpsLocationListener?.let { + locManager.removeUpdates(it) + gpsLocationListener = null + } + + networkLocationListener?.let { + locManager.removeUpdates(it) + networkLocationListener = null + } + + Log.d(TAG, "已停止定位更新") + } + + /** + * 从位置获取地址信息 + */ + private fun getAddressFromLocation(location: Location): String { + return try { + val ctx = appContext ?: return "" + val geocoder = Geocoder(ctx, Locale.getDefault()) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // Android 13+ 使用新的异步API + var addressResult = "" + geocoder.getFromLocation(location.latitude, location.longitude, 1) { addresses -> + if (addresses.isNotEmpty()) { + addressResult = addresses[0].getAddressLine(0) ?: "" + } + } + addressResult + } else { + // 旧版本使用同步API + @Suppress("DEPRECATION") + val addresses = geocoder.getFromLocation(location.latitude, location.longitude, 1) + if (!addresses.isNullOrEmpty()) { + addresses[0].getAddressLine(0) ?: "" + } else { + "" + } + } + } catch (e: Exception) { + Log.w(TAG, "获取地址失败: ${e.message}") + "" + } + } + + /** + * 检查定位权限 + */ + private fun checkPermissions(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED || + ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * 检查精确定位权限 + */ + private fun hasFineLocationPermission(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * 检查粗略定位权限 + */ + private fun hasCoarseLocationPermission(context: Context): Boolean { + return ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } + + /** + * 检查后台定位权限 + */ + private fun hasBackgroundLocationPermission(context: Context): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ContextCompat.checkSelfPermission( + context, + Manifest.permission.ACCESS_BACKGROUND_LOCATION + ) == PackageManager.PERMISSION_GRANTED + } else { + true // Android 9 及以下没有这个权限 + } + } + + /** + * 位置数据类 + */ + data class LocationData( + val latitude: Double, + val longitude: Double, + val timestamp: Long + ) + + /** + * 启动定位超时计时器 + */ + private fun startLocationTimeout() { + // 如果已经有超时计时器在运行,先停止它 + stopLocationTimeout() + + locationTimeoutHandler = Handler(Looper.getMainLooper()) + locationTimeoutHandler?.postDelayed({ + Log.w(TAG, "定位超时,停止定位请求") + + // 停止定位 + stopLocationUpdates() + + // 通知所有等待的回调超时错误 + serviceCallbacks.forEach { callback -> + try { + callback.onLocationError(-6, "定位超时,未能在${LOCATION_TIMEOUT_MS/1000}秒内获取到位置") + } catch (e: Exception) { + Log.e(TAG, "超时回调失败: ${e.message}", e) + } + } + + // 清空回调列表 + serviceCallbacks.clear() + + // 清理超时处理器 + locationTimeoutHandler = null + }, LOCATION_TIMEOUT_MS) + } + + /** + * 停止定位超时计时器 + */ + private fun stopLocationTimeout() { + locationTimeoutHandler?.removeCallbacksAndMessages(null) + locationTimeoutHandler = null + } + + /** + * 清理资源 + */ + fun cleanup() { + stopLocationUpdates() + stopLocationTimeout() + serviceCallbacks.clear() + cachedLocationData = null + initialized = false + Log.d(TAG, "原生定位服务已清理") + } +} + +// 调整精度阈值,更适合混合定位 +private const val HIGH_ACCURACY_THRESHOLD = 100.0f // 放宽到100米 +private const val LOCATION_TIMEOUT_MS = 30000L // 缩短到30秒 +private const val MIN_TIME_BETWEEN_UPDATES = 2000L // 2秒更新 +private const val MIN_DISTANCE_CHANGE_FOR_UPDATES = 5f // 5米距离变化 \ No newline at end of file diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift index 5facd02e0..221a1ed56 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationData.swift @@ -2,15 +2,15 @@ import Foundation /// 位置数据结构 public struct LocationData: Codable { - public let latitude: Double public let longitude: Double + public let latitude: Double public let timestamp: TimeInterval - public let latitudeandlongitude: String + public let location: String - public init(latitude: Double, longitude: Double,timestamp: TimeInterval) { - self.latitude = latitude + public init( longitude: Double,latitude: Double,timestamp: TimeInterval) { self.longitude = longitude + self.latitude = latitude self.timestamp = timestamp - self.latitudeandlongitude = "\(latitude),\(longitude)" + self.location = "\(longitude),\(latitude)" } } diff --git a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift index 53c23686d..faccc1834 100644 --- a/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift +++ b/local_plugins/location_service/ios/location_service/Sources/location_service/LocationService.swift @@ -204,8 +204,8 @@ public class LocationService: NSObject { let longitude = Double(lngStr) { let locationData = LocationData( - latitude: latitude, longitude: longitude, + latitude: latitude, timestamp: Date().timeIntervalSince1970 * 1000 ) completion(.success(locationData)) @@ -245,8 +245,8 @@ public class LocationService: NSObject { /// 处理位置数据 private func processLocation(_ location: CLLocation) { let locationInfo: [String: Any] = [ - "user_lat": String(location.coordinate.latitude), "user_lng": String(location.coordinate.longitude), + "user_lat": String(location.coordinate.latitude), "timestamp": Date().timeIntervalSince1970 * 1000, "accuracy": location.horizontalAccuracy // 添加精度信息 ] @@ -255,8 +255,8 @@ public class LocationService: NSObject { // 更新缓存 cachedLocationData = LocationData( - latitude: location.coordinate.latitude, longitude: location.coordinate.longitude, + latitude: location.coordinate.latitude, timestamp: Date().timeIntervalSince1970 * 1000 ) @@ -268,8 +268,9 @@ public class LocationService: NSObject { /// 格式化位置数据 private func formatLocationData(_ locationData: LocationData) -> [String: Any] { return [ - "user_lat": String(locationData.latitude), "user_lng": String(locationData.longitude), + "user_lat": String(locationData.latitude), + "location": "\(locationData.longitude),\(locationData.latitude)", "timestamp": locationData.timestamp ] } From 88890a4a36c3a3b844974296ebdf116499285ea2 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 12 Aug 2025 11:17:15 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location_service/LocationService.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt index fcdc8ce17..59bea1b2f 100644 --- a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt @@ -16,6 +16,7 @@ import io.flutter.plugin.common.MethodChannel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import org.json.JSONObject object LocationService { @@ -115,6 +116,15 @@ object LocationService { } initialized = true + //首次请求一次定位 + requestLocation(object : LocationCallback { + override fun onLocationSuccess(latitude: Double, longitude: Double) { + } + + override fun onLocationError(errorCode: Int, errorMessage: String) { + } + }) + Log.d(TAG, "定位服务初始化完成 - 优化高精度模式") } catch (e: Exception) { Log.e(TAG, "高德定位初始化失败: ${e.message}", e) @@ -173,14 +183,6 @@ object LocationService { } Log.d(TAG, "定位结果: 来源=${location.provider}, 精度=${location.accuracy}米, 坐标=(${location.latitude}, ${location.longitude})") - - // 检查定位来源,只接受GPS定位 - if (location.provider != "gps") { - Log.w(TAG, "拒绝非GPS定位,当前来源: ${location.provider}") - // 继续等待GPS定位,不回调 - return - } - if (location.errorCode == AMapLocation.LOCATION_SUCCESS) { val accuracy = location.accuracy Log.d(TAG, "GPS定位成功: 精度=${accuracy}米")