在前面一篇文章中,尽管没有具体的代码,我们还是提到了低功耗蓝牙的一些背景以及在这个系列中将要讲解什么。这篇文章中,我们将要定义将要使用的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指出我之前发布的版本一些事实错误,然后我改正了。所有的保留的错误都算上我头上吧。