UI thread and background thread
View
objects; this thread is called the UI thread. Only objects running on the UI thread have access to other objects on that thread.The fact that there is some tasks work in the background (e.x: downloading data), because they run on a thread from a thread pool aren't running on your UI thread, they don't have access to UI objects. So, we said that they are running in the background threads.
Android threading constructs
Thread
class to perform asynchronous processing.Android also supplies the
java.util.concurrent
package to perform something in the background, for example: using the ThreadPools
and Executor
classes.If you need to update the user interface from a new
Thread
, you need to synchronize with the UI thread. The classes that help you solve this problem are Handler
, AsyncTask
,...In this post, I provide a sample project about using
Thread
with Handler
, Runnable
with Hanlder
and how to use an AsyncTask
. So, let's begin!Handling background thread result with Handler
Handler
allows you to send and process Message
and Runnable
objects associated with a thread's MessageQueue
. Each Handler
instance is associated with a single thread and that thread's message queue. When you create a new Handler
, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.There are two main uses for a
Handler
: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.A new Thread is defined by the code like this:
new Thread() { public void run() { bitmap = downloadBitmap(url); // invoke downloading image work imageHandler.sendEmptyMessage(0); //attach event handling with a Handler instance } }.start(); //starting threadNow, creating a subclass of
Handler
to deliver messages, you must override handlerMessage()
method and in order to avoid "Handler should be static or leaks might occur" error, you should initialized it with a WeakReference
:
private static class ImageHandler extends Handler { private final WeakReference<HandlerDemoActivity> weakRef; private HandlerDemoActivity activity; public ImageHandler(HandlerDemoActivity activity) { weakRef = new WeakReference<>(activity); this.activity = activity; } @Override public void handleMessage(Message msg) { activity.imageView.setImageBitmap(activity.bitmap); activity.progressDialog.dismiss(); Toast.makeText(activity, "Download Successful!", Toast.LENGTH_SHORT).show(); } }In this
Activity
, the "download file from URL" work will be invoked after use click a Button
, this is full code of it:
package info.devexchanges.communicatingwithuithread; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.HttpURLConnection; public class HandlerDemoActivity extends AppCompatActivity { private ProgressDialog progressDialog; private ImageView imageView; private String url = "http://i.imgur.com/h5YqScl.jpg"; private Bitmap bitmap = null; private ImageHandler imageHandler; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_handler_demo); imageHandler = new ImageHandler(this); imageView = (ImageView) findViewById(R.id.imageView); Button btnDownload = (Button) findViewById(R.id.btn_download); btnDownload.setOnClickListener(new OnClickListener() { @Override public void onClick(View arg0) { progressDialog = ProgressDialog.show(HandlerDemoActivity.this, "Loading", "Loading data..."); new Thread() { public void run() { bitmap = downloadBitmap(url); imageHandler.sendEmptyMessage(0); } }.start(); } }); } private static class ImageHandler extends Handler { private final WeakReference<HandlerDemoActivity> weakRef; private HandlerDemoActivity activity; public ImageHandler(HandlerDemoActivity activity) { weakRef = new WeakReference<>(activity); this.activity = activity; } @Override public void handleMessage(Message msg) { activity.imageView.setImageBitmap(activity.bitmap); activity.progressDialog.dismiss(); Toast.makeText(activity, "Download Successful!", Toast.LENGTH_SHORT).show(); } } private Bitmap downloadBitmap(String urlString) { try { java.net.URL url = new java.net.URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); } catch (IOException e) { e.printStackTrace(); return null; } } }And this is layout (xml file):
After running app, we will have this result:
|
|
Handler with Runnable
Runnable
represents a command that can be executed. It often declared when create a new Thread
. With Handler
, To process a Runnable
you can use the post()
method:
//defining a new Thread with Runnable new Thread(new Runnable() { public void run() { bitmap = downloadBitmap("http://i.imgur.com/HR5QMOY.jpg"); handler.post(updateUI); } }).start();And this is 2nd
Runnable
declaration:
final Runnable updateUI = new Runnable() { public void run() { if (bitmap != null) { imageView.setImageBitmap(bitmap); progressDialog.dismiss(); Toast.makeText(HandlerWithRunnableActivity.this, "Download Successful!", Toast.LENGTH_SHORT).show(); } } };Quite similar with Activity above, adding some important methods and reuse
activity_handler_demo.xml
layout, we have full code:
package info.devexchanges.communicatingwithuithread; import android.app.ProgressDialog; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; public class HandlerWithRunnableActivity extends AppCompatActivity { private Handler handler; private ImageView imageView; private Button btnDownload; private Bitmap bitmap; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); handler = new Handler(); setContentView(R.layout.activity_handler_demo); imageView = (ImageView) findViewById(R.id.imageView); btnDownload = (Button)findViewById(R.id.btn_download); btnDownload.setText("Download image by Runnable"); btnDownload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog = ProgressDialog.show(HandlerWithRunnableActivity.this, "Loading", "Loading data..."); //defining a new Thread with Runnable new Thread(new Runnable() { public void run() { bitmap = downloadBitmap("http://i.imgur.com/HR5QMOY.jpg"); handler.post(updateUI); } }).start(); } }); } //Update download result (bitmap) to ImageView final Runnable updateUI = new Runnable() { public void run() { if (bitmap != null) { imageView.setImageBitmap(bitmap); progressDialog.dismiss(); Toast.makeText(HandlerWithRunnableActivity.this, "Download Successful!", Toast.LENGTH_SHORT).show(); } } }; private Bitmap downloadBitmap(String urlString) { try { java.net.URL url = new java.net.URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); } catch (IOException e) { e.printStackTrace(); return null; } } }Running this, the actual result will similar to the above case:
AsyncTask - a better way
AsyncTask
is an abstract Android class which helps the applications to handle the UI thread in efficient way. AsyncTask
class allows us to perform long lasting tasks/background operations and show the result on the UI thread without affecting the main thread. This is an AsyncTask
object lifecycle:Declaring an
AsyncTask
with it's parameters:
private class DownloadTask extends AsyncTask<Void, String, Bitmap> {}The important methods:
onPreExcute()
: invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.doInBackground()
: running for long lasting time should be put in this method. When execute method is called in UI main thread, this method is called with the parameters passed. This is method that you must override.onProgressUpdate()
: invoked on the UI thread after a call topublishProgress(Progress...)
. The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.onPostExecute()
: invoked after background computation indoInBackground()
method completes processing. Result of thedoInBackground()
is passed to this method.
AsyncTask
instance will be used for downloading image from URL and this result (a Bitmap
) will be set to ImageView
, the downloaded percentages will be displayed by a ProgressBar
:
private class DownloadTask extends AsyncTask<Void, String, Bitmap> { /** * Downloading file in background thread */ @Override protected Bitmap doInBackground(Void... params) { int count; Bitmap bitmap = null; try { URL url = new URL("http://i.imgur.com/3CBxbOj.jpg"); URLConnection connection = url.openConnection(); connection.connect(); InputStream inputStream = url.openStream(); //this input stream used for caculate download percentages InputStream inputStream1 = url.openStream(); //decode Bitmap object from input stream bitmap = BitmapFactory.decodeStream(inputStream); byte data[] = new byte[1024]; long total = 0; // getting file length int lenghtOfFile = connection.getContentLength(); while ((count = inputStream1.read(data)) != -1) { total += count; // publishing the progress.... // After this onProgressUpdate will be called publishProgress("" + (int) ((total * 100) / lenghtOfFile)); } } catch (Exception e) { e.printStackTrace(); } return bitmap; } /** * Updating progress bar */ protected void onProgressUpdate(String... progress) { progressBar.setProgress(Integer.parseInt(progress[0])); } @Override protected void onPostExecute(Bitmap bitmap) { imageView.setImageBitmap(bitmap); Toast.makeText(AsyncTaskActivity.this, "Download successful!", Toast.LENGTH_SHORT).show(); btnDownload.setEnabled(true); } }Use this in
Activity
or Fragment
by call:
AsyncTask asyncTask = new DownloadTask().execute();Full code for the
Activity
:
package info.devexchanges.communicatingwithuithread; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.Toast; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; public class AsyncTaskActivity extends AppCompatActivity { private ProgressBar progressBar; private View btnDownload; private AsyncTask asyncTask; private ImageView imageView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_asynctask); imageView = (ImageView) findViewById(R.id.imageView); btnDownload = findViewById(R.id.btn_download); progressBar = (ProgressBar) findViewById(R.id.loading); btnDownload.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { asyncTask = new DownloadTask().execute(); Toast.makeText(AsyncTaskActivity.this, "Downloading...", Toast.LENGTH_SHORT).show(); btnDownload.setEnabled(false); } }); } private class DownloadTask extends AsyncTask<Void, String, Bitmap> { /** * Downloading file in background thread */ @Override protected Bitmap doInBackground(Void... params) { int count; Bitmap bitmap = null; try { URL url = new URL("http://i.imgur.com/3CBxbOj.jpg"); URLConnection connection = url.openConnection(); connection.connect(); InputStream inputStream = url.openStream(); //this input stream used for caculate download percentages InputStream inputStream1 = url.openStream(); //decode Bitmap object from input stream bitmap = BitmapFactory.decodeStream(inputStream); byte data[] = new byte[1024]; long total = 0; // getting file length int lenghtOfFile = connection.getContentLength(); while ((count = inputStream1.read(data)) != -1) { total += count; // publishing the progress.... // After this onProgressUpdate will be called publishProgress("" + (int) ((total * 100) / lenghtOfFile)); } } catch (Exception e) { e.printStackTrace(); } return bitmap; } /** * Updating progress bar */ protected void onProgressUpdate(String... progress) { progressBar.setProgress(Integer.parseInt(progress[0])); } @Override protected void onPostExecute(Bitmap bitmap) { imageView.setImageBitmap(bitmap); Toast.makeText(AsyncTaskActivity.this, "Download successful!", Toast.LENGTH_SHORT).show(); btnDownload.setEnabled(true); } } }Running this Activity, we have this result:
|
|
Important Note: With downloading data from URL work, you must provide Internet permission in your
AndroidManifest.xml
: