fix(bluetooth,ui): 修复蓝牙关闭后的断开链路与响应数据空引用
- 关蓝牙时跳过0x70协议告别帧与扫描(BluetoothLeScanner为null会抛NPE中断主页Init) - 初始化前检查蓝牙开关静默跳过,错误回调不再误报初始化失败,断开清理不在蓝牙关闭时自动重扫 - 错误/超时响应统一携带空Data,修复超时回调BitConverter.ToString(null)崩溃 - 主页Init增加异常兜底:保证Loading收尾并记录完整异常
This commit is contained in:
parent
320ece473f
commit
99ffc19f60
@ -2449,7 +2449,7 @@ namespace Kill.Bluetooth
|
|||||||
if (!CheckConnection())
|
if (!CheckConnection())
|
||||||
{
|
{
|
||||||
LogError($"[BLE-DEBUG] 蓝牙未就绪,无法发送指令: {description}");
|
LogError($"[BLE-DEBUG] 蓝牙未就绪,无法发送指令: {description}");
|
||||||
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR });
|
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR, Data = new byte[0] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2465,7 +2465,7 @@ namespace Kill.Bluetooth
|
|||||||
if (!CheckConnection())
|
if (!CheckConnection())
|
||||||
{
|
{
|
||||||
LogError("[BLE-DEBUG] CheckConnection failed, invoking callback with error");
|
LogError("[BLE-DEBUG] CheckConnection failed, invoking callback with error");
|
||||||
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR });
|
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR, Data = new byte[0] });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2516,7 +2516,7 @@ namespace Kill.Bluetooth
|
|||||||
{
|
{
|
||||||
LogError($"发送帧异常: {ex.Message}");
|
LogError($"发送帧异常: {ex.Message}");
|
||||||
IsWaitingResponse = false;
|
IsWaitingResponse = false;
|
||||||
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR });
|
callback?.Invoke(new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR, Data = new byte[0] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2963,7 +2963,9 @@ namespace Kill.Bluetooth
|
|||||||
|
|
||||||
var timeoutResponse = new BLEResponse
|
var timeoutResponse = new BLEResponse
|
||||||
{
|
{
|
||||||
Status = BLEConstants.STATUS_TIMEOUT
|
Status = BLEConstants.STATUS_TIMEOUT,
|
||||||
|
// 超时无数据,给空数组避免回调里 BitConverter.ToString(response.Data) 抛 ArgumentNullException
|
||||||
|
Data = new byte[0]
|
||||||
};
|
};
|
||||||
|
|
||||||
_pendingCallback?.Invoke(timeoutResponse);
|
_pendingCallback?.Invoke(timeoutResponse);
|
||||||
|
|||||||
@ -202,6 +202,18 @@ namespace Kill.Bluetooth
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||||
|
// 安卓:蓝牙已被系统关闭时直接静默跳过。
|
||||||
|
// 断开清理/自动重连等后台流程会反复触发初始化,若继续走到 CheckAndEnableBluetooth
|
||||||
|
// 会弹"蓝牙未开启"Toast 并广播 OnError,造成关蓝牙后一连串误报。
|
||||||
|
// 用户主动触发的初始化走 ForceInitialize,仍能得到提示。
|
||||||
|
if (!CheckIfBluetoothIsOn())
|
||||||
|
{
|
||||||
|
Log("蓝牙未开启,跳过蓝牙初始化");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
Log("正在初始化蓝牙...");
|
Log("正在初始化蓝牙...");
|
||||||
|
|
||||||
// 先检查蓝牙是否开启,如果没有则尝试启用
|
// 先检查蓝牙是否开启,如果没有则尝试启用
|
||||||
@ -218,7 +230,10 @@ namespace Kill.Bluetooth
|
|||||||
public void ForceInitialize()
|
public void ForceInitialize()
|
||||||
{
|
{
|
||||||
_userDeniedBluetooth = false;
|
_userDeniedBluetooth = false;
|
||||||
Initialize();
|
// 用户主动触发:不走 Initialize 的静默跳过守卫,
|
||||||
|
// 蓝牙未开启时由 CheckAndEnableBluetooth 给出提示
|
||||||
|
Log("正在初始化蓝牙...");
|
||||||
|
CheckAndEnableBluetooth(DoInitialize);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -476,7 +491,8 @@ namespace Kill.Bluetooth
|
|||||||
}
|
}
|
||||||
if (IsConnected)
|
if (IsConnected)
|
||||||
{
|
{
|
||||||
Disconnect();
|
// 蓝牙已被系统关闭,0x70 协议告别帧发不出去,跳过直接清理本地状态
|
||||||
|
Disconnect(sendProtocolCommand: false);
|
||||||
}
|
}
|
||||||
if (IsScanning)
|
if (IsScanning)
|
||||||
{
|
{
|
||||||
@ -595,6 +611,13 @@ namespace Kill.Bluetooth
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// 蓝牙已被系统关闭时,插件所有操作(写入/扫描/断开)的失败都会进入这个全局错误回调,
|
||||||
|
// 此刻并非初始化失败,状态已由 MonitorBluetoothState 清理过,只记录不广播,避免误报
|
||||||
|
if (!CheckIfBluetoothIsOn())
|
||||||
|
{
|
||||||
|
Log($"蓝牙已关闭,忽略蓝牙错误: {error}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
Log($"蓝牙初始化失败: {error}");
|
Log($"蓝牙初始化失败: {error}");
|
||||||
_enableBtCoroutine = null;
|
_enableBtCoroutine = null;
|
||||||
OnError?.Invoke($"初始化失败: {error}");
|
OnError?.Invoke($"初始化失败: {error}");
|
||||||
@ -676,6 +699,14 @@ namespace Kill.Bluetooth
|
|||||||
/// <param name="timeout">扫描超时时间(秒),默认10秒</param>
|
/// <param name="timeout">扫描超时时间(秒),默认10秒</param>
|
||||||
public void StartScan(float timeout = 999f)
|
public void StartScan(float timeout = 999f)
|
||||||
{
|
{
|
||||||
|
// 蓝牙已被系统关闭时直接跳过:此时 BluetoothLeScanner 为 null,
|
||||||
|
// 插件扫描调用会抛 NullPointerException(曾导致主页 Init 中断、设备状态不刷新)
|
||||||
|
if (!CheckIfBluetoothIsOn())
|
||||||
|
{
|
||||||
|
Log("蓝牙未开启,跳过扫描");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsInitialized)
|
if (!IsInitialized)
|
||||||
{
|
{
|
||||||
Log("蓝牙未初始化,自动触发初始化流程...");
|
Log("蓝牙未初始化,自动触发初始化流程...");
|
||||||
@ -693,6 +724,12 @@ namespace Kill.Bluetooth
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void DoStartScan(float timeout)
|
private void DoStartScan(float timeout)
|
||||||
{
|
{
|
||||||
|
if (!CheckIfBluetoothIsOn())
|
||||||
|
{
|
||||||
|
Log("蓝牙未开启,跳过扫描");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (IsScanning)
|
if (IsScanning)
|
||||||
{
|
{
|
||||||
Log("已经在扫描中,先停止当前扫描...");
|
Log("已经在扫描中,先停止当前扫描...");
|
||||||
@ -907,7 +944,7 @@ namespace Kill.Bluetooth
|
|||||||
/// 断开当前连接(带协议通知)
|
/// 断开当前连接(带协议通知)
|
||||||
/// 先发送0x70断开命令给设备,再断开BLE连接
|
/// 先发送0x70断开命令给设备,再断开BLE连接
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Disconnect(bool backHome=false)
|
public void Disconnect(bool backHome=false, bool sendProtocolCommand=true)
|
||||||
{
|
{
|
||||||
needBackHome=backHome;
|
needBackHome=backHome;
|
||||||
if (!IsConnected)
|
if (!IsConnected)
|
||||||
@ -933,21 +970,23 @@ namespace Kill.Bluetooth
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 先通过BLE协议发送断开命令(0x70)
|
// 先通过BLE协议发送断开命令(0x70)
|
||||||
Log($"检查BLECommunicationManager: Instance={(BLECommunicationManager.Instance != null ? "存在" : "null")}");
|
if (sendProtocolCommand && BLECommunicationManager.Instance != null)
|
||||||
if (BLECommunicationManager.Instance != null)
|
|
||||||
{
|
{
|
||||||
Log("发送断开命令(0x70)到设备...");
|
Log("发送断开命令(0x70)到设备...");
|
||||||
BLECommunicationManager.Instance.Disconnect((success) =>
|
BLECommunicationManager.Instance.Disconnect((success) =>
|
||||||
{
|
{
|
||||||
Log($"断开命令发送结果: {(success ? "成功" : "失败")}");
|
// 0x70 设备不会回复,success 仅表示已提交插件写入,不代表设备已收到
|
||||||
|
Log($"断开命令已提交: {(success ? "是" : "否")}");
|
||||||
// 无论命令是否成功,都执行BLE断开
|
// 无论命令是否成功,都执行BLE断开
|
||||||
PerformBLEDisconnect(addressToDisconnect);
|
PerformBLEDisconnect(addressToDisconnect);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log("BLECommunicationManager.Instance 为 null,跳过0x70命令,直接断开BLE");
|
Log(!sendProtocolCommand
|
||||||
// 如果没有BLE通信管理器,直接断开BLE
|
? "蓝牙不可用,跳过0x70命令,直接断开BLE"
|
||||||
|
: "BLECommunicationManager.Instance 为 null,跳过0x70命令,直接断开BLE");
|
||||||
|
// 如果没有BLE通信管理器或蓝牙不可用,直接断开BLE
|
||||||
PerformBLEDisconnect(addressToDisconnect);
|
PerformBLEDisconnect(addressToDisconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1000,7 +1039,9 @@ namespace Kill.Bluetooth
|
|||||||
|
|
||||||
// ChangeAimMac 切换设备时是 Disconnect + StartScan 同步调用,
|
// ChangeAimMac 切换设备时是 Disconnect + StartScan 同步调用,
|
||||||
// 本次清理会停掉刚启动的扫描,这里若仍有连接目标则自动重新扫描,避免连接流程中断
|
// 本次清理会停掉刚启动的扫描,这里若仍有连接目标则自动重新扫描,避免连接流程中断
|
||||||
if (!string.IsNullOrEmpty(aimMac) && !IsConnected)
|
// 蓝牙已被系统关闭时不重扫(扫描只会触发一轮失败的初始化),
|
||||||
|
// 等蓝牙重新打开后由 MonitorBluetoothState 的自动初始化流程接管
|
||||||
|
if (!string.IsNullOrEmpty(aimMac) && !IsConnected && CheckIfBluetoothIsOn())
|
||||||
{
|
{
|
||||||
StartScan(10);
|
StartScan(10);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -199,7 +199,7 @@ namespace Kill.Bluetooth
|
|||||||
|
|
||||||
private IEnumerator StartUpgradeCoroutineInternal(BLEFrame frame, Action<bool> callback)
|
private IEnumerator StartUpgradeCoroutineInternal(BLEFrame frame, Action<bool> callback)
|
||||||
{
|
{
|
||||||
BLEResponse response = new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR };
|
BLEResponse response = new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR, Data = new byte[0] };
|
||||||
bool completed = false;
|
bool completed = false;
|
||||||
|
|
||||||
SendFrame(frame, (resp) =>
|
SendFrame(frame, (resp) =>
|
||||||
@ -495,7 +495,7 @@ namespace Kill.Bluetooth
|
|||||||
private IEnumerator SendFrameCoroutine(BLEFrame frame, Action<BLEResponse> callback)
|
private IEnumerator SendFrameCoroutine(BLEFrame frame, Action<BLEResponse> callback)
|
||||||
{
|
{
|
||||||
bool completed = false;
|
bool completed = false;
|
||||||
BLEResponse response = new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR };
|
BLEResponse response = new BLEResponse { Status = BLEConstants.STATUS_DEVICE_ERROR, Data = new byte[0] };
|
||||||
|
|
||||||
SendFrame(frame, (resp) =>
|
SendFrame(frame, (resp) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -494,6 +494,8 @@ namespace Kill.Bluetooth.Protocol
|
|||||||
public static BLEResponse FromFrame(BLEFrame frame)
|
public static BLEResponse FromFrame(BLEFrame frame)
|
||||||
{
|
{
|
||||||
var response = new BLEResponse();
|
var response = new BLEResponse();
|
||||||
|
// 无载荷的响应(如写ACK)也保证 Data 非空,避免回调中 BitConverter.ToString(null) 崩溃
|
||||||
|
response.Data = new byte[0];
|
||||||
if (frame.Data != null && frame.Data.Length > 0)
|
if (frame.Data != null && frame.Data.Length > 0)
|
||||||
{
|
{
|
||||||
response.Status = frame.Data[0];
|
response.Status = frame.Data[0];
|
||||||
|
|||||||
@ -389,6 +389,7 @@ namespace Kill.UI.Pages
|
|||||||
if(bluetoothDeviceInfoCoroutine!=null)
|
if(bluetoothDeviceInfoCoroutine!=null)
|
||||||
{
|
{
|
||||||
StopCoroutine(bluetoothDeviceInfoCoroutine);
|
StopCoroutine(bluetoothDeviceInfoCoroutine);
|
||||||
|
bluetoothDeviceInfoCoroutine = null;
|
||||||
}
|
}
|
||||||
// 设备断开重新加载主页(保持原行为,当前在主页也会重新加载)
|
// 设备断开重新加载主页(保持原行为,当前在主页也会重新加载)
|
||||||
// 但切换设备(ChangeAimMac)导致的断开不重新加载,避免主页被销毁重建导致并发初始化
|
// 但切换设备(ChangeAimMac)导致的断开不重新加载,避免主页被销毁重建导致并发初始化
|
||||||
@ -1004,6 +1005,24 @@ namespace Kill.UI.Pages
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task Init()
|
public async Task Init()
|
||||||
|
{
|
||||||
|
// Init 在 async 链中抛异常会被静默吞掉:LoadingUI 引用计数无法归零(表现为卡 loading),
|
||||||
|
// 且页面状态刷新(UpdateDeviceState)不会执行。这里统一兜底:记录完整异常并保证 Loading 收尾。
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await InitInternal();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogError($"[HomePageCtrl] Init 异常: {ex}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
LoadingUI.Hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task InitInternal()
|
||||||
{
|
{
|
||||||
|
|
||||||
if (DataManager.Instance.userInfo.device_count == 0 || (OwnedDevices.Count == 0 && SharedDevices.Count == 0))
|
if (DataManager.Instance.userInfo.device_count == 0 || (OwnedDevices.Count == 0 && SharedDevices.Count == 0))
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user