Threading best practices : 3 ways on Android Things

Working with Android Things means connecting to peripheral sensors & actuators to communicate sending or receiving commands and data. As with all Android development, you should be evaluating the impact of this and consider offloading the effort onto another thread wherever possible. So what is best practice for threading on Android Things?

Like with all demo code, the threading solutions you see in some examples have to be taken with a pinch of salt. If threading is not the focus of the example then it’s often done in the quickest or simplest way to enable a clear demo. While this might help people understand a specific point, it doesn’t help advocate the best approach to threading. Beware of blindly following the threading solutions in Android Things demo code. Let’s explore some different patterns to find out why.

If you prefer a more theoretical and discussion-driven blog with less code then take a look at this blog post Android Things – 3 ways to do threading discussion.

#1 Directly on the main thread

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            bus = service.openGpio(GREEN_LED_PIN);
        } catch (IOException e) {
            throw new IllegalStateException(GREEN_LED_PIN + " bus cannot be opened.", e);
        }

        try {
            bus.setValue(true); // turn on LED
        } catch (IOException e) {
            throw new IllegalStateException(GREEN_LED_PIN + " cannot be written.", e);
        }
    }

Why?

– The 5 second timeout ANR (Activity Not Responding) check has been removed from Android Things. This means you can get away with doing long running operations on the main thread.

– There are no other apps, the system is yours, no need to worry about being a good citizen, every device is not a village, the system (and probably the hardware device) is all under your control and you can make the most of all the resources available.

– No need to worry about callback hell. Doing things synchronously means no thread hopping, potentially no need to use callbacks because everything can just be a return value and each line of code can wait for the last to finish, _in theory_.

Why not?

– If you have UI components it can still cause jank. Android Things allows you to have an optional UI. UI is drawn on the Main Thread (and some on the Render Thread but lets ignore that complexity). The original reason for the ANR was to avoid blocking the Main Thread with non UI work, so if you add a UI component to your app you could come back to this problem of having a janky, slow, unresponsive UI because your Main Thread is busy doing other things.

– You may want to do more in the future. Right now it might make sense to do the work on the main thread because it’s _”just this one thing”_ and you could say YAGNI, but if you know in the future this will get more complicated (and most the time it does), then doing it on the Main Thread now will cause you a lot of pain refactoring away from that decision in the future.

– It goes against everything you’ve learnt so far. As a developer you will already know practices for threading and doing things as separate tasks. Why go against that knowledge and not use that knowledge if it wouldn’t cause you to slow down. Just because something is easy i.e. coding without Threads, doesn’t mean it’s right, if you have a de facto pattern you use for threading, use it on Android Things as well.

#2 Asynchronously but still on the main thread

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            bus = service.openGpio(GREEN_LED_PIN);
        } catch (IOException e) {
            throw new IllegalStateException(GREEN_LED_PIN + " bus cannot be opened.", e);
        }

        Handler ledToggleHandler = new Handler(Looper.getMainLooper());
        ledToggleHandler.post(turnOnLed);
    }

    private final Runnable turnOnLed = new Runnable() {
        @Override
        public void run() {
          try {
              bus.setValue(true); // turn on LED
          } catch (IOException e) {
              throw new IllegalStateException(GREEN_LED_PIN + " cannot be written.", e);
          }
        }
    }

Why?

– This allows UI components to draw smoothly. Joining the queue for execution means you don’t block the Main Thread and as we discussed earlier this means the UI component can be drawn with less potential for jank.

– You don’t have to worry about how many threads there are. Sometimes hopping between threads can cause real evasive bugs, especially when debugging a lot of callbacks. Also the problem of only being allowed to draw UI components on the Main Thread never appears.

– It’s easier to understand order of execution. Always working on one thread means things cannot happen in parallel, therefore you are never wondering if something could have quickly happened before you expected it to, least not from a multithreaded perspective.

Why not?

– You may want to do more in the future. Right now it might make sense to post the work on the main thread because it’s _”just this one thing”_ and you could say YAGNI, but if you know in the future this will get more complicated (and most the time it does), then doing it asynchronously to the Main Thread now will cause you some pain refactoring away from that decision in the future.

– It seems like a solution but can still lead you somewhere you don’t want to be. You can be caught out with UI jank doing long running work if the messages you post are too big. Also you have less control of execution times, posting 5 messages yourself and the system posting messages, you assume they will be executed as soon as possible, but you don’t have control over this priority.

– It goes against threading you’ve learnt so far. As a developer you will already know practices for threading. Why go against that knowledge and not use that knowledge if it wouldn’t cause you to slow down. Just because something is easier i.e. posting single threaded messages, doesn’t mean it’s right, if you have a de facto pattern you use for threading, use it on Android Things as well.

#3 On a background thread

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        try {
            bus = service.openGpio(GREEN_LED_PIN);
        } catch (IOException e) {
            throw new IllegalStateException(GREEN_LED_PIN + " bus cannot be opened.", e);
        }

        HandlerThread handlerThread = new HandlerThread("MyBackgroundThread");
        handlerThread.start();
        // or use your favourite library / ASyncTask
        ledToggleHandler = new Handler(handlerThread.getLooper());
        ledToggleHandler.post(turnOnLed);
    }

    private final Runnable turnOnLed = new Runnable() {
        @Override
        public void run() {
          try {
              bus.setValue(true); // turn on LED
          } catch (IOException e) {
              throw new IllegalStateException(GREEN_LED_PIN + " cannot be written.", e);
          }
        }
    }

Why?

– Follows conventions learnt over time. Means you have a lot of experience in this area. Also the community has built up a set of practices and techniques that it is beneficial to take advantage of.

– Separation of processing and UI work. A good separation of concerns is always important, and keeping the communication/data sharing tasks separate from the UI drawing work is a part of this. It leans itself towards discussions of improving architectures with separation ideas like Model View Presenter.

– Future-proofing. No matter how complex the future communication becomes, you are now working on a background thread and don’t have to worry about if you may cause the system upset or UI jank by the amount of communication happening on your thread.

Why not?

– More complex for simple tasks. For doing simple things, especially like demo’s show this may be an overly complex solution. There is no need to add extra threads and therefore potential callbacks if the requirements stay simple.

– Callback hell ensues. Mo threads Mo problems. Nobody wants callback hell, but it is a slow creep and the first step to this hellish creep is creating your own threads to do separate work.

– Threading should always be avoided if possible. Deadlock, Livelock and starvation are problems no developer ever wants to face. Then the discussions start about performance overhead and the costs of thread hopping. It all eventually will hurt your brain.

Android Things threading best practices

Concluding from the above. It can seem like there’s no right answer. Depending on your situation you should take into account what’s going to change, when change is likely and what your development needs are, to help you make an informed choice.

Developers who are familiar with Android should find using background threads straightforward, especially when de facto libraries are in use. However, it is necessary to make a conscious effort to write the code this way from the start.

It is important to understand that example code on the internet has often been created to explain another concept, so it uses a quick and dirty approach to threading and shouldn’t be considered best practice. Don’t let the sheer number of these examples influence what you perceive as platform best practice when it comes to threading.

One thought on “Threading best practices : 3 ways on Android Things

Comments are closed.