Android 低功耗藍芽的那點事
近期專案使用到了藍芽技術,菜鳥一枚,網上各種找資料,發現不是不全就是過時,要麼就是抄襲轉載,真實醉了,現在將這一塊的東西整理出來,供大家參考。
基本概念
Android中的藍芽分兩種:經典藍芽、低功耗藍芽。
- 二者本質上沒有太多區別,可以理解為後者是前者的升級優化版本。對於API上的實現區別還是很大的。
- 工作流程:發現裝置->配對/繫結裝置->建立連線->資料通訊。
至於底層如何工作,本人不瞭解,也不是本文關注的重點。 - 官方文件:https://developer.android.com/guide/topics/connectivity/bluetooth.html?hl=zh-cn
重要例項
- 經典藍芽聊天例項:https://github.com/googlesamples/android-BluetoothChat
- 低功耗藍芽:https://github.com/googlesamples/android-BluetoothLeGatt
- 作為外設(API>=21):https://github.com/googlesamples/android-BluetoothAdvertisements
- 基礎概念講解:http://blog.csdn.net/qinxiandiqi/article/details/40741269
- 深入理論:http://www.race604.com/android-ble-in-action/
注意事項
- 低功耗藍芽(BLE)Android 4.3(API 18)以上才支援
- 使用藍芽需要許可權
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
- Android 5.0(API 21) 掃描藍芽需要定位許可權,否則掃描不到裝置,實際使用時候發現 5.0不需要也可以掃描,Android 6.0(API 23)以上必須(不知道什麼原因,測試機器:MI 2,知道原因的可告知)
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> 或 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
官方文件:https://developer.android.com/guide/topics/connectivity/bluetooth-le.html?hl=zh-cn
- 低功耗藍芽要宣告特徵,或者程式碼判斷
// 如果為true表示只能在支援低功耗藍芽的裝置上使用,如果不支援的裝置也可以使用,採用程式碼判斷 <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> // 程式碼判斷 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
- 經典藍芽連線成功後獲取一個socket連線得到輸入輸出流進行通訊,低功耗通過特徵(具體實現不知道是什麼)
- Android 5.0(API 21)之前不能當成外設(藍芽耳機、音響等)來使用,只能作為中心即主機
低功耗藍芽
現在藍芽開發基本上都是低功耗藍芽,比如心率裝置、耳機裝置、手環,所以我們先從重要的開始,講講低功耗藍芽的使用,後面在完善經典藍芽。
宣告許可權
建立專案在AndroidManifest.xml中宣告許可權:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lowett.android"> // 定位許可權第二個包含第一個,所以這裡就宣告瞭一個,兩個都宣告也可以 <!-- <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> // 是否必須支援低功耗藍芽,此處不必須 <uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/> // 是有gps硬體,這個現在的智慧手機沒有不支援的吧 <uses-feature android:name="android.hardware.location.gps"/> </manifest>
如果manifest中宣告的藍芽特性為false,那麼在程式碼中監測是否支援BLE特性,
// 使用此檢查確定BLE是否支援在裝置上,然後你可以有選擇性禁用BLE相關的功能 if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) { Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show(); finish(); }
BluetoothAdapter
獲取BluetoothManager得到藍芽介面卡BluetoothAdapter,注意這兩個類都是系統級別,只有一個,代表了你手機上的藍芽模組
final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();
如果獲取到的Adapter為空說明不支援藍芽,或者沒有藍芽模組
開啟藍芽
前面獲取到了BluetoothAdapter,可以通過呼叫isEnabled()函式去檢測是否開啟了藍芽,false表示沒有開啟,如果沒有開啟需要去啟用,
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
程式碼執行完後,會有彈框提示使用者是否啟用,我們需要在onActivityResult()中判斷返回
@Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == BluetoothLeManager.REQUEST_ENABLE_BT) { if (resultCode == Activity.RESULT_OK) { // something, 去掃描裝置 startScan(); } else { new AlertDialog.Builder(this) .setMessage("請開啟藍芽,連線裝置") .setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); // something } }) .create() .show(); } } }
掃描裝置
低功耗藍芽和經典藍芽的掃描方式不同,如果熟悉經典藍芽,那就要可能掉進坑了起不來了,哈哈。藍芽掃描耗電比較嚴重,所以此處一定要記得在合適的實際停止掃描,比如定時停止、掃描到目標裝置就停止(奇怪的是API為何不提供掃描時長的介面呢?)掃描時呼叫Adapter的startLeScan()方法,然而這個被標記為過時,API>=21被另一個取代。然後通過回掉得到掃描結果(經典藍芽是廣播)
public void startScan() { // 初始化一個handler initHandler(); if (!mScanning) { if (scanRunnable == null) { scanRunnable = new Runnable() { @Override public void run() { stopScan(); } }; } // SCAN_PERIOD = 3 * 10 * 1000, 30s後停止掃面 mHandler.postDelayed(scanRunnable, SCAN_PERIOD); // 新API // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // mBluetoothAdapter.getBluetoothLeScanner().startScan(this); // }else { // this 實現了BluetoothAdapter.LeScanCallback,即掃描結果回掉 mBluetoothAdapter.startLeScan(this); // } mScanning = true; Log.i(TAG, "開始掃描,藍芽裝置"); } }
回撥函式
@Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { // 得到掃描結果 Log.i(TAG, "掃描到的裝置, name=" + device.getName() + ",address=" + device.toString()); }
注意:device:代表外設即目標裝置 rssi:設一個強度值,但是時負值,利用這個值通過公式可以算出離你的距離
scanRecord:廣播資料,附加的資料,沒用到
停止掃描
掃描完成務必停止,因為掃描不僅耗電,還影響連線速度,所以當要連線的時候,先停止掃描時必須的
public void stopScan() { initHandler(); Log.i(TAG, "停止掃描,藍芽裝置"); if (mScanning) { mScanning = false; // 開始掃描的介面,要一樣的不然停止不了 mBluetoothAdapter.stopLeScan(this); } if (scanRunnable != null) { mHandler.removeCallbacks(scanRunnable); scanRunnable = null; } }
連線裝置
通常連線裝置速度還是很快的,連線理論上來說也是無狀態的,所以也需要一個定式任務來,保證超時停止。
public boolean connect(Context context, String address) { if (mConnectionState == STATE_CONNECTED) { return false; } if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) { return false; } initHandler(); if (connectRunnable == null) { connectRunnable = new Runnable() { @Override public void run() { mConnectionState = STATE_DISCONNECTING; disconnect(); } }; } // 30s沒反應停止連線 mHandler.postDelayed(connectRunnable, 30 * 1000); stopScan(); // 獲取到遠端裝置, final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { return false; } // 開始連線,第二個參數列示是否需要自動連線,true裝置靠近自動連線,第三個表示連線回撥 mBluetoothGatt = device.connectGatt(context, false, mGattCallback); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; return true; }
監聽連線回撥
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { // 連線狀態變化 @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { // 連線上 mConnectionState = STATE_CONNECTED; boolean success = mBluetoothGatt.discoverServices(); // 去發現服務 Log.i(TAG, "Attempting to start service discovery:" + success); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { // 連線斷開 mConnectionState = STATE_DISCONNECTED; } } // 發現服務 @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Log.i(TAG,"發現服務"); // 解析服務 discoverService(); } } // 特徵讀取變化 @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { } } // 收到資料 @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { mConnectionState = STATE_CONNECTED; } };
斷開連線
public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { mConnectionState = STATE_DISCONNECTED; return; } // 連線成功的GATT mBluetoothGatt.disconnect(); mBluetoothGatt.close(); }
多裝置連線
藍芽介面卡沒有聽過連線多個裝置的介面,需要我們自己實現,即獲取到目標裝置的address後呼叫連線方法,自己維護多個BluetoothGatt即可(程式碼稍後放出)。
完整程式碼示例
正在做心率相關的藍芽裝置,此處程式碼發出來。
程式碼還在完善中,僅供參考,穩定程式碼將會發在Github中
package com.lowett.android.ble; import android.app.Activity; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Handler; import android.os.Looper; import android.support.annotation.IntDef; import android.text.TextUtils; import com.fit.android.utils.Logger; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.Locale; import java.util.UUID; /** * Email: fvaryu@qq.com */ public class BluetoothLeManager implements BluetoothAdapter.LeScanCallback { public static final int REQUEST_ENABLE_BT = 1; private static final int SCAN_PERIOD = 3 * 10 * 1000; static final int STATE_DISCONNECTED = 1; public static final int STATE_CONNECTING = 2; public static final int STATE_DISCONNECTING = 3; public static final int STATE_CONNECTED = 4; public static final int STATE_DISCOVER_SERVICES = 5; @IntDef(value = {STATE_DISCONNECTED, STATE_CONNECTING, STATE_CONNECTED, STATE_DISCONNECTING, STATE_DISCOVER_SERVICES}) @Retention(RetentionPolicy.SOURCE) public @interface State { } public final static UUID UUID_HEART_RATE_MEASUREMENT = UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT); private static BluetoothLeManager ourInstance = new BluetoothLeManager(); // private Context mContext; private boolean is_inited = false; private android.bluetooth.BluetoothManager mBluetoothManager; private BluetoothAdapter mBluetoothAdapter; private String mBluetoothDeviceAddress; private int mConnectionState; private BluetoothGatt mBluetoothGatt; private boolean mScanning; private Runnable scanRunnable; private Handler mHandler; private Runnable connectRunnable; private OnDataReceivedListener mOnDataReceivedListener; // 記得清掉監聽 洩漏 private OnLeScanListener mOnLeScanListener; private OnConnectionStateChangeListener mOnConnectionStateChangeListener; private int retryCount; public static BluetoothLeManager getInstance() { return ourInstance; } private BluetoothLeManager() { } public void setOnDataReceivedListener(OnDataReceivedListener onDataReceivedListener) { mOnDataReceivedListener = onDataReceivedListener; } public interface OnConnectionStateChangeListener { void onConnectionStateChange(BluetoothGatt gatt, int status, int newState); void onConnectTimeout(); } public void setOnConnectionStateChangeListener(OnConnectionStateChangeListener onConnectionStateChangeListener) { mOnConnectionStateChangeListener = onConnectionStateChangeListener; } public interface OnLeScanListener { void onLeScan(BluetoothDevice device); } public interface OnDataReceivedListener { void onDataReceived(int heart); } private void init() { if (!is_inited) { is_inited = true; } } private boolean initialize(Context context) { init(); if (!is_inited) { throw new RuntimeException("請先呼叫init"); } if (mBluetoothAdapter != null) { return true; } // For API level 18 and above, get a reference to BluetoothAdapter through // BluetoothLeManager. if (mBluetoothManager == null) { mBluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); if (mBluetoothManager == null) { return false; } } mBluetoothAdapter = mBluetoothManager.getAdapter(); return mBluetoothAdapter != null; } public boolean isSupportBluetoothLe(Activity activity) { return activity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); } public boolean isSupportBluetooth(Context context) { return initialize(context); } public void enableBluetooth(Activity activity) { if (!initialize(activity)) { return; } if (!mBluetoothAdapter.isEnabled()) { Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); activity.startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT); } } public boolean isEnabled(Context context) { return initialize(context) && mBluetoothAdapter.isEnabled(); } private void initHandler() { if (mHandler == null) { mHandler = new Handler(Looper.getMainLooper()); } } public void startScan(OnLeScanListener onLeScanListener) { initHandler(); if (!mScanning) { if (scanRunnable == null) { scanRunnable = new Runnable() { @Override public void run() { stopScan(); } }; } mHandler.postDelayed(scanRunnable, SCAN_PERIOD); // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // mBluetoothAdapter.getBluetoothLeScanner().startScan(this); // }else { mBluetoothAdapter.startLeScan(this); // } mScanning = true; this.mOnLeScanListener = onLeScanListener; Logger.i("開始掃描,藍芽裝置"); } } @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { final BluetoothDevice tmp = device; Logger.i("掃描到的裝置, name=" + device.getName() + ",address=" + device.toString()); if (mOnLeScanListener != null) { mHandler.post(new Runnable() { @Override public void run() { mOnLeScanListener.onLeScan(tmp); } }); } } public void stopScan() { initHandler(); mOnLeScanListener = null; Logger.i("停止掃描,藍芽裝置"); if (mScanning) { mScanning = false; mBluetoothAdapter.stopLeScan(this); } if (scanRunnable != null) { mHandler.removeCallbacks(scanRunnable); scanRunnable = null; } } private void removeConnectRunnable() { if (connectRunnable != null) { mHandler.removeCallbacks(connectRunnable); connectRunnable = null; } } private void retry() { if (TextUtils.isEmpty(mBluetoothDeviceAddress)) { return; } mHandler.postDelayed(new Runnable() { @Override public void run() { if (++retryCount < 11 && mConnectionState < STATE_CONNECTED) { reconnect(retryCount); mHandler.postDelayed(this, retryCount * 5 * 1000); Logger.i("藍芽重試次數=" + retryCount); } } }, 2000); } private void reconnect(int count) { if ((mConnectionState >= STATE_CONNECTING)) { return; } if (connectRunnable == null) { connectRunnable = new Runnable() { @Override public void run() { mConnectionState = STATE_DISCONNECTING; disconnect(); } }; } mHandler.postDelayed(connectRunnable, count * 3 * 1000); if (mBluetoothDeviceAddress != null && mBluetoothGatt != null) { mBluetoothGatt.connect(); mConnectionState = STATE_CONNECTING; } } public boolean connect(Context context, String address) { if (mConnectionState == STATE_CONNECTED) { return false; } if (mBluetoothAdapter == null || TextUtils.isEmpty(address)) { return false; } initHandler(); if (connectRunnable == null) { connectRunnable = new Runnable() { @Override public void run() { mConnectionState = STATE_DISCONNECTING; disconnect(); if (mOnConnectionStateChangeListener != null) { mOnConnectionStateChangeListener.onConnectTimeout(); } } }; } mHandler.postDelayed(connectRunnable, 30 * 1000); stopScan(); // Previously connected device. Try to reconnect. if (mBluetoothDeviceAddress != null && address.equals(mBluetoothDeviceAddress) && mBluetoothGatt != null) { if (mBluetoothGatt.connect()) { mConnectionState = STATE_CONNECTING; return true; } else { return false; } } final BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); if (device == null) { return false; } // We want to directly connect to the device, so we are setting the autoConnect // parameter to false. mBluetoothGatt = device.connectGatt(context, false, mGattCallback); mBluetoothDeviceAddress = address; mConnectionState = STATE_CONNECTING; return true; } public void disconnect() { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Logger.i("BluetoothAdapter not initialized"); mConnectionState = STATE_DISCONNECTED; return; } mBluetoothGatt.disconnect(); } public void close() { disconnect(); if (mBluetoothGatt == null) { return; } mBluetoothGatt.close(); mBluetoothGatt = null; } public void disconnectNoRetry() { mBluetoothDeviceAddress = null; close(); } private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothProfile.STATE_CONNECTED) { mConnectionState = STATE_CONNECTED; boolean success = mBluetoothGatt.discoverServices(); Logger.i("Attempting to start service discovery:" + success); removeConnectRunnable(); Logger.i("連結上"); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { mConnectionState = STATE_DISCONNECTED; Logger.i("斷開連結"); retry(); } if (mOnConnectionStateChangeListener != null) { mOnConnectionStateChangeListener.onConnectionStateChange(gatt, status, newState); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { Logger.i("發現服務"); discoverService(); } } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { broadcastUpdate(characteristic); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { mConnectionState = STATE_CONNECTED; broadcastUpdate(characteristic); } }; /** * Retrieves a list of supported GATT services on the connected device. This should be * invoked only after {@code BluetoothGatt#discoverServices()} completes successfully. * * @return A {@code List} of supported services. */ private List<BluetoothGattService> getSupportedGattServices() { if (mBluetoothGatt == null) return null; return mBluetoothGatt.getServices(); } private void discoverService() { if (mConnectionState == STATE_DISCOVER_SERVICES) { return; } mConnectionState = STATE_DISCOVER_SERVICES; List<BluetoothGattService> list = getSupportedGattServices(); /** * BluetoothGattService = 00001800-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a00-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a01-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a04-0000-1000-8000-00805f9b34fb BluetoothGattService = 00001801-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a05-0000-1000-8000-00805f9b34fb 心跳服務 BluetoothGattService = 0000180d-0000-1000-8000-00805f9b34fb 心跳特徵 BluetoothGattCharacteristic = 00002a37-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a38-0000-1000-8000-00805f9b34fb BluetoothGattService = 0000180f-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a19-0000-1000-8000-00805f9b34fb // 裝置名字 BluetoothGattService = 0000180a-0000-1000-8000-00805f9b34fb BluetoothGattCharacteristic = 00002a28-0000-1000-8000-00805f9b34fb */ for (BluetoothGattService s : list) { if (!SampleGattAttributes.HEART_RATE_SERVICES.equals(s.getUuid().toString())) { continue; } final List<BluetoothGattCharacteristic> l = s.getCharacteristics(); for (final BluetoothGattCharacteristic bc : l) { if (!SampleGattAttributes.HEART_RATE_MEASUREMENT.equals(bc.getUuid().toString())) { continue; } Logger.i("連線藍芽 服務成功"); setCharacteristicNotification(bc, true); return; } } } private void broadcastUpdate(final BluetoothGattCharacteristic characteristic) { // This is special handling for the Heart Rate Measurement profile. Data parsing is // carried out as per profile specifications: // http://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.heart_rate_measurement.xml if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { int flag = characteristic.getProperties(); int format = -1; if ((flag & 0x01) != 0) { format = BluetoothGattCharacteristic.FORMAT_UINT16; } else { format = BluetoothGattCharacteristic.FORMAT_UINT8; } int heartRate = characteristic.getIntValue(format, 1); Logger.i(String.format(Locale.getDefault(), "Received heart rate: %d", heartRate)); if (mOnDataReceivedListener != null) { mOnDataReceivedListener.onDataReceived(heartRate); } } // else { // For all other profiles, writes the data formatted in HEX. // final byte[] data = characteristic.getValue(); // if (data != null && data.length > 0) { // final StringBuilder stringBuilder = new StringBuilder(data.length); // for (byte byteChar : data) // stringBuilder.append(String.format("%02X ", byteChar)); // intent.putExtra(EXTRA_DATA, new String(data) + "\n" + stringBuilder.toString()); // } // } /** * 2、 */ // sendBroadcast(intent); } public boolean isConnected() { return mConnectionState == STATE_CONNECTED; } public String getConnectedAddress() { if (!isConnected()) { return null; } return mBluetoothDeviceAddress; } public void readCharacteristic(BluetoothGattCharacteristic characteristic) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Logger.i("BluetoothAdapter not initialized"); return; } mBluetoothGatt.readCharacteristic(characteristic); } private void setCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean enabled) { if (mBluetoothAdapter == null || mBluetoothGatt == null) { Logger.i("BluetoothAdapter not initialized"); return; } mBluetoothGatt.setCharacteristicNotification(characteristic, enabled); // This is specific to Heart Rate Measurement. if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) { BluetoothGattDescriptor descriptor = characteristic.getDescriptor( UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG)); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); mBluetoothGatt.writeDescriptor(descriptor); } } public void clear() { mOnLeScanListener = null; mOnConnectionStateChangeListener = null; } public void release() { // connectRunnable = null; // mHandler = null; // ourInstance = null; } }
總結
專案還在進行中,遇到的問題將會持續丟擲,完善此文件。
相關文章
- Android藍芽那點事——深入瞭解藍芽BlE藍芽 《總結篇》Android藍芽
- Android藍芽那點事——深入瞭解Android藍芽Bluetooth《進階篇》Android藍芽
- 低功耗藍芽(5)藍芽
- 低功耗藍芽(2)藍芽
- 低功耗藍芽(3)藍芽
- BLE低功耗藍芽藍芽
- Android 低功耗藍芽簡單整合記錄Android藍芽
- MASA MAUI Plugin 安卓藍芽低功耗(一)藍芽掃描UIPlugin安卓藍芽
- MASA MAUI Plugin IOS藍芽低功耗(三)藍芽掃描UIPluginiOS藍芽
- BLE藍芽那些事—深入瞭解Android藍芽Bluetooth基礎篇藍芽Android
- Android Activity那點事Android
- Android GC 那點事AndroidGC
- Android藍芽使用詳解(普通藍芽)Android藍芽
- Android藍芽協議-藍芽掃描 startDiscoveryAndroid藍芽協議
- Android打包簽名那點事Android
- AirBuddy技巧:如何檢查您的Mac是否支援低功耗藍芽AIMac藍芽
- 使用RxJava幫助低功耗藍芽(BLE)進行通訊RxJava藍芽
- PostgreSQL的那點事兒SQL
- vue元件的那點事Vue元件
- 跨域的那點事跨域
- oracle hints的那點事Oracle
- Android開發--藍芽操作Android藍芽
- Android-藍芽聊天demoAndroid藍芽
- Android Ble藍芽入門Android藍芽
- Android 開啟藍芽流程Android藍芽
- Android 4.2藍芽介紹Android藍芽
- Android藍芽2.0的使用方法:Android藍芽
- 帶你瞭解CC2640藍芽低功耗無線MCU藍芽
- 《低功耗藍芽開發權威指南》——1.2節設計目標藍芽
- Android開發——說說Adapter那點事AndroidAPT
- 如何翻牆那點事兒【Android | Windows | macOS】AndroidWindowsMac
- Android 檢視高度和陰影的那點事兒Android
- Java synchronized那點事Javasynchronized
- 前端加密那點事前端加密
- 佈局那點事
- 面試那點事兒面試
- MySQL的timeout那點事MySql
- Toast與Snackbar的那點事AST