Paul Brower bio photo

Paul Brower

Android development for fun and profit.

Email Twitter Github Stackoverflow Codementor

Most Java developers who come to Android from writing server code have a difficult time wrapping their heads around the Android threading model. There are few unique ways to do Android threading that are not intuitive or are departures from regular Java conventions.

Let’s start at the most important distinction.

The UI/Main Thread

Not all threads are created equal. In the Java server world a server would be multi-threaded and most likely a thread would be handling each request. The thread would do all it’s work ( DB read, business logic, HTML generation ) in serial and not respond until it’s done. While this is happening the user would see nothing in the browser until it’s finished.

In Android, everything runs on a single thread unless you tell it not too. “Everything” includes user interaction, file reads, DB calls, long computations. These types of things will lock up the app as user interaction (taps) won’t register until the operations are complete.

The thread that all this happens on is called the “main” thread, or the “UI” thread. It handles user interaction and should never be hindered. Any potentially blocking or long-running operation should be off-loaded onto another thread. In fact, Android enforces this practice by considering any app that doesn’t respond to user interaction within 5 seconds as non-responsive and will prompt the user with a “App isn’t responding” and ask the user if they want to close the app, not a great user experience.

Essentially, the upper limit on what you can do on the main thread is limited to 5 seconds. But your app should never come close to that, anything close to blocking or CPU intensive should be handled off the main thread. The Android SDK comes with tools to make this easy. We will talk about some of them.

Some operations have to happen on the Main thread

Although blocking operations should be off-loaded from the main thread, there are some operations that only the main thread can do. One of them is manipulating the View hierarchy. For example, only the UI can set the text in a TextView, remove a view from the hierarchy, or run an Animation. This allows for less confusion on the state of View hierarchy. This means communication between the main thread and a worker thread is essential. Let’s examine a way to do this with a quick example:

Let’s read some numbers from a file, sum them and display them in a TextView

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  findViewById(R.id.calculate).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      calculate();
    }
  });
}

public void calculate() {
  long sum = 0;
  long startTime = System.currentTimeMillis();

  try {
    BufferedReader in = new BufferedReader(new InputStreamReader(getAssets().open("inputs.txt")));
    String line;

    while ((line = in.readLine()) != null) {
      try {
        sum += Long.parseLong(line);
        } catch (NumberFormatException e) {
          Log.e(TAG, e.getMessage(), e);
        }
    }
  } catch (IOException e) {
    Log.e(TAG, e.getMessage(), e);
  }

  ((TextView) findViewById(R.id.text)).setText("SUM=" + sum + " took " + (System.currentTimeMillis() - startTime)/1000 + "s");
}

For a sufficiently large input, say over 2 million numbers, this could take several seconds. Trying to interact with the app while this is happening will most likely result in an “App isn’t responding” popup. Not good. Luckily, Android provides a simple tool to help you offload this work onto a worker thread.

AsyncTask, the easiest way to offload work

AsyncTask is a class that handles the thread pooling, scheduling, and callback structure for you. Let’s rewrite our class to incorporate AsyncTask.

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  findViewById(R.id.calculate).setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
      calculate();
    }
  });
}

public void calculate() {

  CalculateTask calculateTask = new CalculateTask(){
    @Override
    protected void onPostExecute(Pair<Long, Long> results) {
      ((TextView) findViewById(R.id.text)).setText("SUM=" + results.first + " took " + results.second + "s");
    }
  };
  calculateTask.execute("inputs.txt");
}

public class CalculateTask extends AsyncTask<String,Void,Pair<Long,Long>> {

  @Override
  protected Pair<Long, Long> doInBackground(String... params) {
    long sum = 0;
    long startTime = System.currentTimeMillis();

    try {
      BufferedReader in = new BufferedReader(new InputStreamReader(getAssets().open(params[0])));
      String line;

      while ((line = in.readLine()) != null) {
        try {
          sum += Long.parseLong(line);
        } catch (NumberFormatException e) {
          Log.e(TAG, e.getMessage(), e);
        }
      }
    } catch (IOException e) {
      Log.e(TAG, e.getMessage(), e);
    }

    return new Pair<>(sum, (System.currentTimeMillis() - startTime)/1000);
  }
}

AsyncTask handles the threading for you. It will create a ThreadPool to handle the scheduling so you can worry about the business logic. It comes equipped with callbacks:

  • doInBackground runs in the background on a worker thread. This is where the blocking code should go.
  • onPreExecute runs on the UI thread before doInBackground.
  • onProgressUpdate this is called in the UI thread when you call publishProgress. It’s a good place to update progress dialogs and show the user that things are still working.
  • onPostExecute runs on the UI thread and will be delivered the result of doInBackground

In the above code we only overrode doInBackground. Our app runs it but the user has no idea what is happening or whether that app has stuck or in an infinite loop. Let’s add a pretty dialog that tells the user we are still working.

public void calculate() {

  CalculateTask calculateTask = new CalculateTask(this);
  calculateTask.execute("inputs.txt");
}

public class CalculateTask extends AsyncTask<String,Void,Pair<Long,Long>> {

  private Context context;
  private ProgressDialog progressDialog;

  public CalculateTask(Context context) {
    this.context = context;
  }

  @Override
  protected void onPreExecute() {
    progressDialog = new ProgressDialog(context);
    progressDialog.setMessage("Calculating...");
    progressDialog.setCancelable(false);
    progressDialog.show();
  }

  @Override
  protected Pair<Long, Long> doInBackground(String... params) {
    long sum = 0;
    long startTime = System.currentTimeMillis();

    try {
      BufferedReader in = new BufferedReader(new InputStreamReader(getAssets().open(params[0])));
      String line;

      while ((line = in.readLine()) != null) {
        try {
          sum += Long.parseLong(line);
        } catch (NumberFormatException e) {
          Log.e(TAG, e.getMessage(), e);
        }
      }
    } catch (IOException e) {
      Log.e(TAG, e.getMessage(), e);
    }

    return new Pair<>(sum, (System.currentTimeMillis() - startTime)/1000);
  }

  @Override
  protected void onPostExecute(Pair<Long, Long> results) {
    ((TextView) findViewById(R.id.text)).setText("SUM=" + results.first + " took " + results.second + "s");
    progressDialog.dismiss();
  }
}

Here we are using a ProgressDialog that will run while we are calculating the numbers:

This is pretty good but we can do better. It’s nice that the user knows we are working but they want to know how long it’s going to take. Let’s use the the progress dialog and publishProgres to show this:

public void calculate() {

  CalculateTask calculateTask = new CalculateTask(this);
  calculateTask.execute("inputs.txt");
}

public class CalculateTask extends AsyncTask<String,Integer,Pair<Long,Long>> {

  private Context context;
  private ProgressDialog progressDialog;

  public CalculateTask(Context context) {
    this.context = context;
  }

  @Override
  protected void onPreExecute() {
    progressDialog = new ProgressDialog(context);
    progressDialog.setMessage("Calculating...");
    progressDialog.setIndeterminate(false);
    progressDialog.setProgress(0);
    progressDialog.setMax(100);
    progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
    progressDialog.setCancelable(false);
    progressDialog.show();
  }

  @Override
  protected Pair<Long, Long> doInBackground(String... params) {
    long sum = 0;
    long startTime = System.currentTimeMillis();

    try {
      long fileBytes = getAssets().open(params[0]).available();
      long readBytes = 0;
      long bytesSinceLastPublish = 0;

      BufferedReader in = new BufferedReader(new InputStreamReader(getAssets().open(params[0])));
      String line;

      while ((line = in.readLine()) != null) {
        try {
          // plus 1 because this line.length() wont include the newline
          readBytes += line.length() + 1;
          bytesSinceLastPublish += line.length();
          sum += Long.parseLong(line);

          // lets only publish progress every 1% of the file read.  This way we dont spend
          // too much time updating the UI
          if(bytesSinceLastPublish > fileBytes / 100) {
            publishProgress((int) ((double) readBytes / (double) fileBytes * 100));
            bytesSinceLastPublish = 0;
          }

        } catch (NumberFormatException e) {
          Log.e(TAG, e.getMessage(), e);
        }
      }
    } catch (IOException e) {
      Log.e(TAG, e.getMessage(), e);
    }

    return new Pair<>(sum, (System.currentTimeMillis() - startTime)/1000);
  }

  @Override
  protected void onPostExecute(Pair<Long, Long> results) {
    ((TextView) findViewById(R.id.text)).setText("SUM=" + results.first + " took " + results.second + "s");
    progressDialog.dismiss();
  }

  @Override
  protected void onProgressUpdate(Integer... values) {
    progressDialog.setProgress(values[0]);
  }
}

This is a little prettier:

Now the user knows approximately how long before the operation will finish and is much happier. AsyncTask is not the only tool you have to handle threading in your Android app. In part 2 (coming soon) we will talk about some tools for handling multiple background threads.