最近項目有個需求,手機設備連接多個藍牙4.0 設備 並獲取這些設備的數據。 查詢了很多資料終於實現,現進行總結。 從零開始實現一個連接多個藍牙4.0 設備並獲取數據的 Demo 註:如果不想看實現過程的,直接看最下麵的demo源碼即可,或每一步後相關操作步驟的完整代碼。 一、Demo需求 1、搜索設 ...
最近項目有個需求,手機設備連接多個藍牙4.0 設備 並獲取這些設備的數據。
查詢了很多資料終於實現,現進行總結。
---------------------------------------------------------------------------------------------------------------------------------------------------------------
從零開始實現一個連接多個藍牙4.0 設備並獲取數據的 Demo
註:如果不想看實現過程的,直接看最下麵的demo源碼即可,或每一步後相關操作步驟的完整代碼。
一、Demo需求
1、搜索設備 , 選擇多個要連接的設備。
2、開始連接,顯示數據。
二、項目知識儲備
項目中需要用到的三方:
1、RecyclerView
列表,用於顯示掃描得到的所有藍牙設備
2、BaseRecyclerViewAdapterHelper
Recyclerview 幫助框架,快速實現列表操作
3、eventbus
用於消息傳遞,獲取到藍牙傳送的數據之後,刷新界面顯示數據時使用
藍牙4.0框架
許可權管理,適配6.0+設備
添加依賴 gradle.bulld文件
compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.blakequ.androidblemanager:bluetooth-manager-lib:2.1.5' compile 'com.github.hotchemi:permissionsdispatcher:2.1.3' compile 'de.greenrobot:eventbus:2.4.0' compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.18' compile 'com.android.support:design:25.3.1'
三、項目實現,佈局文件
1、demo中一共用到兩個activity 對應兩個佈局文件
先看掃描設備界面
包含:
1、一個列表,顯示 所有掃描到的設備的MAC地址,點擊狀態在 ''已選擇' or '‘未選擇’ 之間改變,表明當前設備有沒有加入到需要連接的設備集合中
2、掃描按鈕
3、結束掃描按鈕
4、完成選擇按鈕,將選擇的設備MAC地址傳回
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_select_device" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.maiji.magkareble40.SelectDeviceActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" > </android.support.v7.widget.RecyclerView> <Button android:id="@+id/btnScan" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="開始掃描" /> <Button android:id="@+id/btnStopScan" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="停止掃描" /> <Button android:id="@+id/btnOk" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="完成選擇設備" /> </LinearLayout>
連接界面。
包含:
1、選擇需要連接的感測器設備 按鈕
2、開始連接 按鈕
3、數據展示
佈局文件代碼:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context="com.maiji.magkareble40.XBleActivity"> <Button android:id="@+id/btnSelectDevice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="選擇需要連接的感測器設備" /> <Button android:id="@+id/btnStartConnect" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="開始連接" /> <TextView android:id="@+id/txtContentMac" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="10dp" android:text=""/> </LinearLayout>
四、Activity實現
1、掃描 設備 選擇設備Activity
(1)、變數聲明
private Button btnScan; //開始掃描按鈕 private Button btnStopScan; //停止掃描按鈕 private Button btnOk; //選擇好了需要連接的mac設備 BluetoothScanManager scanManager ; // 設備掃描管理器 /* 列表相關 */ private RecyclerView recyclerView ; //列表 private ScanDeviceAdapter adapter; //設備掃描適配器 private ArrayList<String> deviceMacs ; // 數據源 : 所有掃描到的設備mac地址 private ArrayList<String> selectDeviceMacs; // 選擇的需要連接的設備的mac集合
關鍵代碼:
(1)、藍牙掃描的初始化設置
/** * 初始化藍牙相關配置 */ private void initBle() { scanManager = BleManager.getScanManager(this); scanManager.setScanOverListener(new ScanOverListener() { @Override public void onScanOver() { } }); scanManager.setScanCallbackCompat(new ScanCallbackCompat() { @Override public void onBatchScanResults(List<ScanResultCompat> results) { super.onBatchScanResults(results); } @Override public void onScanFailed(final int errorCode) { super.onScanFailed(errorCode); } @Override public void onScanResult(int callbackType, ScanResultCompat result) { super.onScanResult(callbackType, result); //scan result // 只有當前列表中沒有該mac地址的時候 添加 if (!deviceMacs.contains(result.getDevice().getAddress())) { deviceMacs.add(result.getDevice().getAddress()); adapter.notifyDataSetChanged(); } } }); }藍牙掃描設置初始化
(2)、開始掃描按鈕 操作
// scanManager.startCycleScan(); //不會立即開始,可能會延時 scanManager.startScanNow(); //立即開始掃描
(3)、停止掃描按鈕 操作
// 如果正在掃描中 停止掃描 if (scanManager.isScanning()) { scanManager.stopCycleScan(); }
(4)、RecyclerView初始化 ,點擊事件操作
recyclerView = (RecyclerView) findViewById(R.id.recyclerView); // 列表相關初始化 recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new ScanDeviceAdapter(deviceMacs); adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { if (!selectDeviceMacs.contains(deviceMacs.get(position))){ //如果改item的mac不在已選中的mac集合中 說明沒有選中,添加進已選中mac集合中,狀態改為"已選擇" selectDeviceMacs.add(deviceMacs.get(position)); ((TextView)view.findViewById(R.id.txtState)).setText("已選擇"); }else { selectDeviceMacs.remove(deviceMacs.get(position)); ((TextView)view.findViewById(R.id.txtState)).setText("未選擇"); } } }); recyclerView.setAdapter(adapter);
activity全部代碼:
package com.maiji.magkareble40; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; import com.blakequ.bluetooth_manager_lib.BleManager; import com.blakequ.bluetooth_manager_lib.BleParamsOptions; import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; import com.blakequ.bluetooth_manager_lib.scan.BluetoothScanManager; import com.blakequ.bluetooth_manager_lib.scan.ScanOverListener; import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanCallbackCompat; import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanFilterCompat; import com.blakequ.bluetooth_manager_lib.scan.bluetoothcompat.ScanResultCompat; import com.chad.library.adapter.base.BaseQuickAdapter; import java.util.ArrayList; import java.util.List; /** * @author xqx * @email [email protected] * blog:http://www.cnblogs.com/xqxacm/ * createAt 2017/9/6 * description: 掃描藍牙設備 選擇需要連接的感測器 */ public class SelectDeviceActivity extends Activity implements View.OnClickListener { private Button btnScan; //開始掃描按鈕 private Button btnStopScan; //停止掃描按鈕 private Button btnOk; //選擇好了需要連接的mac設備 BluetoothScanManager scanManager ; /* 列表相關 */ private RecyclerView recyclerView ; //列表 private ScanDeviceAdapter adapter; private ArrayList<String> deviceMacs ; // 數據源 : 所有掃描到的設備mac地址 private ArrayList<String> selectDeviceMacs; // 選擇的需要連接的設備的mac集合 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_select_device); deviceMacs = new ArrayList<>(); selectDeviceMacs = new ArrayList<>(); initView(); initEvent(); initBle(); } /** * 初始化藍牙相關配置 */ private void initBle() { scanManager = BleManager.getScanManager(this); scanManager.setScanOverListener(new ScanOverListener() { @Override public void onScanOver() { } }); scanManager.setScanCallbackCompat(new ScanCallbackCompat() { @Override public void onBatchScanResults(List<ScanResultCompat> results) { super.onBatchScanResults(results); } @Override public void onScanFailed(final int errorCode) { super.onScanFailed(errorCode); } @Override public void onScanResult(int callbackType, ScanResultCompat result) { super.onScanResult(callbackType, result); //scan result // 只有當前列表中沒有該mac地址的時候 添加 if (!deviceMacs.contains(result.getDevice().getAddress())) { deviceMacs.add(result.getDevice().getAddress()); adapter.notifyDataSetChanged(); } } }); } private void initEvent() { btnScan.setOnClickListener(this); btnStopScan.setOnClickListener(this); btnOk.setOnClickListener(this); } private void initView() { btnScan = (Button) findViewById(R.id.btnScan); btnStopScan = (Button) findViewById(R.id.btnStopScan); btnOk = (Button) findViewById(R.id.btnOk); recyclerView = (RecyclerView) findViewById(R.id.recyclerView); // 列表相關初始化 recyclerView.setLayoutManager(new LinearLayoutManager(this)); adapter = new ScanDeviceAdapter(deviceMacs); adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() { @Override public void onItemClick(BaseQuickAdapter adapter, View view, int position) { if (!selectDeviceMacs.contains(deviceMacs.get(position))){ //如果改item的mac不在已選中的mac集合中 說明沒有選中,添加進已選中mac集合中,狀態改為"已選擇" selectDeviceMacs.add(deviceMacs.get(position)); ((TextView)view.findViewById(R.id.txtState)).setText("已選擇"); }else { selectDeviceMacs.remove(deviceMacs.get(position)); ((TextView)view.findViewById(R.id.txtState)).setText("未選擇"); } } }); recyclerView.setAdapter(adapter); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.btnScan: //開始 掃描 // scanManager.startCycleScan(); //不會立即開始,可能會延時 scanManager.startScanNow(); //立即開始掃描 break; case R.id.btnStopScan: // 如果正在掃描中 停止掃描 if (scanManager.isScanning()) { scanManager.stopCycleScan(); } break; case R.id.btnOk: Intent intent = new Intent(); intent.putExtra("data",selectDeviceMacs); // 設置結果,併進行傳送 this.setResult(1, intent); this.finish(); break; } } @Override protected void onDestroy() { super.onDestroy(); // 如果正在掃描中 停止掃描 if (scanManager.isScanning()) { scanManager.stopCycleScan(); } } }SelectDeviceActivity.class
適配器相關代碼:
package com.maiji.magkareble40; import android.widget.ImageView; import com.chad.library.adapter.base.BaseQuickAdapter; import com.chad.library.adapter.base.BaseViewHolder; import java.util.ArrayList; /** * @author xqx * @email [email protected] * blog:http://www.cnblogs.com/xqxacm/ * createAt 2017/9/6 * description: 掃描得到的藍牙設備列表適配器 */ public class ScanDeviceAdapter extends BaseQuickAdapter<String , BaseViewHolder> { public ScanDeviceAdapter(ArrayList<String> datas) { super(R.layout.item_device, datas); } @Override protected void convert(BaseViewHolder helper, String item) { helper.setText(R.id.txtMac,item); } }ScanDeviceAdapter.class
適配器佈局代碼:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="16dp" android:paddingRight="16dp" android:layout_marginTop="16dp" android:layout_marginBottom="16dp" > <TextView android:id="@+id/txtMac" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="" android:layout_centerVertical="true" /> <TextView android:id="@+id/txtState" android:text="未選擇" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:background="#fff" android:layout_alignParentBottom="true" ></View> </RelativeLayout>item_device.xml
2、連接多設備,獲取數據並展示Activity
(1)、變數聲明
private Button btnSelectDevice ; //選擇需要綁定的設備 private Button btnStartConnect ; //開始連接按鈕 private TextView txtContentMac ; //獲取到的數據解析結果顯示 private final int REQUEST_CODE_PERMISSION = 1; // 許可權請求碼 用於回調 MultiConnectManager multiConnectManager ; //多設備連接 private BluetoothAdapter bluetoothAdapter; //藍牙適配器 private ArrayList<String> connectDeviceMacList ; //需要連接的mac設備集合 ArrayList<BluetoothGatt> gattArrayList; //設備gatt集合
2、關鍵代碼
1、許可權適配
註意:不止藍牙許可權,位置許可權也需要打開
/** * @author xqx * @email [email protected] * blog:http://www.cnblogs.com/xqxacm/ * createAt 2017/8/30 * description: 許可權申請相關,適配6.0+機型 ,藍牙,文件,位置 許可權 */ private String[] allPermissionList = {Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }; /** * 遍歷出需要獲取的許可權 */ private void requestWritePermission() { ArrayList<String> permissionList = new ArrayList<>(); // 將需要獲取的許可權加入到集合中 ,根據集合數量判斷 需不需要添加 for (int i = 0; i < allPermissionList.length; i++) { if (PackageManager.PERMISSION_DENIED == ContextCompat.checkSelfPermission(this, allPermissionList[i])){ permissionList.add(Manifest.permission.WRITE_EXTERNAL_STORAGE); } } String permissionArray[] = new String[permissionList.size()]; for (int i = 0; i < permissionList.size(); i++) { permissionArray[i] = permissionList.get(i); } if (permissionList.size() > 0) ActivityCompat.requestPermissions(this, permissionArray, REQUEST_CODE_PERMISSION); } /** * 許可權申請的回調 * @param requestCode * @param permissions * @param grantResults */ @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == REQUEST_CODE_PERMISSION){ if (permissions[0].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE) &&grantResults[0] == PackageManager.PERMISSION_GRANTED){ //用戶同意使用write }else{ //用戶不同意,自行處理即可 Toast.makeText(XBleActivity.this,"您取消了許可權申請,可能會影響軟體的使用,如有問題請退出重試",Toast.LENGTH_SHORT).show(); } } }許可權適配
2、藍牙開啟、連接等 初始化設置
/** * 對藍牙的初始化操作 */ private void initConfig() { multiConnectManager = BleManager.getMultiConnectManager(this); // 獲取藍牙適配器 try { // 獲取藍牙適配器 bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); // if (bluetoothAdapter == null) { Toast.makeText(this, "藍牙不可用", Toast.LENGTH_LONG).show(); return; } // 藍牙沒打開的時候打開藍牙 if (!bluetoothAdapter.isEnabled()) bluetoothAdapter.enable(); }catch (Exception err){}; BleManager.setBleParamsOptions(new BleParamsOptions.Builder() .setBackgroundBetweenScanPeriod(5 * 60 * 1000) .setBackgroundScanPeriod(10000) .setForegroundBetweenScanPeriod(2000) .setForegroundScanPeriod(10000) .setDebugMode(BuildConfig.DEBUG) .setMaxConnectDeviceNum(7) //最大可以連接的藍牙設備個數 .setReconnectBaseSpaceTime(1000) .setReconnectMaxTimes(Integer.MAX_VALUE) .setReconnectStrategy(ConnectConfig.RECONNECT_LINE_EXPONENT) .setReconnectedLineToExponentTimes(5) .setConnectTimeOutTimes(20000) .build()); }initBle
3、開始連接操作
/** * 連接需要連接的感測器 * @param */ private void connentBluetooth(){ String[] objects = connectDeviceMacList.toArray(new String[connectDeviceMacList.size()]); multiConnectManager.addDeviceToQueue(objects); multiConnectManager.addConnectStateListener(new ConnectStateListener() { @Override public void onConnectStateChanged(String address, ConnectState state) { switch (state){ case CONNECTING: Log.i("connectStateX","設備:"+address+"連接狀態:"+"正在連接"); break; case CONNECTED: Log.i("connectStateX","設備:"+address+"連接狀態:"+"成功"); break; case NORMAL: Log.i("connectStateX","設備:"+address+"連接狀態:"+"失敗"); break; } } }); /** * 數據回調 */ multiConnectManager.setBluetoothGattCallback(new BluetoothGattCallback() { @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { super.onCharacteristicChanged(gatt, characteristic); dealCallDatas(gatt , characteristic); } }); multiConnectManager.setServiceUUID("0000ffe5-0000-1000-8000-00805f9a34fb"); multiConnectManager.addBluetoothSubscribeData( new BluetoothSubScribeData.Builder().setCharacteristicNotify(UUID.fromString("0000ffe4-0000-1000-8000-00805f9a34fb")).build()); //還有讀寫descriptor //start descriptor(註意,在使用時當回調onServicesDiscovered成功時會自動調用該方法,所以只需要在連接之前完成1,3步即可) for (int i = 0; i < gattArrayList.size(); i++) { multiConnectManager.startSubscribe(gattArrayList.get(i)); } multiConnectManager.startConnect(); } /** * 處理回調的數據 * @param gatt * @param characteristic */ float[][] floats = new float[7][30]; private void dealCallDatas(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { int position = connectDeviceMacList.indexOf(gatt.getDevice().getAddress()); //第一個感測器數據 byte[] value = characteristic.getValue(); if (value[0] != 0x55) { //開頭不是0x55的數據刪除 return; } switch (value[1]) { case 0x61: //加速度數據 floats[position][3] = ((((short) value[3]) << 8) | ((short) value[2] & 0xff)) / 32768.0f * 16; //x軸 floats[position][4] = ((((short) value[5]) << 8) | ((short) value[4] & 0xff)) / 32768.0f * 16; //y軸 floats[position][5] = ((((short) value[7]) << 8) | ((short) value[6] & 0xff)) / 32768.0f * 16; //z軸 //角速度數據 floats[position][6] = ((((short) value[9]) << 8) | ((short) value[8] & 0xff)) / 32768.0f * 2000; //x軸 floats[position][7] = ((((short) value[11]) << 8) | ((short) value[10] & 0xff)) / 32768.0f * 2000; //x軸 floats[position][8] = ((((short) value[13]) << 8) | ((short) value[12] & 0xff)) / 32768.0f * 2000; //x軸 break; case 0x62: //四元素 floats[position][13] = ((((short) value[3]) << 8) | ((short) value[2] & 0xff)) / 32768.0f; // q1 floats[position][14] = ((((short) value[5]) << 8) | ((short) value[4] & 0xff)) / 32768.0f; // q2 floats[position][15] = ((((short) value[7]) << 8) | ((short) value[6] & 0xff)) / 32768.0f; // q3 floats[position][16] = ((((short) value[9]) << 8) | ((short) value[8] & 0xff)) / 32768.0f; // q4 //電池電壓 floats[position][21] = (float) 1.2 * 4 * (((value[11] << 8) | value[10]) +