Android蓝牙BLE开发
最近正在研究Android的藍牙BLE開發學習,以下是自己做的個人總結
1.1何為BLE?
首先得說明什么是低功耗藍牙BLE,BLE的全稱為Bluetooth low energy(或稱Blooth LE,BLE),從英文全稱便可以知曉其是一種低功耗的藍牙技術,是藍牙技術聯盟設計和銷售的一種個人局域網技術,旨在用于醫療保健、運動健身、信標、安防、家庭娛樂等領域的新興應用。相較經典藍牙,低功耗藍牙旨在保持同等通信范圍的同時顯著降低功耗和成本。而正因為其低功耗的優點,可以讓Android APP可以具有與低功耗要求的BLE設備通信,如近距離傳感器、心臟速率監視器、健身設備等
1.2基礎術語和概念
在正式開發前,要對基本的藍牙的術語和概念要有個大致的認識,因為我本人學習的也不長,就是個簡單的總結先:
Generic Attribute Profile:簡稱為GATT,現在的低功耗BLE的連接都是建立在GATT協議之上實現的,藍牙技術聯盟規定了許多低功耗設備的配置文件,配置文件是設備如何在特定的應用程序在工作的規格,而一個設備中可以有多個配置文件。
Generic Access Profile:Profile可以視為一種規范,一個標準的通信協議,它存在于從機中,藍牙技術聯盟規定了一些標準的profile,例如防丟器 ,心率計等。每個profile中會包含多個service,每個service代表從機的一種能力。
Service:服務,在BLE從機中,可以有多個服務,例如:電量信息服務,而Service中又有多個Characteristic特征值,而每個具體的特征值才是BLE通信的重點,例如在1電量信息服務中,當前的電量為80%,所以會通過電量的特征值存在從機的profile里,這樣主機就可以通過這個特征值來讀取80%這個數據
Characteristic:特征值,ble主從機通信都是通過特征值實現的,類似于標簽key,可以通過這個key值來獲取信息和數據
UUID:統一識別碼,服務和特征值都需要一個唯一的UUID來標識整理,而每個從機都會有一個叫做profile的東西存在,不管是上面的自定義的simpleprofile,還是標準的防丟器profile,他們都是由一些列service組成,然后每個service又包含了多個characteristic,主機和從機之間的通信,均是通過characteristic來實現。
1.3初始化配置
講完了大概的概念之后便是基本的操作了,以下內容會結合代碼和流程圖進行展示
1.3.1權限
想要使用BLE開發,就得先獲得藍牙必要的權限,需要先在AndroidManifest.xml中設置權限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>如果想聲明你的app只為具有BLE的設備提供,在manifest文件中包括:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>除此之外,如果是Android 6.0以上的手機僅僅是添加以上的藍牙權限是不足的,這樣會造成無法掃描到其他設備,因而還需要添加位置權限:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /><uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /><uses-feature android:name="android.hardware.location.gps" />1.3.2是否支持藍牙BLE
required=true只能是讓支持BLE的Android設備上安裝運行,不支持的則不行,如果想在Java實現上述功能,可以通過下述代碼:
// 手機硬件支持藍牙 if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();finish(); }1.3.3初始化藍牙適配器
所有的藍牙活動都需要藍牙適配器,BluetoothAdapter代表設備本身的藍牙適配器。整個系統只有一個藍牙適配器,而且app需要藍牙適配器與系統交互。下面的代碼片段顯示了如何得到適配器。
注意該方法使用getSystemService()返回BluetoothManager,然后將其用于獲取適配器的一個實例。Android 4.3(API 18)引入BluetoothManager
final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.LOLLIPOP)mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); elsemBluetoothAdapter = bluetoothManager.getAdapter(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();1.3.4開啟藍牙
要操作藍牙,必須先在設備中開啟藍牙。如果當前未啟用藍牙,則可以通過觸發一個Intent調用系統顯示一個對話框來要求用戶啟用藍牙權限
// 打開藍牙權限 if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); }1.3.5初始化ListView列表適配器
/*** @Description: TODO<自定義適配器Adapter,作為listview的適配器>*/ private class LeDeviceListAdapter extends BaseAdapter {private ArrayList<BluetoothMessage> mLeDevices;private LayoutInflater mInflator;public LeDeviceListAdapter(){super();//rssis = new ArrayList<Integer>();mLeDevices = new ArrayList<BluetoothMessage>();mInflator = getLayoutInflater();}public void addDevice(BluetoothMessage device){for (BluetoothMessage mLeDevice : mLeDevices) {if(mLeDevice.getDevice().getAddress().equals(device.getDevice().getAddress())){return; } } mLeDevices.add(device);//rssis.add(rssi);}public BluetoothMessage getDevice(int position){return mLeDevices.get(position);}public void clear(){mLeDevices.clear();//rssis.clear();}@Overridepublic int getCount(){return mLeDevices.size();}@Overridepublic Object getItem(int i){return mLeDevices.get(i);}@Overridepublic long getItemId(int i){return i;}/** * 重寫getview** **/@Overridepublic View getView(int i, View view, ViewGroup viewGroup){// General ListView optimization code.// 加載listview每一項的視圖BluetoothMessage bluetoothMessage = mLeDevices.get(i);return view;} }1.3.6發現BLE設備(掃描設備)
如果要發現BLE設備,使用startLeScan()方法進行掃描,掃描的話就要傳入true執行scanLeDvice(true)方法,然后藍牙適配器就調用startLeScan()方法進行掃描,LeScanCallback是掃描回調,也就是返回掃描結果。
1.3.6.1掃描結果:
private void scanLeDevice(final boolean enable) {if (mBluetoothAdapter == null){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();else {BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();}}if (mBluetoothLeScanner == null){if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner = mBluetoothAdapter.getBluetoothLeScanner();}if (enable) {// Stops scanning after a pre-defined scan period.mHandler.postDelayed(new Runnable(){@Overridepublic void run(){mScanning = false;scan_flag = true;scan_btn.setText("掃描設備");Log.i("SCAN", "stop.....................");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);}}, SCAN_PERIOD);/* 開始掃描藍牙設備,帶mLeScanCallback 回調函數 */Log.i("SCAN", "begin.....................");mScanning = true;scan_flag = false;scan_btn.setText("停止掃描");if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.startScan(mScanCallback);elsemBluetoothAdapter.startLeScan(mLeScanCallback);} else {Log.i("Stop", "stoping................");mScanning = false;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);scan_flag = true;} }1.3.6.2回調方法:
在這里回家上面的掃描方法的掃描結果回調,也就是傳回來。其中在onLeScan方法是重點,藍牙掃描成功后的結果會返回此方法中,然后就可以處理BluetoothDevice拿到設備信息,最后展示到前面初始化的ListView列表中:
- 第一個參數device,表示一個遠程藍牙設備,里面有它獨有的藍牙地址Address和Name等,所以后續需要進行連接藍牙操作也需要用到這里獲取的藍牙Address
- 第二個參數rssi表示掃描到的設備信號強度,這里應該可以用來判斷距離的遠近。
- 第三個參數scanRecord表示遠程設備提供的廣告記錄的內容。
至此已經完成初始化配置、一些設備的判斷邏輯和掃描操作了,如果能成功地掃描到設備并展示到界面上的話,下一步如果用戶點擊了列表,將進行藍牙連接和相關的讀寫操作!
1.4創建BluetoothLeService服務類并初始化藍牙連接
創建了一個BluetoothLeService服務類并繼承了Service,用來完成藍牙設備的初始化、連接、斷開連接、讀取特征值、寫入特征值、設置特征值變化通知以及獲取已連接藍牙的所有服務等操作
1.4.1創建服務
首先,我們得進行第一步,在onCreate()方法中,執行bindService開啟一個服務
這里因為是項目需要,使用了一個虛擬按鈕來進行初始化的連接
//藍牙service,負責后臺的藍牙服務 private static BluetoothLeService mBluetoothLeService; Intent gattServiceIntent; /*** --------------------------------------------onCreate方法-----------------------------------------------------*/ @Override public void onCreate(Bundle savedInstanceState) {.../* 啟動藍牙service */gattServiceIntent = new Intent(this, BluetoothLeService.class);//模擬按鍵點擊事件觸發藍牙連接rev_tv.post(new Runnable() {@Overridepublic void run() {scan_btn.performClick();}});//監聽scan_btnscan_btn.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View v){if (scan_flag){mleDeviceListAdapter = new LeDeviceListAdapter();//lv.setAdapter(mleDeviceListAdapter);scanLeDevice(true);} else {scanLeDevice(false);scan_btn.setText("掃描設備");}if (mScanning) {/* 停止掃描設備 */if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)mBluetoothLeScanner.stopScan(mScanCallback);elsemBluetoothAdapter.stopLeScan(mLeScanCallback);mScanning = false;}bindService(gattServiceIntent, mServiceConnection, BIND_AUTO_CREATE);}});... }開啟服務成功后,便會一樣進行服務回調,當服務回調已經成功連接時,便會獲取一個BlueToohtLeService的實例,接著就執行藍牙連接操作:
/* BluetoothLeService綁定的回調函數 */ // 獲取BluetoothLeService的實例,進行藍牙連接操作 // 以下都是Android官方的demo源碼 private final ServiceConnection mServiceConnection = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName componentName,IBinder service){mBluetoothLeService = ((BluetoothLeService.LocalBinder) service).getService();if (!mBluetoothLeService.initialize()){//Log.e(TAG, "Unable to initialize Bluetooth");finish();}mBluetoothLeService.connect(mDeviceAddress);}@Overridepublic void onServiceDisconnected(ComponentName componentName){mBluetoothLeService = null;} };這個時候,需要單獨創建一個BlueToothService類,因為BlueToohtLeService類既然是服務類,那它父類肯定是繼承于Service
public class BluetoothLeService extends Service {... }這里的BlueToothService類是BindService服務,用于綁定一個服務。這樣當bindService(intent,conn,flags)后,就會綁定一個服務。這樣做可以獲得這個服務對象本身,而用StartService(intent)的方法只能啟動服務。
BindService方法的一般過程:
1.4.1.1開啟BindService服務
// bindService區別于startService,用于綁定服務,可以獲得這個服務對象本身 public class LocalBinder extends Binder {public BluetoothLeService getService(){return BluetoothLeService.this;} } // onBind()是使用bindService開啟的服務才會有回調的一個方法 // onBind()方法給MainActivity返回了BluetoothLeService實例 // 用于方便MainActivity后續的連接和讀寫操作 @Override public IBinder onBind(Intent intent) {return mBinder; }1.4.1.2關閉BindService,關閉藍牙
當服務調用unbindService時,服務的生命周期將會進入onUnbind()方法,接著執行了關閉藍牙的方法
@Override public boolean onUnbind(Intent intent) {close();return super.onUnbind(intent); }private final IBinder mBinder = new LocalBinder();1.4.2初始化藍牙
這個方法是BlueToohtLeService服務類創建之后在MainActivity通過拿到BlueToohtLeService實例調用的,也是官方的源碼
/* service 中藍牙初始化 */ public boolean initialize() {// For API level 18 and above, get a reference to BluetoothAdapter// through// BluetoothManager.if (mBluetoothManager == null){ //獲取系統的藍牙管理器//使用 getSystemService(java.lang.String)與 BLUETOOTH_SERVICE創建一個 BluetoothManager// 然后調用 getAdapter()以獲得 BluetoothAdaptermBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);if (mBluetoothManager == null){Log.e(TAG, "Unable to initialize BluetoothManager.");return false;}}//BluetoothManager 的變量調用 getAdapter()以獲得 BluetoothAdapter 進而對整體藍牙進行管理mBluetoothAdapter = mBluetoothManager.getAdapter();if (mBluetoothAdapter == null){Log.e(TAG, "Unable to obtain a BluetoothAdapter.");return false;}return true; }1.4.3執行connect()和connectGatt
connect()和connectGatt都是連接BLE設備的方法,但二者用法不同
connectGatt是BluetoothDevice類下的方法,功能是向BLE設備發起連接,然后得到一個BluetoothGatt類型的返回值,利用這個返回值可以進行下一步操作。
connect是BluetoothGatt類下的方法,功能是重新連接。如果BLE設備和APP已經連接過,但是因為設備超出了藍牙的連接范圍而斷掉,那么當設備重新回到連接范圍內時,可以通過connect()重新連接
// 連接遠程藍牙public boolean connect(final String address){// 適配器為空或者地址為空就會提示if (mBluetoothAdapter == null || address == null){Log.w(TAG,"BluetoothAdapter not initialized or unspecified address.");return false;}// Previously connected device. Try to reconnect.if (mBluetoothDeviceAddress != null&& address.equals(mBluetoothDeviceAddress)&& mBluetoothGatt != null){Log.d(TAG,"Trying to use an existing mBluetoothGatt for connection.");//mBluetoothGatt.connect()表示連接回遠程設備//在連接斷開后,此方法的功能在于“重新連接到遠程設備”//如果設備曾經連接過,但目前不在范圍內//則一旦設備回到范圍內,則可以通過connect重新連接。if (mBluetoothGatt.connect())//連接藍牙,其實就是調用BluetoothGatt的連接方法{mConnectionState = STATE_CONNECTING;return true;} else{return false;}}/* 獲取遠端的藍牙設備 *///mBluetoothAdapter.getRemoteDevice//藍牙適配器通過調用getRemoteDevice()方法獲取給定的藍牙硬件地址的BluetoothDevice對象//有效的藍牙地址必須是6個字節,即使沒有找到設備,也得返回有效的6個字節,當然,估計就是6個0final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);if (device == null){Log.w(TAG, "Device not found. Unable to connect.");return false;}// We want to directly connect to the device, so we are setting the// autoConnect// parameter to false./* 調用device中的connectGatt連接到遠程設備 *///connectGatt是BluetoothDevice類下的方法//功能是向BLE設備發起連接,然后得到一個BluetoothGatt類型的返回值,利用這個返回值可以進行下一步操作//connectGatt方法往往是和BluetoothGatt類的connect方法一起使用//兩個方法的運行邏輯是://先使用connectGatt方法發起連接,連接狀態的改變會回調callback對象中的onConnectionStateChange// (需要自己定義一個BluetoothGattCallBack對象并重寫onConnectionStateChange)// 并返回一個BluetoothGatt對象,這時BluetoothGatt已經實例化,下一次連接可以調用connect重新連接。mBluetoothGatt = device.connectGatt(this, false, mGattCallback);Log.d(TAG, "Trying to create a new connection.");mBluetoothDeviceAddress = address;mConnectionState = STATE_CONNECTING;System.out.println("device.getBondState==" + device.getBondState());return true;}取消連接
/*** @Title: disconnect* @Description: TODO(取消藍牙連接)* @return void* @throws*/ public void disconnect() {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.disconnect();}1.4.4 BluetoothGattCallback 回調
這個回調十分重要,主要對BluetoothGatt的藍牙連接、斷開、讀、寫、特征值變化等的回調監聽,然后我們可以將這些回調信息通過廣播機制傳播回給廣播監聽器
/* 連接遠程設備的回調函數 */ // BluetoothGattCallback是一個抽象類,目的是用于實現 BluetoothGatt的回調 // 用于將結果傳遞給用戶,例如連接狀態等,以及任何進一步對GATT客戶端的操作 // 因為BluetoothGattCallback是一個抽象類,因此需要對里面的方法進行重寫 private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {// 連接狀態變化時回調,用來檢測藍牙是否連接成功與否,成功失敗兩種情況的操作在這里設置@Overridepublic void onConnectionStateChange(BluetoothGatt gatt, int status,int newState){String intentAction;if (newState == BluetoothProfile.STATE_CONNECTED)//連接成功{ // 連接成功后的操作intentAction = ACTION_GATT_CONNECTED; // 連接外設成功(GATT服務端)mConnectionState = STATE_CONNECTED; // 設備連接完畢/* 通過廣播更新連接狀態 */broadcastUpdate(intentAction); // 廣播更新,查看是否有數據更新Log.i(TAG, "Connected to GATT server.");// Attempts to discover services after successful connection.Log.i(TAG, "Attempting to start service discovery:"+ mBluetoothGatt.discoverServices());} else if (newState == BluetoothProfile.STATE_DISCONNECTED)//連接失敗{ //連接失敗后的操作intentAction = ACTION_GATT_DISCONNECTED;// 連接外設失敗(GATT服務端)mConnectionState = STATE_DISCONNECTED; // 設備無法連接Log.i(TAG, "Disconnected from GATT server.");broadcastUpdate(intentAction);}}/** 重寫onServicesDiscovered,發現藍牙服務 會在藍牙連接的時候調用** */@Overridepublic void onServicesDiscovered(BluetoothGatt gatt, int status){ // GATT_SUCCESS表示GATT操作完成if (status == BluetoothGatt.GATT_SUCCESS)//發現藍牙服務成功{broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);Log.i(TAG, "--onServicesDiscovered called--");} else{Log.w(TAG, "onServicesDiscovered received: " + status);System.out.println("onServicesDiscovered received: " + status);}}/** 特征值的讀寫* */@Overridepublic void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status){if (status == BluetoothGatt.GATT_SUCCESS){Log.i(TAG, "--onCharacteristicRead called--");//從特征值讀取數據// characteristic是特征值,而特征值是用16bit或者128bit,16bit是官方認證過的,128bit是可以自定義的// 這里的兩步操作第一步獲得二進制的特征值,第二步將其變成字符串byte[] sucString = characteristic.getValue();String string = new String(sucString);//將數據通過廣播到Ble_Activity//broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}}/** 特征值的改變* */@Overridepublic void onCharacteristicChanged(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic){System.out.println("++++++++++++++++");broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);// ACTION_DATA_AVAILABLE: 接受來自設備的數據,可以通過讀或通知操作獲得}/** 特征值的寫* */@Overridepublic void onCharacteristicWrite(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status) {super.onCharacteristicWrite(gatt, characteristic, status);if (status == BluetoothGatt.GATT_SUCCESS) {//發送完成mSendState = true;Log.d("AppRun"+getClass().getSimpleName(),"發送完成");}}/** 讀描述值* */@Overridepublic void onDescriptorRead(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status){// TODO Auto-generated method stub// super.onDescriptorRead(gatt, descriptor, status);Log.w(TAG, "----onDescriptorRead status: " + status);byte[] desc = descriptor.getValue();if (desc != null){Log.w(TAG, "----onDescriptorRead value: " + new String(desc));}}/** 寫描述值* */@Overridepublic void onDescriptorWrite(BluetoothGatt gatt,BluetoothGattDescriptor descriptor, int status){// TODO Auto-generated method stub// super.onDescriptorWrite(gatt, descriptor, status);Log.w(TAG, "--onDescriptorWrite--: " + status);}/** 讀寫藍牙信號值* */// Rssi是藍牙的接受信號強度@Overridepublic void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status){// TODO Auto-generated method stub// super.onReadRemoteRssi(gatt, rssi, status);Log.w(TAG, "--onReadRemoteRssi--: " + status);broadcastUpdate(ACTION_DATA_AVAILABLE, rssi);}@Overridepublic void onReliableWriteCompleted(BluetoothGatt gatt, int status){// TODO Auto-generated method stub// super.onReliableWriteCompleted(gatt, status);Log.w(TAG, "--onReliableWriteCompleted--: " + status);}};1.4.5設置特征值變化通知
為了讓手機APP接收藍牙設備發送的數據,必須要設置這個setCharacteristicNotification()方法,這個十分重要。否則,手機APP將無法接受藍牙設備的數據
-
MainActivity代碼
mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); -
BluetoothLeService類的代碼
/*** @Title: setCharacteristicNotification* @Description: TODO(設置特征值通變化通知)* @param @param characteristic(特征值)* @param @param enabled (使能)* @return void* @throws*/ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"));// 其中UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"是HC-08藍牙設備的監聽UUIDif (enabled){clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);} else{clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);}mBluetoothGatt.writeDescriptor(clientConfig); }
1.4.6讀取特征值
開啟對特征值的讀
-
MainActivity代碼
mBluetoothGatt.readCharacteristic(characteristic); -
BluetoothLeService類的代碼
/*** @Title: readCharacteristic* @Description: TODO(讀取特征值)* @param @param characteristic(要讀的特征值)* @return void 返回類型* @throws*/ public void readCharacteristic(BluetoothGattCharacteristic characteristic) {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.readCharacteristic(characteristic);}
在藍牙設備連接成功后自動讀一次特征值,如果讀成功,將返回BluetoothLeService類中的OnDataAvailableListener接口,并進入如下的方法
public void onCharacteristicRead(BluetoothGatt gatt,BluetoothGattCharacteristic characteristic, int status);1.4.7寫入特征值
開啟對特征值的寫,也就是向藍牙外設寫入數據
-
MainActivity代碼
mBluetoothLeService.writeCharacteristic(gattCharacteristic); -
BluetoothLeService類的代碼
// 寫入特征值 public void writeCharacteristic(byte[] bytes) {if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}mList.add(bytes);//mBluetoothGatt.writeCharacteristic(characteristic); }
這是完成手機APP向藍牙設備寫數據的操作
1.4.8獲取已連接藍牙的所有服務
/*** @Title: getSupportedGattServices* @Description: TODO(得到藍牙的所有服務)* @param @return 無* @return List<BluetoothGattService>* @throws*/ public List<BluetoothGattService> getSupportedGattServices() {if (mBluetoothGatt == null)return null;return mBluetoothGatt.getServices();}返回已經連接藍牙設備的所有服務
1.4.9讀取藍牙設備的RSSI值
該方法返回的是已連接的藍牙設備的信號值(RSSI),而RSSI值是藍牙的信號值,離得越遠信號越小,反之亦然
// 讀取RSSi public void readRssi() {if (mBluetoothAdapter == null || mBluetoothGatt == null){Log.w(TAG, "BluetoothAdapter not initialized");return;}mBluetoothGatt.readRemoteRssi(); }該方法返回的是已連接的藍牙設備的信號值(RSSI)
1.4.10發送廣播
通過廣播的形式將數據發出去,在MainActivity中通過設置過濾器接收對應的廣播
//廣播意圖 private void broadcastUpdate(final String action, int rssi) {final Intent intent = new Intent(action);intent.putExtra(EXTRA_DATA, String.valueOf(rssi));sendBroadcast(intent); } //廣播意圖 private void broadcastUpdate(final String action) {final Intent intent = new Intent(action);sendBroadcast(intent); }/* 廣播遠程發送過來的數據 */ public void broadcastUpdate(final String action,final BluetoothGattCharacteristic characteristic) {final Intent intent = new Intent(action);//從特征值獲取數據final byte[] data = characteristic.getValue();MainActivity.revDataForCharacteristic =data;if (data != null && data.length > 0){final StringBuilder stringBuilder = new StringBuilder(data.length);for (byte byteChar : data){stringBuilder.append(String.format("%02X ", byteChar));Log.i(TAG, "***broadcastUpdate: byteChar = " + byteChar);}Log.e("AppRunTime","測試一");intent.putExtra("BLE_BYTE_DATA", data);intent.putExtra(EXTRA_DATA, new String(data));System.out.println("broadcastUpdate for read data:"+ new String(data));}sendBroadcast(intent); }1.5廣播監聽器:
廣播的目的:
- 讓別人能發現自己,對于一個不廣播的設備,周圍設備感覺不到其存在的,因此,要讓別的設備能發現,則必須向外廣播,在廣播中可以帶上豐富的數據,比如設備的能力,設備名字以及其他自定義的數據,這也就有了第二種可能
- 給不需要建立連接的應用廣播數據,比如一個BLE溫度計,其本身可以不接收任何連接,而可以選擇通過廣播將溫度發送出去。檢測者只要監聽廣播就能獲取當前的溫度
掃描者只有在收到廣播數據后,才能去與廣播者建立連接。廣播是周期性的將廣播數據從廣播通道上發送出去
意圖過濾器:
IntentFilter翻譯成中文就是“意圖過濾器”,主要用來過濾隱式意圖。當用戶進行一項操作的時候,Android系統會根據配置的 “意圖過濾器” 來尋找可以響應該操作的組件,服務。這里的意圖過濾器就是讓用戶對服務端也就是硬件外設進行操作
1.5.1注冊/取消注冊廣播監聽
在官方Demo中,便用了廣播來作為activity和service之間的數據傳遞;MainActivity開啟了前面的服務之后,就在MainActivity中注冊了這個mGattUpdateReceiver廣播,以下代碼是MainActivity中的
// 取消注冊廣播和IntentFilter @Override protected void onDestroy() {super.onDestroy();//解除廣播接收器unregisterReceiver(mGattUpdateReceiver);mBluetoothLeService = null; }// Activity出來時候,綁定廣播接收器,監聽藍牙連接服務傳過來的事件 // 在官方demo中,廣播接收器也叫廣播監聽器,用廣播實現activity和service的數據傳遞 // 在MainActivity執行了bindService,開啟了藍牙服務 // 而在這里,就通過registerReceiver注冊了mGattUpdateReceiver廣播和IntentFilter @Override protected void onResume() {super.onResume();//綁定廣播接收器registerReceiver(mGattUpdateReceiver, makeGattUpdateIntentFilter());if (mBluetoothLeService != null){//根據藍牙地址,建立連接final boolean result = mBluetoothLeService.connect(mDeviceAddress);} } /* 意圖過濾器 */ private static IntentFilter makeGattUpdateIntentFilter() {final IntentFilter intentFilter = new IntentFilter();intentFilter.addAction(BluetoothLeService.ACTION_GATT_CONNECTED);intentFilter.addAction(BluetoothLeService.ACTION_GATT_DISCONNECTED);intentFilter.addAction(BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED);intentFilter.addAction(BluetoothLeService.ACTION_DATA_AVAILABLE);return intentFilter; }上述代碼利用registerReceiver()和unregisterReceiver()方法完成注冊和取消注冊廣播,下面的代碼是設置廣播接收和過濾器
1.5.2廣播回調監聽
廣播回調監聽,便是MainActivity接收從Service發送過來的信息,上面說到的上文有說到BluetoothService類的方法BluetoothGattCallback,就是從這里發送廣播的
/*** 廣播接收器,負責接收BluetoothLeService類發送的數據*/ // 下面是對前面注冊的廣播的回調監聽,作用是接受從Service發送回來的信息 // 從BluetoothGattCallback中發送廣播,這里接受信息 private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent){final String action = intent.getAction();if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action))//Gatt連接成功{mConnected = true;//status = "connected";//更新連接狀態//updateConnectionState(status);System.out.println("BroadcastReceiver :" + "device connected");} else if (BluetoothLeService.ACTION_GATT_DISCONNECTED//Gatt連接失敗.equals(action)){mConnected = false;//status = "disconnected";//更新連接狀態//updateConnectionState(status);System.out.println("BroadcastReceiver :"+ "device disconnected");} else if (BluetoothLeService.ACTION_GATT_SERVICES_DISCOVERED//發現GATT服務器.equals(action)){// Show all the supported services and characteristics on the// user interface.//獲取設備的所有藍牙服務//這里留意一下:當連接成功后,首先service那邊會發現服務特征值,通過廣播傳輸回來,然后執行下面的方法displayGattServices(mBluetoothLeService.getSupportedGattServices());System.out.println("BroadcastReceiver :"+ "device SERVICES_DISCOVERED");} else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action))//有效數據{//處理發送過來的數據try {if (intent.getExtras().getString(BluetoothLeService.EXTRA_DATA)!=null) {displayData(intent.getExtras().getString(BluetoothLeService.EXTRA_DATA), intent);System.out.println("BroadcastReceiver onData:"+ intent.getStringExtra(BluetoothLeService.EXTRA_DATA));}}catch (Exception e){e.printStackTrace();}}} };在接收廣播的代碼中,displayGattServices()是進一步的完成發現服務,而displayData()則是進一步的完成數據接收處理
1.5.3處理數據的輸入和獲取
private static BluetoothGattCharacteristic target_chara = null; //藍牙service,負責后臺的藍牙服務 private static BluetoothLeService mBluetoothLeService; private Handler mhandler = new Handler(); /*** @Title: displayGattServices* @Description: TODO(處理藍牙服務)* @param* @return void* @throws*/ // 處理數據的輸入和獲取 private void displayGattServices(List<BluetoothGattService> gattServices) {if (gattServices == null)return;String uuid = null;String unknownServiceString = "unknown_service";String unknownCharaString = "unknown_characteristic";// 服務數據,可擴展下拉列表的第一級數據ArrayList<HashMap<String, String>> gattServiceData = new ArrayList<HashMap<String, String>>();// 特征數據(隸屬于某一級服務下面的特征值集合)ArrayList<ArrayList<HashMap<String, String>>> gattCharacteristicData = new ArrayList<ArrayList<HashMap<String, String>>>();// 部分層次,所有特征值集合mGattCharacteristics = new ArrayList<ArrayList<BluetoothGattCharacteristic>>();// Loops through available GATT Services.for (BluetoothGattService gattService : gattServices){// 獲取服務列表HashMap<String, String> currentServiceData = new HashMap<String, String>();uuid = gattService.getUuid().toString();// 查表,根據該uuid獲取對應的服務名稱。SampleGattAttributes這個表需要自定義。gattServiceData.add(currentServiceData);System.out.println("Service uuid:" + uuid);ArrayList<HashMap<String, String>> gattCharacteristicGroupData = new ArrayList<HashMap<String, String>>();// 從當前循環所指向的服務中讀取特征值列表List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();ArrayList<BluetoothGattCharacteristic> charas = new ArrayList<BluetoothGattCharacteristic>();// Loops through available Characteristics.// 對于當前循環所指向的服務中的每一個特征值for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics){charas.add(gattCharacteristic);HashMap<String, String> currentCharaData = new HashMap<String, String>();uuid = gattCharacteristic.getUuid().toString();if (gattCharacteristic.getUuid().toString().equals(HEART_RATE_MEASUREMENT)){// 測試讀取當前Characteristic數據,會觸發mOnDataAvailable.onCharacteristicRead()mhandler.postDelayed(new Runnable(){@Overridepublic void run(){// TODO Auto-generated method stubmBluetoothLeService.readCharacteristic(gattCharacteristic);}}, 200);// 接受Characteristic被寫的通知,收到藍牙模塊的數據后會觸發mOnDataAvailable.onCharacteristicWrite()mBluetoothLeService.setCharacteristicNotification(gattCharacteristic, true);target_chara = gattCharacteristic;// 設置數據內容// 往藍牙模塊寫入數據// mBluetoothLeService.writeCharacteristic(gattCharacteristic);}List<BluetoothGattDescriptor> descriptors = gattCharacteristic.getDescriptors();for (BluetoothGattDescriptor descriptor : descriptors){System.out.println("---descriptor UUID:"+ descriptor.getUuid());// 獲取特征值的描述mBluetoothLeService.getCharacteristicDescriptor(descriptor);// mBluetoothLeService.setCharacteristicNotification(gattCharacteristic,// true);}gattCharacteristicGroupData.add(currentCharaData);}// 按先后順序,分層次放入特征值集合中,只有特征值mGattCharacteristics.add(charas);// 構件第二級擴展列表(服務下面的特征值)gattCharacteristicData.add(gattCharacteristicGroupData);}}1.5.4數據的接收
下面的代碼完成數據的接收,將其顯示到scrollview中
/*** @Title: displayData* @Description: TODO(接收到的數據在scrollview上顯示)* @param @param rev_string(接受的數據)* @return void* @throws*/ private void displayData(String rev_string, Intent intent) {try {byte[] data = intent.getByteArrayExtra("BLE_BYTE_DATA");if(data==null)System.out.println("data is null!!!!!!");if (receptionHex)rev_string = bytesToHexString(data);elserev_string = new String(data, 0, data.length, "GB2312");//GB2312編碼} catch (UnsupportedEncodingException e) {// TODO Auto-generated catch blocke.printStackTrace();}bluedata = rev_string;//更新UIrunOnUiThread(new Runnable(){@Overridepublic void run(){// rev_tv.setText(bluedata);if(bluedata.length() == RFID ){rfid = bluedata;rev_tv.setText(rfid);}else if(bluedata.length() == DISTANCE ){Log.d("data","*"+bluedata+"*");int a = bluedata.charAt(1) - '0';int b = bluedata.charAt(4) - '0';int c = bluedata.charAt(7) - '0';distance = a * 100 + b * 10 + c;if(distance>100 && distance < 500 )takephoto = false;rev_tv.setText(new Integer(distance).toString());}}});}1.5.5發送數據
下面的代碼是在BluetoothLeService類中,用于給外設的藍牙模塊寫入數據的方法
// 發送數據 public void startSend(final BluetoothGattCharacteristic characteristic){if (mBluetoothAdapter == null || mBluetoothGatt == null) {Log.w(TAG, "BluetoothAdapter not initialized");return;}Log.d("AppRun"+getClass().getSimpleName(),"添加完成,開始發送");if (mList.size() != 0){new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0;i<mList.size();) {try {if (mSendState) {Thread.sleep(5);characteristic.setValue(mList.get(i));mSendState = false;mBluetoothGatt.writeCharacteristic(characteristic);++i;} else {Log.d("AppRun"+getClass().getSimpleName(),"等待中..");Thread.sleep(20);}}catch (Exception e){e.printStackTrace();}}Log.d("AppRun"+getClass().getSimpleName(),"發送完畢..");mList.clear();}}).start();} }下面是將數據進行分包,因為每次藍牙傳輸數據只能是20個字節,如果一個數據超過20個字節,必須要分成n個存放20字節的包
/*** 將數據分包** **/ public int[] dataSeparate(int len) {int[] lens = new int[2];lens[0]=len/20;lens[1]=len%20;return lens; }/*** 將16進制字符串轉換為byte[]*/ public static byte[] hexString2ByteArray(String bs) {if (bs == null) {return null;}int bsLength = bs.length();if (bsLength % 2 != 0) {bs = "0"+bs;bsLength = bs.length();}byte[] cs = new byte[bsLength / 2];String st;for (int i = 0; i < bsLength; i = i + 2) {st = bs.substring(i, i + 2);cs[i / 2] = (byte) Integer.parseInt(st, 16);}return cs; }//byte數組轉String public static String bytesToHexString(byte[] bArray) {StringBuffer sb = new StringBuffer(bArray.length);String sTemp;for (int i = 0; i < bArray.length; i++) {sTemp = Integer.toHexString(0xFF & bArray[i]);if (sTemp.length() < 2)sb.append(0);sb.append(sTemp.toUpperCase());}int length = sb.length();if (length == 1||length == 0){return sb.toString();}if (length%2==1){sb.insert(length-1," ");length= length-1;}for (int i = length;i>0;i=i-2){sb.insert(i," ");}return sb.toString(); }總結
以上是生活随笔為你收集整理的Android蓝牙BLE开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无源滤波器基础
- 下一篇: ureport2下载EXCEL报错:or