This is an old revision of the document!


Lab 4. Threading in Android

Objectives

  • Learn to execute code on multiple threads in an Android app
  • Understand and practice the basic way of multi-threading in Android using handlers, runnables and thread pools.
  • Learn why AsyncTasks are not recommended to be used.
  • Revise basic concurrency objects available in Java

Threading in Android

When an application is launched, the system creates a process for it (or more, if it declares services in separate processes) and a main thread (UI thread). As discussed in the previous labs, any code you write in your components (unless they are Intent Services) is run on the main thread. But an app might need to perform processing in worker threads, for example, in case of networking or database operations. If we block the UI thread while performing computational intensive code or waiting for certain events, the system will display an ANR (Application Not Responding) dialog.

For working with threads and scheduling tasks we can leverage language-specific APIs for threading, Android specific components or many third-party libraries. In this lab we will explore some of the thread management mechanisms from the first two categories and also provide recommendations for third-party libraries.

:!: Calling methods from the Android UI API must be done on the UI thread. For example, if you want to show a toast from a worker-thread, use runOnUiThread.

Regarding the relationship between the threading components and the lifecycle the UI components (activities and fragments), certain threading objects can be tied and other not. The IntentServices we discussed in Lab3 are not tied to the activities or fragments lifecycle, but the AsyncTasks (Android specific threading components), are terminated when the UI components are destroyed.

In Lab 3 we presented IntentServices which also run on separate threads. The mechanisms we will focus on this lab, some of them you might be familiar with from Java:

  • Runnables and Handlers
  • ThreadPoolExecutor
  • AsyncTasks - not recommended
  • JobScheduler

Other threading mechanisms we recommend:

  • Reactive programming using RxJava or RxKotlin - do not need to explicitly create threads, just choose a scheduler
    • proceed with caution! (completely changes the design of the app, choose it depending on what you need to do, e.g. it's very useful if your app relies heavily on event-driven logic)
  • Coroutines - Kotlin specific light-weight threads
  • androidannotations - not to be confused with the annotations offered by the Android API

When the IDE generates override methods it sometimes adds the @WorkerThread annotation. This does not mean that the method will be implicitly run on a worker thread! It is a recommendation or a warning that the developers must explicitly use them on worker thread. They have no effect at runtime, only the linters may check them.

Handlers and Runnables

In Android we can create new threads by creating objects of type Thread and Runnable, as we do in Java programming. Since we also need communication with the Android's UI components, the SDK offers a specific type of objects: Handler.

Using runnables

In the following example we trigger a new thread everytime we press a button:

findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        Log.d(TAG, "In a different thread " + Thread.currentThread());
                    }
                };
                Thread thread = new Thread(runnable);
                thread.start();
            }
        });
 

What happens if we want to show a Toast from the run() method?

public void run() {
    Log.d(TAG, "In a different thread " + Thread.currentThread());   Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
}

If we run the app and click the button we get a crash caused by a fatal exception:

2019-03-17 12:17:37.370 4008-4057/com.smd.lab4app D/MainActivity: In a different thread Thread[Thread-2,5,main]
2019-03-17 12:17:37.378 4008-4057/com.smd.lab4app E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.smd.lab4app, PID: 4008
    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

So, what are our options now?

We can post work on the UI thread using the View's post(Runnable) method. In our example, since Button extends View, we can:

button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
 
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    Log.d(TAG, "In a different thread " + Thread.currentThread());
 
                    button.post(new Runnable() {
                        @Override
                        public void run() {
                            Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            };
            Thread thread = new Thread(runnable);
            thread.start();
        }
    });

Using Handlers

We need a messaging mechanism between threads, and Android offers Handlers. These are closely connected to other objects needed for messaging:

  • Looper - provided to a Handler, it runs the message queue in a loop
  • Message - the message to be sent to a Handler
  • MessageQueue - the messages dispatached to a Looper, the developer doesn't interact directly with it, most interaction is through the Handler class
  • HandlerThread - a thread that has a looper

The Handler is attached to a thread, which means that the code that handles the received messages is run on that thread. If we want to update the UI, we attach the Handler to the main thread obtained with Looper.getMainLooper(), as in the next example:

Creating a Handler
handler = new Handler(Looper.getMainLooper()) {
    @Override
    public void handleMessage(Message inputMessage) {
        String messageText = (String) inputMessage.obj;
        Toast.makeText(MainActivity.this, messageText, Toast.LENGTH_SHORT).show();
    }
};
Sending a message from runnable to handler
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
 
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
            Log.d(TAG, "In a different thread " + Thread.currentThread());
 
            Message message = handler.obtainMessage();
            message.obj = "hello";
            message.sendToTarget();
            }
        };
        Thread thread = new Thread(runnable);
                thread.start();
    }
});

From the Handler docs: There are two main uses for a Handler:

  1. to schedule messages and runnables to be executed at some point in the future
  2. to enqueue an action to be performed on a different thread than your own.

Schedule tasks on a Handler

Using the Handler's postDelayed method we can schedule runnables to be executed in the future, on that thread. In this way we can also implement timers in Android.

One of the common scenarios is to handle timeouts for the tasks or operations necessary in the app. For instance, if we initiate a connect to a remote device, we can schedule a postDelayed runnable that will inform us when it timeouts. If the task ends in time, it also has to remove the timeout runnable it scheduled. In the next example we show how to create and schedule a runnable.

HandlerThread initialization
private HandlerThread timeoutHandlerThread;
private Handler timeoutHandler;
 
private void postDelayedExample() {
    timeoutHandlerThread = new HandlerThread(TAG);
    timeoutHandlerThread.start();
    timeoutHandler = new Handler(timeoutHandlerThread.getLooper());
}
Scheduling a runnable
public void startTask() {
    //create the task we need to track the timeout for
    //....
    scheduleTimeout(getTimeoutRunnable(), TIMEOUT_MS);
}
 
public Runnable getTimeoutRunnable() {
    return new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Timeout - task too more than " + TIMEOUT_MS/1000 + "seconds");
        }
    };
}
 
// called when the task starts
public void scheduleTimeout(Runnable timeoutRunnable, long millis) {
    removeTimeout(); //remove previous timeouts
    timeoutHandler.postDelayed(timeoutRunnable, millis);
}
 
// this is called when the tasks ends either in success or in error
public void removeTimeout() {
    timeoutHandler.removeCallbacksAndMessages(null);
}

Scheduling work can be done also using Android’s JobScheduler. In this case we can trigger tasks not only based on a specific time, but also by certain conditions (battery, network) defined when creating the job (see JobInfo.Builder). The scheduled job is handled by a JobService, which, being a service, runs on the UI main thread. Therefore we need to explicitly start a thread if we want that task to be executed on a worked thread, asynchronously, like in this example

If we need to only schedule a task to happen at a specific time, regardless of other conditions we can also use AlarmManager.

ThreadPoolExecutor

In the previous examples we executed the runnables on a thread that we created and started explicitly. One other way of running them is to use the Executor objects provided by the java concurrent package. We submit runnables to Executors, which, depending on their implementation, execute them on worker threads. One type of executor of interest in this lab is ThreadPoolExecutor. If we need to parallelize several operations (E.g. download tens of photos, decode them etc) we can use ThreadPoolExecutor as in this tutorial.

When using a ThreadPoolExecutor, we need to specify not only its size parameters but also a BlockingQueue in which we add the runnables that need to be executed (our tasks).

AsyncTask

Using AsyncTasks we can run logic in background on a thread and pass results back to the UI (main thread).

The AsyncTask is an abstract task and developers need to extend it and implement at least its only abstract method, doInBackground, which is always run on the dedicated thread. Inside it we can call publishProgress if we want to send updates to the UI while the task is still running. The rest of its methods are run on the main UI thread and usually called implicitly: onPreExecute, onProgressUpdate, onPostExecute. You should not call them from your UI components, just implement the ones you need, they will be called by the system.

The official documentation recommends the AsyncTasks only for short operations (a few seconds)

AsyncTasks offer the advantage of easily running code on a different thread and also send the results to the UI, but they are not recommended for operations longer than a few seconds, because of their dependence on UI.

  • The UI can be blocked in some cases because pre and post execute methods are run on the UI thread: e.g. when one task takes more than expected, and other tasks are triggered from the UI, the second task’s preExecute method is blocked until the first task’s postExecute method is called.
  • Can lead to memory leaks if the activity that started the task was destroyed in the meantime.

Synchronization mechanisms

  • Synchronized - java keyword
    • Can be used for methods or specific blocks of code
    • Implemented as a monitor
    • Only one thread can execute the synchronized region, the others are suspended until they can enter the locked region.
  • Synchronized collections: e.g. ConcurrentHashMap
  • Atomic variables: e.g. AtomicInteger

Tasks

Task 0

Create an Android Studio project and name it `Lab 4`.

Task 1 (3p)

In this task we will download a file from a URL and will inform the user about the progress and final result. All of this will be done on a background thread using an AsyncTask.

  • In MainActivity create a private inner AsyncTask class and name it FileDownloadTask. The AsyncTask will receive a URL, will send an Integer as update and will return a Long result.
  • In the FileDownloadTask override onPreExecute(), doInBackground(), onProgressUpdate() and onPostExecute(). Add a debug Log statement with the current method and thread name.
  • In doInBackground() download the file from the URL and publish an update based on the total bytes downloaded. In this exercise we will not download a real file. In exchange will put a Thread.sleep(100) and will increment a counter representing the total bytes transferred. Use the code for doInBackground() from the lab archive.
  • In onProgressUpdate() call a method which updates the text of a TextView to show to the user the progress of the download. Use the code for onProgressUpdate() from the lab archive.
  • In onPostExecute() display the final value and present a Toast message to the user informing that the file download is complete. Use the code for onPostExecute() from the lab archive.

Task 2 (2p)

Using the code from the previous task we will see why AsyncTask is not recommended to be used.

  • Change the Thread.sleep() value to 1000 milliseconds and make a configuration change(example screen rotation). What happens with the AsyncTask?
  • Close the AsyncTask when the activity lifecycle has ended. When the task is stopped?

Task 3 (3p)

Implement the file download mocking from Task 1 using a runnable and handler.

Hint: Check the code examples from the lab.

Task 4 (2p)

Using the notification code from the lab archive and a service from Lab 3 create a foreground service by adding the notification code. Create a button that will stop the notification using stopForeground().

Disclaimer: leftover topic from Lab3, it is not related to threading, the service runs in the main thread.

Task 5 (2p bonus)

Mock the download of several files (at least 10) in parallel on multiple threads using Runnables and ThreadPoolExecutor (documentation, creation example).

  • For the “mock” download, each thread should sleep a random number between 100 and 200 ms.
  • The UI should update in real time a text view with the following content:
     Image-3: done
     Image-2: done
     Image-5: done
      …
      
  • The text view must have the option android:inputType=“textMultiLine”.

Resources

  • lab4-skel - code snippets for the AsyncTask and Foreground Service exercise
smd/laboratoare/04.1552914669.txt.gz · Last modified: 2019/03/18 15:11 by adriana.draghici
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0