編輯:關於Android編程
最近一段時間在寫支持BLE藍牙的Android應用。是時候總結一下了。
1、什麼是BLE。(總得先知道BLE是什麼吧~~~)
Bluetooth Low Energy(低功耗藍牙),縮寫為Bluetooth LE,或BLE,作為藍牙4.0 (有時稱為藍牙智能)規范的一部分,並針對上述的這些具體問題而被引入。就提高電池壽命而言,許多制造商聲稱一些傳感器能維持數月甚至數年的時間(我必須承認我有點懷疑制造商的估計一般是基於最好的情況下,而不涉及實際的使用情況)。Google在Android 4.3(API18)中加入了對BLE的支持。
* 這裡附帶提一下傳統的藍牙Bluetooth。傳統的藍牙采用的是類似於Socket的通信機制:Socket的基本原理就是有ServerSocket、ClientSocket,服務器端和客戶端都指定一下網絡地址、端口,然後服務器端accept(),處於等待客戶端發起連接的狀態;客戶端Socket對象創建後,就可以拿到裡面的一些輸入輸出流了,發起請求,然後實現各種數據傳遞業務邏輯。
而,BLE的機制不是這樣的!BLE的機制是中心周邊機制,以傳輸屬性類型的原子數據為特征。(這句話是不是很繞。舉個例子來說:Android Wear是有一個支持BLE的設備,可以測你的心率;Android phone上有個心率App,兩者通過藍牙連接。Android phone(接收心率數據)就是BLE中心,Android Wear(發送心率數據)就是BLE周邊。傳輸的數據是心率,可能是 60 次/分 ,顯然有單位。所以說這個數據是一個屬性數據;這個數據是以數據傳輸的最小單元的形式發送的,是個原子數據。好難解釋。大概是這樣理解的~~~)
可以這麼認為:接收數據的一端(Android phone)認為是BLE中心(BluetoothGatt),其他低能耗藍牙設備(比如說帶藍牙的溫度傳感器、心率傳感器等等)。
-----------------------------------------------華麗麗的分割線-------------------------------------------------------------------------------------------------------------
2、進行BLE藍牙操作的准備工作(工欲善其事,必先利其器!)
* 先啥也別想,把所有需要的權限加上。
2.1 Service 與 Activity 之間的通信
為啥要知道這個? 後面就知道了。先按下不表~~~(賤人就是矯情,還和我賣關子!)
記得Service 在清單配置文件AndroidManifest.xml中要寫
首先,建立一個Activity並綁定Service,它將使我們能夠把所有的藍牙操作從UI中解耦,同時讓我們從BLE接收到數據後更新UI。
使用Messenger模式。它能夠幫助我們不通過任何直接的方法調用而實現兩個組件之間的通信。Messenger模式要求每個組件來實現自身的Messenger實現:當類的實例被創建後處理傳入的Message對象。
/** * 此類用來建立一個藍牙BLE的服務,借此將藍牙操作和UI更新解耦 * 繼承Service服務類,需要在清單配置文件中注冊 * 實現藍牙適配器的回調接口LeScanCallback實現掃描周邊支持藍牙BLE協議的設備 */ @SuppressLint("NewApi") public class BluetoothLeService extends Service implements LeScanCallback{ // BluetoothLeService 一個標記 public static final String TAG = "BluetoothLeService"; //注冊BleService public static final int MSG_REGISTER = 0x00010000; //解除注冊BleService public static final int MSG_UNREGISTER = 0x00020000; //開始掃描BLE藍牙設備 public static final int MSG_START_SCAN = 0x00200000; // BLE藍牙設備被發現 public static final int MSG_DEVICE_FOUND = 0x00400000; // BLE藍牙不存在 public static final int MSG_NO_BLUETOOTH = 0x00800000; // BLE藍牙設備狀態改變 public static final int MSG_STATE_CHANGED = 0x00040000; // BLE藍牙設備連接 public static final int MSG_DEVICE_CONNECT = 0x00080000; // 接收BLE藍牙設備數據 public static final int MSG_DEVICE_DATA = 0x00002000; // BLE藍牙設備斷開 public static final int MSG_DEVICE_DISCONNECT = 0x00004000; // 開啟藍牙 public static final int MSG_OPEN_BLUETOOTH = 0x00008000; // 更新藍牙列表 public static final int MSG_UPDATE_BLUETOOOTHLIST = 0x00000200; //BleService的Messenger對象 Messenger.send(Message)方法表示將Message發送給Messenger對象 //Message由Messenger關聯的Handler處理 private Messenger mMessengerOfService; private Handler mHandler; //MainActivity的Messenger對象 private Messenger mMessengerOfActivity; //1.藍牙管理器對象 //2.藍牙適配器對象 private BluetoothAdapter mBluetoothAdapter; //3.藍牙中央 private BluetoothGatt mBluetoothGatt; //4.藍牙周邊的服務集合 private ListmListOfBTService; //5.藍牙周邊的特性集合 private List mListOfBTCharac; //連接標記 private boolean mConnected; public BluetoothLeService() { super(); mConnected = false; } @Override public void onCreate() { BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); if(mBluetoothManager != null)mBluetoothAdapter = mBluetoothManager.getAdapter(); mHandler = new IncomingHandler(this); mMessengerOfService = new Messenger(mHandler); super.onCreate(); } @Override public IBinder onBind(Intent intent) { //建立MainActivity與BleService之間的綁定 return mMessengerOfService.getBinder(); } /** * 此處不要使用非靜態內部類,否則會導致內存洩漏 * @author 沈煜 * */ @SuppressLint("HandlerLeak") private class IncomingHandler extends Handler{ private final WeakReference mService; public IncomingHandler(BluetoothLeService service) { super(); mService = new WeakReference (service); } @Override public void handleMessage(Message msg) { BluetoothLeService service = mService.get(); mMessengerOfActivity = msg.replyTo; if(service != null){ switch (msg.what) { case MSG_START_SCAN: //用戶設備沒有藍牙硬件,不支持藍牙功能 if(mBluetoothAdapter == null){ try { Message msg1 = Message.obtain(null, BluetoothLeService.MSG_NO_BLUETOOTH); msg1.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg1); } catch (RemoteException e) { e.printStackTrace(); } break; } //判斷如果藍牙適配器為空或者藍牙適配器不可用(說明藍牙適配器沒有打開),使用藍牙隱式意圖打開用戶的藍牙 if(!mBluetoothAdapter.isEnabled()){ try { //This is an asynchronous call-->阻塞是方法 //mBluetoothAdapter.enable(); Message msg2 = Message.obtain(null, MSG_OPEN_BLUETOOTH); msg2.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg2); break; } catch (RemoteException e) { e.printStackTrace(); break; } } //開始掃描 scanLeDevice(true); break; case MSG_DEVICE_CONNECT: //TODO 建立連接 break; case MSG_DEVICE_DISCONNECT: //TODO 斷開連接 break; case MSG_DEVICE_FOUND: //TODO 發現設備 break; case MSG_STATE_CHANGED: //TODO 改變狀態 break; default: super.handleMessage(msg); break; } } } } @Override public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { // TODO 具體執行掃描的方法 } @SuppressWarnings("deprecation") public void scanLeDevice(final boolean enable){ // TODO 開始掃描 為什麼有 onLeScan方法還要此方法,此方法中設置超時停止掃描,給mScanning設置狀態等 } private BluetoothGattCallback callback = new BluetoothGattCallback() { @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { //何時執行發現服務的方法 Log.d("TEST", "onServicesDiscovered"); } @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { //何時執行連接狀態改變的方法 } @Override public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { Log.d("TEST", "onCharacteristicChanged"); } @Override public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { } @Override public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { } }; }
解釋一下這段代碼。(媽蛋,神經病吧,上來一段這麼長的代碼,你以為有注釋我就能看懂嗎?天真)
2.然後就是Messenger對象了,Messenger對象是一個可以跨進程傳遞消息、數據的信使。 我這裡用的是Messenger(Handler handler)這個構造方法,在Messenger上綁定了當前進程的Handler對象。Service的Messenger綁定Service的,Activity綁定Activity的(寫在Acitivity那邊了), 主要還是有個Message的 replyTo屬性,表示的是接到此Message後回復的對象是誰?,例如:msg.replyTo = mMessengerOfService;mMessengerOfActivity.send(msg);表示消息發給了Activity,Activity的回復對象是Service,實際上就是,Service發消息給Activity! 而Message對象的msg中設置好要傳遞的數據,Activity就能收到了!
再看看Activity那一段吧。
@SuppressLint("NewApi") public class MainActivity extends Activity implements OnClickListener, OnItemClickListener, OnLongClickListener{ private static final String TAG = "BluetoothLE"; private Messenger mMessengerOfActivity; private Intent mServiceIntent; private Messenger mMessengerOfService; private boolean BIND_SUCCESS = false; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { BIND_SUCCESS = false; mMessengerOfService = null; } /** * 此方法在Activity與Service綁定成功是被調用 */ @Override public void onServiceConnected(ComponentName name, IBinder service) { BIND_SUCCESS = true; // 使用一個IBinder對象作為目標,獲得Service端的Messenger對象 mMessengerOfService = new Messenger(service); try { Message msg = Message.obtain(null, BluetoothLeService.MSG_REGISTER); if (msg != null){ msg.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg); } else { mMessengerOfService = null; } } catch (Exception e) { e.printStackTrace(); } } }; private MyBroadcastReceiver mMyBroadcastReceiver; private TextView NameTV; private TextView AddressTV; private View list1111; private View list2222; @SuppressLint("HandlerLeak") @Override protected void onCreate(Bundle savedInstanceState) { getWindow().setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //初始化各種View onInit(); //初始化Activity的Messenger對象 //關聯了Activity的Handler對象,用來處理來自Service的消息 mMessengerOfActivity = new Messenger(new IncomingHandler(this)); //意圖對象,用來綁定Service的 mServiceIntent = new Intent(this, BluetoothLeService.class); } @Override protected void onStart() { //綁定BluetoothLeService來進行低能耗藍牙掃描、藍牙連接、藍牙數據傳輸!--->實現藍牙操作與UI操作之間的解耦 if(mServiceIntent != null && mConnection != null)bindService(mServiceIntent, mConnection, BIND_AUTO_CREATE); super.onStart(); } @Override protected void onStop() { if (mMessengerOfService != null) { try { Message msg = Message.obtain(null, BluetoothLeService.MSG_UNREGISTER); if (msg != null) { msg.replyTo = mMessengerOfService; mMessengerOfActivity.send(msg); } } catch (Exception e) { Log.w(TAG, "Error unregistering with BleService",e); mMessengerOfService = null; } } super.onStop(); } /** * 此類是一個實例內部類,用來實現MainActivity與BluetoothLeService之間的數據傳遞 * 此類繼承了Handler類,是一個消息處理類 * @author 沈煜 * */ @SuppressLint("HandlerLeak") private class IncomingHandler extends Handler { //使用弱引用關聯當前的Activity,避免Activity被強引用關聯無法銷毀 private final WeakReferencemActivity; //在構造方法中初始化Activity public IncomingHandler(MainActivity activity) { mActivity = new WeakReference (activity); } //重寫handleMessage方法 @Override public void handleMessage(Message msg) { MainActivity activity = mActivity.get(); if (activity != null) { //TODO 此處應該處理來自Service的消息。 switch (msg.what) { case BluetoothLeService.MSG_REGISTER: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "Service與Activity綁定成功!Activity收到來自Service的MSG_REGISTER消息"); break; case BluetoothLeService.MSG_UNREGISTER: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "Service與Activity解除綁定!Activity收到來自Service的MSG_UNREGISTER消息"); unbindService(mConnection); break; case BluetoothLeService.MSG_NO_BLUETOOTH: if(BuildConfig.DEBUG)Log.i("MainActivity.IncomingHandler", "不支持BLE藍牙連接!Activity收到來自Service的MSG_NO_BLUETOOTH消息"); Toast.makeText(getApplicationContext(), "親,此設備不支持BLE藍牙連接", Toast.LENGTH_SHORT).show(); break; case BluetoothLeService.MSG_OPEN_BLUETOOTH: Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enabler, Constants.REQUEST_ENABLE_BLUETOOTH); break; case BluetoothLeService.MSG_UPDATE_BLUETOOOTHLIST: break; case BluetoothLeService.MSG_DEVICE_DATA: break; default: super.handleMessage(msg); break; } } } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == Constants.REQUEST_ENABLE_BLUETOOTH) { Log.i("test", "resultCode="+resultCode); //三星打印resultCode=0,華為打印resultCode=-1 //-1表示允許打開藍牙,是用戶點確認後返回的值,三星等廠商自定義了藍牙開啟系統界面,默認返回0,另外有對話框讓用戶確認是否開啟藍牙 if(resultCode == RESULT_OK) { Toast.makeText(this, "藍牙已開啟", Toast.LENGTH_SHORT).show(); startScan(); } else if(resultCode == RESULT_CANCELED) { Toast.makeText(this, "未允許開啟藍牙", Toast.LENGTH_LONG).show(); } } super.onActivityResult(requestCode, resultCode, data); } }
現在應該可以看出來,將藍牙操作全部封裝在一個Service中的好處了,那就是更新UI和藍牙操作解耦合了,這樣思路會清晰一些,再加以完善後可以做個小框架了。
2.2 藍牙連接中心、周邊、掃描、數據傳輸的基礎知識。
先看看Android BLE SDK的四個關鍵類(class):
a)BluetoothGattServer作為周邊來提供數據;BluetoothGattServerCallback返回周邊的狀態。
b)BluetoothGatt作為中央來使用和處理數據;BluetoothGattCallback返回中央的狀態和周邊提供的數據。
1、藍牙中心的創建:
// 1.獲得藍牙管理器 BluetoothManager mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); // 2.獲得藍牙適配器 if(mBluetoothManager != null)mBluetoothAdapter = mBluetoothManager.getAdapter(); // 3.使用藍牙適配器掃描 mBluetoothAdapter.startLeScan(this); // 4.使用掃描到的藍牙設備獲得藍牙中心 mBluetoothGatt = mDeviceHaveChoose.connectGatt(BluetoothLeService.this, false, callback);
2. 在藍牙回調中獲得數據,使用UUID
一個中央可以與多個周邊連接,一個周邊由多個Service組成,每個Service由多個Charicteristic組成,不同的周邊由BluetoothDevice的MAC地址區分,不同的Service、Charicteristic之間由UUID 唯一標識符區分。特定的 Characteristic 中存儲你需要的數據。
--------------------------------------------------看到這裡時,說明你已經准備好了,下一集具體的BLE藍牙操作。-----------------
記事本涉及到的僅僅是對string 的存儲,而且在讀取上並不存在什麼難點,直接用textview顯示便可以了。需要做的主要是使用SQLite對數據進行一個整理。而錄音筆需
前言: 目前工作負責兩個醫療APP項目的開發,同時使用LeanCloud進行雲端配合開發,完全單挑。 現大框架已經完成,正在進行細節模塊上的開發 抽空總結一下And
什麼是XMLXML全稱為Extensible Markup Language, 意思是可擴展的標記語言,它是 SGML(標准通用標記語言)的一個子集。XML語法上和HTM
RecyclerView 已經出來很久了,但是在項目中之前都使用的是ListView,最近新的項目上了都大量的使用了RecycleView.尤其是瀑布流的下拉刷新,網上吧