在前面一篇文章中,儘管沒有具體的程式碼,我們還是提到了低功耗藍芽的一些背景以及在這個系列中將要講解什麼。這篇文章中,我們將要定義將要使用的Service、Activity結構來確保藍芽操作與介面分離。
在繼續討論之前,首先需要說明的是我們將不會深入討論BLE(低功耗藍芽)技術的細節。首先,我們先建立一個Activity以及繫結一個服務。這樣,我們可以讓藍芽操作與介面分離,同時在收到來自藍芽的資料時也可以更新介面。
為了實現目標,我們使用了訊息模式。通過它我們可以在兩個元件之間通訊而不直接呼叫函式。訊息模式需要每一個元件都實現它自己的訊息介面,訊息介面可以在它父類被建立的執行緒中處理傳入的訊息物件。Activity和Service介面實現都是在UI執行緒執行的。但是,我們會讓它們在Java方法中沒有直接呼叫關係。
通過實現各自的父類訊息介面,我們也在相關的地方包含處理邏輯。這樣可以讓我們的程式碼更好地理解和維護。
BleService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
; html-script: false ] 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
; html-script: false ] 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顯示了註冊和解綁的執行正如我們預期的那樣。
1 2 3 |
; html-script: false ] com.stylingandroid.ble D/BleService﹕ Registered com.stylingandroid.ble D/BleService﹕ Unegistered |
所以我們現在有了一個適當的程式框架,這個允許我們在Service中收集資料同時把更新推送給UI。
這裡,先道個歉。由於上週的文章沒有任何的程式碼。同時,這周的文章沒有任何特定的跟這個系列(BLE)有關的程式碼。但是,我們現在可以很好的開始講BLE相關的東西,在下一篇文章我們會重點關注發現過程(裝置搜尋)。
這篇文章的程式碼可以在這裡獲得。
備註:我十分感謝多才的Sebastiano Poggi指出我之前釋出的版本一些事實錯誤,然後我改正了。所有的保留的錯誤都算上我頭上吧。