using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading; using UnityEngine; using UnityEngine.Android; namespace SmartBowSDK.CMD { public class CMDManager { // ---- GC 防止回调被销毁 ---- private CMDScannerCallbackProxy scannerProxy; private CMDBleCallbackProxy bleCallbackProxy; private string pendingScanPattern = null; public event Action OnCMDDeviceFound; public event Action OnCMDScanFailed; public event Action OnCMDBLEConnected; public event Action OnCMDBLEDisconnected; public event Action OnCMDBLEReady; public event Action OnCMDBLENotify; public event Action OnPermissionDenied; // 某权限被拒绝 public event Action OnPermissionDontAsk; // 被永久拒绝 //public int scanTimeout = 10; // 传超时 ,默认10秒 // Android 12+ private readonly string[] BLE_PERMISSIONS_31 = { "android.permission.BLUETOOTH_SCAN", "android.permission.BLUETOOTH_CONNECT" }; // Android 6~11 private readonly string[] BLE_PERMISSIONS = { Permission.FineLocation }; // ----------- 统一复用对象 ----------- private AndroidJavaObject unityActivity; // currentActivity private AndroidJavaClass bleManagerClass; // CMDBleManager.class private AndroidJavaObject bleManagerInst; // CMDBleManager.getInstance() private AndroidJavaClass transparentProxyActivityClass; // CMDBleManager.class // ========= 队列 & 后台解析线程 ========= private readonly ConcurrentQueue notifyQueue = new ConcurrentQueue(); private readonly AutoResetEvent notifyEvent = new AutoResetEvent(false); private Thread notifyWorker; private volatile bool notifyWorkerRunning = false; // 主线程派发节流:最多每 dispatchIntervalMs 毫秒派发一次到主线程 private readonly int dispatchIntervalMs = 0; // 50 Hz 向主线程派发(可调) private long lastDispatchTime = 0; // -------- Constructor -------- public CMDManager() { // 初始化统一复用对象 -------- unityActivity = new AndroidJavaClass("com.unity3d.player.UnityPlayer") .GetStatic("currentActivity"); //bleManagerClass = new AndroidJavaClass("com.ble.mycdmmanager.CMDBleManager"); //bleManagerInst = bleManagerClass.CallStatic("getInstance"); bleManagerInst = new AndroidJavaObject( "com.ble.mycdmmanager.CMDBleManager", unityActivity // 传 context ); transparentProxyActivityClass = new AndroidJavaClass("com.ble.mycdmmanager.CMDTransparentProxyActivity"); // BLE callback bleCallbackProxy = new CMDBleCallbackProxy(); bleCallbackProxy.OnBLEConnectedEvent += () => OnCMDBLEConnected?.Invoke(); bleCallbackProxy.OnBLEDisconnectedEvent += () => OnCMDBLEDisconnected?.Invoke(); bleCallbackProxy.OnBLEReadyEvent += () => OnCMDBLEReady?.Invoke(); bleCallbackProxy.OnBLENotifyEvent += (mergedDataObj) => { //SmartBowLogger.LogError(this, "[OnBLENotifyEvent]:"+ BitConverter.ToString(data)); //long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); //SmartBowLogger.Log(this,$"[CMDBleManager→Unity] NotifyArrived | t={now} | size={data.Length}"); //OnCMDBLENotify?.Invoke(data); // 实时调用 // 快速入队(非阻塞) //EnqueueNotify(data); // --- 1. 使用 Buffer.BlockCopy 安全地将 JNI 数组转换为 C# byte[] --- if (!(mergedDataObj is Array sourceArray)) { Debug.LogError($"[CMDManager] JNI 回调期望一个数组,但接收到: {mergedDataObj?.GetType()}"); return; } int totalLen = sourceArray.Length; byte[] mergedData = new byte[totalLen]; // BlockCopy 可以在 byte[] 和 sbyte[] 之间进行安全的原始内存复制 System.Buffer.BlockCopy(sourceArray, 0, mergedData, 0, totalLen); // --- 2. 拆分多条数据 --- int index = 0; while (index + 2 <= mergedData.Length) { // 确保 Java 和 C# 的字节序一致(您的代码使用了小端序 for BitConverter.ToUInt16) ushort len = BitConverter.ToUInt16(mergedData, index); index += 2; if (index + len > mergedData.Length) { Debug.LogWarning("[CMDManager] 数据长度超出合并数组范围"); break; } byte[] singleData = new byte[len]; Array.Copy(mergedData, index, singleData, 0, len); index += len; // --- 3. 快速入队(非阻塞) --- EnqueueNotify(singleData); // 使用您的高性能 C# 队列和后台线程 } }; // 启动主线程 dispatcher(如果未存在) MainThreadDispatcher.EnsureCreated(); // 启动后台线程 StartNotifyWorker(); //bleManagerClass.CallStatic("setGlobalCMDBleCallback", bleCallbackProxy); bleManagerInst.Call("setCmdBleCallback", bleCallbackProxy); } /// /// 设置服务特征值 /// /// /// /// public void SetUUIDs(string service, string write, string notify) { bleManagerInst.Call("setUUIDs", service, write, notify ); } // -------- 权限处理 -------- private string[] GetRequiredPermissions() => GetAndroidSDKInt() >= 31 ? BLE_PERMISSIONS_31 : BLE_PERMISSIONS; private bool CheckAllBLEPermissions() { foreach (var p in GetRequiredPermissions()) { if (!Permission.HasUserAuthorizedPermission(p)) return false; } return true; } private int GetAndroidSDKInt() { using (var v = new AndroidJavaClass("android.os.Build$VERSION")) return v.GetStatic("SDK_INT"); } private void RequestAllPermissions(Action onAllGranted) { var perms = GetRequiredPermissions(); RequestPermissionRecursive(perms, 0, onAllGranted); } private void RequestPermissionRecursive(string[] perms, int index, Action onAllGranted) { if (index >= perms.Length) { onAllGranted?.Invoke(); return; } string permission = perms[index]; PermissionManager.Request( permission, onGranted: () => { // 继续请求下一项权限 RequestPermissionRecursive(perms, index + 1, onAllGranted); }, onDenied: () => { Debug.LogError($"[CMDManager] 权限被拒绝:{permission}"); // ---- 调用事件 ---- OnPermissionDenied?.Invoke(permission); }, onDontAsk: () => { Debug.LogError($"[CMDManager] 权限永久拒绝:{permission}"); // ---- 调用事件 ---- OnPermissionDontAsk?.Invoke(permission); } ); } private void EnsureBLEPermissionThen(Action doWork) { if (CheckAllBLEPermissions()) { doWork?.Invoke(); } else { RequestAllPermissions(() => { doWork?.Invoke(); }); } } // ---- 扫描入口 ---- public void StartScan(string pattern) { pendingScanPattern = pattern; EnsureBLEPermissionThen(() => { LaunchTransparentProxyScan(pendingScanPattern); }); } // ---- 启动透明 Activity ---- private void LaunchTransparentProxyScan(string pattern) { // Scanner Callback scannerProxy = new CMDScannerCallbackProxy(); scannerProxy.OnDeviceFoundEvent = (name, mac) => { //Debug.Log("[CMDManager] DeviceFound: " + name + " - " + mac); OnCMDDeviceFound?.Invoke(name, mac); }; scannerProxy.OnScanFailedEvent = (state,reason) => { //Debug.LogError("[CMDManager] ScanFailed: " + reason); OnCMDScanFailed?.Invoke(state,reason); }; // ---- 设置静态回调 ---- transparentProxyActivityClass.SetStatic("globalCMDScannerCallback", scannerProxy); // ---- 启动 Activity ---- AndroidJavaObject intent = new AndroidJavaObject( "android.content.Intent", unityActivity, new AndroidJavaClass("com.ble.mycdmmanager.CMDTransparentProxyActivity") ); //扫描设备名字字符串 intent.Call("putExtra", "pattern", pattern); //intent.Call("putExtra", "timeout_sec", scanTimeout); //intent.Call("addFlags", 0x10000000); // FLAG_ACTIVITY_NEW_TASK unityActivity.Call("startActivity", intent); } /// /// 通过 MAC 地址连接设备 /// public bool ConnectMac(string mac) { if (string.IsNullOrEmpty(mac)) return false; bool result = false; EnsureBLEPermissionThen(() => { result = bleManagerInst.Call("connectMac", mac); }); return result; } /** * 断开cmd蓝牙连接 */ public void Disconnect() { try { bleManagerInst.Call("disconnect"); //清空队列 ClearNotifyQueue(); SmartBowLogger.Log(this, "[CMDManager] Disconnect called (OK)"); } catch (Exception e) { SmartBowLogger.LogError(this, "[CMDManager] Disconnect error: " + e); } } /** * 清空cmd manager */ public void Cleanup() { try { SmartBowLogger.Log(this, "[CMDManager] begin"); // 1) 停 Worker StopNotifyWorker(); // 2) 清空队列 ClearNotifyQueue(); // 3) 注销静态回调,避免旧 CMD 的 notify 残留 //try //{ // // 移除 Scanner 回调 // transparentProxyActivityClass.SetStatic("globalCMDScannerCallback", null); // // 移除 BLE 回调 // bleManagerClass.CallStatic("setGlobalCMDBleCallback", null); // SmartBowLogger.Log(this, "[CMDManager] static callbacks cleared"); //} //catch (Exception ex) //{ // SmartBowLogger.LogError(this, "[CMDManager] clear static callback error:" + ex); //} // 4) 最后再断开 Android BLE bleManagerInst.Call("disconnect"); SmartBowLogger.Log(this, "[CMDManager] Cleanup done"); } catch (Exception e) { SmartBowLogger.LogError(this, "[CMDManager] Cleanup error: " + e); } } public bool SendCommand(string text) { if (string.IsNullOrWhiteSpace(text)) return false; bool success; // 判断是否 HEX(01 02 03) if (System.Text.RegularExpressions.Regex.IsMatch(text, @"^([0-9A-Fa-f]{2}\s*)+$")) { byte[] bytes = ParseHexString(text); success = WriteBytes(bytes); } else { success = WriteString(text); } if (!success) { SmartBowLogger.LogError(this,"[CMDManager] Send failed, BLE not ready"); return false; } SmartBowLogger.Log(this,"[CMDManager] Sent: " + text); return true; } public bool WriteString(string str) { try { return bleManagerInst.Call("writeString", str); } catch (System.Exception e) { SmartBowLogger.LogError(this,"[CMDManager] writeString error: " + e); return false; } } public bool WriteBytes(byte[] bytes) { try { return bleManagerInst.Call("writeBytes", bytes); } catch (System.Exception e) { SmartBowLogger.LogError(this,"[CMDManager] writeBytes error: " + e); return false; } } private byte[] ParseHexString(string hex) { try { string[] parts = hex.Trim().Split(' '); byte[] bytes = new byte[parts.Length]; for (int i = 0; i < parts.Length; i++) { bytes[i] = System.Convert.ToByte(parts[i], 16); } return bytes; } catch { return null; } } // -------- 队列入队(由 CMDBleCallbackProxy 调用) -------- internal void EnqueueNotify(byte[] data) { if (data == null) return; notifyQueue.Enqueue(data); notifyEvent.Set(); // 唤醒后台线程 } private void StartNotifyWorker() { if (notifyWorkerRunning) return; notifyWorkerRunning = true; notifyWorker = new Thread(NotifyWorkerLoop) { IsBackground = true, Name = "CMDManager-NotifyWorker", Priority = System.Threading.ThreadPriority.AboveNormal // 提升优先级 }; notifyWorker.Start(); } private void StopNotifyWorker() { notifyWorkerRunning = false; notifyEvent.Set(); try { notifyWorker?.Join(500); } catch { } notifyWorker = null; } private void ClearNotifyQueue() { while (notifyQueue.TryDequeue(out _)) ; SmartBowLogger.Log(this, "[CMDManager] NotifyQueue cleared"); } private void NotifyWorkerLoop() { // 后台线程不断消费队列,尽快完成解析工作 while (notifyWorkerRunning) { try { // 等待直到有数据 if (!notifyQueue.TryDequeue(out var data)) { // 等待唤醒或超时 notifyEvent.WaitOne(50); continue; } // 处理单条数据(在后台线程) ProcessNotifyBackground(data); // 批量消费:尽可能多地一次性取完队列,以减少唤醒次数 while (notifyQueue.TryDequeue(out data)) { ProcessNotifyBackground(data); } } catch (Exception ex) { Debug.LogError("[CMDManager] NotifyWorkerLoop error: " + ex); } } } private void ProcessNotifyBackground(byte[] bytes) { // 直接传引用,不做限频派发 MainThreadDispatcher.Enqueue(() => { OnCMDBLENotify?.Invoke(bytes); }); // 后台处理完毕后,只做限频派发 //DispatchToMainThreadWithThrottle(bytes); } // 将数据派发到主线程,但节流:每 dispatchIntervalMs 才发一次 private void DispatchToMainThreadWithThrottle(byte[] processed) { long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); if (now - lastDispatchTime >= dispatchIntervalMs) { lastDispatchTime = now; // 直接传引用,不做 Array.Copy MainThreadDispatcher.Enqueue(() => { OnCMDBLENotify?.Invoke(processed); }); } } } }