In the previous article we covered a bit of the background of Bluetooth LE and what we’re going to develop in this series, but there was no actual code. We’ll rectify that in this article and define the Service / Activity architecture that were going to use to ensure that we’re going to keep our Bluetooth operations decoupled from the UI.
Before we continue I should point out that we’re not going to get in to the nitty gritty of BLE in this article. Before we do that we are going to set up an Activity and bound Service which will enable us to keep all of the Bluetooth operations decoupled from the UI while allowing us to update the UI when we receive data over BLE.
To achieve this we will use the Messenger pattern which enables us to communicate between two components without making any direct method calls. The Messenger pattern requires each component to implement its own Messenger implementation which will process incoming Message objects on the thread in which its patent class was created. Both our Activity and Service implementations will be running on the UI thread, but we’ll keep them agnostic of each other in terms of Java method calls.
By implementing the respective Messenger implementations within their parents we also include the processing logic in the relevant place, and make our code easier to understand and maintain.
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 ListmClients = new LinkedList (); 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 mService; public IncomingHandler(BleService service) { mService = new WeakReference (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); } } } } }
The basic code is pretty simple, but there are some subtleties which are probably worth some explanation.
Firstly, InnerHandler is declared as static. Do not be tempted to implement this as a non-static inner class or you will start leaking memory, possibly quite heavily. The reason for this is that a non-static inner class can hold a reference to the instance of its containing class as it has direct access to its fields and methods. In very simple terms, the Java garbage collector will not destroy objects which are referenced by other objects (it’s actually a bit more complex than this, but the simplified view of what’s happening is sufficient for this explanation). The parent class of this Handler instance is the Service object (an Android Context), so if anything holds a reference to the Handler instance it will implicitly prevent the Service object from being garbage collected. This is what is known as a “Context leak”. It is also a very bad thing to do because a Context can be quite large.
The way that we avoid context leaks is to always declare inner classes as static if they are declared within classes which subclass Context. This then means that we have removed the main advantage of using an inner class: the ability to access is parents fields and / or methods. We can easily overcome this by using a WeakReference which allows us to hold a reference to an object without preventing it from being garbage collected.
So our InnerHandler class gets constructed with a reference to an instance of its parent class. Rather than hold this reference directly, it wraps it within a WeakReference. Later on it can call the get()
method on the WeakReference to obtain a reference to the parent class instance. We need a null check as this may be null if the parent instance had been garbage collected, but if it’s not null then we can use it in exactly the same way as we could use our patent instance in a non-static inner class.
The other thing worth mentioning is that we currently have two message types: Register and Unregister. These allow multiple consumers to subscribe to the message that the service will send out (as it gets updates from our BLE device). In our example app there is only the Activity which is going to get updates from the Service, but in a real world app there may be more components which require this data, so the publish/subscribe model seems appropriate.
Our Activity has slightly more going on:
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 WeakReferencemActivity; public IncomingHandler(BleActivity activity) { mActivity = new WeakReference (activity); } @Override public void handleMessage(Message msg) { BleActivity activity = mActivity.get(); if (activity != null) { //TODO: Do something } super.handleMessage(msg); } } }
It binds and unbinds to the Service in onStart()
and onStop()
respectively. In the ServiceConnection methods (which get called when service binding and unbinding operations complete), we send the appropriate messages to to Service Messenger to register and unregister the Activity‘s Messenger as a consumer.
After adding the appropriate declarations to Manifest (I have not shown them here, but they are visible in the source) we have a simple Activity and Service pairing which are able to communicate with each other. If we run the app it does nothing very much, but a look at logcat shows that our register / deregister is working as we would expect:
com.stylingandroid.ble D/BleService﹕ Registered com.stylingandroid.ble D/BleService﹕ Unegistered
So we now have a skeleton app in place which enables us to gather data in a Service and push updates to our UI.
Apologies that we’ve gone from last week’s article which contained no code, to this weeks article which has contained nothing specific to the subject matter of this series (Bluetooth LE), but we are now good to start on the BLE stuff, and in the next article it will all begin to come together as we look at the discovery process.
The source code for this article is available here.
Note: I am indebted to the insanely (and annoyingly!) talented Sebastiano Poggi for pointing out some factual errors in my original version of this post which resulted in an update. Any errors which remain are all mine!
© 2014, Mark Allison. All rights reserved.
Copyright © 2014 Styling Android. All Rights Reserved.
Information about how to reuse or republish this work may be available at http://blog.stylingandroid.com/license-information.
Hello Mark and thanks for this series. I am just reading through it…
One thing I am thinking about the last days (and it surprisingly just comes up in this article) is that “context leak” topic. I think I understood the problem at hand, but I am still trying to get my head around a recipe-type of behaviour.
In your case, the BleService object (a heavy context) holds a reference to a Messenger object which in turn hols a reference to your IncomingHandler object which than again holds a reference to the BleService (the context).
Since the IncomingHandler object is used nowhere else, its lifecycle should be bound to the Messanger object which is bound to the BleService. So wouldnt it be safe to directly save the reference to the service? (I am just talking about being save, not about good programming style).
If the IncomingHandler would hold a static reference to the service than I would understand the carefulness.
So are you doing the WeakReference just for completeness and coding style reasons or is there something I miss?
And sorry for not commenting on the topic of Bluetooth itself. I might do later when I read all of it 🙂
I think you’re missing a key concept, it is not the explicit references that I am addressing with this pattern, it is an implicit reference. If InnerHandler is not declared as a static class, it holds an implicit reference to the enclosing BleService instance (as it has access to the fields and methods of the enclosing BleService instance). By declaring it as static, that implicit reference is removed, and we use a WeakReference to enable an instance of InnerHandler to be able to access the BleService instance without holding a strong reference to it which would prevent it from being GC’d.
If we didn’t do this, then anything which references an InnerHandler instance (which appears small and lightweight) would hold an implicit reference to its enclosing BleService instance (which extends Context, a heavy object) and prevent the Context from being GC’d, and thus leaking it.
If this still does not make sense, then think about replacing the WeakReference with a standard, strong reference. It would leak the Context in exactly the same way, but it would be fairly obvious why holding a reference to an InnerHandler instance would prevent the Context that the InnerHandler is holding a reference to from being GC’d. That is exactly what a non-static inner class does, and that is what we’re avoiding by using this pattern.
I hope that makes sense.
Hello, Mark!
This article shows nice example and helped me alot. But there is one thing I still can’t understand: why you don’t null ‘mService’ reference in the finally block, but in the catch? I suppose the main idea was to null it in the onServiceDisconnected() method, but javaDoc says: “Called when a connection to the Service has been lost. This typically happens when the process hosting the service has crashed or been killed.” So, it wouldn’t be called even if the service will be stopped and it’s onDestroy() will be called.
Or maybe i mistaken and missed some important detail?
Thank you.
In the catch the service connection has definitely failed, so we null it. In the finally we always try and disconnect gracefully, and the cleanup is done in the onServiceDisconnected() callback.
But the onServiceDisconnected() callback is called only in the extreme case. http://developer.android.com/reference/android/content/ServiceConnection.html#onServiceDisconnected(android.content.ComponentName)
So we will still have mService reference even after successful unbinding.
It seems this is not a real problem, because we have decoupled context with WeakReference.
Hello,
I would like to ask you an important thing: does this code scan only the BLE devices that their name is “SensorTag” ? If for example my device is called “MyDevice, can this code discover it?
I run your code in API 23 and it finally says: “No devices found”.
Could you please give me some tips ? I don’t understand what is wrong. I am very beginner at Android apps.
I am looking forward to hearing from you.
kind regards,
Yes, this will only find devices named “SensorTag” there is an explanation of this in Part 3 of this series. There is further discussion in Part 5 where we look at searching based on GATT IDs.