Friday, August 21, 2015

Services and Broadcast Receivers

The Google Cloud Messaging (GCM) implementation was full of services, intents, etc., and while I understood the gist, I felt that it was important to rewind a bit and understand services and working with them a bit more.

The first thing I recall from the GCM implementation is the intent-filter and implicit intents on the services and receivers. So, we learn about these in the context of activities at: http://developer.android.com/training/basics/intents/index.html. The key phrase is:
When your app is installed on a device, the system identifies your intent filters and adds the information to an internal catalog of intents supported by all installed apps.

note: The discussion about services running in the main thread got me sidetracked;  thinking about how Android works under the hood; essentially with a message loop: http://codetheory.in/android-handlers-runnables-loopers-messagequeue-handlerthread/.

Looks like a common pattern is to simply use the IntentService class as it will allow for background processing; separate thread and stopping itself, e.g., a file download etc.

Also, one important thing to note is that services are singletons, i.e., subsequent calls to startService only trigger the onStartCommand method (not the onCreate).

In our example, we will create an IntentService that will run with a delay of five seconds and throw up a status bar notification.


Looking back at the GCM example code then: then: https://github.com/larkintuckerllc/HelloAndroid/commit/3c023cca2c460a81c8f580174c13276b58e10067

we have three exported = false services (i.e., can only be started by components in the application).

RegistrationIntentService

This service has no intent-filters and thus must be started explicitly, i.e., initiated by a call including the class itself.  It extends IntentService; so it runs in its own thread and exits on its own.  It is called from MainActivity's onCreate method (essentially when the application is launched).

Looking at the functionality, it is designed to register the application with GCM (listening to certain topics) and then to pass an identifying token to a backend server (say to allow it to message it individually).

MyGcmListenerService

This service registers itself with the action of (but is can only be initiated by the application itself) com.google.android.c2dm.intent.RECEIVE. It extends GcmListenerService (unclear if it is IntentService under the hood).

Looking at the functionality, it is designed to take action when the GCM message comes in.

note: At first, it was not clear as to why this service had to be started with an implicit intent (likewise with the MyInstanceIDListenerService  service).  But, my general understanding is that it is Google's code that needs to trigger these services and it is not aware of the actual classes (in JavaScript, one would have simply passed call-back functions to Google's code).

MyInstanceIDListenerService

This service registers itself with the action of (but is can only be initiated by the application itself)
com.google.android.gms.iid.InstanceID It extends InstanceIDListenerService (unclear if it is IntentService under the hood).

Looking at the functionality, it is designed to take action (calling the RegistrationIntentService) when something happens to the service that requires it to re-register.

Broadcast Receivers

Just when I was getting a handle on services, it still want clear how the GCM messages received outside the application were triggering the internal only services, e.g., MyGcmListenerService (remember these were not exported).

But, there is another bit of the example that I did not understand, i.e., the receiver in the AndroidManifest.xml and the BroadcastReceiver code in MainActivity's onCreate method.

The answer lies in broadcast receivers: http://developer.android.com/guide/topics/manifest/receiver-element.html.  What makes them a little confusing is that they are somewhat similar to services in that they can respond to intents (e.g, seems very similar to our IntentService).  Seems like the primary difference is that implicit intents and services will only be sent to a single service (OS will limit it to the one that is picked by the user) and intents and broadcast receivers can go to multiple.

com.google.android.gms.gcm.GcmReceiver

This broadcast receiver is registered in AndroidManifest.xml and implemented in Google's code. It is exported with an intent filter looking for:

action: com.google.android.c2dm.intent.RECEIVE
category: com.larkintuckerllc.helloandroid

It also requires the sender to have the permission: com.google.android.c2dm.permission.SEND

This is the missing link that is catching the GCM messages and dispatching them to the locally defined services: MyGcmListenerService and MyInstanceIDListenerService.

mRegistrationBroadcastReceiver

One more broadcast receiver is setup; this one only local (not exported) and is created in the MainActivity's onCreate method (one of those funky anonymous classes) and registered (and deregistered) in the activity's onResume (and onPause) methods.

This is how the MainActivity can be informed when the RegistrationIntentService completes (the broadcast is send at the end).

Security

First one needs to understand permissions: http://developer.android.com/guide/topics/security/permissions.html.

In the GCM example the broadcast receiver registered in the AndroidManifest.xml requires the permission com.google.android.c2dm.permission.SEND on the sender before receiving.  Seems that an application can simply set itself to use this permission to fake send GCM messages (but guess the user would have to allow this to install).

The only other thing that is non-obvious is how does the GCM solution prevent other applications from snooping on the GCM messages (they are broadcast).  Guessing the answer is that the messages are encrypted and unlocked by GcmReceiver using the secret data in the google-services.json file.

Whew!

No comments:

Post a Comment