Android native applications and micro-services architecture

Mauricio Andrada
5 min readApr 11, 2019

--

Introduction

In this article we’ll explore the use of the micro-services architecture as the structure for developing native Android applications.

The way the Android framework organizes applications in terms of Activities and Services is particularly suitable for separating user experience from business logic (to a certain point).

On top of that the current prevalence of Agile approaches for software development, with multiple teams working on the application code at the same time, is greatly facilitated if the code is loosely coupled.

Hence it seems natural to apply the micro-services approach to native applications.

Concept

I will use an approach that mimics the way micro-services are used in cloud-based applications.

When using a cloud-based app the user opens the browser, go to some URL and interacts with the page presented. What is presented to the user may be the consolidated result of data provided by multiple micro-services potentially running in different servers and accessible via their own URL and APIs.

Similarly, one can imagine an analogous flow for an Android application: the user opens the app’s main Activity and interacts with the presented screen; the information displayed on the screen may come from multiple Services either automatically initiated by the Activity or as a result of user actions. Requests from the Activity are routed to each service by binding to each front-end Service using an Intent — the equivalent of an URL; internally each Service can then access other Services to complete the requested operation.

Structure

Then you’d cleverly ask “Well, this does not seem very useful; the Activity or its Fragments still must know which Service to talk to in order to get the data” and you’d be absolutely right. So how can we structure the Activities and Services in such a way that the Activity and its parts must be aware of just one or two Services?

The structure proposed here is to use a Binary Tree of Services, where each Service is responsible to process a very small set of all the possible requests that can be issued by the Activity.

Your software would look like this:

As you can see, when the Activity makes a request, that request goes to the MainService and then cascades down through the tree until some Service can handle it; if the request has sub-requests that are not handled by a Service, the sub-requests are then cascaded down the sub-tree.

Along with the request the Activity provides an instance of a Messenger that will receive the JSON response; that Messenger is also cascaded down to each Service.

The results are aggregated in a response by each Service and passed to the next; the last Service in the sequence then invokes the Messenger provided by the Activity with the full response.

Analysis

The main advantage of such structure is the loose coupling between Services; if a new request is added to the application only 3 components are modified: the Activity making the request, the Service either added or modified to handle the new request and — in the case of a new Service — the one that will serve as the parent node.

Even better, the Activity can be modified to use a new request and handle the response without having to wait until the actual Service implementation is deployed.

Another interesting aspect of using a binary tree is that Services can be organized by functionality, from a generic feature down to detailed operations.

But you are a savvy developer and already know that there’s a catch (or catches). And once again, you’d be right.

Services, in Android, normally run in the main thread; if we keep it that way there’s a possibility that we’ll have to traverse the whole tree sequentially before we reach a service that can handle the request; that does not sound very efficient.

This can be mitigated by creating a separate process for each Service (that is done in the AndroidManifest.xml) when they are launched; I’ll leave this as an exercise to the reader.

By doing this the commands can be issued in parallel to all Services; this improves performance but with a cost: now your app may require a lot of resources to function and resources, in a smartphone, equal battery life.

One way to mitigate this is to design the binary tree in such a way that it is balanced; in other words, starting from the root node the tree knows which branch to follow in order to get to the Service that handles the command so it minimizes the number of Services engaged in the search. In the source code we’ll show a way of doing this by defining requests in such a way that it gives hints where to go.

The other issue with this parallel approach is that even if a Service handles the command the search continues through the other branches of the tree which is a waist of resources.

To mitigate that there must be a way for all services to be notified when a request was handled and the search can stop. We’ll not implement such mechanism, this will be left as an exercise to the reader.

Finally, there’s the tree organization itself; where each Service is placed in the tree is important. Services that are used more often should be reached earlier than services that are seldom used; services must be grouped together by functionality so it’s guaranteed that a request will be handled by a branch in the tree without the code having to traverse the whole tree.

Results

The source code will implement about 7 Services used by 1 Activity; the Services don’t do much other than report if they handled a request and register timestamps so I could profile the solution in terms of performance and compare to the solution where the Activity invokes the Services directly.

I created 2 types of requests: 1) the ones that can be handled directly by a Service 2) the ones that require collaboration between multiple services; this was done to show how the results are aggregated and cascaded back to the Activity.

You’ll see that the implementation of the service is not quite the same; some services must be aware of which service will handle the “continuation” of a request.

We are using IntentServices to minimize the resource usage — once a Service is done with its work it can stop — but nothing prevents us from using bound services; we’ll leave this as an exercise to the user.

As mentioned before, we are using a Messenger for the communication between Services and Activities; if you decide to use bound Services than you can use AIDL to implement a Listener that is directly invoked by the last Service to send results back.

You can get the source code for used on this article at:

https://github.com/mauricioandrada2018/androidmicroservices.git

--

--

Mauricio Andrada

20+ years of experience with software development in the Telecommunications industry; currently DMTS at Verizon working on applications for 5G, AI and MEC