[desc]:更新蓝牙模块

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

@ -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",

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

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

@ -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,23 +86,22 @@ 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) {
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

@ -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")
if (mBtClient != null) {
mBtClient!!.search(3000, true)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<BluetoothDevice> {
override fun onSubscribe(d: Disposable) {}
override fun onSubscribe(d: Disposable) {
d(TAG, "ble onSubscribe")
}
override fun onNext(bleDevice: BluetoothDevice) {
d(TAG, "ble onScanResult")
bleScanListener?.onResult(bleDevice)
}
override fun onError(e: Throwable) {
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地址
*/
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<BluetoothDevice>? {
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 {
/**
* 结果
*
* @param result 回调
*/
fun onResult(result: CommonMsg)
/**
* 接收服务端通知
* 接收外围设备的消息
*
* @param msg 消息内容
*/
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
/**
* 经典蓝牙监听
* 蓝牙消息监听
*
* @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
}
}

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

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

@ -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<String>() {
@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) {
this.clientListener = clientListener;
}

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

@ -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 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<byte[]> 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<BluetoothDevice>() {
@Override
public void subscribe(@NonNull final ObservableEmitter<BluetoothDevice> 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<BluetoothDevice> 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<String> connect(final String mac) {
public Observable<String> connect(@NonNull final String mac) {
return Observable.create(new ObservableOnSubscribe<String>() {
@Override
public void subscribe(@NonNull final ObservableEmitter<String> 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<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) {
this.notifyCallback = notifyCallback;
}

@ -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)) {

@ -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() {
@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();
}
/**
*
*

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

@ -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();
}
@ -212,7 +254,7 @@ public class BluetoothClassicBase extends BluetoothCommon {
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);
}
}
}

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

@ -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<BluetoothDevice> {
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<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 {
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.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<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[]
*/

@ -33,13 +33,13 @@ public class BtDeviceListAdapter extends RecyclerView.Adapter<BtDeviceListAdapte
@NonNull
@Override
public BtDeviceListAdapter.BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
public BtViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
mBinding = RvBtDeviceItemBinding.inflate(LayoutInflater.from(context));
return new BtViewHolder(mBinding);
}
@Override
public void onBindViewHolder(@NonNull BtDeviceListAdapter.BtViewHolder holder, int position) {
public void onBindViewHolder(@NonNull BtViewHolder holder, int position) {
final int currentIndex = position;
BluetoothDevice device = bluetoothDevices.get(position);
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