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 of the UI components (activities and fragments), certain threading objects can be tied to the UI lifecycle 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:
We will also briefly discuss AsyncTasks, which are not recommended and will be deprecated in the next Android release (R).
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.
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(); } });
We need a messaging mechanism between threads, and Android offers Handlers. These are closely connected to other objects needed for messaging:
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:
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(); } };
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(); } });
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.
private HandlerThread timeoutHandlerThread; private Handler timeoutHandler; private void postDelayedExample() { timeoutHandlerThread = new HandlerThread(TAG); timeoutHandlerThread.start(); timeoutHandler = new Handler(timeoutHandlerThread.getLooper()); }
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.
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).
Using AsyncTasks we can run logic in background on a thread and pass results back to the UI (main thread).
They have been widely used in the Android world for more than 10 years. When searching for tutorials on asynchronous programming/threading in Android you might encounter a lot of examples with AsyncTask, but bear in mind their deprecation and if you really want to use them, use them only for very short operations.
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.
Create an Android Studio project and name it `Lab 4`.
Create an application that mocks the download of a file from an URL and will inform the user about the progress and final result. All of this will be done on a background thread using an a runnable and handler.
Hint: Check the code examples from the lab.
Mock the download of several files (at least 10) in parallel on multiple threads using Runnables and ThreadPoolExecutor (documentation, example).
Image-3: done Image-2: done Image-5: done …
android:inputType=“text”
to the TextView