package com.blakequ.bluetooth_manager_lib.connect; import android.annotation.TargetApi; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.os.Looper; import android.os.SystemClock; import com.blakequ.bluetooth_manager_lib.BleManager; import com.blakequ.bluetooth_manager_lib.BleParamsOptions; import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; import com.blankj.utilcode.util.LogUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * Copyright (C) BlakeQu All Rights Reserved *

* Licensed under the blakequ.com License, Version 1.0 (the "License"); * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *

* author : quhao
* date : 2016/8/18 11:29
* last modify author :
* version : 1.0
* description: 用于当前蓝牙的连接管理,负责连接的建立断开,使用时需要设置回调{@link #setBluetoothGattCallback(BluetoothGattCallback)}, * 连接{@link #connect(String)},断开连接{@link #disconnect(String)}, 关闭连接{@link #close(String)}. * 注意:
* 1.在进行蓝牙断开和连接调用的时候,需要在主线程执行,否则在三星手机会出现许多异常错误或无法连接的情况
* 2.该连接管理只能连接一个设备,不支持同时连接多个设备
* 3.可以自定义断开后重连次数和重连的间隔时间
* 4.如果要订阅服务数据(read,write,notify)Service UUID {@link #setServiceUUID(String)}, 否则不能自动的进行通知和char和desc的读写操作(还需要{@link #addBluetoothSubscribeData(BluetoothSubScribeData)})
* 5.单独订阅数据,需要调用{@link #cleanSubscribeData()}清除订阅历史列表, {@link #addBluetoothSubscribeData(BluetoothSubScribeData)}添加参数, {@link #startSubscribe(BluetoothGatt)}启动订阅,会自动回调{@link #setBluetoothGattCallback(BluetoothGattCallback)}订阅结果 */ @TargetApi(18) public final class BluetoothConnectManager extends BluetoothConnectInterface{ private static final String TAG = "BluetoothConnectManager"; private static BluetoothConnectManager INSTANCE = null; private final BluetoothUtils mBluetoothUtils; private BluetoothGattCallback mBluetoothGattCallback; private BluetoothManager bluetoothManager; private final Map gattMap; //保存连接过的gatt private final Queue subscribeQueue; private static String serviceUUID; private ReconnectParamsBean reconnectParamsBean; private List connectStateListeners; private ConnectState currentState = ConnectState.NORMAL; private static Object obj = new Object(); public BluetoothConnectManager(Context context) { super(context); subscribeQueue = new ConcurrentLinkedQueue(); mBluetoothUtils = BluetoothUtils.getInstance(context); bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); gattMap = new ConcurrentHashMap(); //会有并发的断开和连接,故而必须使用并发ConcurrentHashMap才行,否则会有ConcurrentModificationException connectStateListeners = new ArrayList<>(); BleManager.getBleParamsOptions(); } @Override public void release() { closeAll(); gattMap.clear(); reconnectParamsBean = null; } public static BluetoothConnectManager getInstance(Context context){ if (INSTANCE == null){ synchronized (obj){ if (INSTANCE == null){ INSTANCE = new BluetoothConnectManager(context); } } } return INSTANCE; } /** * add callback of gatt connect, notice:
* 1. can not do any task which need a lot of time
* 2. you should update UI in the main thread, in callback method use {@link #runOnUiThread(Runnable)} * @param callback * @see #runOnUiThread(Runnable) */ public void setBluetoothGattCallback(BluetoothGattCallback callback){ this.mBluetoothGattCallback = callback; } /** * add listener of connect state * @param listener * @see #removeConnectStateListener(ConnectStateListener) */ public void addConnectStateListener(ConnectStateListener listener){ synchronized(connectStateListeners){ connectStateListeners.add(listener); } } /** * remove listener * @param listener * @see #addBluetoothSubscribeData(BluetoothSubScribeData) */ public void removeConnectStateListener(ConnectStateListener listener){ synchronized(connectStateListeners){ connectStateListeners.remove(listener); } } /** * add subscribe data while auto read or write characteristic(or descriptor) after discover service, you can clean subscribe list by {@link #cleanSubscribeData()} * @param data * @see #cleanSubscribeData() * @see #startSubscribe(BluetoothGatt) * @see #setServiceUUID(String) */ public void addBluetoothSubscribeData(BluetoothSubScribeData data){ subscribeQueue.add(data); } /** * clean subscribe list * @see #addBluetoothSubscribeData(BluetoothSubScribeData) */ public void cleanSubscribeData(){ subscribeQueue.clear(); } /** * set bluetooth service uuid, can not be null * @see #addBluetoothSubscribeData(BluetoothSubScribeData) * @param serviceUUID */ public void setServiceUUID(String serviceUUID){ this.serviceUUID = serviceUUID; } @Override public BluetoothGatt getBluetoothGatt(String address){ if (!isEmpty(address) && gattMap.containsKey(address)){ return gattMap.get(address); } return null; } @Override protected BluetoothGattCallback getBluetoothGattCallback() { return mBluetoothGattCallback; } @Override protected String getServiceUUID() { return serviceUUID; } @Override protected void onDeviceDisconnect(final BluetoothGatt gatt, int errorState) { //is bluetooth enable //可以不关闭,以便重用,因为在连接connect的时候可以快速连接 if (!checkIsSamsung() || !mBluetoothUtils.isBluetoothIsEnable()){//三星手机断开后直接连接 LogUtils.e( "Disconnected from GATT server address:"+gatt.getDevice().getAddress()); close(gatt.getDevice().getAddress()); //防止出现status 133 }else { updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.NORMAL); } //if disconnect by hand, so not run reconnect device runOnUiThread(new Runnable() { @Override public void run() { reconnectDevice(gatt.getDevice().getAddress()); //如果设备断开则指定时间后尝试重新连接,重新连接 } }); } @Override protected void onDeviceConnected(BluetoothGatt gatt) { updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.CONNECTED); reconnectParamsBean = null; } @Override protected void onDiscoverServicesFail(final BluetoothGatt gatt) { if (!checkIsSamsung() || !mBluetoothUtils.isBluetoothIsEnable()){//三星手机断开后直接连接 LogUtils.e( "Disconnected from GATT server address:"+gatt.getDevice().getAddress()); close(gatt.getDevice().getAddress()); //防止出现status 133 }else { updateConnectStateListener(gatt.getDevice().getAddress(), ConnectState.NORMAL); } runOnUiThread(new Runnable() { @Override public void run() { reconnectDevice(gatt.getDevice().getAddress()); } }); } public void updateReconnectParams(){ reconnectParamsBean = null; } @Override protected void onDiscoverServicesSuccess(BluetoothGatt gatt) { } @Override protected Queue getSubscribeDataQueue() { return subscribeQueue; } /** * has device is connected or connecting * @return */ public boolean isConnectDevice(){ if (gattMap.size() == 0) return false; return true; } /** * 获取已经连接的设备,注意:返回的设备不一定全部是当前APP所连接的设备,需要通过UUID或设备名字等区分 * @return */ public List getConnectedDevice(){ if (isConnectDevice()){ return bluetoothManager.getConnectedDevices(BluetoothProfile.GATT); } return Collections.EMPTY_LIST; } public ConnectState getCurrentState(){ return currentState; } /** * Connects to the GATT server hosted on the Bluetooth LE device. 为保证只有一个连接,当连接创建或初始化成功则会强制关闭其他连接 * * @param address The device address of the destination device. * @return Return true if the connection is initiated successfully. The connection result * is reported asynchronously through the * {@code BluetoothGattCallback#onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int)} * callback. */ public boolean connect(final String address) { BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); if (mAdapter == null || address == null) { LogUtils.w( "BluetoothAdapter not initialized or unspecified address."); updateConnectStateListener(address, ConnectState.NORMAL); return false; } if (!mBluetoothUtils.isBluetoothIsEnable()){ LogUtils.e( "bluetooth is not enable."); updateConnectStateListener(address, ConnectState.NORMAL); return false; } // Previously connected device. Try to reconnect. if (gattMap.containsKey(address)){ BluetoothGatt mBluetoothGatt = gattMap.get(address); LogUtils.i( "Trying to use an existing gatt and reconnection device " + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); if (mBluetoothGatt.connect()) { closeOtherDevice(address); updateConnectStateListener(address, ConnectState.CONNECTING); return true; } else { close(address); return false; } } BluetoothDevice device = mAdapter.getRemoteDevice(address); if (device != null){ /*if We want to directly connect to the device, we can setting the autoConnect parameter to false.*/ BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, gattCallback); if (mBluetoothGatt != null){ LogUtils.d( "create a new connection address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); gattMap.put(address, mBluetoothGatt); closeOtherDevice(address); updateConnectStateListener(address, ConnectState.CONNECTING); return true; }else{ LogUtils.e( "Get Gatt fail!, address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); } }else{ LogUtils.e( "Device not found, address=" + address); } updateConnectStateListener(address, ConnectState.NORMAL); return false; } /** * close bluetooth, release resource * @param address */ public boolean close(String address) { if (!isEmpty(address) && gattMap.containsKey(address)){ LogUtils.w("close gatt server " + address); BluetoothGatt mBluetoothGatt = gattMap.get(address); mBluetoothGatt.close(); gattMap.remove(address); updateConnectStateListener(address, ConnectState.NORMAL); return true; } return false; } /** * close all bluetooth connect, release all resource */ public void closeAll(){ for (String address:gattMap.keySet()) { close(address); } } /** * 断开蓝牙连接,不会释放BluetoothGatt持有的所有资源,可以调用mBluetoothGatt.connect()很快重新连接上 * 如果不及时释放资源,可能出现133错误,http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html * @param address */ public void disconnect(String address){ if (!isEmpty(address) && gattMap.containsKey(address)){ reconnectParamsBean = new ReconnectParamsBean(address); reconnectParamsBean.setNumber(1000); LogUtils.w("disconnect gatt server " + address); BluetoothGatt mBluetoothGatt = gattMap.get(address); mBluetoothGatt.disconnect(); updateConnectStateListener(address, ConnectState.NORMAL); } } /** * 重新连接断开的设备 * @param address */ private void reconnectDevice(final String address){ if (reconnectParamsBean != null){ if (!reconnectParamsBean.getAddress().equals(address)){ reconnectParamsBean.updateAddress(address); }else { if (reconnectParamsBean.getNumber() == 0){//same device reconnectParamsBean.updateAddress(address); }else if(reconnectParamsBean.getNumber() == 1000){//disconnect by hand LogUtils.i("reconnect fail! disconnect by hand"); reconnectParamsBean.setNumber(0); return; } } reconnectParamsBean.addNumber(); }else{ reconnectParamsBean = new ReconnectParamsBean(address); } //计算下一次重连的时间 long nextReconnectTime = reconnectParamsBean.getNextReconnectTime() - SystemClock.elapsedRealtime(); if (nextReconnectTime < 0){ nextReconnectTime = 0; } LogUtils.i("next reconnect time " + reconnectParamsBean.toString()+" after:"+nextReconnectTime/1000+"seconds"); getMainLooperHandler().postDelayed(new Runnable() { @Override public void run() { //重新连接要求没有已经连接的设备,没有正在连接的,蓝牙可用 if (mBluetoothUtils.isBluetoothIsEnable()) { boolean isReconncted = false; if (gattMap.containsKey(address)) { if (gattMap.size() == 1) isReconncted = true; } else if (gattMap.size() == 0) { isReconncted = true; } //如果已经连接上,也不重连 if (!isEmpty(getConnectedDevice())) { isReconncted = false; reconnectParamsBean = null; } if (isReconncted && getConnectedDevice().size() == 0) { LogUtils.d("reconnecting! will reconnect " + address); if (reconnectParamsBean != null){ //重连必须在主线程运行 runOnUiThread(new Runnable() { @Override public void run() { connect(address); } }); }else { LogUtils.w("Fail to reconnect, ReconnectParams is null"); } } else { LogUtils.w("Fail to reconnect, refuse! " + address + " flag:" + isReconncted); } }else{ LogUtils.w("Fail to reconnect, the bluetooth is disable!"); } } }, nextReconnectTime); } /** * 关闭除了当前地址的设备外的其他连接 * @param address */ private void closeOtherDevice(String address){ if (!isEmpty(address)){ //关闭正在已经连接的设备 List list = getConnectedDevice(); for (BluetoothDevice device:list) { if (!device.getAddress().equals(address)){ close(device.getAddress()); } } //关闭其他已经断开的设备 for (String ads:gattMap.keySet()) { BluetoothGatt mBluetoothGatt = gattMap.get(ads); if (!ads.equals(address)){ close(ads); } } } } private void updateConnectStateListener(String address, ConnectState state){ synchronized (connectStateListeners){ currentState = state; if (state == ConnectState.CONNECTING){ //start check time out connect BleParamsOptions options = BleManager.getBleParamsOptions(); getMainLooperHandler().postDelayed(timeOutTask, options.getConnectTimeOutTimes()); } for (ConnectStateListener listener:connectStateListeners){ if (listener != null) listener.onConnectStateChanged(address, state); } } } /** * connect time out task */ private Runnable timeOutTask = new Runnable() { @Override public void run() { if (!mBluetoothUtils.isBluetoothIsEnable()){ LogUtils.w("Fail to connect device! Bluetooth is not enable!"); closeAll(); } } }; }