From 8fb4839c36f0c53ba5395f345deebe9ed6dbd1a1 Mon Sep 17 00:00:00 2001 From: yimiao Date: Wed, 23 Feb 2022 17:18:52 +0800 Subject: [PATCH] =?UTF-8?q?[desc]:=E6=9B=B4=E6=96=B0=E8=93=9D=E7=89=99?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=20[author]:wangyimiao?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buildCommon/commonLibConfig.gradle | 2 +- commonbt/build.gradle | 1 + .../java/com/common/bluetooth/BtConstants.kt | 27 +-- .../com/common/bluetooth/BtDemoActivity.java | 27 +-- .../java/com/common/bluetooth/BtManager.kt | 203 ++++++++++++------ .../java/com/common/bluetooth/DeviceJson.java | 24 +++ .../java/com/common/bluetooth/DeviceList.java | 16 ++ .../bluetooth/callback/BLEClientListener.kt | 7 +- .../bluetooth/callback/BLEScanListener.kt | 23 ++ .../bluetooth/callback/BtMsgListener.kt | 11 +- .../callback/BtMsgReceiverListener.kt | 16 ++ .../bluetooth/callback/BtStatusListener.kt | 23 ++ .../bluetooth/interfaces/IBluetoothClient.kt | 14 +- .../bluetooth/service/BluetoothCommon.kt | 18 +- .../service/ble/BLEClientService.java | 62 +++++- .../service/ble/BLEReceiveService.java | 10 +- .../service/ble/BluetoothBLeClient.java | 79 +++++++ .../ble/BluetoothClientBLEAdapter.java | 52 ++++- .../service/ble/BluetoothLeConnector.java | 13 +- .../bluetooth/service/ble/BtBleSearcher.kt | 147 +++++++++++++ .../service/bt/BTCClientService.java | 16 +- .../service/bt/BTCReceiverService.java | 8 +- .../service/bt/BluetoothClassicBase.java | 66 ++++-- .../service/bt/BluetoothClassicClient.java | 17 +- .../bt/BluetoothClientClassicAdapter.kt | 10 +- .../bluetooth/service/bt/BtClassicSearcher.kt | 33 +++ .../com/common/bluetooth/utils/BtUtils.kt | 132 +++++++++++- .../bluetooth/view/BtDeviceListAdapter.java | 4 +- .../bluetooth/view/FullScreenDialog.java | 38 ++++ commonbt/src/main/res/values/styles.xml | 10 + 30 files changed, 945 insertions(+), 164 deletions(-) create mode 100644 commonbt/src/main/java/com/common/bluetooth/DeviceJson.java create mode 100644 commonbt/src/main/java/com/common/bluetooth/DeviceList.java create mode 100644 commonbt/src/main/java/com/common/bluetooth/callback/BLEScanListener.kt create mode 100644 commonbt/src/main/java/com/common/bluetooth/callback/BtMsgReceiverListener.kt create mode 100644 commonbt/src/main/java/com/common/bluetooth/callback/BtStatusListener.kt create mode 100644 commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothBLeClient.java create mode 100644 commonbt/src/main/java/com/common/bluetooth/service/ble/BtBleSearcher.kt create mode 100644 commonbt/src/main/java/com/common/bluetooth/service/bt/BtClassicSearcher.kt create mode 100644 commonbt/src/main/java/com/common/bluetooth/view/FullScreenDialog.java create mode 100644 commonbt/src/main/res/values/styles.xml diff --git a/buildCommon/commonLibConfig.gradle b/buildCommon/commonLibConfig.gradle index a420ec5..fe9d71f 100644 --- a/buildCommon/commonLibConfig.gradle +++ b/buildCommon/commonLibConfig.gradle @@ -26,6 +26,7 @@ project.ext { rxjava : "3.0.13", rxandroid : "3.0.0", kotlin : "1.5.10", + kotlin_android : "1.5.0", converter_gson : '2.9.0', retrofit_rxjava : "2.9.0", room : "2.3.0", @@ -35,7 +36,6 @@ project.ext { glide : "4.12.0", photo_view : "2.3.0", luban : "1.1.8", - kotlin_android : "1.4.1", gson : "2.8.6", arouter : "1.5.2", mmkv : "1.2.10", diff --git a/commonbt/build.gradle b/commonbt/build.gradle index 213a000..86c1a1b 100644 --- a/commonbt/build.gradle +++ b/commonbt/build.gradle @@ -21,6 +21,7 @@ dependencies { // 添加kotlin依赖 implementation rootProject.ext.dependencies.kotlin + implementation rootProject.ext.dependencies.kotlin_android // 添加rxAndroid依赖 implementation rootProject.ext.dependencies.rxandroid // 添加gson依赖 diff --git a/commonbt/src/main/java/com/common/bluetooth/BtConstants.kt b/commonbt/src/main/java/com/common/bluetooth/BtConstants.kt index a33e5d0..49d1991 100644 --- a/commonbt/src/main/java/com/common/bluetooth/BtConstants.kt +++ b/commonbt/src/main/java/com/common/bluetooth/BtConstants.kt @@ -8,12 +8,10 @@ import java.util.* /** * BT静态方法 * - * @author wangym + * @author Alex Wang * @since 2021-10-14 */ object BtConstants { - const val BT_NAME = "innovation bt" - val DEFAULT_CHARSET = StandardCharsets.UTF_8 /** @@ -31,21 +29,6 @@ object BtConstants { BLE } - /** - * 蓝牙类型 - */ - enum class CLIENT_TYPE { - /** - * 服务端 - */ - SERVER, - - /** - * 客户端 - */ - CLIENT - } - private const val STATUS_BASE = 0x1 const val CONNECT_SUCCESS = STATUS_BASE shl 1 const val CONNECT_ERROR = STATUS_BASE shl 2 @@ -87,10 +70,10 @@ object BtConstants { * 经典蓝牙连接UUID */ val CLASSIC_BT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") - val UUID_SERVICE = UUID.fromString("66f564dc-121f-3e7f-80b1-f005d3f194c9") - val UUID_CHARACTERISTIC_NOTIFY = UUID.fromString("66f564dd-121f-3e7f-80b1-f005d3f194c9") - val UUID_CHARACTERISTIC_WRITE = UUID.fromString("66f564de-121f-3e7f-80b1-f005d3f194c9") - val UUID_DESCRIPTOR = UUID.fromString("66f564df-121f-3e7f-80b1-f005d3f194c9") + val UUID_SERVICE = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb7") + val UUID_CHARACTERISTIC_READ_NOTIFY = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb8") + val UUID_CHARACTERISTIC_WRITE = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cba") + val UUID_DESC_NOTITY = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb") /** * 获取UUID diff --git a/commonbt/src/main/java/com/common/bluetooth/BtDemoActivity.java b/commonbt/src/main/java/com/common/bluetooth/BtDemoActivity.java index a8f1d75..6068ff6 100644 --- a/commonbt/src/main/java/com/common/bluetooth/BtDemoActivity.java +++ b/commonbt/src/main/java/com/common/bluetooth/BtDemoActivity.java @@ -21,6 +21,8 @@ import com.common.bluetooth.bean.AssociateSourceEvent; import com.common.bluetooth.bean.CommonMsg; import com.common.bluetooth.bean.KeyboardEvent; import com.common.bluetooth.callback.BLEClientListener; +import com.common.bluetooth.callback.BtMsgReceiverListener; +import com.common.bluetooth.callback.BtStatusListener; import com.common.bluetooth.databinding.ActivityMainBinding; import com.common.bluetooth.utils.BtUtils; import com.common.bluetooth.view.BtDeviceListAdapter; @@ -84,24 +86,23 @@ public class BtDemoActivity extends AppCompatActivity { index -> { BtManager.INSTANCE.connect(bluetoothDevices.get(index).getAddress()); - BtManager.INSTANCE.setClientListener(new BLEClientListener() { + BtManager.INSTANCE.setBtStatusListener(new BtStatusListener() { @Override - public void onResult(@NonNull CommonMsg result) { - Log.e("wangym", "onResult = " + result.getMsg()); - runOnUiThread( - () -> Toast.makeText(BtDemoActivity.this, result.getMsg(), - Toast.LENGTH_SHORT).show()); + public void onConnected(@Nullable String mac) { + } @Override - public void onNotifyMsgReceive(@NonNull byte[] msg) { - Log.e("wangym", "notify = " + new String(msg)); - runOnUiThread( - () -> Toast.makeText(BtDemoActivity.this, new String(msg), - Toast.LENGTH_SHORT).show()); + public void onDisconnect(@Nullable String msg) { } }); + BtManager.INSTANCE.setBtMsgReceiverListener(msg -> { + Log.e("wangym", "notify = " + new String(msg)); + runOnUiThread( + () -> Toast.makeText(BtDemoActivity.this, new String(msg), + Toast.LENGTH_SHORT).show()); + }); }); } @@ -116,7 +117,7 @@ public class BtDemoActivity extends AppCompatActivity { mBinding.buildBleServer.setOnClickListener(v -> { BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.BLE); - BtManager.INSTANCE.setMsgReceiverListener(msg -> runOnUiThread(() -> { + BtManager.INSTANCE.setBtMsgReceiverListener(msg -> runOnUiThread(() -> { Object event = BtUtils.INSTANCE.getEventFromSendMsg(msg); if (event == null) { mBinding.receiveContent.setText(new String(msg)); @@ -135,7 +136,7 @@ public class BtDemoActivity extends AppCompatActivity { mBinding.btcServerBuild.setOnClickListener(v -> { BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.CLASSIC); - BtManager.INSTANCE.setMsgReceiverListener(msg -> { + BtManager.INSTANCE.setBtMsgReceiverListener(msg -> { Log.d(TAG, new String(msg)); runOnUiThread(new Runnable() { @Override diff --git a/commonbt/src/main/java/com/common/bluetooth/BtManager.kt b/commonbt/src/main/java/com/common/bluetooth/BtManager.kt index 6fdf0f7..40ac9a0 100644 --- a/commonbt/src/main/java/com/common/bluetooth/BtManager.kt +++ b/commonbt/src/main/java/com/common/bluetooth/BtManager.kt @@ -11,21 +11,19 @@ import android.content.ServiceConnection import android.content.pm.PackageManager import android.os.Build import android.os.IBinder +import android.text.TextUtils import android.util.Log.* import androidx.core.content.ContextCompat.checkSelfPermission import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE import com.common.bluetooth.service.ble.BluetoothClientBLEAdapter import com.common.bluetooth.bean.CommonMsg -import com.common.bluetooth.callback.BLEClientListener -import com.common.bluetooth.callback.BtMsgListener -import com.common.bluetooth.callback.BtServiceListener -import com.common.bluetooth.callback.MsgReceiverListener +import com.common.bluetooth.callback.* import com.common.bluetooth.interfaces.IBluetoothClient import com.common.bluetooth.service.ble.BLEClientService import com.common.bluetooth.service.ble.BLEClientService.BLEClientBinder import com.common.bluetooth.service.ble.BLEReceiveService import com.common.bluetooth.service.ble.BLEReceiveService.ReceiverBinder -import com.common.bluetooth.service.ble.BluetoothLeClient +import com.common.bluetooth.service.ble.BluetoothBLeClient import com.common.bluetooth.service.bt.BTCClientService import com.common.bluetooth.service.bt.BTCReceiverService import com.common.bluetooth.service.bt.BluetoothClassicClient @@ -38,7 +36,7 @@ import java.util.concurrent.atomic.AtomicBoolean /** * 蓝牙管理类 * - * @author wangym + * @author Alex Wang * @since 2021-10-28 */ object BtManager { @@ -52,7 +50,7 @@ object BtManager { /** * BLE客户端服务 */ - private var clientService: BLEClientService? = null + private var bleClientService: BLEClientService? = null /** * 经典蓝牙客户端服务 @@ -72,18 +70,23 @@ object BtManager { /** * 消息接收监听 */ - var msgReceiverListener: MsgReceiverListener? = null + var btMsgReceiverListener: BtMsgReceiverListener? = null /** - * 蓝牙客户端监听 + * 蓝牙状态监听 */ - var clientListener: BLEClientListener? = null + var btStatusListener: BtStatusListener? = null /** * 蓝牙服务链接监听 */ var btServiceListener: BtServiceListener? = null + /** + * BLE扫描结果监听 + */ + var bleScanListener: BLEScanListener? = null + /** * 是否绑定了服务端 */ @@ -117,36 +120,44 @@ object BtManager { /** * 初始化蓝牙服务端模块 + * + * @param context 上下文 + * @param btType 蓝牙模式 */ fun initServer(context: Context, btType: BLUETOOTH_TYPE) { if (btType == BLUETOOTH_TYPE.BLE) { mBtClient = BluetoothClientBLEAdapter( - BluetoothLeClient.getInstance(context) + BluetoothBLeClient.getInstance(context) ) initBleReceiverService(context) } else if (btType == BLUETOOTH_TYPE.CLASSIC) { - mBtClient = BluetoothClientClassicAdapter(BluetoothClassicClient(context)) + mBtClient = + BluetoothClientClassicAdapter(BluetoothClassicClient.getInstance(context.applicationContext)) initClassicReceiverService(context) } + // 检查蓝牙设备是否支持 mBtClient!!.checkBluetoothDevice(btType) - } /** * 初始化蓝牙客户端模块 + * + * @param context 上下文 + * @param btType 蓝牙模式 */ fun initClient(context: Activity, btType: BLUETOOTH_TYPE) { if (btType == BLUETOOTH_TYPE.BLE) { mBtClient = BluetoothClientBLEAdapter( - BluetoothLeClient.getInstance(context) + BluetoothBLeClient.getInstance(context) ) initBleClientService(context) } else if (btType == BLUETOOTH_TYPE.CLASSIC) { mBtClient = BluetoothClientClassicAdapter( - BluetoothClassicClient(context) + BluetoothClassicClient.getInstance(context.application) ) initClassicClientService(context) } + // 检查蓝牙设备是否支持 mBtClient!!.checkBluetoothDevice(btType) } @@ -188,9 +199,9 @@ object BtManager { object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { serverService = (service as ReceiverBinder).service - serverService?.setMsgReceiveListener(object : MsgReceiverListener { + serverService?.setMsgReceiveListener(object : BtMsgReceiverListener { override fun onMsgReceive(msg: ByteArray) { - msgReceiverListener?.onMsgReceive(msg) + btMsgReceiverListener?.onMsgReceive(msg) } }) btServiceListener?.onServiceReady() @@ -208,8 +219,8 @@ object BtManager { override fun onServiceConnected(name: ComponentName, service: IBinder) { classicServerService = (service as BTCReceiverService.ClassicReceiverBinder).service classicServerService?.setMsgReceiveListener(object : BtMsgListener() { - override fun socketNotify(state: Int, msg: String) { - msgReceiverListener?.onMsgReceive(msg.toByteArray()) + override fun messageReceive(state: Int, msg: String) { + btMsgReceiverListener?.onMsgReceive(msg.toByteArray()) } }) btServiceListener?.onServiceReady() @@ -225,33 +236,29 @@ object BtManager { private val bleClientConn: ServiceConnection by lazy { object : ServiceConnection { override fun onServiceConnected(name: ComponentName, service: IBinder) { - clientService = (service as BLEClientBinder).service - clientService?.setClientListener(object : BLEClientListener { + bleClientService = (service as BLEClientBinder).service + bleClientService?.setClientListener(object : BLEClientListener { override fun onResult(result: CommonMsg) { // 如果连接成功,更新连接的设备 if (result.msgType == BtConstants.CONNECT_SUCCESS) { curConnectMac = result.msg + btStatusListener?.onConnected(result.msg) } else if (result.msgType == BtConstants.DISCONNECT) { curConnectMac = "" + btStatusListener?.onDisconnect("") } - clientListener?.onResult(result) } override fun onNotifyMsgReceive(msg: ByteArray) { - clientListener?.onNotifyMsgReceive(msg) + btMsgReceiverListener?.onMsgReceive(msg) } }) btServiceListener?.onServiceReady() } override fun onServiceDisconnected(name: ComponentName) { - clientService = null - clientListener?.onResult( - CommonMsg( - BtConstants.DISCONNECT, - "disconnect from server" - ) - ) + bleClientService = null + btStatusListener?.onDisconnect("disconnect from server") btServiceListener?.onServiceDisConnected() } } @@ -262,23 +269,22 @@ object BtManager { override fun onServiceConnected(name: ComponentName, service: IBinder) { classicClientService = (service as BTCClientService.ClassicClientBinder).service classicClientService?.setMsgReceiveListener(object : BtMsgListener() { - override fun socketNotify(state: Int, msg: String) { + override fun messageReceive(state: Int, msg: String) { when (state) { CONNECTED -> { curConnectMac = msg - clientListener?.onResult( - CommonMsg( - BtConstants.CONNECT_SUCCESS, - msg - ) - ) + btStatusListener?.onConnected(msg) } DISCONNECTED -> { curConnectMac = "" - clientListener?.onResult(CommonMsg(BtConstants.DISCONNECT, msg)) + btStatusListener?.onDisconnect(msg) } MSG -> { - clientListener?.onNotifyMsgReceive(msg.toByteArray()) + btMsgReceiverListener?.onMsgReceive(msg.toByteArray()) + } + CONN_ERROR -> { + curConnectMac = "" + btStatusListener?.onDisconnect(msg) } else -> { e(TAG, "got error state:$state") @@ -302,6 +308,7 @@ object BtManager { * @param btType 蓝牙类型 */ fun searchBtDevice(btType: BLUETOOTH_TYPE) { + d(TAG, "start search $btType") if (btType === BLUETOOTH_TYPE.CLASSIC) { val mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() if (mBluetoothAdapter != null) { @@ -315,31 +322,76 @@ object BtManager { } } else if (btType === BLUETOOTH_TYPE.BLE) { d(TAG, "ble search start") - mBtClient!!.search(3000, true) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - override fun onNext(bleDevice: BluetoothDevice) { - d(TAG, "ble onScanResult") - } + if (mBtClient != null) { + mBtClient!!.search(3000, true) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Observer { + override fun onSubscribe(d: Disposable) { + d(TAG, "ble onSubscribe") + } - override fun onError(e: Throwable) { - d(TAG, "ble onScanFailed") - } + override fun onNext(bleDevice: BluetoothDevice) { + d(TAG, "ble onScanResult") + bleScanListener?.onResult(bleDevice) + } - override fun onComplete() {} - }) + override fun onError(e: Throwable) { + d(TAG, "ble onScanFailed") + } + + override fun onComplete() { + d(TAG, "ble onComplete") + bleScanListener?.onComplete() + } + }) + } } } /** - * 连接设备(BLE的连接方式) + * 停止扫描蓝牙设备 + * + * @param btType 蓝牙类型 + */ + fun stopSearch(btType: BLUETOOTH_TYPE) { + d(TAG, "stop search $btType") + mBtClient?.stopSearch() + } + + /** + * 连接设备 * * @param mac MAC地址 */ fun connect(mac: String) { - if (clientService != null) { - clientService!!.connect(mac, true) + connect(mac, true) + } + + /** + * 连接设备 + * + * @param mac MAC地址 + * @param enableNotify 是否注册通知 + */ + fun connect(mac: String, enableNotify: Boolean) { + connect(mac, enableNotify, null) + } + + /** + * 连接设备 + * + * @param mac MAC地址 + * @param enableNotify 是否注册通知 + * @param btConnectListener 链接回调 + */ + fun connect(mac: String, enableNotify: Boolean, btConnectListener: BtConnectListener?) { + if (TextUtils.isEmpty(mac)) { + e(TAG, "connect error with empty mac") + btConnectListener?.onFail("error mac address") + return + } + if (bleClientService != null) { + bleClientService!!.connect(mac, enableNotify, btConnectListener) } else { w(TAG, "init clientService first") } @@ -352,15 +404,21 @@ object BtManager { } /** - * 连接设备(BLE的连接方式) + * 断开连接 * - * @param mac MAC地址 + * @param mac mac地址 + * @param btType 蓝牙类型 */ - fun connect(mac: String, enableNotify: Boolean) { - if (clientService == null) { - e(TAG, "writeMsg pls init first") - } else { - clientService!!.connect(mac, enableNotify) + fun disconnect(mac: String, btType: BLUETOOTH_TYPE) { + if (TextUtils.isEmpty(mac)) { + e(TAG, "[disConnect] error with empty mac") + // TODO 添加断开失败回调 + return + } + if (btType == BLUETOOTH_TYPE.CLASSIC) { + classicClientService?.disconnect(mac) + } else if (btType == BLUETOOTH_TYPE.BLE) { + bleClientService?.disconnect(mac) } } @@ -380,21 +438,24 @@ object BtManager { * @param msg 传输的内容 */ fun write(msg: ByteArray) { - if (clientService != null) { - clientService!!.write(msg) - } - - if (classicClientService != null) { - classicClientService!!.write(msg) - } + bleClientService?.write(msg) + classicClientService?.write(msg) + classicServerService?.write(msg) + } - if (classicServerService != null) { - classicServerService!!.write(msg) - } + /** + * 读取外围设备内容 + * + * NOTE: only support for Ble + */ + fun read() { + bleClientService?.read() } /** * 获取已配对的设备 + * + * @return 已配对的设备 */ fun getBoundDevices(): Set? { return mBtClient?.getBondedDevices() diff --git a/commonbt/src/main/java/com/common/bluetooth/DeviceJson.java b/commonbt/src/main/java/com/common/bluetooth/DeviceJson.java new file mode 100644 index 0000000..cca799d --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/DeviceJson.java @@ -0,0 +1,24 @@ +package com.common.bluetooth; + + +public class DeviceJson { + private String mac; + + public String getMac() { + return mac; + } + + public void setMac(String mac) { + this.mac = mac; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + private String name; +} diff --git a/commonbt/src/main/java/com/common/bluetooth/DeviceList.java b/commonbt/src/main/java/com/common/bluetooth/DeviceList.java new file mode 100644 index 0000000..4a43710 --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/DeviceList.java @@ -0,0 +1,16 @@ +package com.common.bluetooth; + + +import java.util.List; + +public class DeviceList { + public List getDeviceJsons() { + return deviceJsons; + } + + public void setDeviceJsons(List deviceJsons) { + this.deviceJsons = deviceJsons; + } + + private List deviceJsons; +} diff --git a/commonbt/src/main/java/com/common/bluetooth/callback/BLEClientListener.kt b/commonbt/src/main/java/com/common/bluetooth/callback/BLEClientListener.kt index 0d383a6..1cae6fc 100644 --- a/commonbt/src/main/java/com/common/bluetooth/callback/BLEClientListener.kt +++ b/commonbt/src/main/java/com/common/bluetooth/callback/BLEClientListener.kt @@ -4,16 +4,21 @@ import com.common.bluetooth.bean.CommonMsg /** * 客户端监听 + * + * @author Alex Wang */ interface BLEClientListener { /** * 结果 + * * @param result 回调 */ fun onResult(result: CommonMsg) /** - * 接收服务端通知 + * 接收外围设备的消息 + * + * @param msg 消息内容 */ fun onNotifyMsgReceive(msg: ByteArray) } \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/callback/BLEScanListener.kt b/commonbt/src/main/java/com/common/bluetooth/callback/BLEScanListener.kt new file mode 100644 index 0000000..c40f2cc --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/callback/BLEScanListener.kt @@ -0,0 +1,23 @@ +package com.common.bluetooth.callback + +import android.bluetooth.BluetoothDevice + +/** + * BLE扫描数据监听 + * + * @author Alex Wang + * @since 2022-2-18 + */ +interface BLEScanListener { + /** + * 扫描的结果 + * + * @param result 扫描到的ble设备数据 + */ + fun onResult(result: BluetoothDevice) + + /** + * 扫描的完成 + */ + fun onComplete() +} \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgListener.kt b/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgListener.kt index 1bc35e5..adc7cb3 100644 --- a/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgListener.kt +++ b/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgListener.kt @@ -1,7 +1,7 @@ package com.common.bluetooth.callback /** - * 经典蓝牙监听 + * 蓝牙消息监听器 * * @author wangym * @since 2021-12-1 @@ -12,7 +12,7 @@ abstract class BtMsgListener { * @param state 消息类型 * @param msg 消息内容 */ - open fun socketNotify(state: Int, msg: String) {} + open fun messageReceive(state: Int, msg: String) {} companion object { /** @@ -25,9 +25,14 @@ abstract class BtMsgListener { */ const val CONNECTED = 1 + /** + * 连接错误 + */ + const val CONN_ERROR = 2 + /** * 消息 */ - const val MSG = 2 + const val MSG = 3 } } \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgReceiverListener.kt b/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgReceiverListener.kt new file mode 100644 index 0000000..46401d9 --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/callback/BtMsgReceiverListener.kt @@ -0,0 +1,16 @@ +package com.common.bluetooth.callback + +/** + * 数据接收监听 + * + * @author Alex Wang + * @since 2022-2-18 + */ +interface BtMsgReceiverListener { + /** + * 接收的消息 + * + * @param msg 传输的数据 + */ + fun onMsgReceive(msg: ByteArray) +} \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/callback/BtStatusListener.kt b/commonbt/src/main/java/com/common/bluetooth/callback/BtStatusListener.kt new file mode 100644 index 0000000..2a1d1b5 --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/callback/BtStatusListener.kt @@ -0,0 +1,23 @@ +package com.common.bluetooth.callback + +/** + * 蓝牙状态监听 + * + * @author Alex Wang + * @since 2022-2-18 + */ +interface BtStatusListener { + /** + * 连接成功 + * + * @param mac MAC地址 + */ + fun onConnected(mac: String?) + + /** + * 断开连接 + * + * @param msg 信息 + */ + fun onDisconnect(msg: String?) +} \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/interfaces/IBluetoothClient.kt b/commonbt/src/main/java/com/common/bluetooth/interfaces/IBluetoothClient.kt index c0ec305..0eef4fe 100644 --- a/commonbt/src/main/java/com/common/bluetooth/interfaces/IBluetoothClient.kt +++ b/commonbt/src/main/java/com/common/bluetooth/interfaces/IBluetoothClient.kt @@ -61,6 +61,18 @@ interface IBluetoothClient { values: ByteArray ): Observable? + /** + * 向一个蓝牙设备写入值 + * + * @param mac 设备 mac 地址 + * @param service 设备服务地址 + * @param characteristic 设备 characteristic 地址 + * @return 写入成功返回 + */ + fun read( + mac: String, service: UUID, characteristic: UUID + ): Observable? + /** * 向蓝牙设备注册一个通道值改变的监听器, * 每一个设备的每一个通道只允许同时存在一个监听器。 @@ -104,7 +116,7 @@ interface IBluetoothClient { /** * 启动蓝牙 - * 启动前优先检查蓝牙设备是否支持 [IBluetoothClient.checkBluetoothDevice] )} + * 启动前优先检查蓝牙设备是否支持 [IBluetoothClient.checkBluetoothDevice] */ fun openBluetooth() diff --git a/commonbt/src/main/java/com/common/bluetooth/service/BluetoothCommon.kt b/commonbt/src/main/java/com/common/bluetooth/service/BluetoothCommon.kt index 3122067..8b9e34f 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/BluetoothCommon.kt +++ b/commonbt/src/main/java/com/common/bluetooth/service/BluetoothCommon.kt @@ -8,8 +8,9 @@ import android.content.pm.PackageManager import android.util.Log import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE import com.common.bluetooth.interfaces.IBluetoothSearch +import com.common.bluetooth.service.ble.BtBleSearcher import com.common.bluetooth.service.bt.BluetoothClassicBase -import com.common.bluetooth.service.bt.BluetoothClassicSearcher +import com.common.bluetooth.service.bt.BtClassicSearcher /** * 通用蓝牙 @@ -18,7 +19,10 @@ import com.common.bluetooth.service.bt.BluetoothClassicSearcher * @since 2021-12-1 */ open class BluetoothCommon(val mContext: Context) { - val TAG = "BluetoothCommon" + companion object { + const val TAG = "BluetoothCommon" + } + var mBluetoothAdapter: BluetoothAdapter? = null var mBluetoothManager: BluetoothManager? = null var mBluetoothSearcher: IBluetoothSearch? = null @@ -84,7 +88,7 @@ open class BluetoothCommon(val mContext: Context) { Log.e(TAG, "BluetoothManager do not init") return false } - return mBluetoothAdapter!!.isEnabled() || mBluetoothAdapter!!.enable() + return mBluetoothAdapter!!.isEnabled || mBluetoothAdapter!!.enable() } fun closeBt(): Boolean { @@ -107,11 +111,15 @@ open class BluetoothCommon(val mContext: Context) { * * @return 蓝牙扫描对象 */ - fun getBluetoothSearcher(): IBluetoothSearch { + fun getBluetoothSearcher(btType: BLUETOOTH_TYPE): IBluetoothSearch { if (mBluetoothSearcher == null) { synchronized(BluetoothClassicBase::class.java) { if (mBluetoothSearcher == null) { - mBluetoothSearcher = BluetoothClassicSearcher(mContext, mBluetoothAdapter) + mBluetoothSearcher = if (btType == BLUETOOTH_TYPE.BLE) { + BtBleSearcher(mContext, mBluetoothAdapter) + } else { + BtClassicSearcher(mContext, mBluetoothAdapter) + } } } } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEClientService.java b/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEClientService.java index 76669c7..b7e18f6 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEClientService.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEClientService.java @@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.disposables.Disposable; @@ -80,7 +81,7 @@ public class BLEClientService extends Service { public void onCreate() { super.onCreate(); - mBtClient = new BluetoothClientBLEAdapter(BluetoothLeClient.getInstance(this)); + mBtClient = new BluetoothClientBLEAdapter(BluetoothBLeClient.getInstance(this)); mBtClient.checkBluetoothDevice(BtConstants.BLUETOOTH_TYPE.BLE); mBtClient.openBluetooth(); } @@ -131,7 +132,7 @@ public class BLEClientService extends Service { // 判断是否需要启动通知 if (enableNotify) { observables[1] = mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(), - BtConstants.INSTANCE.getUUID_CHARACTERISTIC_NOTIFY(), notifyCallback); + BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY(), notifyCallback); } Observable.concatArray(observables).subscribe(new Observer() { @Override @@ -293,6 +294,63 @@ public class BLEClientService extends Service { } } + /** + * 从外围设备读取数据 + */ + public void read() { + if (mBtClient == null) { + Log.e(TAG, "read msg mBtClient got null"); + return; + } + if (TextUtils.isEmpty(connectMac)) { + connectMac = BtUtils.INSTANCE.getConnectMac(BLEClientService.this); + if (TextUtils.isEmpty(connectMac)) { + Log.e(TAG, "write msg connectMac got null"); + return; + } + } + + Observable observable = mBtClient.read(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(), + BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY()); + + if (observable == null) { + Log.e(TAG, "got read observable is null"); + return; + } + observable.observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer() { + @Override + public void onSubscribe(@io.reactivex.rxjava3.annotations.NonNull Disposable d) { + + } + + @Override + public void onNext(@io.reactivex.rxjava3.annotations.NonNull String s) { + if (clientListener != null) { + clientListener.onNotifyMsgReceive(s.getBytes()); + } + } + + @Override + public void onError(@io.reactivex.rxjava3.annotations.NonNull Throwable e) { + } + + @Override + public void onComplete() { + } + }); + } + + /** + * 断开连接 + * + * @param mac MAC地址 + */ + public void disconnect(String mac) { + if (mBtClient != null && !TextUtils.isEmpty(mac)) { + mBtClient.disconnect(mac); + } + } + public void setClientListener(BLEClientListener clientListener) { this.clientListener = clientListener; } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEReceiveService.java b/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEReceiveService.java index 414dfce..b1cc9bd 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEReceiveService.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BLEReceiveService.java @@ -23,7 +23,7 @@ import android.util.Log; import androidx.annotation.Nullable; import com.common.bluetooth.BtConstants; -import com.common.bluetooth.callback.MsgReceiverListener; +import com.common.bluetooth.callback.BtMsgReceiverListener; /** * BLE接收服务 @@ -48,7 +48,7 @@ public class BLEReceiveService extends Service { /** * 蓝牙消息接收监听 */ - private MsgReceiverListener receiverListener; + private BtMsgReceiverListener receiverListener; /** * 远端设备 */ @@ -156,7 +156,7 @@ public class BLEReceiveService extends Service { } } - public void setMsgReceiveListener(MsgReceiverListener listener) { + public void setMsgReceiveListener(BtMsgReceiverListener listener) { receiverListener = listener; } @@ -213,13 +213,13 @@ public class BLEReceiveService extends Service { //添加指定UUID的可读characteristic BluetoothGattCharacteristic characteristicNotify = new BluetoothGattCharacteristic( - BtConstants.INSTANCE.getUUID_CHARACTERISTIC_NOTIFY(), + BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY(), BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PERMISSION_READ); //添加可读characteristic的descriptor - BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR(), + BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY(), BluetoothGattCharacteristic.PERMISSION_WRITE); characteristicNotify.addDescriptor(descriptor); service.addCharacteristic(characteristicNotify); diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothBLeClient.java b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothBLeClient.java new file mode 100644 index 0000000..64d652b --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothBLeClient.java @@ -0,0 +1,79 @@ +package com.common.bluetooth.service.ble; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; + +import com.common.bluetooth.service.BluetoothCommon; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 蓝牙操作管理类 + * + * @author wangym + * @since 2021-10-19 + */ +public class BluetoothBLeClient extends BluetoothCommon { + @SuppressLint("StaticFieldLeak") + private static volatile BluetoothBLeClient mInstance; + + private final static String TAG = "BluetoothLeClient"; + private final Context mContext; + private static Handler mBluetoothWorker; + + private final Map mGattConnectorMap + = new ConcurrentHashMap<>(); + + private BluetoothBLeClient(Context context) { + super(context.getApplicationContext()); + mContext = context.getApplicationContext(); + HandlerThread thread = new HandlerThread("bluetooth client worker"); + thread.start(); + mBluetoothWorker = new Handler(thread.getLooper()); + } + + public static BluetoothBLeClient getInstance(Context context) { + if (mInstance == null) { + synchronized (BluetoothBLeClient.class) { + if (mInstance == null) { + mInstance = new BluetoothBLeClient(context); + } + } + } + + return mInstance; + } + + public BluetoothLeConnector getBluetoothLeConnector(String mac) { + BluetoothLeConnector result; + if ((result = mGattConnectorMap.get(mac)) != null) { + return result; + } + + result = new BluetoothLeConnector(mContext, getMBluetoothAdapter(), mac, mBluetoothWorker); + mGattConnectorMap.put(mac, result); + return result; + } + + public void cleanConnector(String mac) { + BluetoothLeConnector result; + if ((result = mGattConnectorMap.get(mac)) != null) { + mGattConnectorMap.remove(mac); + result.disconnect(); + result.setOnDataAvailableListener(null); + } + } + + /** + * 在不在需要连接蓝牙设备的时候, + * 或者生命周期暂停的时候调用这一个方法 + */ + public void cleanAllConnector() { + for (String mac : mGattConnectorMap.keySet()) { + cleanConnector(mac); + } + } +} diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothClientBLEAdapter.java b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothClientBLEAdapter.java index baccdb0..0b67b2e 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothClientBLEAdapter.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothClientBLEAdapter.java @@ -7,6 +7,7 @@ import android.os.HandlerThread; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.common.bluetooth.BtConstants; import com.common.bluetooth.callback.BaseResultCallback; @@ -35,7 +36,7 @@ import io.reactivex.rxjava3.core.ObservableOnSubscribe; public class BluetoothClientBLEAdapter implements IBluetoothClient { private static final String TAG = "BluetoothClient"; - private final BluetoothLeClient mClient; + private final BluetoothBLeClient mClient; /** * 监听通知的回调 @@ -43,7 +44,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { */ private volatile static BaseResultCallback notifyCallback = null; - public BluetoothClientBLEAdapter(BluetoothLeClient client) { + public BluetoothClientBLEAdapter(BluetoothBLeClient client) { mClient = client; HandlerThread workThread = new HandlerThread("bluetooth ble worker"); @@ -55,7 +56,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { return Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(@NonNull final ObservableEmitter emitter) { - IBluetoothSearch searcher = mClient.getBluetoothSearcher(); + IBluetoothSearch searcher = mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE); if (searcher.isScanning() && !cancel) { emitter.onError(new BluetoothSearchConflictException("is searching now")); @@ -66,7 +67,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { stopSearch(); } - mClient.getBluetoothSearcher() + mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE) .startScan(millis, new BtScanCallBack() { private final Set devices = new HashSet<>(); @@ -97,12 +98,12 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { @Override public void stopSearch() { - mClient.getBluetoothSearcher().stopScan(); + mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE).stopScan(); } @NonNull @Override - public Observable connect(final String mac) { + public Observable connect(@NonNull final String mac) { return Observable.create(new ObservableOnSubscribe() { @Override public void subscribe(@NonNull final ObservableEmitter emitter) { @@ -125,6 +126,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { @Override public void onError(String msg) { + emitter.onError(new Throwable(msg)); } }); } @@ -178,6 +180,44 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient { }); } + @Nullable + @Override + public Observable read(@NonNull String mac, @NonNull UUID service, @NonNull UUID characteristic) { + return Observable.create(new ObservableOnSubscribe() { + @Override + public void subscribe(@NonNull final ObservableEmitter emitter) { + BluetoothLeConnector connector = mClient.getBluetoothLeConnector(mac); + + connector.setOnDataAvailableListener(new BluetoothLeConnector.OnDataAvailableListener() { + @Override + public void onCharacteristicRead(byte[] values, int status) { + emitter.onNext(new String(values)); + emitter.onComplete(); + } + + @Override + public void onCharacteristicChange(UUID characteristic, byte[] values) { + } + + @Override + public void onCharacteristicWrite(UUID characteristic, int status) { + } + + @Override + public void onDescriptorWrite(UUID descriptor, int status) { + } + + @Override + public void onError(BtConstants.EXCEPTION msg) { + Log.e(TAG, "write got error, msg = " + msg); + emitter.onError(new Throwable(msg.name())); + } + }); + connector.readCharacteristic(service, characteristic); + } + }); + } + public void setNotifyCallback(BaseResultCallback notifyCallback) { this.notifyCallback = notifyCallback; } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothLeConnector.java b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothLeConnector.java index 932280f..8a0a6a8 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothLeConnector.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BluetoothLeConnector.java @@ -65,6 +65,9 @@ public class BluetoothLeConnector { private final BluetoothAdapter mBluetoothAdapter; + /** + * 连接的MAC地址 + */ private final String mBluetoothDeviceAddress; private final Handler mWorkHandler; @@ -166,7 +169,6 @@ public class BluetoothLeConnector { }), 3000L); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { - if (!mIsStartService.get()) { String err = "service not found force disconnect"; Log.e(TAG, err); @@ -210,7 +212,6 @@ public class BluetoothLeConnector { characteristic.getValue(), status); } - } @Override @@ -291,7 +292,7 @@ public class BluetoothLeConnector { // 最终造成 gatt 泄漏问题. // 一个解决方案就是延长连接硬件的时间 if (mConnectStatus.get() != BluetoothGatt.STATE_DISCONNECTED) { - String err = "Device is connecting"; + String err = "error status,Device is connecting or disconnecting"; Log.e(TAG, err); callback.onError(err); return; @@ -435,7 +436,7 @@ public class BluetoothLeConnector { public void accept(BluetoothGattCharacteristic bluetoothGattCharacteristic) throws Exception { - if (getBluetoothGatt() + if (!getBluetoothGatt() .readCharacteristic(bluetoothGattCharacteristic)) { callDataAvailableListenerError(BtConstants.EXCEPTION.READ_EXCEPTION); @@ -493,7 +494,7 @@ public class BluetoothLeConnector { getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, true); // 修改描述设置为开启蓝牙通知 BluetoothGattDescriptor descriptor = gattCharacteristic - .getDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR()); + .getDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY()); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); if (!getBluetoothGatt().writeDescriptor(descriptor)) { @@ -503,7 +504,7 @@ public class BluetoothLeConnector { Log.i(TAG, "Disable Notification"); getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, false); BluetoothGattDescriptor descriptor = gattCharacteristic - .getDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR()); + .getDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY()); descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); if (!getBluetoothGatt().writeDescriptor(descriptor)) { diff --git a/commonbt/src/main/java/com/common/bluetooth/service/ble/BtBleSearcher.kt b/commonbt/src/main/java/com/common/bluetooth/service/ble/BtBleSearcher.kt new file mode 100644 index 0000000..7df7780 --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/service/ble/BtBleSearcher.kt @@ -0,0 +1,147 @@ +package com.common.bluetooth.service.ble + +import android.Manifest +import android.bluetooth.BluetoothAdapter +import android.bluetooth.le.ScanResult +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import android.util.Log +import android.util.Log.d +import androidx.core.content.ContextCompat +import com.common.bluetooth.callback.BtScanCallBack +import com.common.bluetooth.interfaces.IBluetoothSearch +import kotlinx.coroutines.* +import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.CoroutineContext + +/** + * ble搜索器 + * + * @author Alex Wang + * @since 2022-2-17 + */ +class BtBleSearcher( + private val mContext: Context, + private val mBluetoothAdapter: BluetoothAdapter? +) : IBluetoothSearch, CoroutineScope { + override val coroutineContext: CoroutineContext by lazy { Dispatchers.Default } + + companion object { + const val TAG = "BleSearcher" + } + + private var mScanCallback: BtScanCallBack? = null + private val mScanning = AtomicBoolean(false) + + private fun wrapCallBack(callBack: BtScanCallBack?): BtScanCallBack { + return object : BtScanCallBack() { + override fun onComplete() { + launch(Dispatchers.Main) { + callBack?.onComplete() + } + } + + override fun onError(msg: String?) { + launch(Dispatchers.Main) { + callBack?.onError(msg) + } + } + + override fun onScanResult(callbackType: Int, result: ScanResult) { + launch(Dispatchers.Main) { + callBack?.onResult(result) + } + } + + override fun onBatchScanResults(results: List) { + super.onBatchScanResults(results) + } + } + } + + override fun startScan(delayTime: Long, callBack: BtScanCallBack?) { + launch(Dispatchers.Main) { + val permissionCheck1 = ContextCompat.checkSelfPermission( + mContext, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + + val permissionCheck2 = ContextCompat.checkSelfPermission( + mContext, + Manifest.permission.ACCESS_FINE_LOCATION + ) + + if (permissionCheck1 or permissionCheck2 != PackageManager.PERMISSION_GRANTED + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + ) { + val err = "Cannot have location permission" + Log.e(TAG, err) + callBack?.onError(err) + return@launch + } + + if (mScanning.get()) { + stopScan() + } + + // 将回调切换到UI线程 + mScanCallback = wrapCallBack(callBack) + + mScanning.set(true) + + // 定义一个回调接口供扫描结束处理 + // 指定扫描特定的支持service的蓝牙设备 + // call startLeScan(UUID[], BluetoothAdapter.LeScanCallback) + // 可以使用rssi计算蓝牙设备的距离 + // 计算公式: + // d = 10^((abs(RSSI) - A) / (10 * n)) + // 其中: + // d - 计算所得距离 + // RSSI - 接收信号强度(负值) + // A - 射端和接收端相隔1米时的信号强度 + // n - 环境衰减因子 + + // 定义一个回调接口供扫描结束处理 + // 指定扫描特定的支持service的蓝牙设备 + // call startLeScan(UUID[], BluetoothAdapter.LeScanCallback) + // 可以使用rssi计算蓝牙设备的距离 + // 计算公式: + // d = 10^((abs(RSSI) - A) / (10 * n)) + // 其中: + // d - 计算所得距离 + // RSSI - 接收信号强度(负值) + // A - 射端和接收端相隔1米时的信号强度 + // n - 环境衰减因子 + mBluetoothAdapter?.bluetoothLeScanner?.startScan(mScanCallback) + + // Stops scanning after a pre-defined scan period. + // 预先定义停止蓝牙扫描的时间(因为蓝牙扫描需要消耗较多的电量) + notifyStopScan(delayTime) + } + } + + /** + * 通知停止扫描 + */ + private suspend fun notifyStopScan(delayTime: Long) = withContext(Dispatchers.IO) { + delay(delayTime) + d(TAG, "StopScan") + stopScan() + } + + override fun stopScan() { + if (mScanning.get()) { + mScanning.set(false) + launch(Dispatchers.Main) { + mScanCallback!!.onComplete() + } + mBluetoothAdapter?.bluetoothLeScanner?.stopScan(mScanCallback) + mScanCallback = null + } + } + + override fun isScanning(): Boolean { + return mScanning.get() + } +} \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCClientService.java b/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCClientService.java index 8c3f24c..7ea5c0a 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCClientService.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCClientService.java @@ -30,11 +30,11 @@ public class BTCClientService extends Service { */ private final BtMsgListener mMsgListener = new BtMsgListener() { @Override - public void socketNotify(int state, @NonNull String msg) { - super.socketNotify(state, msg); + public void messageReceive(int state, @NonNull String msg) { + super.messageReceive(state, msg); // 将内容消息透传给外部调用者 if (msgListener != null) { - msgListener.socketNotify(state, msg); + msgListener.messageReceive(state, msg); } } }; @@ -49,7 +49,7 @@ public class BTCClientService extends Service { super.onCreate(); Log.i(TAG, "initialize BluetoothManager"); - classicClient = new BluetoothClassicClient(this); + classicClient = BluetoothClassicClient.getInstance(this.getApplication()); classicClient.checkBtDevice(BtConstants.BLUETOOTH_TYPE.CLASSIC); } @@ -78,6 +78,14 @@ public class BTCClientService extends Service { classicClient.connect(mac); } + /** + * 断开连接 + * @param mac mac地址 + */ + public void disconnect(String mac) { + classicClient.close(); + } + /** * 向客户端发送通知 * diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCReceiverService.java b/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCReceiverService.java index 9bc8c23..4ee329b 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCReceiverService.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BTCReceiverService.java @@ -11,8 +11,6 @@ import androidx.annotation.Nullable; import com.common.bluetooth.callback.BtMsgListener; -import java.nio.charset.StandardCharsets; - /** * 经典蓝牙服务端 * @@ -31,8 +29,8 @@ public class BTCReceiverService extends Service { */ private final BtMsgListener mMsgListener = new BtMsgListener() { @Override - public void socketNotify(int state, @NonNull String msg) { - super.socketNotify(state, msg); + public void messageReceive(int state, @NonNull String msg) { + super.messageReceive(state, msg); // 当客户端断开连接时,需要重新开始监听客户端的连接 if (state == BtMsgListener.DISCONNECTED) { if (classicServer != null) { @@ -42,7 +40,7 @@ public class BTCReceiverService extends Service { // 服务端只关心内容消息 // 将内容消息透传给外部调用者 if (msgListener != null) { - msgListener.socketNotify(state, msg); + msgListener.messageReceive(state, msg); } } } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicBase.java b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicBase.java index 562b9f9..4f627ab 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicBase.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicBase.java @@ -16,6 +16,7 @@ import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; @@ -24,7 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * 客户端和服务端的基类,用于管理socket长连接 * * @author wangym - * @since 2.21-12-2 + * @since 2021-12-2 */ public class BluetoothClassicBase extends BluetoothCommon { private static final String TAG = "BluetoothClassicBase"; @@ -32,13 +33,13 @@ public class BluetoothClassicBase extends BluetoothCommon { private static final int FLAG_MSG = 0; //消息标记 private static final int FLAG_FILE = 1; //文件标记 - protected BluetoothSocket mSocket; + protected static BluetoothSocket mSocket; private DataOutputStream mOut; private BtMsgListener mListener; /** - * 是否在读取中 + * 是否连接中 */ - private final AtomicBoolean isRead = new AtomicBoolean(false); + private final AtomicBoolean isConnected = new AtomicBoolean(false); /** * 是否在发送中 */ @@ -67,6 +68,7 @@ public class BluetoothClassicBase extends BluetoothCommon { */ void loopRead(BluetoothSocket socket) { mSocket = socket; + DataInputStream in = null; try { if (!mSocket.isConnected()) { mSocket.connect(); @@ -76,9 +78,21 @@ public class BluetoothClassicBase extends BluetoothCommon { // 将链接成功的MAC地址保存下来 BtUtils.INSTANCE.saveConnectMac(getMContext(), mac); mOut = new DataOutputStream(mSocket.getOutputStream()); - DataInputStream in = new DataInputStream(mSocket.getInputStream()); - isRead.set(true); - while (isRead.get()) { //死循环读取 + in = new DataInputStream(mSocket.getInputStream()); + isConnected.set(true); + } catch (Throwable e) { + closeByConnect(); + } + doRead(in); + } + + private void doRead(DataInputStream in) { + if (in == null) { + Log.e(TAG, "got in is null"); + return; + } + try { + while (isConnected.get()) { //死循环读取 switch (in.readInt()) { case FLAG_MSG: //读取短消息 String msg = in.readUTF(); @@ -108,6 +122,12 @@ public class BluetoothClassicBase extends BluetoothCommon { } } catch (Throwable e) { close(); + } finally { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } } } @@ -181,13 +201,33 @@ public class BluetoothClassicBase extends BluetoothCommon { mOut.flush(); notifyUI(BtMsgListener.MSG, "文件发送完成."); } catch (Throwable e) { - close(); + closeByConnect(); } isSending.set(false); } }); } + /** + * 连接失败关闭socket + */ + public void closeByConnect() { + Log.d(TAG, "closeByConnect"); + try { + String mac = ""; + if (mSocket != null) { + mSocket.close(); + mac = mSocket.getRemoteDevice().getAddress(); + } + // 如果是从连接状态断开,那么发送断开事件 + // 如果是从未连接状态则认为是连接异常 + notifyUI(BtMsgListener.CONN_ERROR, mac); + isConnected.set(false); + } catch (Throwable e) { + e.printStackTrace(); + } + } + /** * 关闭Socket连接 */ @@ -195,12 +235,14 @@ public class BluetoothClassicBase extends BluetoothCommon { Log.d(TAG, "close"); try { String mac = ""; - isRead.set(false); if (mSocket != null) { mSocket.close(); mac = mSocket.getRemoteDevice().getAddress(); } + // 如果是从连接状态断开,那么发送断开事件 + // 如果是从未连接状态则认为是连接异常 notifyUI(BtMsgListener.DISCONNECTED, mac); + isConnected.set(false); } catch (Throwable e) { e.printStackTrace(); } @@ -209,10 +251,10 @@ public class BluetoothClassicBase extends BluetoothCommon { /** * 无回调关闭Socket */ - public void closeSilence(){ + public void closeSilence() { Log.d(TAG, "closeSilence"); try { - isRead.set(false); + isConnected.set(false); if (mSocket != null) { mSocket.close(); } @@ -253,7 +295,7 @@ public class BluetoothClassicBase extends BluetoothCommon { */ protected void notifyUI(final int state, final String msg) { if (mListener != null) { - mListener.socketNotify(state, msg); + mListener.messageReceive(state, msg); } } } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicClient.java b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicClient.java index adb25b9..0b1118b 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicClient.java +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClassicClient.java @@ -1,5 +1,6 @@ package com.common.bluetooth.service.bt; +import android.annotation.SuppressLint; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.Context; @@ -19,10 +20,24 @@ import java.util.concurrent.Executors; public class BluetoothClassicClient extends BluetoothClassicBase { private static final String TAG = "BtClient"; - public BluetoothClassicClient(Context context) { + @SuppressLint("StaticFieldLeak") + private static BluetoothClassicClient mInstance; + + private BluetoothClassicClient(Context context) { super(context); } + public static BluetoothClassicClient getInstance(Context context) { + if (mInstance == null) { + synchronized (BluetoothClassicClient.class) { + if (mInstance == null) { + mInstance = new BluetoothClassicClient(context); + } + } + } + return mInstance; + } + /** * 与远端设备建立长连接 * diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClientClassicAdapter.kt b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClientClassicAdapter.kt index 076f227..9c8bd9f 100644 --- a/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClientClassicAdapter.kt +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BluetoothClientClassicAdapter.kt @@ -5,7 +5,7 @@ import com.common.bluetooth.BtConstants import com.common.bluetooth.callback.BaseResultCallback import com.common.bluetooth.interfaces.IBluetoothClient import io.reactivex.rxjava3.core.Observable -import java.util.UUID +import java.util.* /** * 经典蓝牙适配器 @@ -19,13 +19,13 @@ class BluetoothClientClassicAdapter(private var mClient: BluetoothClassicClient) override fun search(millis: Long, cancel: Boolean): Observable { return Observable.create { - mClient.getBluetoothSearcher().startScan(millis, null) + mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).startScan(millis, null) it.onComplete() } } override fun stopSearch() { - mClient.getBluetoothSearcher().stopScan() + mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).stopScan() } override fun connect(mac: String): Observable { @@ -57,6 +57,10 @@ class BluetoothClientClassicAdapter(private var mClient: BluetoothClassicClient) } } + override fun read(mac: String, service: UUID, characteristic: UUID): Observable? { + return null + } + override fun checkBluetoothDevice(type: BtConstants.BLUETOOTH_TYPE): Boolean { return mClient.checkBtDevice(type) } diff --git a/commonbt/src/main/java/com/common/bluetooth/service/bt/BtClassicSearcher.kt b/commonbt/src/main/java/com/common/bluetooth/service/bt/BtClassicSearcher.kt new file mode 100644 index 0000000..54109b4 --- /dev/null +++ b/commonbt/src/main/java/com/common/bluetooth/service/bt/BtClassicSearcher.kt @@ -0,0 +1,33 @@ +package com.common.bluetooth.service.bt + +import android.bluetooth.BluetoothAdapter +import android.content.Context +import com.common.bluetooth.callback.BtScanCallBack +import com.common.bluetooth.interfaces.IBluetoothSearch + +/** + * 经典蓝牙搜索器 + * + * @author Alex Wang + * @since 2022-2-17 + */ +class BtClassicSearcher( + private val mContext: Context, + private val mBluetoothAdapter: BluetoothAdapter? +) : IBluetoothSearch { + override fun startScan(delayTime: Long, callBack: BtScanCallBack?) { + // 每次启动前,检查一下蓝牙是否已经在扫描之中 + stopScan() + mBluetoothAdapter?.startDiscovery() + } + + override fun stopScan() { + if (mBluetoothAdapter != null && mBluetoothAdapter.isDiscovering) { + mBluetoothAdapter.cancelDiscovery() + } + } + + override fun isScanning(): Boolean { + return mBluetoothAdapter?.isDiscovering ?: false + } +} \ No newline at end of file diff --git a/commonbt/src/main/java/com/common/bluetooth/utils/BtUtils.kt b/commonbt/src/main/java/com/common/bluetooth/utils/BtUtils.kt index bc73922..355fb9a 100644 --- a/commonbt/src/main/java/com/common/bluetooth/utils/BtUtils.kt +++ b/commonbt/src/main/java/com/common/bluetooth/utils/BtUtils.kt @@ -5,7 +5,11 @@ import android.content.SharedPreferences import android.text.TextUtils import android.util.Log.d import android.util.Log.e +import com.common.bluetooth.DeviceJson import com.common.bluetooth.bean.* +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import java.util.ArrayList /** * 蓝牙工具类 @@ -21,11 +25,21 @@ object BtUtils { */ const val SP_NAME = "config" + /** + * sp文件名称 + */ + const val SP_DEVICE_NAME = "config_device" + /** * MAC地址的KEY */ const val MAC_KEY = "mac" + /** + * MAC地址的KEY + */ + const val MAC_AND_NAME_KEY = "mac_and_name" + enum class EVENT_TYPE(val type: String, val index: String) { /** * 点击事件 @@ -40,7 +54,12 @@ object BtUtils { /** * 模式切换事件 */ - EVENT_SWITCH("switch", "3") + EVENT_SWITCH("switch", "3"), + + /** + * UI交互 + */ + EVENT_UI("ui", "4") } /** @@ -118,6 +137,8 @@ object BtUtils { */ const val MOD_S = "7" + const val UI_HIND_COVER = "1" + /** * 中间包 */ @@ -347,6 +368,115 @@ object BtUtils { return sharedPreferences.getString(MAC_KEY, "") } + /** + * 保存连接的MAC和名字 + * + * @param mac 设备MAC + * @param context 上下文 + */ + fun saveConnectMacAndName(context: Context, mac: String,name:String) { + val sharedPreferences: SharedPreferences = + context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE) + val edit = sharedPreferences.edit() + var deviceMacList: ArrayList = ArrayList() + var deviceNameList: ArrayList = ArrayList() + var deviceMacList1: ArrayList = ArrayList() + var deviceNameList1: ArrayList = ArrayList() + val gson = Gson() + deviceMacList.clear() + deviceNameList.clear() + if (TextUtils.isEmpty(getConnectMacAndName(context, MAC_AND_NAME_KEY))) { + deviceMacList.add(mac) + deviceNameList.add(name) + edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) + edit.putString(MAC_KEY, gson.toJson(deviceMacList)) + } else { + val listType = object : TypeToken?>() {}.type + deviceMacList1 = gson.fromJson>( + getConnectMacAndName(context, MAC_KEY), + listType + ) + val listType2 = object : TypeToken?>() {}.type + deviceNameList1 = gson.fromJson>( + getConnectMacAndName(context, MAC_AND_NAME_KEY), + listType2 + ) + var i = 0 + for (s in deviceMacList1) { + if (deviceMacList1.get(i) == mac) { + break + } else if (deviceMacList1.get(deviceMacList1.size - 1) != mac ) { + deviceMacList.add(mac) + deviceMacList.addAll(deviceMacList1) + deviceNameList.add(name) + deviceNameList.addAll(deviceNameList1) + edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) + edit.putString(MAC_KEY, gson.toJson(deviceMacList)) + } + i++ + } + + } + edit.apply() + } + + /** + * 获取连接的MAC和名字 + * + * @param context 上下文 + * @return 保存的设备MAC + */ + fun getConnectMacAndName(context: Context, string: String): String? { + val sharedPreferences: SharedPreferences = + context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE) + if (string == MAC_AND_NAME_KEY) { + return sharedPreferences.getString(MAC_AND_NAME_KEY, "") + } else if (string == MAC_KEY) { + return sharedPreferences.getString(MAC_KEY, "") + } + return "" + } + + /** + * 清除sp + * + * @param context 上下文 + */ + fun clearConnectMacAndName(context: Context) { + val sharedPreferences: SharedPreferences = + context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE) + val edit = sharedPreferences.edit() + edit.clear() + edit.commit(); + } + + /** + * 修改sp + * + * @param mac 设备MAC + * @param context 上下文 + */ + fun changeConnectMacAndName(context: Context, device: MutableList) { + val sharedPreferences: SharedPreferences = + context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE) + val edit = sharedPreferences.edit() + var deviceMacList: ArrayList = ArrayList() + var deviceNameList: ArrayList = ArrayList() + val gson = Gson() + deviceMacList.clear() + deviceNameList.clear() + var i = 0 + for (s in device) { + deviceMacList.add(device.get(i).mac) + deviceNameList.add(device.get(i).name) + edit.putString(MAC_AND_NAME_KEY, gson.toJson(deviceNameList)) + edit.putString(MAC_KEY, gson.toJson(deviceMacList)) + i++ + } + + edit.apply() + } + /** * 合并byte[] */ diff --git a/commonbt/src/main/java/com/common/bluetooth/view/BtDeviceListAdapter.java b/commonbt/src/main/java/com/common/bluetooth/view/BtDeviceListAdapter.java index ab42f3e..1fb62df 100644 --- a/commonbt/src/main/java/com/common/bluetooth/view/BtDeviceListAdapter.java +++ b/commonbt/src/main/java/com/common/bluetooth/view/BtDeviceListAdapter.java @@ -33,13 +33,13 @@ public class BtDeviceListAdapter extends RecyclerView.Adapter= Build.VERSION_CODES.KITKAT) { + int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_FULLSCREEN; + view.setSystemUiVisibility(uiOptions); + } + } + + @Override + public void show() { + this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + super.show(); + fullScreenImmersive(getWindow().getDecorView()); + this.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + } +} diff --git a/commonbt/src/main/res/values/styles.xml b/commonbt/src/main/res/values/styles.xml new file mode 100644 index 0000000..2723a84 --- /dev/null +++ b/commonbt/src/main/res/values/styles.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file