[desc]:修复BLE蓝牙BUG

[author]:Alex Wang
master
yimiao 3 years ago
parent a6a7b80b73
commit 9c88f40291

@ -38,6 +38,8 @@ dependencies {
implementation 'com.aill:AndroidSerialPort:1.0.8' implementation 'com.aill:AndroidSerialPort:1.0.8'
implementation 'com.airbnb.android:lottie:5.0.2'
implementation(rootProject.ext.dependencies.mmkv) implementation(rootProject.ext.dependencies.mmkv)
// kotlin // kotlin

@ -38,7 +38,7 @@ interface IBluetoothClient {
* @param mac 需要连接蓝牙设备的地址 * @param mac 需要连接蓝牙设备的地址
* @return 成功返回连接设备的地址 * @return 成功返回连接设备的地址
*/ */
fun connect(mac: String): Observable<String> fun connect(mac: String, callback: BaseResultCallback<String>)
/** /**
* 断开蓝牙连接, 释放蓝牙连接占用的蓝牙服务 * 断开蓝牙连接, 释放蓝牙连接占用的蓝牙服务

@ -114,6 +114,21 @@ public class BLEClientService extends Service {
} }
}; };
/**
*
*/
private final BaseResultCallback<String> connectCallback = new BaseResultCallback<String>() {
@Override
public void onSuccess(String data) {
Log.d(TAG, "got connectCallback onSuccess = " + data);
}
@Override
public void onFail(String msg) {
Log.d(TAG, "got connectCallback onFail = " + msg);
}
};
/** /**
* *
* *
@ -127,14 +142,13 @@ public class BLEClientService extends Service {
if (TextUtils.isEmpty(connectMac)) { if (TextUtils.isEmpty(connectMac)) {
connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this); connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this);
} }
Observable<String>[] observables = new Observable[2]; mBtClient.connect(connectMac, new BaseResultCallback<String>() {
observables[0] = mBtClient.connect(connectMac); @Override
// 判断是否需要启动通知 public void onSuccess(String data) {
Log.d(TAG, "got connectCallback onSuccess = " + data);
if (enableNotify) { if (enableNotify) {
observables[1] = mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(), mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(),
BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY(), notifyCallback); BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY(), notifyCallback).subscribe(new Observer<String>() {
}
Observable.concatArray(observables).subscribe(new Observer<String>() {
@Override @Override
public void onSubscribe(@NonNull Disposable d) { public void onSubscribe(@NonNull Disposable d) {
Log.d(TAG, "connect onSubscribe"); Log.d(TAG, "connect onSubscribe");
@ -173,6 +187,33 @@ public class BLEClientService extends Service {
} }
} }
}); });
} else {
// 将链接成功的MAC地址保存
BtUtils.INSTANCE.saveConnectMac(BLEClientService.this, mac);
if (clientListener != null) {
clientListener.onResult(new CommonMsg(CONNECT_SUCCESS, mac));
}
if (connectListener != null) {
connectListener.onSuccess();
}
}
}
@Override
public void onFail(String msg) {
Log.d(TAG, "got connectCallback onFail = " + msg);
// 链接过程报错,断开链接
mBtClient.disconnect(connectMac);
connectMac = "";
if (clientListener != null) {
clientListener.onResult(new CommonMsg(BtConstants.CONNECT_ERROR, msg));
}
if (connectListener != null) {
connectListener.onFail(msg);
}
}
});
} }
/** /**

@ -46,7 +46,6 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
public BluetoothClientBLEAdapter(BluetoothBLeClient client) { public BluetoothClientBLEAdapter(BluetoothBLeClient client) {
mClient = client; mClient = client;
HandlerThread workThread = new HandlerThread("bluetooth ble worker"); HandlerThread workThread = new HandlerThread("bluetooth ble worker");
workThread.start(); workThread.start();
} }
@ -101,12 +100,8 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE).stopScan(); mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE).stopScan();
} }
@NonNull
@Override
public Observable<String> connect(@NonNull final String mac) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override @Override
public void subscribe(@NonNull final ObservableEmitter<String> emitter) { public void connect(@NonNull final String mac, BaseResultCallback<String> callback) {
BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac); BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac);
connector.connect(new BluetoothLeConnector.OnConnectListener() { connector.connect(new BluetoothLeConnector.OnConnectListener() {
@ -120,15 +115,12 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
@Override @Override
public void onServiceDiscover() { public void onServiceDiscover() {
emitter.onNext(mac); callback.onSuccess(mac);
emitter.onComplete();
} }
@Override @Override
public void onError(String msg) { public void onError(String msg) {
emitter.onError(new Throwable(msg)); callback.onFail(msg);
}
});
} }
}); });
} }

@ -164,7 +164,8 @@ public class BluetoothLeConnector {
mConnectHandler.postDelayed(() -> mConnectHandler.postDelayed(() ->
mWorkHandler.post(() -> { mWorkHandler.post(() -> {
if (!mIsStartService.get()) { if (!mIsStartService.get()) {
gatt.disconnect(); disconnectGatt();
mOnConnectListener.onError("disCoverServices time out");
} }
}), 3000L); }), 3000L);
@ -309,6 +310,11 @@ public class BluetoothLeConnector {
mIsStartService.set(false); mIsStartService.set(false);
mConnectTime.set(SystemClock.elapsedRealtime()); mConnectTime.set(SystemClock.elapsedRealtime());
// 再次链接前检查是否存在上一次的链接,如果存在,尝试断开连接
if (getBluetoothGatt() != null) {
getBluetoothGatt().disconnect();
getBluetoothGatt().close();
}
setBluetoothGatt(device.connectGatt(mContext, false, mGattCallback)); setBluetoothGatt(device.connectGatt(mContext, false, mGattCallback));
if (getBluetoothGatt() == null) { if (getBluetoothGatt() == null) {
String err = "bluetooth is not open!"; String err = "bluetooth is not open!";
@ -320,7 +326,7 @@ public class BluetoothLeConnector {
// 自定义MTU // 自定义MTU
// getBluetoothGatt().requestMtu(512); // getBluetoothGatt().requestMtu(512);
// 开一个定时器,如果超出 20s 就强制断开连接 // 开一个定时器,如果超出 10s 就强制断开连接
// 这个定时器必须在连接上设备之后清掉 // 这个定时器必须在连接上设备之后清掉
mConnectHandler.removeCallbacksAndMessages(null); mConnectHandler.removeCallbacksAndMessages(null);
mConnectHandler.postDelayed(() -> mConnectHandler.postDelayed(() ->
@ -331,7 +337,7 @@ public class BluetoothLeConnector {
Log.e(TAG, err); Log.e(TAG, err);
callback.onError(err); callback.onError(err);
} }
}), 20000L); }), 10000L);
}); });
} }
@ -353,16 +359,12 @@ public class BluetoothLeConnector {
return; return;
} }
if (mConnectStatus.get() == BluetoothGatt.STATE_DISCONNECTED) { if (mConnectStatus.get() == BluetoothGatt.STATE_CONNECTING) {
close(); mConnectHandler.removeCallbacksAndMessages(null);
return;
} }
getBluetoothGatt().disconnect();
// 确保 Gatt 一定会被 close // 确保 Gatt 一定会被 close
if (mConnectStatus.get() == BluetoothGatt.STATE_CONNECTING) { if (mConnectStatus.get() != BluetoothGatt.STATE_DISCONNECTING) {
mConnectHandler.removeCallbacksAndMessages(null);
close(); close();
} }
} }
@ -378,6 +380,7 @@ public class BluetoothLeConnector {
Log.e(TAG, "BluetoothAdapter not initialized"); Log.e(TAG, "BluetoothAdapter not initialized");
return; return;
} }
getBluetoothGatt().disconnect();
getBluetoothGatt().close(); getBluetoothGatt().close();
setBluetoothGatt(null); setBluetoothGatt(null);
mConnectStatus.set(BluetoothGatt.STATE_DISCONNECTED); mConnectStatus.set(BluetoothGatt.STATE_DISCONNECTED);

@ -134,7 +134,7 @@ class BtBleSearcher(
if (mScanning.get()) { if (mScanning.get()) {
mScanning.set(false) mScanning.set(false)
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
mScanCallback!!.onComplete() mScanCallback?.onComplete()
} }
mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback) mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback)
mScanCallback = null mScanCallback = null

@ -28,14 +28,11 @@ class BluetoothClientClassicAdapter(private var mClient: BluetoothClassicClient)
mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).stopScan() mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).stopScan()
} }
override fun connect(mac: String): Observable<String> { override fun connect(mac: String, callback: BaseResultCallback<String>) {
return Observable.create {
if (!mClient.isConnected((mac))) { if (!mClient.isConnected((mac))) {
mClient.connect(mac) mClient.connect(mac)
} }
it.onNext(mac) callback.onSuccess(mac)
it.onComplete()
}
} }
override fun disconnect(mac: String) { override fun disconnect(mac: String) {

@ -9,7 +9,6 @@ import com.common.bluetooth.DeviceJson
import com.common.bluetooth.bean.* import com.common.bluetooth.bean.*
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import java.util.ArrayList
/** /**
* 蓝牙工具类 * 蓝牙工具类
@ -36,10 +35,15 @@ object BtUtils {
const val MAC_KEY = "mac" const val MAC_KEY = "mac"
/** /**
* MAC地址的KEY * MAC名称的KEY
*/ */
const val MAC_AND_NAME_KEY = "mac_and_name" const val MAC_AND_NAME_KEY = "mac_and_name"
/**
* MAC名字的KEY
*/
const val NAME_KEY = "name"
enum class EVENT_TYPE(val type: String, val index: String) { enum class EVENT_TYPE(val type: String, val index: String) {
/** /**
* 点击事件 * 点击事件
@ -382,14 +386,19 @@ object BtUtils {
var deviceNameList: ArrayList<String> = ArrayList() var deviceNameList: ArrayList<String> = ArrayList()
var deviceMacList1: ArrayList<String> = ArrayList() var deviceMacList1: ArrayList<String> = ArrayList()
var deviceNameList1: ArrayList<String> = ArrayList() var deviceNameList1: ArrayList<String> = ArrayList()
var seatNameList: ArrayList<String> = ArrayList()
var seatNameList1: ArrayList<String> = ArrayList()
val gson = Gson() val gson = Gson()
deviceMacList.clear() deviceMacList.clear()
deviceNameList.clear() deviceNameList.clear()
seatNameList.clear()
if (TextUtils.isEmpty(getConnectMacAndName(context, MAC_AND_NAME_KEY))) { if (TextUtils.isEmpty(getConnectMacAndName(context, MAC_AND_NAME_KEY))) {
deviceMacList.add(mac) deviceMacList.add(mac)
deviceNameList.add(name) deviceNameList.add(name)
seatNameList.add(name)
edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList))
edit.putString(MAC_KEY, gson.toJson(deviceMacList)) edit.putString(MAC_KEY, gson.toJson(deviceMacList))
edit.putString(NAME_KEY, gson.toJson(seatNameList))
} else { } else {
val listType = object : TypeToken<List<String?>?>() {}.type val listType = object : TypeToken<List<String?>?>() {}.type
deviceMacList1 = gson.fromJson<ArrayList<String>>( deviceMacList1 = gson.fromJson<ArrayList<String>>(
@ -401,17 +410,29 @@ object BtUtils {
getConnectMacAndName(context, MAC_AND_NAME_KEY), getConnectMacAndName(context, MAC_AND_NAME_KEY),
listType2 listType2
) )
val listType3 = object : TypeToken<List<String?>?>() {}.type
seatNameList1 = gson.fromJson<ArrayList<String>>(
getConnectMacAndName(context, NAME_KEY),
listType3
)
var i = 0 var i = 0
for (s in deviceMacList1) { for (s in deviceMacList1) {
if (deviceMacList1.get(i) == mac) { if (deviceMacList1.get(i) == mac) {
break break
} else if (deviceMacList1.get(deviceMacList1.size - 1) != mac) { } else if (deviceMacList1.get(deviceMacList1.size - 1) != mac) {
deviceMacList.clear()
deviceMacList.add(mac) deviceMacList.add(mac)
deviceMacList.addAll(deviceMacList1) deviceMacList.addAll(deviceMacList1)
deviceNameList.clear()
deviceNameList.add(name) deviceNameList.add(name)
deviceNameList.addAll(deviceNameList1) deviceNameList.addAll(deviceNameList1)
seatNameList.clear()
seatNameList.add(name)
seatNameList.addAll(seatNameList1)
edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList))
edit.putString(MAC_KEY, gson.toJson(deviceMacList)) edit.putString(MAC_KEY, gson.toJson(deviceMacList))
edit.putString(NAME_KEY, gson.toJson(seatNameList))
} }
i++ i++
} }
@ -433,6 +454,8 @@ object BtUtils {
return sharedPreferences.getString(MAC_AND_NAME_KEY, "") return sharedPreferences.getString(MAC_AND_NAME_KEY, "")
} else if (string == MAC_KEY) { } else if (string == MAC_KEY) {
return sharedPreferences.getString(MAC_KEY, "") return sharedPreferences.getString(MAC_KEY, "")
} else if (string == NAME_KEY) {
return sharedPreferences.getString(NAME_KEY, "")
} }
return "" return ""
} }
@ -456,21 +479,29 @@ object BtUtils {
* @param mac 设备MAC * @param mac 设备MAC
* @param context 上下文 * @param context 上下文
*/ */
fun changeConnectMacAndName(context: Context, device: MutableList<DeviceJson>) { fun changeConnectMacAndName(
context: Context,
device: MutableList<DeviceJson>,
list: MutableList<String>
) {
val sharedPreferences: SharedPreferences = val sharedPreferences: SharedPreferences =
context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE) context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE)
val edit = sharedPreferences.edit() val edit = sharedPreferences.edit()
var deviceMacList: ArrayList<String> = ArrayList() var deviceMacList: ArrayList<String> = ArrayList()
var deviceNameList: ArrayList<String> = ArrayList() var deviceNameList: ArrayList<String> = ArrayList()
var seatNameList: ArrayList<String> = ArrayList()
val gson = Gson() val gson = Gson()
deviceMacList.clear() deviceMacList.clear()
deviceNameList.clear() deviceNameList.clear()
seatNameList.clear()
var i = 0 var i = 0
for (s in device) { for (s in device) {
deviceMacList.add(device.get(i).mac) deviceMacList.add(device.get(i).mac)
deviceNameList.add(device.get(i).name) deviceNameList.add(device.get(i).name)
seatNameList.add(list.get(i))
edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList))
edit.putString(MAC_KEY, gson.toJson(deviceMacList)) edit.putString(MAC_KEY, gson.toJson(deviceMacList))
edit.putString(NAME_KEY, gson.toJson(seatNameList))
i++ i++
} }

@ -0,0 +1,29 @@
package com.common.bluetooth.utils
import android.content.Context
import android.view.LayoutInflater
import android.widget.TextView
import android.widget.Toast
import com.common.bluetooth.R
/**
* toast工具类
*/
class ToastUtil {
companion object {
/**
* 展示自定义背景和信息的toast
*/
fun showCustomToast(context: Context, msg: String) {
val view = LayoutInflater.from(context).inflate(R.layout.my_toast_layout, null, false)
val textView = view.findViewById<TextView>(R.id.tv_toast_msg)
textView.text = msg
var toast: Toast = Toast(context)
toast.duration = Toast.LENGTH_SHORT
toast.view = view
toast.show()
}
}
}

@ -33,13 +33,13 @@ public class BtDeviceListAdapter extends RecyclerView.Adapter<BtDeviceListAdapte
@NonNull @NonNull
@Override @Override
public BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public BtDeviceListAdapter.BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
mBinding = RvBtDeviceItemBinding.inflate(LayoutInflater.from(context)); mBinding = RvBtDeviceItemBinding.inflate(LayoutInflater.from(context));
return new BtViewHolder(mBinding); return new BtViewHolder(mBinding);
} }
@Override @Override
public void onBindViewHolder(@NonNull BtViewHolder holder, int position) { public void onBindViewHolder(@NonNull BtDeviceListAdapter.BtViewHolder holder, int position) {
final int currentIndex = position; final int currentIndex = position;
BluetoothDevice device = bluetoothDevices.get(position); BluetoothDevice device = bluetoothDevices.get(position);
holder.binding.btDeviceNameTv.setText("Device Name:" + device.getName()); holder.binding.btDeviceNameTv.setText("Device Name:" + device.getName());

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/color_E6565D65" />
<corners android:radius="4dp" />
<padding
android:bottom="15dp"
android:left="52dp"
android:right="52dp"
android:top="15dp" />
</shape>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv_toast_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/custom_toast_layout"
android:textColor="@color/white"
android:textSize="18dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -18,4 +18,5 @@
<color name="composing_color">#ff000000</color> <color name="composing_color">#ff000000</color>
<color name="composing_color_hl">#ffffffff</color> <color name="composing_color_hl">#ffffffff</color>
<color name="composing_color_idle">#ff777777</color> <color name="composing_color_idle">#ff777777</color>
<color name="color_E6565D65">#E6565D65</color>
</resources> </resources>

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Progress.Dialog" parent="@android:style/Theme.Holo.Light.Dialog"> <style name="Progress.Dialog" parent="@android:style/Theme.Holo.Light.Dialog">
<item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item> <item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
@ -7,4 +8,12 @@
<item name="android:windowIsFloating">true</item> <item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item> <item name="android:backgroundDimEnabled">true</item>
</style> </style>
<style name="PregressCircle" parent="@android:style/Widget.ProgressBar.Large">
<item name="android:maxWidth">72dp</item>
<item name="minWidth">72dp</item>
<item name="android:maxHeight">72dp</item>
<item name="minHeight">72dp</item>
<item name="android:indeterminateDuration">800</item>
</style>
</resources> </resources>
Loading…
Cancel
Save