[desc]:更新蓝牙模块

[author]:wangyimiao
master
yimiao 3 years ago
parent 524cf9408a
commit 8fb4839c36

@ -26,6 +26,7 @@ project.ext {
rxjava : "3.0.13", rxjava : "3.0.13",
rxandroid : "3.0.0", rxandroid : "3.0.0",
kotlin : "1.5.10", kotlin : "1.5.10",
kotlin_android : "1.5.0",
converter_gson : '2.9.0', converter_gson : '2.9.0',
retrofit_rxjava : "2.9.0", retrofit_rxjava : "2.9.0",
room : "2.3.0", room : "2.3.0",
@ -35,7 +36,6 @@ project.ext {
glide : "4.12.0", glide : "4.12.0",
photo_view : "2.3.0", photo_view : "2.3.0",
luban : "1.1.8", luban : "1.1.8",
kotlin_android : "1.4.1",
gson : "2.8.6", gson : "2.8.6",
arouter : "1.5.2", arouter : "1.5.2",
mmkv : "1.2.10", mmkv : "1.2.10",

@ -21,6 +21,7 @@ dependencies {
// kotlin // kotlin
implementation rootProject.ext.dependencies.kotlin implementation rootProject.ext.dependencies.kotlin
implementation rootProject.ext.dependencies.kotlin_android
// rxAndroid // rxAndroid
implementation rootProject.ext.dependencies.rxandroid implementation rootProject.ext.dependencies.rxandroid
// gson // gson

@ -8,12 +8,10 @@ import java.util.*
/** /**
* BT静态方法 * BT静态方法
* *
* @author wangym * @author Alex Wang
* @since 2021-10-14 * @since 2021-10-14
*/ */
object BtConstants { object BtConstants {
const val BT_NAME = "innovation bt"
val DEFAULT_CHARSET = StandardCharsets.UTF_8 val DEFAULT_CHARSET = StandardCharsets.UTF_8
/** /**
@ -31,21 +29,6 @@ object BtConstants {
BLE BLE
} }
/**
* 蓝牙类型
*/
enum class CLIENT_TYPE {
/**
* 服务端
*/
SERVER,
/**
* 客户端
*/
CLIENT
}
private const val STATUS_BASE = 0x1 private const val STATUS_BASE = 0x1
const val CONNECT_SUCCESS = STATUS_BASE shl 1 const val CONNECT_SUCCESS = STATUS_BASE shl 1
const val CONNECT_ERROR = STATUS_BASE shl 2 const val CONNECT_ERROR = STATUS_BASE shl 2
@ -87,10 +70,10 @@ object BtConstants {
* 经典蓝牙连接UUID * 经典蓝牙连接UUID
*/ */
val CLASSIC_BT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB") val CLASSIC_BT_UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
val UUID_SERVICE = UUID.fromString("66f564dc-121f-3e7f-80b1-f005d3f194c9") val UUID_SERVICE = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb7")
val UUID_CHARACTERISTIC_NOTIFY = UUID.fromString("66f564dd-121f-3e7f-80b1-f005d3f194c9") val UUID_CHARACTERISTIC_READ_NOTIFY = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cb8")
val UUID_CHARACTERISTIC_WRITE = UUID.fromString("66f564de-121f-3e7f-80b1-f005d3f194c9") val UUID_CHARACTERISTIC_WRITE = UUID.fromString("0783b03e-8535-b5a0-7140-a304d2495cba")
val UUID_DESCRIPTOR = UUID.fromString("66f564df-121f-3e7f-80b1-f005d3f194c9") val UUID_DESC_NOTITY = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
/** /**
* 获取UUID * 获取UUID

@ -21,6 +21,8 @@ import com.common.bluetooth.bean.AssociateSourceEvent;
import com.common.bluetooth.bean.CommonMsg; import com.common.bluetooth.bean.CommonMsg;
import com.common.bluetooth.bean.KeyboardEvent; import com.common.bluetooth.bean.KeyboardEvent;
import com.common.bluetooth.callback.BLEClientListener; 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.databinding.ActivityMainBinding;
import com.common.bluetooth.utils.BtUtils; import com.common.bluetooth.utils.BtUtils;
import com.common.bluetooth.view.BtDeviceListAdapter; import com.common.bluetooth.view.BtDeviceListAdapter;
@ -84,23 +86,22 @@ public class BtDemoActivity extends AppCompatActivity {
index -> index ->
{ {
BtManager.INSTANCE.connect(bluetoothDevices.get(index).getAddress()); BtManager.INSTANCE.connect(bluetoothDevices.get(index).getAddress());
BtManager.INSTANCE.setClientListener(new BLEClientListener() { BtManager.INSTANCE.setBtStatusListener(new BtStatusListener() {
@Override @Override
public void onResult(@NonNull CommonMsg result) { public void onConnected(@Nullable String mac) {
Log.e("wangym", "onResult = " + result.getMsg());
runOnUiThread(
() -> Toast.makeText(BtDemoActivity.this, result.getMsg(),
Toast.LENGTH_SHORT).show());
} }
@Override @Override
public void onNotifyMsgReceive(@NonNull byte[] msg) { public void onDisconnect(@Nullable String msg) {
}
});
BtManager.INSTANCE.setBtMsgReceiverListener(msg -> {
Log.e("wangym", "notify = " + new String(msg)); Log.e("wangym", "notify = " + new String(msg));
runOnUiThread( runOnUiThread(
() -> Toast.makeText(BtDemoActivity.this, new String(msg), () -> Toast.makeText(BtDemoActivity.this, new String(msg),
Toast.LENGTH_SHORT).show()); Toast.LENGTH_SHORT).show());
}
}); });
}); });
} }
@ -116,7 +117,7 @@ public class BtDemoActivity extends AppCompatActivity {
mBinding.buildBleServer.setOnClickListener(v -> { mBinding.buildBleServer.setOnClickListener(v -> {
BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.BLE); BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.BLE);
BtManager.INSTANCE.setMsgReceiverListener(msg -> runOnUiThread(() -> { BtManager.INSTANCE.setBtMsgReceiverListener(msg -> runOnUiThread(() -> {
Object event = BtUtils.INSTANCE.getEventFromSendMsg(msg); Object event = BtUtils.INSTANCE.getEventFromSendMsg(msg);
if (event == null) { if (event == null) {
mBinding.receiveContent.setText(new String(msg)); mBinding.receiveContent.setText(new String(msg));
@ -135,7 +136,7 @@ public class BtDemoActivity extends AppCompatActivity {
mBinding.btcServerBuild.setOnClickListener(v -> { mBinding.btcServerBuild.setOnClickListener(v -> {
BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.CLASSIC); BtManager.INSTANCE.initServer(this, BtConstants.BLUETOOTH_TYPE.CLASSIC);
BtManager.INSTANCE.setMsgReceiverListener(msg -> { BtManager.INSTANCE.setBtMsgReceiverListener(msg -> {
Log.d(TAG, new String(msg)); Log.d(TAG, new String(msg));
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
@Override @Override

@ -11,21 +11,19 @@ import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.IBinder import android.os.IBinder
import android.text.TextUtils
import android.util.Log.* import android.util.Log.*
import androidx.core.content.ContextCompat.checkSelfPermission import androidx.core.content.ContextCompat.checkSelfPermission
import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE
import com.common.bluetooth.service.ble.BluetoothClientBLEAdapter import com.common.bluetooth.service.ble.BluetoothClientBLEAdapter
import com.common.bluetooth.bean.CommonMsg import com.common.bluetooth.bean.CommonMsg
import com.common.bluetooth.callback.BLEClientListener import com.common.bluetooth.callback.*
import com.common.bluetooth.callback.BtMsgListener
import com.common.bluetooth.callback.BtServiceListener
import com.common.bluetooth.callback.MsgReceiverListener
import com.common.bluetooth.interfaces.IBluetoothClient import com.common.bluetooth.interfaces.IBluetoothClient
import com.common.bluetooth.service.ble.BLEClientService import com.common.bluetooth.service.ble.BLEClientService
import com.common.bluetooth.service.ble.BLEClientService.BLEClientBinder import com.common.bluetooth.service.ble.BLEClientService.BLEClientBinder
import com.common.bluetooth.service.ble.BLEReceiveService import com.common.bluetooth.service.ble.BLEReceiveService
import com.common.bluetooth.service.ble.BLEReceiveService.ReceiverBinder 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.BTCClientService
import com.common.bluetooth.service.bt.BTCReceiverService import com.common.bluetooth.service.bt.BTCReceiverService
import com.common.bluetooth.service.bt.BluetoothClassicClient 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 * @since 2021-10-28
*/ */
object BtManager { object BtManager {
@ -52,7 +50,7 @@ object BtManager {
/** /**
* BLE客户端服务 * 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 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) { fun initServer(context: Context, btType: BLUETOOTH_TYPE) {
if (btType == BLUETOOTH_TYPE.BLE) { if (btType == BLUETOOTH_TYPE.BLE) {
mBtClient = BluetoothClientBLEAdapter( mBtClient = BluetoothClientBLEAdapter(
BluetoothLeClient.getInstance(context) BluetoothBLeClient.getInstance(context)
) )
initBleReceiverService(context) initBleReceiverService(context)
} else if (btType == BLUETOOTH_TYPE.CLASSIC) { } else if (btType == BLUETOOTH_TYPE.CLASSIC) {
mBtClient = BluetoothClientClassicAdapter(BluetoothClassicClient(context)) mBtClient =
BluetoothClientClassicAdapter(BluetoothClassicClient.getInstance(context.applicationContext))
initClassicReceiverService(context) initClassicReceiverService(context)
} }
// 检查蓝牙设备是否支持
mBtClient!!.checkBluetoothDevice(btType) mBtClient!!.checkBluetoothDevice(btType)
} }
/** /**
* 初始化蓝牙客户端模块 * 初始化蓝牙客户端模块
*
* @param context 上下文
* @param btType 蓝牙模式
*/ */
fun initClient(context: Activity, btType: BLUETOOTH_TYPE) { fun initClient(context: Activity, btType: BLUETOOTH_TYPE) {
if (btType == BLUETOOTH_TYPE.BLE) { if (btType == BLUETOOTH_TYPE.BLE) {
mBtClient = BluetoothClientBLEAdapter( mBtClient = BluetoothClientBLEAdapter(
BluetoothLeClient.getInstance(context) BluetoothBLeClient.getInstance(context)
) )
initBleClientService(context) initBleClientService(context)
} else if (btType == BLUETOOTH_TYPE.CLASSIC) { } else if (btType == BLUETOOTH_TYPE.CLASSIC) {
mBtClient = BluetoothClientClassicAdapter( mBtClient = BluetoothClientClassicAdapter(
BluetoothClassicClient(context) BluetoothClassicClient.getInstance(context.application)
) )
initClassicClientService(context) initClassicClientService(context)
} }
// 检查蓝牙设备是否支持
mBtClient!!.checkBluetoothDevice(btType) mBtClient!!.checkBluetoothDevice(btType)
} }
@ -188,9 +199,9 @@ object BtManager {
object : ServiceConnection { object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) {
serverService = (service as ReceiverBinder).service serverService = (service as ReceiverBinder).service
serverService?.setMsgReceiveListener(object : MsgReceiverListener { serverService?.setMsgReceiveListener(object : BtMsgReceiverListener {
override fun onMsgReceive(msg: ByteArray) { override fun onMsgReceive(msg: ByteArray) {
msgReceiverListener?.onMsgReceive(msg) btMsgReceiverListener?.onMsgReceive(msg)
} }
}) })
btServiceListener?.onServiceReady() btServiceListener?.onServiceReady()
@ -208,8 +219,8 @@ object BtManager {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) {
classicServerService = (service as BTCReceiverService.ClassicReceiverBinder).service classicServerService = (service as BTCReceiverService.ClassicReceiverBinder).service
classicServerService?.setMsgReceiveListener(object : BtMsgListener() { classicServerService?.setMsgReceiveListener(object : BtMsgListener() {
override fun socketNotify(state: Int, msg: String) { override fun messageReceive(state: Int, msg: String) {
msgReceiverListener?.onMsgReceive(msg.toByteArray()) btMsgReceiverListener?.onMsgReceive(msg.toByteArray())
} }
}) })
btServiceListener?.onServiceReady() btServiceListener?.onServiceReady()
@ -225,33 +236,29 @@ object BtManager {
private val bleClientConn: ServiceConnection by lazy { private val bleClientConn: ServiceConnection by lazy {
object : ServiceConnection { object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) {
clientService = (service as BLEClientBinder).service bleClientService = (service as BLEClientBinder).service
clientService?.setClientListener(object : BLEClientListener { bleClientService?.setClientListener(object : BLEClientListener {
override fun onResult(result: CommonMsg) { override fun onResult(result: CommonMsg) {
// 如果连接成功,更新连接的设备 // 如果连接成功,更新连接的设备
if (result.msgType == BtConstants.CONNECT_SUCCESS) { if (result.msgType == BtConstants.CONNECT_SUCCESS) {
curConnectMac = result.msg curConnectMac = result.msg
btStatusListener?.onConnected(result.msg)
} else if (result.msgType == BtConstants.DISCONNECT) { } else if (result.msgType == BtConstants.DISCONNECT) {
curConnectMac = "" curConnectMac = ""
btStatusListener?.onDisconnect("")
} }
clientListener?.onResult(result)
} }
override fun onNotifyMsgReceive(msg: ByteArray) { override fun onNotifyMsgReceive(msg: ByteArray) {
clientListener?.onNotifyMsgReceive(msg) btMsgReceiverListener?.onMsgReceive(msg)
} }
}) })
btServiceListener?.onServiceReady() btServiceListener?.onServiceReady()
} }
override fun onServiceDisconnected(name: ComponentName) { override fun onServiceDisconnected(name: ComponentName) {
clientService = null bleClientService = null
clientListener?.onResult( btStatusListener?.onDisconnect("disconnect from server")
CommonMsg(
BtConstants.DISCONNECT,
"disconnect from server"
)
)
btServiceListener?.onServiceDisConnected() btServiceListener?.onServiceDisConnected()
} }
} }
@ -262,23 +269,22 @@ object BtManager {
override fun onServiceConnected(name: ComponentName, service: IBinder) { override fun onServiceConnected(name: ComponentName, service: IBinder) {
classicClientService = (service as BTCClientService.ClassicClientBinder).service classicClientService = (service as BTCClientService.ClassicClientBinder).service
classicClientService?.setMsgReceiveListener(object : BtMsgListener() { classicClientService?.setMsgReceiveListener(object : BtMsgListener() {
override fun socketNotify(state: Int, msg: String) { override fun messageReceive(state: Int, msg: String) {
when (state) { when (state) {
CONNECTED -> { CONNECTED -> {
curConnectMac = msg curConnectMac = msg
clientListener?.onResult( btStatusListener?.onConnected(msg)
CommonMsg(
BtConstants.CONNECT_SUCCESS,
msg
)
)
} }
DISCONNECTED -> { DISCONNECTED -> {
curConnectMac = "" curConnectMac = ""
clientListener?.onResult(CommonMsg(BtConstants.DISCONNECT, msg)) btStatusListener?.onDisconnect(msg)
} }
MSG -> { MSG -> {
clientListener?.onNotifyMsgReceive(msg.toByteArray()) btMsgReceiverListener?.onMsgReceive(msg.toByteArray())
}
CONN_ERROR -> {
curConnectMac = ""
btStatusListener?.onDisconnect(msg)
} }
else -> { else -> {
e(TAG, "got error state:$state") e(TAG, "got error state:$state")
@ -302,6 +308,7 @@ object BtManager {
* @param btType 蓝牙类型 * @param btType 蓝牙类型
*/ */
fun searchBtDevice(btType: BLUETOOTH_TYPE) { fun searchBtDevice(btType: BLUETOOTH_TYPE) {
d(TAG, "start search $btType")
if (btType === BLUETOOTH_TYPE.CLASSIC) { if (btType === BLUETOOTH_TYPE.CLASSIC) {
val mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() val mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (mBluetoothAdapter != null) { if (mBluetoothAdapter != null) {
@ -315,31 +322,76 @@ object BtManager {
} }
} else if (btType === BLUETOOTH_TYPE.BLE) { } else if (btType === BLUETOOTH_TYPE.BLE) {
d(TAG, "ble search start") d(TAG, "ble search start")
if (mBtClient != null) {
mBtClient!!.search(3000, true) mBtClient!!.search(3000, true)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<BluetoothDevice> { .subscribe(object : Observer<BluetoothDevice> {
override fun onSubscribe(d: Disposable) {} override fun onSubscribe(d: Disposable) {
d(TAG, "ble onSubscribe")
}
override fun onNext(bleDevice: BluetoothDevice) { override fun onNext(bleDevice: BluetoothDevice) {
d(TAG, "ble onScanResult") d(TAG, "ble onScanResult")
bleScanListener?.onResult(bleDevice)
} }
override fun onError(e: Throwable) { override fun onError(e: Throwable) {
d(TAG, "ble onScanFailed") d(TAG, "ble onScanFailed")
} }
override fun onComplete() {} override fun onComplete() {
d(TAG, "ble onComplete")
bleScanListener?.onComplete()
}
}) })
} }
} }
}
/**
* 停止扫描蓝牙设备
*
* @param btType 蓝牙类型
*/
fun stopSearch(btType: BLUETOOTH_TYPE) {
d(TAG, "stop search $btType")
mBtClient?.stopSearch()
}
/** /**
* 连接设备BLE的连接方式 * 连接设备
* *
* @param mac MAC地址 * @param mac MAC地址
*/ */
fun connect(mac: String) { fun connect(mac: String) {
if (clientService != null) { connect(mac, true)
clientService!!.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 { } else {
w(TAG, "init clientService first") 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) { fun disconnect(mac: String, btType: BLUETOOTH_TYPE) {
if (clientService == null) { if (TextUtils.isEmpty(mac)) {
e(TAG, "writeMsg pls init first") e(TAG, "[disConnect] error with empty mac")
} else { // TODO 添加断开失败回调
clientService!!.connect(mac, enableNotify) 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 传输的内容 * @param msg 传输的内容
*/ */
fun write(msg: ByteArray) { fun write(msg: ByteArray) {
if (clientService != null) { bleClientService?.write(msg)
clientService!!.write(msg) classicClientService?.write(msg)
} classicServerService?.write(msg)
if (classicClientService != null) {
classicClientService!!.write(msg)
} }
if (classicServerService != null) { /**
classicServerService!!.write(msg) * 读取外围设备内容
} *
* NOTE: only support for Ble
*/
fun read() {
bleClientService?.read()
} }
/** /**
* 获取已配对的设备 * 获取已配对的设备
*
* @return 已配对的设备
*/ */
fun getBoundDevices(): Set<BluetoothDevice>? { fun getBoundDevices(): Set<BluetoothDevice>? {
return mBtClient?.getBondedDevices() return mBtClient?.getBondedDevices()

@ -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;
}

@ -0,0 +1,16 @@
package com.common.bluetooth;
import java.util.List;
public class DeviceList {
public List<DeviceJson> getDeviceJsons() {
return deviceJsons;
}
public void setDeviceJsons(List<DeviceJson> deviceJsons) {
this.deviceJsons = deviceJsons;
}
private List<DeviceJson> deviceJsons;
}

@ -4,16 +4,21 @@ import com.common.bluetooth.bean.CommonMsg
/** /**
* 客户端监听 * 客户端监听
*
* @author Alex Wang
*/ */
interface BLEClientListener { interface BLEClientListener {
/** /**
* 结果 * 结果
*
* @param result 回调 * @param result 回调
*/ */
fun onResult(result: CommonMsg) fun onResult(result: CommonMsg)
/** /**
* 接收服务端通知 * 接收外围设备的消息
*
* @param msg 消息内容
*/ */
fun onNotifyMsgReceive(msg: ByteArray) fun onNotifyMsgReceive(msg: ByteArray)
} }

@ -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()
}

@ -1,7 +1,7 @@
package com.common.bluetooth.callback package com.common.bluetooth.callback
/** /**
* 经典蓝牙监听 * 蓝牙消息监听
* *
* @author wangym * @author wangym
* @since 2021-12-1 * @since 2021-12-1
@ -12,7 +12,7 @@ abstract class BtMsgListener {
* @param state 消息类型 * @param state 消息类型
* @param msg 消息内容 * @param msg 消息内容
*/ */
open fun socketNotify(state: Int, msg: String) {} open fun messageReceive(state: Int, msg: String) {}
companion object { companion object {
/** /**
@ -25,9 +25,14 @@ abstract class BtMsgListener {
*/ */
const val CONNECTED = 1 const val CONNECTED = 1
/**
* 连接错误
*/
const val CONN_ERROR = 2
/** /**
* 消息 * 消息
*/ */
const val MSG = 2 const val MSG = 3
} }
} }

@ -0,0 +1,16 @@
package com.common.bluetooth.callback
/**
* 数据接收监听
*
* @author Alex Wang
* @since 2022-2-18
*/
interface BtMsgReceiverListener {
/**
* 接收的消息
*
* @param msg 传输的数据
*/
fun onMsgReceive(msg: ByteArray)
}

@ -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?)
}

@ -61,6 +61,18 @@ interface IBluetoothClient {
values: ByteArray values: ByteArray
): Observable<String>? ): Observable<String>?
/**
* 向一个蓝牙设备写入值
*
* @param mac 设备 mac 地址
* @param service 设备服务地址
* @param characteristic 设备 characteristic 地址
* @return 写入成功返回
*/
fun read(
mac: String, service: UUID, characteristic: UUID
): Observable<String>?
/** /**
* 向蓝牙设备注册一个通道值改变的监听器, * 向蓝牙设备注册一个通道值改变的监听器,
* 每一个设备的每一个通道只允许同时存在一个监听器 * 每一个设备的每一个通道只允许同时存在一个监听器
@ -104,7 +116,7 @@ interface IBluetoothClient {
/** /**
* 启动蓝牙 * 启动蓝牙
* 启动前优先检查蓝牙设备是否支持 [IBluetoothClient.checkBluetoothDevice] )} * 启动前优先检查蓝牙设备是否支持 [IBluetoothClient.checkBluetoothDevice]
*/ */
fun openBluetooth() fun openBluetooth()

@ -8,8 +8,9 @@ import android.content.pm.PackageManager
import android.util.Log import android.util.Log
import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE import com.common.bluetooth.BtConstants.BLUETOOTH_TYPE
import com.common.bluetooth.interfaces.IBluetoothSearch 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.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 * @since 2021-12-1
*/ */
open class BluetoothCommon(val mContext: Context) { open class BluetoothCommon(val mContext: Context) {
val TAG = "BluetoothCommon" companion object {
const val TAG = "BluetoothCommon"
}
var mBluetoothAdapter: BluetoothAdapter? = null var mBluetoothAdapter: BluetoothAdapter? = null
var mBluetoothManager: BluetoothManager? = null var mBluetoothManager: BluetoothManager? = null
var mBluetoothSearcher: IBluetoothSearch? = null var mBluetoothSearcher: IBluetoothSearch? = null
@ -84,7 +88,7 @@ open class BluetoothCommon(val mContext: Context) {
Log.e(TAG, "BluetoothManager do not init") Log.e(TAG, "BluetoothManager do not init")
return false return false
} }
return mBluetoothAdapter!!.isEnabled() || mBluetoothAdapter!!.enable() return mBluetoothAdapter!!.isEnabled || mBluetoothAdapter!!.enable()
} }
fun closeBt(): Boolean { fun closeBt(): Boolean {
@ -107,11 +111,15 @@ open class BluetoothCommon(val mContext: Context) {
* *
* @return 蓝牙扫描对象 * @return 蓝牙扫描对象
*/ */
fun getBluetoothSearcher(): IBluetoothSearch { fun getBluetoothSearcher(btType: BLUETOOTH_TYPE): IBluetoothSearch {
if (mBluetoothSearcher == null) { if (mBluetoothSearcher == null) {
synchronized(BluetoothClassicBase::class.java) { synchronized(BluetoothClassicBase::class.java) {
if (mBluetoothSearcher == null) { if (mBluetoothSearcher == null) {
mBluetoothSearcher = BluetoothClassicSearcher(mContext, mBluetoothAdapter) mBluetoothSearcher = if (btType == BLUETOOTH_TYPE.BLE) {
BtBleSearcher(mContext, mBluetoothAdapter)
} else {
BtClassicSearcher(mContext, mBluetoothAdapter)
}
} }
} }
} }

@ -24,6 +24,7 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.Observer; import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.disposables.Disposable;
@ -80,7 +81,7 @@ public class BLEClientService extends Service {
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
mBtClient = new BluetoothClientBLEAdapter(BluetoothLeClient.getInstance(this)); mBtClient = new BluetoothClientBLEAdapter(BluetoothBLeClient.getInstance(this));
mBtClient.checkBluetoothDevice(BtConstants.BLUETOOTH_TYPE.BLE); mBtClient.checkBluetoothDevice(BtConstants.BLUETOOTH_TYPE.BLE);
mBtClient.openBluetooth(); mBtClient.openBluetooth();
} }
@ -131,7 +132,7 @@ public class BLEClientService extends Service {
// 判断是否需要启动通知 // 判断是否需要启动通知
if (enableNotify) { if (enableNotify) {
observables[1] = mBtClient.registerNotify(connectMac, BtConstants.INSTANCE.getUUID_SERVICE(), 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<String>() { Observable.concatArray(observables).subscribe(new Observer<String>() {
@Override @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<String> 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<String>() {
@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) { public void setClientListener(BLEClientListener clientListener) {
this.clientListener = clientListener; this.clientListener = clientListener;
} }

@ -23,7 +23,7 @@ import android.util.Log;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.common.bluetooth.BtConstants; import com.common.bluetooth.BtConstants;
import com.common.bluetooth.callback.MsgReceiverListener; import com.common.bluetooth.callback.BtMsgReceiverListener;
/** /**
* BLE * 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; receiverListener = listener;
} }
@ -213,13 +213,13 @@ public class BLEReceiveService extends Service {
//添加指定UUID的可读characteristic //添加指定UUID的可读characteristic
BluetoothGattCharacteristic characteristicNotify = new BluetoothGattCharacteristic( BluetoothGattCharacteristic characteristicNotify = new BluetoothGattCharacteristic(
BtConstants.INSTANCE.getUUID_CHARACTERISTIC_NOTIFY(), BtConstants.INSTANCE.getUUID_CHARACTERISTIC_READ_NOTIFY(),
BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE |
BluetoothGattCharacteristic.PROPERTY_READ | BluetoothGattCharacteristic.PROPERTY_READ |
BluetoothGattCharacteristic.PROPERTY_NOTIFY, BluetoothGattCharacteristic.PROPERTY_NOTIFY,
BluetoothGattCharacteristic.PERMISSION_READ); BluetoothGattCharacteristic.PERMISSION_READ);
//添加可读characteristic的descriptor //添加可读characteristic的descriptor
BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR(), BluetoothGattDescriptor descriptor = new BluetoothGattDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY(),
BluetoothGattCharacteristic.PERMISSION_WRITE); BluetoothGattCharacteristic.PERMISSION_WRITE);
characteristicNotify.addDescriptor(descriptor); characteristicNotify.addDescriptor(descriptor);
service.addCharacteristic(characteristicNotify); service.addCharacteristic(characteristicNotify);

@ -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<String, BluetoothLeConnector> 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);
}
}
}

@ -7,6 +7,7 @@ import android.os.HandlerThread;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.common.bluetooth.BtConstants; import com.common.bluetooth.BtConstants;
import com.common.bluetooth.callback.BaseResultCallback; import com.common.bluetooth.callback.BaseResultCallback;
@ -35,7 +36,7 @@ import io.reactivex.rxjava3.core.ObservableOnSubscribe;
public class BluetoothClientBLEAdapter implements IBluetoothClient { public class BluetoothClientBLEAdapter implements IBluetoothClient {
private static final String TAG = "BluetoothClient"; 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<byte[]> notifyCallback = null; private volatile static BaseResultCallback<byte[]> notifyCallback = null;
public BluetoothClientBLEAdapter(BluetoothLeClient client) { public BluetoothClientBLEAdapter(BluetoothBLeClient client) {
mClient = client; mClient = client;
HandlerThread workThread = new HandlerThread("bluetooth ble worker"); HandlerThread workThread = new HandlerThread("bluetooth ble worker");
@ -55,7 +56,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
return Observable.create(new ObservableOnSubscribe<BluetoothDevice>() { return Observable.create(new ObservableOnSubscribe<BluetoothDevice>() {
@Override @Override
public void subscribe(@NonNull final ObservableEmitter<BluetoothDevice> emitter) { public void subscribe(@NonNull final ObservableEmitter<BluetoothDevice> emitter) {
IBluetoothSearch searcher = mClient.getBluetoothSearcher(); IBluetoothSearch searcher = mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE);
if (searcher.isScanning() && !cancel) { if (searcher.isScanning() && !cancel) {
emitter.onError(new BluetoothSearchConflictException("is searching now")); emitter.onError(new BluetoothSearchConflictException("is searching now"));
@ -66,7 +67,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
stopSearch(); stopSearch();
} }
mClient.getBluetoothSearcher() mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE)
.startScan(millis, new BtScanCallBack() { .startScan(millis, new BtScanCallBack() {
private final Set<BluetoothDevice> devices = new HashSet<>(); private final Set<BluetoothDevice> devices = new HashSet<>();
@ -97,12 +98,12 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
@Override @Override
public void stopSearch() { public void stopSearch() {
mClient.getBluetoothSearcher().stopScan(); mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.BLE).stopScan();
} }
@NonNull @NonNull
@Override @Override
public Observable<String> connect(final String mac) { public Observable<String> connect(@NonNull final String mac) {
return Observable.create(new ObservableOnSubscribe<String>() { return Observable.create(new ObservableOnSubscribe<String>() {
@Override @Override
public void subscribe(@NonNull final ObservableEmitter<String> emitter) { public void subscribe(@NonNull final ObservableEmitter<String> emitter) {
@ -125,6 +126,7 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
@Override @Override
public void onError(String msg) { public void onError(String msg) {
emitter.onError(new Throwable(msg));
} }
}); });
} }
@ -178,6 +180,44 @@ public class BluetoothClientBLEAdapter implements IBluetoothClient {
}); });
} }
@Nullable
@Override
public Observable<String> read(@NonNull String mac, @NonNull UUID service, @NonNull UUID characteristic) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull final ObservableEmitter<String> 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<byte[]> notifyCallback) { public void setNotifyCallback(BaseResultCallback<byte[]> notifyCallback) {
this.notifyCallback = notifyCallback; this.notifyCallback = notifyCallback;
} }

@ -65,6 +65,9 @@ public class BluetoothLeConnector {
private final BluetoothAdapter mBluetoothAdapter; private final BluetoothAdapter mBluetoothAdapter;
/**
* MAC
*/
private final String mBluetoothDeviceAddress; private final String mBluetoothDeviceAddress;
private final Handler mWorkHandler; private final Handler mWorkHandler;
@ -166,7 +169,6 @@ public class BluetoothLeConnector {
}), 3000L); }), 3000L);
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) { } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
if (!mIsStartService.get()) { if (!mIsStartService.get()) {
String err = "service not found force disconnect"; String err = "service not found force disconnect";
Log.e(TAG, err); Log.e(TAG, err);
@ -210,7 +212,6 @@ public class BluetoothLeConnector {
characteristic.getValue(), characteristic.getValue(),
status); status);
} }
} }
@Override @Override
@ -291,7 +292,7 @@ public class BluetoothLeConnector {
// 最终造成 gatt 泄漏问题. // 最终造成 gatt 泄漏问题.
// 一个解决方案就是延长连接硬件的时间 // 一个解决方案就是延长连接硬件的时间
if (mConnectStatus.get() != BluetoothGatt.STATE_DISCONNECTED) { if (mConnectStatus.get() != BluetoothGatt.STATE_DISCONNECTED) {
String err = "Device is connecting"; String err = "error status,Device is connecting or disconnecting";
Log.e(TAG, err); Log.e(TAG, err);
callback.onError(err); callback.onError(err);
return; return;
@ -435,7 +436,7 @@ public class BluetoothLeConnector {
public void accept(BluetoothGattCharacteristic bluetoothGattCharacteristic) public void accept(BluetoothGattCharacteristic bluetoothGattCharacteristic)
throws Exception { throws Exception {
if (getBluetoothGatt() if (!getBluetoothGatt()
.readCharacteristic(bluetoothGattCharacteristic)) { .readCharacteristic(bluetoothGattCharacteristic)) {
callDataAvailableListenerError(BtConstants.EXCEPTION.READ_EXCEPTION); callDataAvailableListenerError(BtConstants.EXCEPTION.READ_EXCEPTION);
@ -493,7 +494,7 @@ public class BluetoothLeConnector {
getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, true); getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, true);
// 修改描述设置为开启蓝牙通知 // 修改描述设置为开启蓝牙通知
BluetoothGattDescriptor descriptor = gattCharacteristic BluetoothGattDescriptor descriptor = gattCharacteristic
.getDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR()); .getDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY());
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
if (!getBluetoothGatt().writeDescriptor(descriptor)) { if (!getBluetoothGatt().writeDescriptor(descriptor)) {
@ -503,7 +504,7 @@ public class BluetoothLeConnector {
Log.i(TAG, "Disable Notification"); Log.i(TAG, "Disable Notification");
getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, false); getBluetoothGatt().setCharacteristicNotification(gattCharacteristic, false);
BluetoothGattDescriptor descriptor = gattCharacteristic BluetoothGattDescriptor descriptor = gattCharacteristic
.getDescriptor(BtConstants.INSTANCE.getUUID_DESCRIPTOR()); .getDescriptor(BtConstants.INSTANCE.getUUID_DESC_NOTITY());
descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
if (!getBluetoothGatt().writeDescriptor(descriptor)) { if (!getBluetoothGatt().writeDescriptor(descriptor)) {

@ -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<ScanResult>) {
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()
}
}

@ -30,11 +30,11 @@ public class BTCClientService extends Service {
*/ */
private final BtMsgListener mMsgListener = new BtMsgListener() { private final BtMsgListener mMsgListener = new BtMsgListener() {
@Override @Override
public void socketNotify(int state, @NonNull String msg) { public void messageReceive(int state, @NonNull String msg) {
super.socketNotify(state, msg); super.messageReceive(state, msg);
// 将内容消息透传给外部调用者 // 将内容消息透传给外部调用者
if (msgListener != null) { if (msgListener != null) {
msgListener.socketNotify(state, msg); msgListener.messageReceive(state, msg);
} }
} }
}; };
@ -49,7 +49,7 @@ public class BTCClientService extends Service {
super.onCreate(); super.onCreate();
Log.i(TAG, "initialize BluetoothManager"); Log.i(TAG, "initialize BluetoothManager");
classicClient = new BluetoothClassicClient(this); classicClient = BluetoothClassicClient.getInstance(this.getApplication());
classicClient.checkBtDevice(BtConstants.BLUETOOTH_TYPE.CLASSIC); classicClient.checkBtDevice(BtConstants.BLUETOOTH_TYPE.CLASSIC);
} }
@ -78,6 +78,14 @@ public class BTCClientService extends Service {
classicClient.connect(mac); classicClient.connect(mac);
} }
/**
*
* @param mac mac
*/
public void disconnect(String mac) {
classicClient.close();
}
/** /**
* *
* *

@ -11,8 +11,6 @@ import androidx.annotation.Nullable;
import com.common.bluetooth.callback.BtMsgListener; 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() { private final BtMsgListener mMsgListener = new BtMsgListener() {
@Override @Override
public void socketNotify(int state, @NonNull String msg) { public void messageReceive(int state, @NonNull String msg) {
super.socketNotify(state, msg); super.messageReceive(state, msg);
// 当客户端断开连接时,需要重新开始监听客户端的连接 // 当客户端断开连接时,需要重新开始监听客户端的连接
if (state == BtMsgListener.DISCONNECTED) { if (state == BtMsgListener.DISCONNECTED) {
if (classicServer != null) { if (classicServer != null) {
@ -42,7 +40,7 @@ public class BTCReceiverService extends Service {
// 服务端只关心内容消息 // 服务端只关心内容消息
// 将内容消息透传给外部调用者 // 将内容消息透传给外部调用者
if (msgListener != null) { if (msgListener != null) {
msgListener.socketNotify(state, msg); msgListener.messageReceive(state, msg);
} }
} }
} }

@ -16,6 +16,7 @@ import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -24,7 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* socket * socket
* *
* @author wangym * @author wangym
* @since 2.21-12-2 * @since 2021-12-2
*/ */
public class BluetoothClassicBase extends BluetoothCommon { public class BluetoothClassicBase extends BluetoothCommon {
private static final String TAG = "BluetoothClassicBase"; 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_MSG = 0; //消息标记
private static final int FLAG_FILE = 1; //文件标记 private static final int FLAG_FILE = 1; //文件标记
protected BluetoothSocket mSocket; protected static BluetoothSocket mSocket;
private DataOutputStream mOut; private DataOutputStream mOut;
private BtMsgListener mListener; 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) { void loopRead(BluetoothSocket socket) {
mSocket = socket; mSocket = socket;
DataInputStream in = null;
try { try {
if (!mSocket.isConnected()) { if (!mSocket.isConnected()) {
mSocket.connect(); mSocket.connect();
@ -76,9 +78,21 @@ public class BluetoothClassicBase extends BluetoothCommon {
// 将链接成功的MAC地址保存下来 // 将链接成功的MAC地址保存下来
BtUtils.INSTANCE.saveConnectMac(getMContext(), mac); BtUtils.INSTANCE.saveConnectMac(getMContext(), mac);
mOut = new DataOutputStream(mSocket.getOutputStream()); mOut = new DataOutputStream(mSocket.getOutputStream());
DataInputStream in = new DataInputStream(mSocket.getInputStream()); in = new DataInputStream(mSocket.getInputStream());
isRead.set(true); isConnected.set(true);
while (isRead.get()) { //死循环读取 } 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()) { switch (in.readInt()) {
case FLAG_MSG: //读取短消息 case FLAG_MSG: //读取短消息
String msg = in.readUTF(); String msg = in.readUTF();
@ -108,6 +122,12 @@ public class BluetoothClassicBase extends BluetoothCommon {
} }
} catch (Throwable e) { } catch (Throwable e) {
close(); close();
} finally {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
} }
@ -181,13 +201,33 @@ public class BluetoothClassicBase extends BluetoothCommon {
mOut.flush(); mOut.flush();
notifyUI(BtMsgListener.MSG, "文件发送完成."); notifyUI(BtMsgListener.MSG, "文件发送完成.");
} catch (Throwable e) { } catch (Throwable e) {
close(); closeByConnect();
} }
isSending.set(false); 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 * Socket
*/ */
@ -195,12 +235,14 @@ public class BluetoothClassicBase extends BluetoothCommon {
Log.d(TAG, "close"); Log.d(TAG, "close");
try { try {
String mac = ""; String mac = "";
isRead.set(false);
if (mSocket != null) { if (mSocket != null) {
mSocket.close(); mSocket.close();
mac = mSocket.getRemoteDevice().getAddress(); mac = mSocket.getRemoteDevice().getAddress();
} }
// 如果是从连接状态断开,那么发送断开事件
// 如果是从未连接状态则认为是连接异常
notifyUI(BtMsgListener.DISCONNECTED, mac); notifyUI(BtMsgListener.DISCONNECTED, mac);
isConnected.set(false);
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -209,10 +251,10 @@ public class BluetoothClassicBase extends BluetoothCommon {
/** /**
* Socket * Socket
*/ */
public void closeSilence(){ public void closeSilence() {
Log.d(TAG, "closeSilence"); Log.d(TAG, "closeSilence");
try { try {
isRead.set(false); isConnected.set(false);
if (mSocket != null) { if (mSocket != null) {
mSocket.close(); mSocket.close();
} }
@ -253,7 +295,7 @@ public class BluetoothClassicBase extends BluetoothCommon {
*/ */
protected void notifyUI(final int state, final String msg) { protected void notifyUI(final int state, final String msg) {
if (mListener != null) { if (mListener != null) {
mListener.socketNotify(state, msg); mListener.messageReceive(state, msg);
} }
} }
} }

@ -1,5 +1,6 @@
package com.common.bluetooth.service.bt; package com.common.bluetooth.service.bt;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.content.Context; import android.content.Context;
@ -19,10 +20,24 @@ import java.util.concurrent.Executors;
public class BluetoothClassicClient extends BluetoothClassicBase { public class BluetoothClassicClient extends BluetoothClassicBase {
private static final String TAG = "BtClient"; private static final String TAG = "BtClient";
public BluetoothClassicClient(Context context) { @SuppressLint("StaticFieldLeak")
private static BluetoothClassicClient mInstance;
private BluetoothClassicClient(Context context) {
super(context); super(context);
} }
public static BluetoothClassicClient getInstance(Context context) {
if (mInstance == null) {
synchronized (BluetoothClassicClient.class) {
if (mInstance == null) {
mInstance = new BluetoothClassicClient(context);
}
}
}
return mInstance;
}
/** /**
* *
* *

@ -5,7 +5,7 @@ import com.common.bluetooth.BtConstants
import com.common.bluetooth.callback.BaseResultCallback import com.common.bluetooth.callback.BaseResultCallback
import com.common.bluetooth.interfaces.IBluetoothClient import com.common.bluetooth.interfaces.IBluetoothClient
import io.reactivex.rxjava3.core.Observable 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<BluetoothDevice> { override fun search(millis: Long, cancel: Boolean): Observable<BluetoothDevice> {
return Observable.create { return Observable.create {
mClient.getBluetoothSearcher().startScan(millis, null) mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).startScan(millis, null)
it.onComplete() it.onComplete()
} }
} }
override fun stopSearch() { override fun stopSearch() {
mClient.getBluetoothSearcher().stopScan() mClient.getBluetoothSearcher(BtConstants.BLUETOOTH_TYPE.CLASSIC).stopScan()
} }
override fun connect(mac: String): Observable<String> { override fun connect(mac: String): Observable<String> {
@ -57,6 +57,10 @@ class BluetoothClientClassicAdapter(private var mClient: BluetoothClassicClient)
} }
} }
override fun read(mac: String, service: UUID, characteristic: UUID): Observable<String>? {
return null
}
override fun checkBluetoothDevice(type: BtConstants.BLUETOOTH_TYPE): Boolean { override fun checkBluetoothDevice(type: BtConstants.BLUETOOTH_TYPE): Boolean {
return mClient.checkBtDevice(type) return mClient.checkBtDevice(type)
} }

@ -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
}
}

@ -5,7 +5,11 @@ import android.content.SharedPreferences
import android.text.TextUtils import android.text.TextUtils
import android.util.Log.d import android.util.Log.d
import android.util.Log.e import android.util.Log.e
import com.common.bluetooth.DeviceJson
import com.common.bluetooth.bean.* 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" const val SP_NAME = "config"
/**
* sp文件名称
*/
const val SP_DEVICE_NAME = "config_device"
/** /**
* MAC地址的KEY * MAC地址的KEY
*/ */
const val MAC_KEY = "mac" 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) { 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 MOD_S = "7"
const val UI_HIND_COVER = "1"
/** /**
* 中间包 * 中间包
*/ */
@ -347,6 +368,115 @@ object BtUtils {
return sharedPreferences.getString(MAC_KEY, "") 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<String> = ArrayList()
var deviceNameList: ArrayList<String> = ArrayList()
var deviceMacList1: ArrayList<String> = ArrayList()
var deviceNameList1: ArrayList<String> = 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<List<String?>?>() {}.type
deviceMacList1 = gson.fromJson<ArrayList<String>>(
getConnectMacAndName(context, MAC_KEY),
listType
)
val listType2 = object : TypeToken<List<String?>?>() {}.type
deviceNameList1 = gson.fromJson<ArrayList<String>>(
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<DeviceJson>) {
val sharedPreferences: SharedPreferences =
context.getSharedPreferences(SP_DEVICE_NAME, Context.MODE_PRIVATE)
val edit = sharedPreferences.edit()
var deviceMacList: ArrayList<String> = ArrayList()
var deviceNameList: ArrayList<String> = 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[] * 合并byte[]
*/ */

@ -33,13 +33,13 @@ public class BtDeviceListAdapter extends RecyclerView.Adapter<BtDeviceListAdapte
@NonNull @NonNull
@Override @Override
public BtDeviceListAdapter.BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public 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 BtDeviceListAdapter.BtViewHolder holder, int position) { public void onBindViewHolder(@NonNull 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,38 @@
package com.common.bluetooth.view;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.view.WindowManager;
public class FullScreenDialog extends ProgressDialog {
public FullScreenDialog(Context context) {
super(context);
}
public FullScreenDialog(Context context, int theme) {
super(context, theme);
}
private void fullScreenImmersive(View view) {
if (Build.VERSION.SDK_INT >= 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);
}
}

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Progress.Dialog" parent="@android:style/Theme.Holo.Light.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
</resources>
Loading…
Cancel
Save