在前面一篇文章中,盡管沒有具體的代碼,我們還是提到了低功耗藍牙的一些背景以及在這個系列中將要講解什么。這篇文章中,我們將要定義將要使用的Service、Activity結構來確保藍牙操作與界面分離。
在繼續討論之前,首先需要說明的是我們將不會深入討論BLE(低功耗藍牙)技術的細節。首先,我們先建立一個Activity以及綁定一個服務。這樣,我們可以讓藍牙操作與界面分離,同時在收到來自藍牙的數據時也可以更新界面。
為了實現目標,我們使用了消息模式。通過它我們可以在兩個組件之間通信而不直接調用函數。消息模式需要每一個組件都實現它自己的消息接口,消息接口可以在它父類被創建的線程中處理傳入的消息對象。Activity和Service接口實現都是在UI線程運行的。但是,我們會讓它們在Java方法中沒有直接調用關系。
通過實現各自的父類消息接口,我們也在相關的地方包含處理邏輯。這樣可以讓我們的代碼更好地理解和維護。
BleService.java
public class BleService extends Service { public static final String TAG = "BleService"; static final int MSG_REGISTER = 1; static final int MSG_UNREGISTER = 2; private final Messenger mMessenger; private final List<Messenger> mClients = new LinkedList<Messenger>(); public BleService() { mMessenger = new Messenger( new IncomingHandler(this)); } @Override public IBinder onBind(Intent intent) { return mMessenger.getBinder(); } private static class IncomingHandler extends Handler { private final WeakReference<BleService> mService; public IncomingHandler(BleService service) { mService = new WeakReference<BleService>(service); } @Override public void handleMessage(Message msg) { BleService service = mService.get(); if (service != null) { switch (msg.what) { case MSG_REGISTER: service.mClients.add(msg.replyTo); Log.d(TAG, "Registered"); break; case MSG_UNREGISTER: service.mClients.remove(msg.replyTo); Log.d(TAG, "Unegistered"); break; default: super.handleMessage(msg); } } } } }
基本的代碼相當簡單,有幾個微妙的小地方值得解釋一下。
首先,InnerHandler是被定義為靜態類。不要冒險地在繼承時把它弄為非靜態的內部類,否則可能發生內存泄露,可能會帶來特別嚴重的后果。因為非靜態的內部類由于可以直接訪問包含類實例的方法和字段,從而可以持有包含類的一個引用。簡而言之,Java垃圾回收器就不會銷毀被其他對象引用的對象(實際上比這個更復雜一些,但是目前這個對于發生的現象可以解釋得十分充分)。Handler實例的父類是一個Service對象(Android Context),所以任何對象保持了Handler實例的引用就會隱式地阻止Service對象被垃圾回收器回收。這就是所謂的“上下文泄露”。另外一個十分不好的原因是因為一個Context可能十分大。
我們避免上下文泄露的一個方式是,如果它們在Context子類中被聲明,則永遠把內部類聲明為靜態類。這樣也就是說,我們削弱了使用內部類的主要優勢:訪問父類對象的字段和方法。但通過弱引用可以很輕松的達到這個目的。弱引用允許我們持有一個對象的引用,但是不妨礙它被垃圾回收器回收。
所以我們的InnerHander類通過父類實例的一個引用來構造。與其直接引用(強引用),不如使用弱引用。然后我們通過調用弱引用的get()
方法來獲得父類實例的引用。由于父類實例可能被垃圾回收器回收了,我們需要一個非null
檢查。但是如果不為null
,就可以跟在非靜態類中使用父類實例基本一樣的方法去使用它。
另外一個值得提到的事情是,我們目前有兩種消息類型:注冊和解除注冊。這將允許不同的消耗者注冊我們的Service發出的消息(隨著service獲得BLE設備的狀態更新)。在我們的應用例子中只有Activity要從服務獲得更新,但是在真實情況下可能會有更多的組件需要數據,所以發布/注冊模式似乎是合適的。
接下來,我們的Activity實現會略多一些:
BleActivity.java
public class BleActivity extends Activity { public static final String TAG = "BluetoothLE"; private final Messenger mMessenger; private Intent mServiceIntent; private Messenger mService = null; private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { mService = new Messenger(service); try { Message msg = Message.obtain(null, BleService.MSG_REGISTER); if (msg != null) { msg.replyTo = mMessenger; mService.send(msg); } else { mService = null; } } catch (Exception e) { Log.w(TAG, "Error connecting to BleService", e); mService = null; } } @Override public void onServiceDisconnected(ComponentName name) { mService = null; } }; public BleActivity() { super(); mMessenger = new Messenger(new IncomingHandler(this)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ble); mServiceIntent = new Intent(this, BleService.class); } @Override protected void onStop() { if (mService != null) { try { Message msg = Message.obtain(null, BleService.MSG_UNREGISTER); if (msg != null) { msg.replyTo = mMessenger; mService.send(msg); } } catch (Exception e) { Log.w(TAG, "Error unregistering with BleService", e); mService = null; } finally { unbindService(mConnection); } } super.onStop(); } @Override protected void onStart() { super.onStart(); bindService(mServiceIntent, mConnection, BIND_AUTO_CREATE); } private static class IncomingHandler extends Handler { private final WeakReference<BleActivity> mActivity; public IncomingHandler(BleActivity activity) { mActivity = new WeakReference<BleActivity>(activity); } @Override public void handleMessage(Message msg) { BleActivity activity = mActivity.get(); if (activity != null) { //TODO: Do something } super.handleMessage(msg); } } }
Activity在onStart以及onStop中分別綁定和解綁Service。在ServiceCOnnection(當服務綁定和解綁操作完成時被調用)方法中,我們給Service消息者發送合適的消息來注冊和解綁Activity的消費者。
在Manifest(我沒有在這展示出來,但是可以在代碼中看到)中添加適當的聲明之后,我們有了一個簡單的Activity和Service組合,他們之間可以互相通信。如果我們運行這個應用,它似乎并沒有做什么事。但是看下logcat,它展示的log顯示了注冊和解綁的運行正如我們預期的那樣。
com.stylingandroid.ble D/BleService﹕ Registered com.stylingandroid.ble D/BleService﹕ Unegistered
所以我們現在有了一個適當的程序框架,這個允許我們在Service中收集數據同時把更新推送給UI。
這里,先道個歉。由于上周的文章沒有任何的代碼。同時,這周的文章沒有任何特定的跟這個系列(BLE)有關的代碼。但是,我們現在可以很好的開始講BLE相關的東西,在下一篇文章我們會重點關注發現過程(設備搜尋)。
這篇文章的代碼可以在這里獲得。
備注:我十分感謝多才的Sebastiano Poggi指出我之前發布的版本一些事實錯誤,然后我改正了。所有的保留的錯誤都算上我頭上吧。