Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Eating Too Much CPU Time In The UI Causes A Nasty Result

Author: Daniel Fowler
Published? false
FormatLanguage: WikiFormat

Problem:

Any time consuming or intensive code in an event listener will make the UI appear slow and potentially cause an Application Not Responding error.

Solution:

The main UI thread should just be that, keeping the UI going and updated. Any heavy duty, regularly executing or potentially slow code needs shoving to a background task, the Android AsyncTask class is ideal for that job.

Discussion:

When Android starts an application it assigns it to run on a single main thread, also known as the User Interface (UI) thread. The UI thread, as the name suggests handles the interface, posting events for the various widgets on the screen and then running the code in the listeners for those events. If the code executing from a listener takes too long it will slow down event processing, including the UI events that tell widgets to redraw, the UI becomes unresponsive. The slow code running on the UI thread ultimately results in the Application Not Responding (ANR) error. This can occur if screen elements do not get a chance to process their pending requests after about five seconds. When an ANR appears the user can then forcibly close the App (and then probably remove it which would be a nasty result).

Tasks that can chew up UI CPU cycles included:

  • Accessing large amounts of data, especially through slow connections or peripherals.
  • Jittery connections when accessing networks and Internet services.
  • The need to run some recurring code, for animation, polling, timing.
  • Parsing large data files or XML files.
  • Creating, reading, updating, deleting large databases records.
  • Code with too many loops.
  • Intensive graphics operations.

In the following example an App with a TextView and Button is calling a method, ThisTakesAWhile(), which mimics a slow process. The code tries to keep the UI updated on progress by updating the TextView. In Studio start with a basic App and add a button, move the TextView below the button:

Here is a layout for the screen:

<?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.slowprocess.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Press GO to start."
        android:id="@+id/textView"
        android:layout_below="@+id/button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"
        android:textSize="18sp"
        android:textAlignment="center" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="GO"
        android:id="@+id/button"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignRight="@+id/textView"
        android:layout_alignEnd="@+id/textView"
        android:textSize="18sp" />
</RelativeLayout>

Here is the code to mimic a long running process:

package com.example.slowprocess;

import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView tv; //for class wide reference to update status
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //get the references to on screen items
        tv=(TextView) findViewById(R.id.textView);
        //handle button presses
        findViewById(R.id.button).setOnClickListener(new doButtonClick());
    }

    class doButtonClick implements View.OnClickListener {
        public void onClick(View v) {
            tv.setText("Processing, please wait.");
            ThisTakesAWhile();
            tv.setText("Finished.");
        }
    }

    private void ThisTakesAWhile() {
        //mimic long running code
        int count = 0;
        do{
            SystemClock.sleep(1000);
            count++;
            tv.setText("Processed " + count + " of 10.");
        } while(count<10);
    }
}

When this code is run and the Button pressed the text only changes after ten seconds to Finished. The messages that are meant to provide feedback are never shown. This illustrates how the UI can be blocked by code executing on the main thread. In fact bash the button a few times and an ANR occurs.

The solution is to run the time consuming code away from UI events. To help achieve this there are standard Java classes, such as the Timer, the Thread and the ScheduledThreadPoolExecutor. Android also provides helpful classes with the CountDownTimer, the Handler and Service. Ideally for potentially slow operations we want to start them from the UI in a background thread, get regular reports on progress, cancel them if need be and get a result when they have finished. In Android the AsyncTask class does all of that easily without having to crank out a lot of code.

With AsyncTask the time consuming code is placed into a doInBackground() method, there are onPreExecute() and onPostExecute() methods (for pre and post task work), and an onProgressUpdate() method can provide feedback. The background task can be cancelled by calling the cancel() method (causing onCancelled() to execute). The above example is changed to a use an AsyncTask object.

package com.example.slowprocess;

import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    TextView tv;        //for class wide reference to update status
    int count;          //number of times process has run, used for feedback
    boolean processing; //defaults false, set true when the slow process starts
    Button bt;          //used to update button caption
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //get the references to on screen items
        tv=(TextView) findViewById(R.id.textView);
        //handle button presses
        findViewById(R.id.button).setOnClickListener(new doButtonClick());
        bt=(Button) findViewById(R.id.button);
    }

    class doButtonClick implements View.OnClickListener {
        ThisTakesAWhile ttaw;//defaults null
        public void onClick(View v) {
            if(!processing){
                ttaw = new ThisTakesAWhile();
                ttaw.execute(10);    //loop 10 times
            } else {
                ttaw.cancel(true);
            }
        }
    }

    class ThisTakesAWhile extends AsyncTask<Integer, Integer, Integer>{
        int numcycles;  //total number of times to execute process
        protected void onPreExecute(){
            //Executes in UI thread before task begins
            //Can be used to set things up in UI such as showing progress bar
            count=0;    //count number of cycles
            processing=true;
            tv.setText("Processing, please wait.");
            bt.setText("STOP");
        }
        protected Integer doInBackground(Integer... arg0) {
            //Runs in a background thread
            //Used to run code that could block the UI
            numcycles=arg0[0];  //Run arg0 times
            //Need to check isCancelled to see if cancel was called
            while(count < numcycles && !isCancelled()) {
                //wait one second (simulate a long process)
                SystemClock.sleep(1000);
                //count cycles
                count++;
                //signal to the UI (via onProgressUpdate)
                //class arg1 determines type of data sent
                publishProgress(count);
            }
            //return value sent to UI via onPostExecute
            //class arg2 determines result type sent
            return count;
        }
        protected void onProgressUpdate(Integer... arg1){
            //called when background task calls publishProgress
            //in doInBackground
            if(isCancelled()) {
                tv.setText("Cancelled! Completed " + arg1[0] + " processes.");
            } else {
                tv.setText("Processed " + arg1[0] + " of " + numcycles + ".");
            }
        }
        protected void onPostExecute(Integer result){
            //result comes from return value of doInBackground
            //runs on UI thread, not called if task cancelled
            tv.setText("Processed " + result + ", finished!");
            processing=false;
            bt.setText("GO");
        }
        protected void onCancelled() {
            //run on UI thread if task is cancelled
            processing=false;
            bt.setText("GO");
        }
    }
}

With the slow process wrapped up in the AsyncTask object the UI thread is freed from waiting and the feedback messages get displayed to tell the users what is happening:

When cancel is called the background process may not stop immediately. For example two cycles have completed and the third starts just as cancel is called. Then the third cycle could still complete. In some scenarios the third process may need to be thrown away, rolled back, or handled differently. With version 3.0 of Android AsyncTask was extended to provide an onCancelled(Object) method that can be used, also called when cancel() is executed. This version of onCancelled takes the same object as that returned by doInBackground. This allows the program to determine the state of the background task when cancel was called. If this version was being used then the overridden onCancelled could be:

protected void onCancelled(Integer result) {
    //run on UI thread if task is cancelled
    //result comes from return value of doInBackground
    tv.setText("Cancelled called after "+ result + " processes.");
    processing=false;
    bt.setText("GO");
}

This recipe has shown how to move code that could potentially frustrate users and cause poor performance into the useful AsyncTask object. For more details on AsyncTask see the Android Reference web site.

See Also:

[1]

Download:

The source code for this project can be downloaded from http://tekeye.uk/android/examples/download/slowprocess.zip.
GR8DAN 2012-01-31 11:56:45.707 Code tested on a 1.5 Virtual Device, Motorola Defy 2.2.2, Galaxy Tab 2.3.3 - worked on all
AndrewG 2011-11-26 12:46:58.794 This doesn't work! I launch on a real device (Android 2.3.3)I don't have problems with running the first code,the second code(AsyncTask) doesn't work after I clicked button "go"