Foreword

Devs are busy most of the time and don’t have time to read very long articles. This article is as short as possible and contains as much as possible information about threading in Android. Let’s start!

Architecture

Multitasking and Multithreading

  • Android is based on Linux and supports:
    • multitasking: performing multiple tasks (aka processes) over a certain period of time by executing them concurrently
    • multithreading: performing multiple threads within the context of one process

Processes

  • The execution of apps is based on Linux processes
  • A Linux process is started for every new app
  • Android can run multiple apps at the same time (each app runs in its own process)
  • Every process contains:
    • a runtime: Dalvik or ART (Android Runtime)
    • a running app within the runtime
  • Each process has its own memory space and communicates with each other through interprocess communication (IPC)
  • A scheduler determines what thread the CPU should process and for how long

Android process architecture

Note: It is also possible but very rare that…

  • an app runs in several processes
  • several apps run in the same process

Application Lifecycle

Application start

General:

  • Android starts a new Linux process for an app when an app component (Activity, Service, Content Provider, Broadcast Receiver) needs to be started and the app does not have a running process yet
  • All app components of the same app run in the same process and thread (main thread) → default behavior
  • If an app component starts and there already exists a process for that app, then the new component is started within that already existing process

App startup sequence:

  1. Start Linux process
  2. Create runtime: Dalvik or ART (Android Runtime)
  3. Create Application instance (android.app.Application.onCreate() is called)
  4. Create the component for the application (e.g. Activity, Service, …) - It is the component that needs to be executed and triggered the startup

Application end

Android kills the process when:

  • it’s no longer needed
  • the system must recover memory for other (more important) apps

Important:
There’s no guarantee that onDestroy() (Activity, Service) will be called

Memory-Management

General

  • Processes are kept alive in memory for as long as possible
  • Processes are killed when memory is needed for the more important processes

Process Hierarchy

  • Android kills processes (not components!) to release resources
  • Processes with the lowest importance are killed first
  • Android can re-launch an app (the process) based on its last state when the process was killed

Process importance (from high to low):

  • Foreground Processes
  • Visible Processes
  • Service Processes
  • Background Processes
  • Empty Processes

Foreground Process:

  • Process that has components that the user is interacting with like
    • a visible and active component (Activity)
    • a Service is bound to an Activity in front in a remote process (same with Content Providers)
    • a foreground Service
    • a running BroadcastReceiver

Visible Process:

  • A process that has visible activities which are not in the foreground (= partly obscured)
  • dependent processes are not prematurely killed while the activity is visible (a bound service or content providers gets the same process status as the activity which still lives)

Service Process:

  • has one or more started Services which are not interacting with a visible component or foreground component
  • common state for most background services, the process is only killed if there is much memory pressure

Background Process:

  • contains activities that are not visible
  • Android kills processes of this type in order of least recent usage (oldest is reclaimed first)

Empty Process:

  • don’t have any active app components
  • can be easily killed at any point
  • do only exist to improve start-up time when a component needs to be (re-)activated

Important:
Priorities are done at the process level and not the component level. A single component can push the entire process into the foreground level.

Thread Scheduling

A thread scheduler decides which threads in the Android system should run, when, and for how long

Android’s thread scheduler uses two main factors to determine the scheduling:

  • Niceness Values
  • Control Groups (Cgroups)

Niceness Values

  • a thread with a higher niceness value will run less often than those with a lower niceness value (this sounds paradoxical)
  • niceness value has the range of -20 (most prioritized) to 19 (least prioritized); default value is 0
  • a new Thread inherits its priority from the thread where it is started
  • it is possible to change the priority via:
    • thread.setPriority(int priority) - values: 0 (least prioritized) to 10 (most prioritized)
    • process.setThreadPriority(int priority) - values: -20 (most prioritized) to 19 (least prioritized)

Control Groups (Cgroups)

  • Android has multiple control groups. The most important are:
    • the Foreground Group
    • the Background Group
  • every thread belongs to a thread control group (e.g. Foreground Group)
  • threads in the different control groups are allocated different amounts of CPU execution time
  • threads in the Foreground Group receive a lot more execution time than threads in the Background Group
  • if an application runs at the Foreground or Visible process level (see above), the threads created by that application will belong to the Foreground Group
  • all threads belonging to applications which are not currently running in the foreground are implicitly moved to the Background Group

Main Thread aka UI Thread

General:

  • By default, all app components of the same app run in the same thread called main thread
  • The main thread is also called UI thread because it is the only thread the system allows to update UI components

Responsiveness:
Always ensure that no long-running tasks are executed on the UI thread!
A long running task will block other sensitive tasks on the UI thread (e.g. animations)

Example: Let’s consider a simple view animation:

  • Animations are updated in an event loop where every event updates the animation with one frame
  • An event is just a very small change (e.g. move view element by 1px)
  • The more events/drawing changes that can be executed per time frame, the better the animation is perceived
  • 60 drawing changes per seconds (60 fps) is the ultimate goal. Result: Every frame has to render within 16 ms (1000 ms / 60 frames = 16,66 ms)
  • If another task is running on the UI thread simultaneously, both the drawing cycle and the “blocking” task have to finish within 16 ms to avoid a stuttering animation

The calculation is a bit simplified. However, be cautious about possible long running tasks.

Service

Use Service for very short tasks without UI; for longer tasks use threads within Service

General

  • the Service runs in the UI thread!
  • only the first start request creates and starts the Service. Consecutive start requests just pass on the Intent to the started Service (onStartCommand)
  • the Service lifecycle does not control the lifetime of background threads!
  • a bound Service keeps a reference counter on the number of bound components. When the reference counter is decremented to zero, the Service is destroyed
  • a foreground service has the same priority as an active activity. It should (but you never know… ) not be killed by the Android system, even if is low on memory

Lifecycle

Start: A service starts…

  • with a call to context.startService(Intent) - Regular Service
  • with a call to context.bindService(Intent, ServiceConnection, int) - Bound Service

End: A service ends…

  • with a call to context.stopService(Intent)
  • with a call to service.stopSelf()
  • the system must recover memory for other (more important) apps

Example

public class MyService extends Service {
   @Override
   public int onStartCommand(Intent intent, int flags, int startId) {
      return MY_START_MODE;
   }

   @Override
   public IBinder onBind(Intent intent) {
      return null;
   } 
}

IntentService

Use IntentService for repetitive and/or long tasks that should run on the background, independent of the UI

General:

  • executes tasks sequentially
  • runs in the background! No need to use Thread, HandlerThread…
  • excellent for “Fire-and-Forget” tasks
  • not possible to cancel tasks from the outside

Lifecycle

Start:

  • an IntentService starts with a call to context.startService(Intent)
  • if the IntentService is already running, the new task is queued (FIFO)

End:

  • the IntentService stops itself automatically when there are no more Intents to process (No need to call stopSelf()!)
  • the system must recover memory for other (more important) apps

Example


public class MyIntentService extends IntentService {
    public static final String MY_PARAM = "param";

    public MyIntentService() {
        super("MyIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        String msg = intent.getStringExtra(MY_PARAM);
        // do something
    }
}

AsyncTask

Use AsyncTask for short(!) (repetitive) tasks that are tightly bound to the UI

General

  • allows to run code in the background and to synchronize again with the main thread
  • excellent for short background operations which need to update the user interface
  • a convenient way to perform long operations (a few seconds) from within an Activity
  • computation runs on a background thread and whose result is published on the UI thread
  • a task can be executed only once
  • tasks are executed sequentially on a single thread! If you want parallel execution, you have to use asyncTask.executeOnExecutor(THREAD_POOL_EXECUTOR, args)
  • does not handle configuration changes automatically (common solution: use a retained headless fragment)
  • AsyncTask instance must be created on the UI thread.

Lifecycle

Start:

  • an AsyncTask starts with a call to asyncTask.execute(Params, Progress, Result)

End:

  • when the AsyncTask finished work
  • by calling asyncTask.cancel(false). The implementation of doInBackground(Params…) has to check periodically the return value of isCancelled().

Example


public class MyActivity extends Activity {
	private TextView textViewStatus;
    public void onCreate(Bundle savedInstanceState) {
           // init...
           MyAsyncTask task = new MyAsyncTask();
           task.execute(urls); // e.g download urls
    }

	private static class MyAsyncTask extends AsyncTask<String, Void, String> {
	        @Override
	        protected String doInBackground(String... urls) {
	                // download something via url and return result
	                return downloadResultAsString; // e.g. "lorem ipsum"
	        }

	        @Override
	        protected void onPostExecute(String result) {
	                textViewStatus.setText(result); // sets "lorem ipsum" to TextView textViewStatus
	        }
	}
}

Message Passing

Scenario: “We are running on a client thread and want to have code executed in another thread context”

Message Passing Parts:

General:

  • A Thread can have exactly one Looper (the UI thread does automatically have a Looper)
  • A Looper has exactly one MessageQueue
  • A MessageQueue can have many Messages
  • A Handler is bound to a Thread via Looper
  • Multiple Handler instances can be bound to the same Thread (via Looper)

Android threading handler relation

Process:

  1. The producer thread inserts a Message into the MessageQueue by using a Handler which is connected to the consumer Thread
  2. The Looper runs in the consumer Thread, retrieves the Message from the MessageQueue and “sends” the Message to the Handler which is still connected to the consumer Thread
  3. The Handler executes the Message via handleMessage(Message)

Android threading handler relation

More Details:

Thread:

  • A Thread can have only one Looper. A Looper can have many unique Handlers associated with it
  • A Thread gets a Looper and MessageQueue by calling Looper.prepare() after its running

MessageQueue:

  • Unbounded linked list of messages to be processed on the consumer thread
  • Holds the list of messages to be dispatched by a Looper
  • A Looper has only one MessageQueue
  • Messages are added via Handler objects associated with the Looper

Message:

  • Container object carrying either a data item or a task (Runnable)
  • Inserted by producer threads
  • Consumed by consumer thread

Looper:

  • Message dispatcher associated with the consumer thread
  • A thread can only have one Looper at the same time
  • Looper runs in the consumer thread
  • Looper is a message handling loop
  • Looper retrieves messages from the MessageQueue in a sequential order

Handler:

  • consumer thread message handler
  • A Handler is bound to a specific Thread and its Looper (and MessageQueue)
  • Handler is responsible for adding, removing, dispatching messages of the associated thread’s MessageQueue
  • A Looper can have many associated handlers. All handlers insert messages into the same queue

Example


class MyLooperThread extends Thread {
    public Handler handler;

    public void run() {
        // Initialize a Looper and associate it with the current thread
        Looper.prepare();
        handler = new Handler() {
            @Override public void handleMessage(Message msg) {
                // handle incoming message
            }
        };
        // Starts the Looper
        Looper.loop();
    }
}

class MainActivity extends Activity {
	public void onCreate(...) {
		// Other stuff
		MyLooperThread myThread = new MyLooperThread();
		myThread.start();

		// much later, send a message
		Message msg = myThread.handler.obtainMessage(0);
        myThread.handler.sendMessage(msg);
	}
}

HandlerThread

Use HandlerThread to establish an ongoing, one-way inter-thread communication

General

  • Just a “normal” Thread which sets up the internal message passing mechanism automatically → No need to init a Looper manually…
  • Use HandlerThread instead of “normal” Java Thread with manual Looper setup (see code example above)
  • Applicable to many background execution use cases, where sequential execution and control of the message queue is desired
  • Use the constructor with the name argument → simplifies debugging
  • Started in the same way as a Thread → start()
  • Terminated either with quit() or quitSafely()
    • quit() - Terminates the Looper without processing any more messages in the MessageQueue
    • quitSafely() - Similar to the previous version but makes sure that the pending messages that are due to be delivered are handled

Example


HandlerThread myThread = new HandlerThread("MyHandlerThread");
myThread.start();
Handler handler = new Handler(myThread.getLooper()) {
	@Override
	public void handleMessage(Message msg) {
		super.handleMessage(msg);
		// TODO: process message
	}
};

Loaders

General

  • framework to run asynchronous operations with content providers or other data sources
  • loads data asynchronously
  • callback when content changes or is added to the data source
  • accessible via Activity or Fragment
  • supports configuration change

Development Phase & Debugging

  • StrictMode helps to identify long-running operations on the main thread
  • Simulate how your app responds to being killed: adb shell am force-stop com.example.packagename
  • Get process info: adb shell ps | grep com.example.packagename

Feedback? Questions? Errors? Let me know what you think.