Bluetooth LE(低功耗蓝牙) - 第二部分

时间:2022-06-07 09:47:39

回顾

在前面的文章中我们介绍了Bluetooth LE的背景也说明了我们在本系列文章中将要开发什么,但是还没有实际的代码。我们将在这篇文章中纠正这一点,我们将通过定义 Service/Activity 架构来确保蓝牙操作从UI中解耦。

Bluetooth LE(低功耗蓝牙) - 第二部分

Service 与 Activity 通信

在我们继续之前,我应该指出的是,我们不打算在这篇文章中去探究BLE的细节。起初,我们打算建立一个Activity并绑定Service,它将使我们能够把所有的蓝牙操作从UI中解耦,同时让我们从BLE接收到数据后更新UI。

要做到这一点,我们将使用Messenger模式。它能够帮助我们不通过任何直接的方法调用而实现两个组件之间的通信。 Messenger模式要求每个组件来实现自身的Messenger实现:当类的实例被创建后处理传入的Message对象。Activity和Service的实现将运行在UI线程,但是我们将确保他们在各自的方法调用上彼此透明。

通过实现他们各自的Messenger实现以及在相应地方的逻辑处理,使我们的代码更容易理解和维护。

Bluetooth LE(低功耗蓝牙) - 第二部分
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);
}
}
}
}
}
Bluetooth LE(低功耗蓝牙) - 第二部分

基本代码非常简单,但仍有一些细微之处值得去解释。

首先,InnerHandler 声明为静态。不要试图以一个非静态内部类来实现这个,否则会泄漏内存(leaking memory),这可能非常严重。这是因为一个类的非静态内部类可以持有一个该类的实例,并可以直接访问其成员变量和方法。简单来说,Java垃圾收集器将不会破坏被其他对象引用的对象(实际上比这更复杂一点,但也足够解释所发生的情况)。该Handler的父类实例是一个Service对象(一个Android Context),所以如果有任意一个对象持有该Handler实例的引用,都将隐式地阻止该Service对象被垃圾回收掉。这就是所谓的“Context  leak (上下文泄漏)”。它也是一个非常糟糕的事情,因为上下文可能相当大。

(译者注:关于“Context Leak”更多的介绍请查看http://www.androiddesignpatterns.com/2013/01/inner-class-handler-memory-leak.html

我们避免上下文泄漏的方法是始终将内部类声明为静态,if they are declared within classes which subclass Context (不知道该怎么翻译)。 这也意味着我们已经失去了使用内部类的一个主要优点:访问父类属性和(or或)方法的能力。但我们可以很容易地通过使用WeakReference(弱引用)来克服这一点。弱引用可以使我们保持对一个对象的引用,并且不会阻止它被垃圾回收机制回收。

所以我们的InnerHandler类被构造成拥有一个包裹在WeakReference对象中的它父类的实例的引用。而不是直接拥有其父类的引用。这样InnerHandler可以调用 WeakReference 的get()方法获取其父类实例的引用。我们需要做一个null判断因为父类的实例如果被垃圾回收那么该引用将为空,但如果它不为空,那么我们可以以与非静态内部类完全相同的方式使用该实例的引用。

另一件值得一提的事是,我们目前有两种类型的消息:注册和取消注册。这允许多个用户订阅Service发出的信息(Service会从我们的BLE设备中获得更新信息)。在我们的示例应用程序中只有Activity将从Service中得到更新信息,但在现实世界中应用程序可能有更多的组件所需要的数据,所以发布/订阅模型是合适的。

我们的Activity:

Bluetooth LE(低功耗蓝牙) - 第二部分
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);
}
}
}
Bluetooth LE(低功耗蓝牙) - 第二部分

在Activity的onstart()和onstop()方法中绑定和解绑Service。在ServiceConnection方法(当Service绑定和解除绑定操作完成时回调)中,Activity‘s Messenger  则通过向Service Messenger 发送适当的消息实现注册和反注册。

之后我们需要在Manifest中添加适当的声明(我没有将它展示在这里,但在源码中可以看到) 。我们有一个简单的Activity和Service配对,他们能够互相通信。如果我们运行这个app,它什么都没做,但在logcat中显示了我们所期望的注册/注销工作:

com.stylingandroid.ble D/BleService﹕ Registered
com.stylingandroid.ble D/BleService﹕ Unegistered

所以现在我们拥有了一个app的框架,它使我们能够从Service中收集数据并更新我们的UI。

下期预告

抱歉,我们已经从上周没有包含代码的文章中脱离出来了,这周的文章中虽然包含代码却和本系列文章的主题(Bluetooth LE)不怎么相关。但是对学习BLE来现在是一个好的开始,在下一篇文章将开始把BLE与我们的发现结合在一起。

这篇文章的源代码可以在 这里 找到。