Android Basic Training Course: Dealing with Threads

    Users prefer vividly applications, do not like the slow application. Making your application flexibility for users is to use threading capability built into Android. This post will guide you through the issues related to the management of threads in Android and some options to keep sharp interface and responsive.

UI thread and background thread

    Every app has its own special thread that runs UI objects such as 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

    Android supports the usage of the 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

     From official Google doc: a 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 thread
    Now, 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:
Downloading data...
Download process completed

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 to publishProgress(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 in doInBackground() method completes processing. Result of the doInBackground() is passed to this method.
    In this example, your 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:
Downloading data...
Download process completed

    Important Note: With downloading data from URL work, you must provide Internet permission in your AndroidManifest.xml:

Conclusions

    By this post, you've learned about creating and managing multi-threads in Android. You can choose the suitable way to handling background threads result with solutions above. Finally, you can get full code from @Github by clicking button below.



Share


Previous post
« Prev Post
Next post
Next Post »