Android coding techniques
Introduction
I’m writing this article to share some solutions I used in my day-to-day work to go around some Android choices that did not quite fit our needs or that are not clearly explained by documentation.
This article heavily depends on the source code associated to it. It’s very important that the reader downloads and reads the code; even better if you compile and run it in a device or emulator.
The source code is structured for Android Studio; the whole code can be cloned from GitHub using git clone https://github.com/mauricioandrada2018/androidtechniquesarticle.git
In each section I also provide the individual GitHub link for the code pertaining that section.
I purposefully only put a few comments in the code; during my career as a software developer I had to work with legacy code with comments like “Are you feeling lucky?”; one time I was working with code that had a large section block commented out and the only explanation the previous developer wrote was “DANGER!!!”. So think of this as practice on reading “code as documentation”.
I also strongly suggest the readers to modify and play with the code; the solutions I presented are basic, in the sense that I only care about the “sunny day” scenario. I encourage the reader to add code handling exception cases like broken connections to the services and other use cases that can make the code presented more robust and higher quality.
Communication between Components
One of the limitations in Android is communication with a launched Activity, even within the same app.
Once startActivity() is invoked neither the calling object nor the new Activity have a reference to each other and can only communicate via Intents, which is not always practical.
The Messenger class however provides a neat way to implement IPC communication between 2 objects, even if running in different process. So here is some code that implements the basic framework for that communication.
https://github.com/mauricioandrada2018/androidtechniquesarticle/tree/master/ActivityCommunication
The code implements just the minimum code necessary for one-way communication. It relies on the ability of sending a Messenger as an extra in an Intent which can then be used by the invoked Activity to send data back to the caller; this concept will be explored further in the following sections.
Sorting
Java figured out sorting a long time ago. In order to show how easy it is to do it, here is a problem and the solution using Comparable/Comparator.
https://github.com/mauricioandrada2018/androidtechniquesarticle/tree/master/Sorting
Textbook and tutorial examples normally only show the trivial cases for using these interfaces where comparisons are limited to one or two fields and the comparisons are hard-coded; in this section I intend to expand those concepts a little further with relatively more complex examples.
Problem:
Create a function that gets a string with a sentence and an integer N and do the following:
- Count the frequency of each word, ignoring case (Cat and cat are the same)
- Sort the words descending by frequency (words with the highest frequency go on top)
- If 2 words have the same frequency, sort them alphabetically
- Returns an array with the N first words from the sorted list
Example:
Sentence: “The dog and the cat are friends. The dog is brown and the dog has many friends.”
get_word_list(sentence,1): {“the”} //the occurs 4 times in the sentence and is the most occurring word
get_word_list(sentence,2): {“the”, “dog”} //dog occurs 3 times and is the second most occurring word in the sentence
get_word_list(sentence,3): {“the”,”dog”,”and”}// both and and friends occur 2 times but “and” is higher in the list so it’s the only word included
get_word_list(sentence,4): {“the”,”dog”,”and”,”friends”}// now friends can be included
Here is a solution:
package mauricio.com.sorting;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import java.util.Arrays;
import java.util.HashMap;
import java.util.StringTokenizer;
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String sentence = "The dog and the cat are friends. The dog is brown and the dog has many friends.";
String[] list = get_word_list(sentence,4);
if (list != null) {
for (String item:list)
Log.v("TestSort",item);
}
}
String[] get_word_list(String sentence,int num_words) {
if (num_words < 0 )
return null;
if (num_words == 0)
return new String[0];
if (sentence == null)
return null;
String[] result = new String[num_words];
StringTokenizer stringTokenizer = new StringTokenizer(sentence.toLowerCase()," ,;:'!?.");
/**
* This Hashmap makes it easier to find out if a word has been countet yet
*/
HashMap<String, Word> map = new HashMap<>();
while (stringTokenizer.hasMoreTokens()) {
String word = stringTokenizer.nextToken();
Word item = map.get(word);
if (item == null) {
item = new Word(word);
}
item.count++;
map.put(word,item);
}
Word[] wordList = map.values().toArray(new Word[]{});
Arrays.sort(wordList);
for (int i = 0; i < num_words; i++) {
result[i] = wordList[i].word;
}
return result;
}
private class Word implements Comparable<Word> {
private final String word;
private int count;
public Word(String word) {
this.word = word;
}
@Override
public int compareTo(@NonNull Word word2) {
/**
* Compares the counts and if they are the same sort alphabetically in ascending order
*/
int result = word2.count;
if (result == 0)
return this.word.compareTo(word2.word);
return result;
}
}
}
What if the fields and rules for sorting are dynamic, in a SQL-like fashion? Can we still use the Comparable/Comparator approach? It turns out we can.
In the code below we use a JSON object to define the rules; we also use the Comparator interface instead of making Word implement Comparable:
package mauricio.com.sorting;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.StringTokenizer;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String sentence = "The dog and the cat are friends. The dog is brown and the dog has many friends.";
String[] list = get_word_list(sentence,4);
if (list != null) {
for (String item:list)
Log.v("TestSort",item);
}
}
String[] get_word_list(String sentence,int num_words) {
if (num_words < 0 )
return null;
if (num_words == 0)
return new String[0];
if (sentence == null)
return null;
String[] result = new String[num_words];
//Parses out all the words; notice how we invoked toLowerCase() to make the sentence case insensitive
StringTokenizer stringTokenizer = new StringTokenizer(sentence.toLowerCase()," ,;:'!?.");
HashMap<String, Word> map = new HashMap<>();
/*
Using a HashMap to get the list of words along with the count for each word
The idea here is to use the word as an index to an instance of Word so we can quickly find it and increment the count
*/
while (stringTokenizer.hasMoreTokens()) {
String word = stringTokenizer.nextToken();
Word item = map.get(word);
//First time we find a word, put it in the HashMap along with the instance
if (item == null) {
item = new Word();
item.word = word;
}
item.count++;
map.put(word,item);
} //done with populating the list with the words and their amounts
/*
On this section we'll setup the JSON with the nested sorting rules.
*/
JSONObject rulesRoot = new JSONObject();
JSONObject rules1 = new JSONObject();
JSONObject rules2 = new JSONObject();
try {
/**
* rules are: first sort by count in descending order; if same count then sort by word (alphabetically) in ascending order
* The column names must match the names for the fields in Word class.
* In the Comparator we'll use reflection to get values for each column.
*/
rules1.put("column","count");
rules1.put("order","desc");
rules2.put("column","word");
rules2.put("order","asc");
rules1.put("rule",rules2);
rulesRoot.put("rule",rules1);
} catch (JSONException e) {
e.printStackTrace();
}
GenericComparator genericComparator = new GenericComparator(rulesRoot);
Word[] wordList = map.values().toArray(new Word[]{});
Arrays.sort(wordList,genericComparator);
for (int i = 0; i < num_words; i++) {
result[i] = wordList[i].word;
}
return result;
}
private class Word {
private String word;
private int count;
}
class GenericComparator implements Comparator<Word> {
private JSONObject sortingRules;
public GenericComparator(JSONObject sortingRules) {
this.sortingRules = sortingRules;
}
private Object getValue(Word w1, String column) {
try {
/*
Using reflection to get the field names.
The Word class is not Comparable but the fields are assumed to be.
*/
Field f = w1.getClass().getField(column);
return f.get(w1);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
@Override
public int compare(Word w1, Word w2) {
int result = 0;
try {
JSONObject rule = sortingRules;
//loop through the nested rules
while (rule.has("rule")) {
rule = rule.getJSONObject("rule");
String column = rule.getString("column");
String order = rule.getString("order");
/**
* Word class is not comparable but its fields are assumed to be.
*/
Comparable obj1 = (Comparable) getValue(w1,column);
Comparable obj2 = (Comparable) getValue(w2,column);
boolean ascending = order.toLowerCase().equals("asc");
int aux = obj1.compareTo(obj2);
if (aux == 0) {
continue;
}
if (ascending)
result = aux;
else
result = -aux;
break;
};
} catch (JSONException e) {
return result;
}
return result;
}
}
}
The JSON object is convenient because we can embed the rules hierarchically into each other, making it suitable for use with the recursive sorting function inside the GenericComparator.
Make sure to play with the classes by adding more rules or playing with different data sets.
Try to answer these questions:
As implemented, will the solution properly handle non-existing columns?
What about changing it to handle small and capital cases differently?
Can you add more nested rules?
What if the fields of class Word are not Comparable? What changes would be needed to fix the solution?
Bound Services and Callbacks
From the Android documentation one would learn that all the calls to a bound service are synchronous. But what if one wants to make the Service method asynchronous? Well, it’s possible to use AIDL to create remote listeners invoked by the Service and implement asynchronous Service calls.
Here is the source code:
https://github.com/mauricioandrada2018/androidtechniquesarticle/tree/master/Callback
In the code you can see how we defined the listener classes also using AIDL and use them inside the AIDL file defining the service interface itself.
You can see in the code that, in order to have this t work, we have to define classes extending the Stub class automatically generated for both AIDL files in the project.
Try to modify the code so the Service runs in a separate APK; you may have to replace calls to startService() with startForegroundService() depending on the Android OS version you’re working with.
Global variables
Java does not have the concept of global variables, an that’s for a very simple reason: object-oriented classes are supposed to be self-contained and if data from an object must be used by another object then it must be sent through a message. This implies that the object calling the message must have an instance of the target object receiving the message.
Android made this harder by allowing applications to instantiate classes indirectly (e.g. via startActivity() or startService()) so the calling object does not have a reference to the created object; therefore its common for apps to use the Singleton design pattern to share data among Activities and Services.
This of course is not a good approach, specially when it comes to garbage collection. Singleton classes are known to be hard if not impossible to extend. Also access must be carefully synchronized so it is thread safe.
Here we’ll show how to implement global variables using a Service, a static variable and a ContentProvider and explore the pros and cons of each.
In all cases we’ll also implement wrapper classes that allow the activities to read and write to the “global” variables while hiding the implementation so you can play with all of them and decide which one works better for you.
As a bonus we’ll implement the wrapper classes using the Factory design pattern, one of the extremely useful design patterns along with Observer/Observable and Consumer/Producer.
As an extra bonus we’ll again use Messenger for IPC communication between classes and the Service (this is kind of a common topic across this article).
https://github.com/mauricioandrada2018/androidtechniquesarticle/tree/master/GlobalVariable
Here are some pros and cons of each approach:
- Using a ContentProvider backed by a database or file requires your global objects to be Serializable
- Using the Service approach requires your global objects to be Parcelable, so they can be stored in a Bundle
- Using the static variable approach you have the issue with the garbage collector; however the approach using a static array allows for the the actual global variable to be garbage collected if needed.
So, which one works best for you? Can you find any other cons for each approach (e.g. CPU overhead, memory overhead, etc.)?
You’ll see that I only added synchronization to the static variable implementation to make it thread safe. Do the other implementation require such protection?
I also want to bring your attention to the Service implementation. Since we are not binding our Activity to the Service but we are using a Messenger instead, the communication is by nature asynchronous. However, we want the methods in the Service wrapper to behave synchronously. In order to achieve that we had to do a few things:
- We use wait()/notify() to halt the main thread and allow the Service to process the Message sent to the Messenger; once the Message is processed then the main thread is released
- We rely on a worker thread to handle Messages coming from the Service; if we did that in the main thread we’d not be able to process the incoming messages once the main thread was blocked
- The Service — which by default runs in the app main thread — must be started in a different private process. That’s what we did using the process attribute for the Service in the Manifest file.
<service android:name=".GlobalVariableService"
android:process=":remote"/>
We’ll use some of these concepts in the section about writing apps with decoupled components.
Decoupling application components
MVC — model-view-controller — and its variants has been around for many decades now and it has been incorporated in many different programming languages and frameworks (Java, Swift, etc.).
The Model is responsible for manipulating, storing and retrieving data; the Controller is the one that knows how to display the data; the View is the actual visuals that displaying the data.
Android is not different: it uses the model-view-adapter variant and recently introduced the model-view-viewmodel pattern via AndroidX/Jetpack. In the model-view-adapter model the data is processed and stored by a Service — which implements the Model — and exposed to the UI via an AdapterView that bridges the link between the data representation and the visual representation of the data.
So if that is there then why am I writing this section?
Well, while in Android the Model role can be implemented by a Service and a ContentProvider, the Controller and View functionality are grouped together in the Activity (or FragmentActivity) class.
So would it be possible to achieve an even greater decoupling where the UI is completely “dumb” and just displays the data and detect user actions, with no knowledge of any other components in the app except the Controller and its interface?
The framework presented below is my proposal for such highly decoupled architecture where each component is completely self-contained and only communicate with the Controller. The proposed architecture is inspired by the way distributed web applications are built but because it takes advantage of the optimized IPC mechanism in Android there’s minimal impact in performance.
You’ll notice that the communication model between components and Controller follows the Messenger pattern we introduced in section XX and the Controller is designed as a state machine where state transitions are determined by the current state and events; events can be user actions or external events (e.g. unsolicited data received from the network).
The Controller is implemented as a Service so the other components don’t have to worry about managing instances. I also included a ControllerLib that abstracts the communication details between components and the Controller.
https://github.com/mauricioandrada2018/androidtechniquesarticle/tree/master/DecoupledApp
So as you can see in the code the communication between Activities and Services is done indirectly via a Controller using a JSON document that has some fixed headers that allow Activities to route the data to the proper Service and Services to send data to the proper Activity.
The Activity then displays the data anyway the developer desires.
All the calls are asynchronous, so in our example app, for the cases that require synchronous behavior, we simply disable the UI until we get the data we need.
Another important functionality in the Controller is that it keeps track of the different components so Activities and Services don’t have to worry about broken binders and remote exceptions.
Do you have other ideas on how to decouple Activities, Services and BroadcastReceivers? What about ContentProviders, would it make sense to also add them to the Broker?
Try to measure the overhead of this implementation compared to a direct IPC call between Activities and Services using AIDL. Do you see any major performance issue?
As designed today the Controller service is only instantiated by the main Activity; try to modify the code so the Controller is started by some unsolicited event. Is there any advantage in doing so?
I also left a small bug in the app; if you close the app you’ll see it crashes. Can you figure out how to fix this?