Browse Source

上传iOS 网络优化

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
38ae361e9e
  1. 47
      local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift
  2. 2
      local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift
  3. 190
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift
  4. 20
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift
  5. 179
      local_plugins/chat_api/ios/chat_api/Sources/chat_api/NetworkStateMonitor.swift

47
local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift

@ -1712,24 +1712,25 @@ class ChatApiStreamCallback: StreamCallback {
do {
guard let agentService = try agentService else { return }
if sessionid == agentService.currsessionId {
// 停止等待音效
agentService.audioPlayer?.stopAwaitSound()
// os_log("ChatAPI错误: code:%{public}@ message:%{public}@", log: agentService.logger, type: .error, code,message)
if (sessionid != agentService.currsessionId) {
return
}
// 停止等待音效
agentService.audioPlayer?.stopAwaitSound()
try agentService.sendEvent(name: "error", data: [
"sessionid":sessionid,
"code": code,
"message": message
])
//
agentService.isAiStreaming = false
}
os_log("因错误设置AI流式状态为false", log: agentService.logger, type: .info)
os_log(
"liwei--------------- AI Call onError code: %{public}d message: %{public}@ ",
log: agentService.logger,
type: .info,
code, // 直接传 Int 类型
message // 假设 message 是 String 类型
)
}catch{
print("liwei--------------- AI Call onError 异常: \(error)")
}
@ -1966,16 +1967,23 @@ class AudioPlayer {
}
func stopAwaitSound() {
// 停止音频播放
awaitPlayer?.stop()
awaitPlayer = nil
// 停止超时定时器
stopAwaitTimeout()
os_log("停止等待音效播放", log: logger, type: .debug)
// 确保在主线程执行音频停止操作
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
// 停止音频播放
if let player = self.awaitPlayer {
if player.isPlaying {
player.stop()
}
self.awaitPlayer = nil
}
// 停止超时定时器
self.stopAwaitTimeout()
os_log("停止等待音效播放", log: self.logger, type: .debug)
}
}
private func playSound(named: String, fileType: String = "mp3") {
@ -2181,7 +2189,6 @@ extension AgentServiceImpl: AzureAsrHelper.ContinuousRecognizeCallback {
func onError(sessionid:String ,_ errorCode: Int, _ error: String) {
let data: [String: Any] = ["sessionid":sessionid,"code":errorCode, "message": error.isEmpty ? "未知错误" : error]
audioPlayer?.stopAwaitSound()
sendEvent(name: "error", data: data)
isRecognizing = false
// 新增:出错时复位"启动中/待停止"状态

2
local_plugins/ble_service/ios/ble_service/Sources/ble_service/SwiftBleServicePlugin.swift

@ -141,7 +141,7 @@ public class SwiftBleServicePlugin: NSObject, FlutterPlugin {
case "getPairedMacAddress":
let pairedUUID = BleService.shared.getPairedMacAddress()
result(nil)
result(pairedUUID)
case "clearAssociations":
BleService.shared.clearAssociations()
result(true)

190
local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift

@ -60,9 +60,15 @@ public class ChatApiService: NSObject {
private var toolCalls: [Int: ToolCallInfo] = [:]
private var currSessionId = ""
// 网络监控 - 延迟初始化
private var networkMonitor: NetworkStateMonitor!
// MARK: - 初始化
public override init() {
super.init()
// 在这里初始化网络监控
networkMonitor = NetworkStateMonitor()
networkMonitor.initialize(listener: self)
}
// MARK: - 公共方法
@ -278,6 +284,11 @@ public func sendMessageStream(_ sessionId: String, messages: [[String: Any]]) {
getSessionCallback(sessionId)?.onError(sessionId,1002,"服务未初始化")
return
}
//网络检测
if (!checkNetworkStatus()) {
getSessionCallback(sessionId)?.onError(sessionId,1000,"当前网络不可用")
return
}
// 保存当前消息和回调
currentMessages = messages
@ -433,7 +444,6 @@ private func roughTokenCount(text: String) -> Int {
}
/// 处理工具调用
/// 处理工具调用
private func processToolCalls(_ sessionId:String) async -> Bool {
// 验证工具调用集合不为空
if toolCalls.isEmpty {
@ -467,8 +477,6 @@ private func processToolCalls(_ sessionId:String) async -> Bool {
// 转换为JSON对象格式
let jsonFunctionCall = convertMapToJsonObject(functionCall)
getSessionCallback(sessionId)?.onFunctionCall(sessionId, jsonFunctionCall)
// 通知上层工具调用事件
// getSessionCallback(sessionId)?.onFunctionCall(sessionId, functionCall)
// 在后台处理工具调用
Task { [weak self] in
@ -479,73 +487,86 @@ private func processToolCalls(_ sessionId:String) async -> Bool {
// 解析参数
let args = try self.parseJsonArguments(firstToolCall.arguments)
// 通过MCP客户端处理工具调用
// 通过MCP客户端处理工具调用,添加8秒超时
var result: [String: Any] = [:]
var isError = false
var errorStr = ""
if let client = self.mcpClient {
// 调用MCP工具
if let toolResult = await client.callTool(name: firstToolCall.name, arguments: args) {
print("[Session: \(sessionId)] AI调用工具结果: \(firstToolCall.name), 参数: \(args), 结果: \(toolResult)")
// 统一结果格式
if toolResult["isError"] as? Bool == true {
if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let errorText = firstContent["text"] as? String {
errorStr = errorText
result = ["context": errorText]
// 检查网络状态
if !self.checkNetworkStatus() {
print("[Session: \(sessionId)] 网络连接不可用,工具调用失败")
result = ["context": "网络连接不可用,请检查网络设置"]
isError = true
} else {
// 使用8秒超时调用MCP工具
do {
let toolResult = try await withTimeout(seconds: 8.0) {
return await client.callTool(name: firstToolCall.name, arguments: args)
}
if let toolResult = toolResult {
print("[Session: \(sessionId)] AI调用工具结果: \(firstToolCall.name), 参数: \(args), 结果: \(toolResult)")
// 统一结果格式
if toolResult["isError"] as? Bool == true {
if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let errorText = firstContent["text"] as? String {
errorStr = errorText
result = ["context": errorText]
} else {
result = ["context": "Tool execution failed"]
}
} else if let context = toolResult["context"] {
// 本地函数结果
result = ["context": context]
} else if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let text = firstContent["text"] as? String {
// MCP工具结果
result = ["context": text]
} else {
print("[Session: \(sessionId)] MCP工具调用返回无法解析的结果")
result = ["context": "Tool call failed"]
}
} else {
result = ["context": "Tool execution failed"]
print("[Session: \(sessionId)] MCP工具调用返回nil")
result = ["context": "Tool call failed"]
}
} else if let context = toolResult["context"] {
// 本地函数结果
result = ["context": context]
} else if let content = toolResult["content"] as? [[String: Any]],
let firstContent = content.first,
let text = firstContent["text"] as? String {
// MCP工具结果
result = ["context": text]
} else {
print("[Session: \(sessionId)] MCP工具调用返回无法解析的结果")
result = ["context": "Tool call failed"]
} catch is TimeoutError {
print("[Session: \(sessionId)] 工具调用超时: \(firstToolCall.name)")
result = ["context": "工具调用超时,请重试"]
isError = true
} catch {
print("[Session: \(sessionId)] 工具调用异常: \(error.localizedDescription)")
result = ["context": "工具调用失败: \(error.localizedDescription)"]
isError = true
}
} else {
print("[Session: \(sessionId)] MCP工具调用返回nil")
result = ["context": "Tool call failed"]
}
} else {
// 工具不存在
result = ["context": "Tool not found: \(firstToolCall.name)"]
}
// 再次检查会话是否仍然有效
if sessionId == self.currSessionId {
// if (firstToolCall.name != "set_user_profile_field"){
// 处理结果
if (!isError){
self.getSessionCallback(sessionId)?.onFunctionCallResult(
sessionId,
functionCall,
result
)
}
//else{ mcp 的错误需要交给ai处理 不需要返回到上层
// self.getSessionCallback(sessionId)?.onError(
// sessionId,
// ChatApiException(errorStr)
// )
// }
// 将结果发送回OpenAI继续对话
await self.sendFunctionCallResultInternal(
sessionId: sessionId,
messages: self.currentMessages,
functionCall: functionCall,
functionResult: self.jsonToString(result) ?? "{}"
// 处理结果
if (!isError){
self.getSessionCallback(sessionId)?.onFunctionCallResult(
sessionId,
functionCall,
result
)
// }else{
// self.getSessionCallback(sessionId)?.onComplete(sessionId)
// self.clearCurrentSession()
// }
}
// 将结果发送回OpenAI继续对话
await self.sendFunctionCallResultInternal(
sessionId: sessionId,
messages: self.currentMessages,
functionCall: functionCall,
functionResult: self.jsonToString(result) ?? "{}"
)
}
}
} catch {
@ -996,6 +1017,65 @@ private func convertMapToJsonObject(_ map: [String: Any]) -> [String: Any] {
return chatTools
}
/**
* 检查网络状态
* @return 网络是否可用
*/
private func checkNetworkStatus() -> Bool {
// 直接使用 NetworkStateMonitor 的 checkNetworkStatus 方法
let isAvailable = networkMonitor.checkNetworkStatus()
return isAvailable
}
/// 分析网络错误并返回错误代码和消息
private func analyzeNetworkError(_ error: Error) -> (code: Int, message: String) {
let errorDescription = error.localizedDescription.lowercased()
// 使用 NetworkStateMonitor 的网络错误检测
if networkMonitor.isNetworkRelatedError(reason: errorDescription, errorDetails: "") {
if errorDescription.contains("timeout") {
return (1004, "网络请求超时")
} else if errorDescription.contains("connection") {
if errorDescription.contains("abort") {
return (1005, "网络连接中断")
} else if errorDescription.contains("reset") {
return (1006, "网络连接重置")
} else {
return (1007, "网络连接失败")
}
} else if errorDescription.contains("dns") || errorDescription.contains("host") {
return (1008, "域名解析失败")
} else if errorDescription.contains("ssl") || errorDescription.contains("tls") {
return (1009, "SSL/TLS 连接失败")
} else {
return (1010, "网络错误: \(error.localizedDescription)")
}
}
// 非网络错误
return (1003, "流式请求失败: \(error.localizedDescription)")
}
}
// MARK: - NetworkStateListener
extension ChatApiService: NetworkStateMonitor.NetworkStateListener {
public func onNetworkAvailable() {
print("[ChatApiService] 网络连接恢复")
// 可以在这里通知上层网络恢复
}
public func onNetworkLost() {
print("[ChatApiService] 网络连接丢失")
// 网络丢失时中止当前请求
if !currSessionId.isEmpty {
getSessionCallback(currSessionId)?.onError(currSessionId, 1001, "网络连接丢失")
abortCurrentSession()
}
}
}
/// 本地函数处理器

20
local_plugins/chat_api/ios/chat_api/Sources/chat_api/MCPClient.swift

@ -450,7 +450,7 @@ public class MCPClient {
private func initializeSystemFunctions() {
do {
let handler = SystemFunctionHandler()
let handler: SystemFunctionHandler = SystemFunctionHandler()
handler.registerAllFunctions(client: self)
} catch {
@ -460,8 +460,8 @@ public class MCPClient {
private func initializeMusicSystemFunction() {
do {
// 初始化QQ音乐功能
let qqMusicHandler = MusicSystemFunctionHandler()
qqMusicHandler.registerAllFunctions(client: self)
let musicHandler: MusicSystemFunctionHandler = MusicSystemFunctionHandler()
musicHandler.registerAllFunctions(client: self)
} catch {
// 静默处理错误
}
@ -469,7 +469,7 @@ public class MCPClient {
private func initializeQQMusicSystemFunction() {
do {
// 初始化QQ音乐功能
let qqMusicHandler = QQMusicSystemFunctionHandler()
let qqMusicHandler: QQMusicSystemFunctionHandler = QQMusicSystemFunctionHandler()
qqMusicHandler.registerAllFunctions(client: self)
} catch {
// 静默处理错误
@ -479,26 +479,26 @@ public class MCPClient {
public func connectToSSE(mcpConfigJson: String) async throws -> Bool {
// 清除现有连接
for client in subClients.values {
for client: MCPSubClient in subClients.values {
await client.close()
}
subClients.removeAll()
guard let data = mcpConfigJson.data(using: .utf8) else {
guard let data: Data = mcpConfigJson.data(using: .utf8) else {
return false
}
guard let config = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
guard let config: [String : Any] = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else {
return false
}
guard let mcpServers = config["mcpServers"] as? [String: Any] else {
guard let mcpServers: [String : Any] = config["mcpServers"] as? [String: Any] else {
return false
}
var connectedCount = 0
for (serverId, serverConfig) in mcpServers {
guard let configDict = serverConfig as? [String: Any] else {
for (serverId: String, serverConfig) in mcpServers {
guard let configDict: [String : Any] = serverConfig as? [String: Any] else {
continue
}

179
local_plugins/chat_api/ios/chat_api/Sources/chat_api/NetworkStateMonitor.swift

@ -0,0 +1,179 @@
import Foundation
import SystemConfiguration
import Network
/// 网络状态监听器(iOS)
public final class NetworkStateMonitor {
private let tag = "NetworkStateMonitor"
// iOS 12+
private var monitor: NWPathMonitor?
private let monitorQueue = DispatchQueue(label: "NetworkStateMonitor.queue")
// iOS 10–11 回退
private var reachability: SCNetworkReachability?
private var reachabilityQueue: DispatchQueue?
private(set) var isNetworkAvailable: Bool = true
private weak var networkStateListener: NetworkStateListener?
/// 网络状态监听接口
public protocol NetworkStateListener: AnyObject {
func onNetworkAvailable()
func onNetworkLost()
}
public init() {}
/// 初始化网络状态监听
public func initialize(listener: NetworkStateListener? = nil) {
networkStateListener = listener
initNetworkMonitoring()
}
/// 检查当前网络状态
/// - Returns: true 表示网络可用
public func checkNetworkStatus() -> Bool {
if #available(iOS 12.0, *) {
// NWPathMonitor 只在回调里更新状态,因此这里返回缓存的 isNetworkAvailable
return isNetworkAvailable
} else {
// 使用 Reachability 即时检测
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
let ref = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}
guard let reachRef = ref else { return false }
var flags = SCNetworkReachabilityFlags()
if !SCNetworkReachabilityGetFlags(reachRef, &flags) { return false }
return Self.isReachable(flags: flags)
}
}
/// 释放资源
public func dispose() {
if #available(iOS 12.0, *) {
monitor?.cancel()
monitor = nil
} else {
if let reachability = reachability {
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
SCNetworkReachabilitySetCallback(reachability, nil, nil)
}
reachability = nil
reachabilityQueue = nil
}
networkStateListener = nil
}
/// 判断是否为网络相关错误(关键字简单匹配)
public func isNetworkRelatedError(reason: String, errorDetails: String) -> Bool {
let networkErrorKeywords = [
"network","connection","timeout","unreachable",
"dns","socket","ssl","tls","certificate",
"网络","连接","超时","不可达"
]
let combined = (reason + " " + errorDetails).lowercased()
return networkErrorKeywords.contains { combined.contains($0.lowercased()) }
}
}
// MARK: - Private
private extension NetworkStateMonitor {
func initNetworkMonitoring() {
if #available(iOS 12.0, *) {
let monitor = NWPathMonitor()
self.monitor = monitor
monitor.pathUpdateHandler = { [weak self] path in
guard let self = self else { return }
let hasInternet = (path.status == .satisfied)
// 只有状态变化时才回调
if hasInternet && !self.isNetworkAvailable {
self.isNetworkAvailable = true
self.networkStateListener?.onNetworkAvailable()
Self.log(self.tag, "网络连接可用")
} else if !hasInternet && self.isNetworkAvailable {
self.isNetworkAvailable = false
self.networkStateListener?.onNetworkLost()
Self.log(self.tag, "网络连接丢失")
} else {
Self.log(self.tag, "网络能力变化,是否有网络: \(hasInternet)")
}
}
monitor.start(queue: monitorQueue)
} else {
// iOS 10–11 使用 Reachability 回退
var zeroAddress = sockaddr_in()
zeroAddress.sin_len = UInt8(MemoryLayout<sockaddr_in>.size)
zeroAddress.sin_family = sa_family_t(AF_INET)
reachability = withUnsafePointer(to: &zeroAddress) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
SCNetworkReachabilityCreateWithAddress(nil, $0)
}
}
guard let reachability = reachability else {
Self.log(tag, "初始化网络监听失败: Reachability 创建失败")
return
}
let queue = DispatchQueue(label: "NetworkStateMonitor.reachability")
reachabilityQueue = queue
var context = SCNetworkReachabilityContext(
version: 0,
info: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque()),
retain: nil,
release: nil,
copyDescription: nil
)
let callback: SCNetworkReachabilityCallBack = { (_, flags, info) in
guard let info = info else { return }
let monitor = Unmanaged<NetworkStateMonitor>.fromOpaque(info).takeUnretainedValue()
let hasInternet = NetworkStateMonitor.isReachable(flags: flags)
if hasInternet && !monitor.isNetworkAvailable {
monitor.isNetworkAvailable = true
monitor.networkStateListener?.onNetworkAvailable()
NetworkStateMonitor.log(monitor.tag, "网络连接可用")
} else if !hasInternet && monitor.isNetworkAvailable {
monitor.isNetworkAvailable = false
monitor.networkStateListener?.onNetworkLost()
NetworkStateMonitor.log(monitor.tag, "网络连接丢失")
} else {
NetworkStateMonitor.log(monitor.tag, "网络能力变化,是否有网络: \(hasInternet)")
}
}
if SCNetworkReachabilitySetCallback(reachability, callback, &context),
SCNetworkReachabilitySetDispatchQueue(reachability, queue) {
// 触发一次初始查询
var flags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachability, &flags) {
self.isNetworkAvailable = Self.isReachable(flags: flags)
}
} else {
Self.log(tag, "初始化网络监听失败: 设置回调/队列失败")
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
self.reachability = nil
}
}
}
static func isReachable(flags: SCNetworkReachabilityFlags) -> Bool {
// 经典 Reachability 判定
let reachable = flags.contains(.reachable)
let requiresConnection = flags.contains(.connectionRequired)
let canConnectAutomatically = flags.contains(.connectionOnTraffic) || flags.contains(.connectionOnDemand)
let canConnectWithoutUser = canConnectAutomatically && !flags.contains(.interventionRequired)
return reachable && (!requiresConnection || canConnectWithoutUser)
}
static func log(_ tag: String, _ message: String) {
#if DEBUG
print("[\(tag)] \(message)")
#endif
}
}
Loading…
Cancel
Save