Android Loopers and Handlers – Code Tutorial

In our last post we tried to understood the concept of Looper and Handler. Now let us implement these in this tutorial. We will be creating a download manager which download files(we are using delay for this simulation) one by one.

Implementation Details

Basically, I created a DownloadTask class that emulates a download task of a random duration. I did not want to do actual downloads because I didn’t want to eat your data plans, but it’s straightforward to change this into real work. Then, the DownloadThread has an interface that allows to enqueue DownloadTask instances and also to request it to exit gracefully. There is also the DownloadThreadListener interface that allows the download thread to notify some other entity about status updates. In our case, that interface will be implemented by the  DownloadQueueActivity because we want to reflect the download progress in the UI.

Source code:

 

[su_button url=”https://github.com/androidsrc/SimpleLooper” target=”blank” style=”stroked” background=”#51d461″ color=”#ffffff” size=”6″ center=”yes” radius=”0″ icon=”icon: arrow-circle-o-down”]Download Complete Source Code[/su_button]

 

Preview:

[su_youtube url=”http://youtu.be/U3aTFNWxV6o”]

Lets get started :

1. Create a new project in Eclipse by navigating to File ⇒ New Android ⇒ Application Project and fill required details.

2. Lets create DownloadThreadListener.java interface which will be used to get thread updates.

package com.example.looper;
public interface DownloadThreadListener {
	void handleDownloadThreadUpdate();
}

3. Lets now create a class named DownloadTask.java which will simulate the downloading. We will be using random time sleep to simulate the download time.

package com.example.looper;
import java.util.Random;
import android.util.Log;
/**
 * This is not a real download task. It just sleeps for some random time when
 * it's launched. The idea is not to require a connection and not to eat it.
 *
 */
public class DownloadTask implements Runnable {
	private static final String TAG = DownloadTask.class.getSimpleName();
	private static final Random random = new Random();
	private int lengthSec;
	public DownloadTask() {
		lengthSec = random.nextInt(3) + 1;
	}

	@Override
	public void run() {
		try {
			Thread.sleep(lengthSec * 1000);
			// it's a good idea to always catch Throwable
			// in isolated "codelets" like Runnable or Thread
			// otherwise the exception might be sunk by some
			// agent that actually runs your Runnable - you
			// never know what it might be.
		} catch (Throwable t) {
			Log.e(TAG, "Error in DownloadTask", t);
		}
	}
}

 4. Now lets create the thread subclass which will act as pipeline. First we will call Looper.prepare() to make this Thread act as pipeline. Next new Handler() will be called to handle message queue on this thread. Finally Looper.loop() will be called to start running the message loop. New tasks will be added using enqueueDownload(final DownloadTask task) . Code for DownloadThread.java

package com.example.looper;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

public final class DownloadThread extends Thread {

    private static final String TAG = DownloadThread.class.getSimpleName();
    private Handler handler;
    private int totalQueued;
    private int totalCompleted;
    private DownloadThreadListener listener;

    public DownloadThread(DownloadThreadListener listener) {
        this.listener = listener;
    }

    @Override
    public void run() {
        try {
            // preparing a looper on current thread
            // the current thread is being detected implicitly
            Looper.prepare();

            Log.i(TAG, "DownloadThread entering the loop");

            // now, the handler will automatically bind to the
            // Looper that is attached to the current thread
            // You don't need to specify the Looper explicitly
            handler = new Handler();

            // After the following line the thread will start
            // running the message loop and will not normally
            // exit the loop unless a problem happens or you
            // quit() the looper (see below)
            Looper.loop();

            Log.i(TAG, "DownloadThread exiting gracefully");
        } catch (Throwable t) {
            Log.e(TAG, "DownloadThread halted due to an error", t);
        }
    }

    // This method is allowed to be called from any thread
    public synchronized void requestStop() {
        // using the handler, post a Runnable that will quit()
        // the Looper attached to our DownloadThread
        // obviously, all previously queued tasks will be executed
        // before the loop gets the quit Runnable
        handler.post(new Runnable() {
            @Override
            public void run() {
                // This is guaranteed to run on the DownloadThread
                // so we can use myLooper() to get its looper
                Log.i(TAG, "DownloadThread loop quitting by request");

                Looper.myLooper().quit();
            }
        });
    }

    public synchronized void enqueueDownload(final DownloadTask task) {
        // Wrap DownloadTask into another Runnable to track the statistics
        handler.post(new Runnable() {
            @Override
            public void run() {
                try {
                    task.run();
                } finally {
                    // register task completion
                    synchronized (DownloadThread.this) {
                        totalCompleted++;
                    }
                    // tell the listener something has happened
                    signalUpdate();
                }
            }
        });

        totalQueued++;
        // tell the listeners the queue is now longer
        signalUpdate();
    }

    public synchronized int getTotalQueued() {
        return totalQueued;
    }

    public synchronized int getTotalCompleted() {
        return totalCompleted;
    }

    // Please note! This method will normally be called from the download
    // thread.
    // Thus, it is up for the listener to deal with that (in case it is a UI
    // component,
    // it has to execute the signal handling code in the UI thread using Handler
    // - see
    // DownloadQueueActivity for example).
    private void signalUpdate() {
        if (listener != null) {
            listener.handleDownloadThreadUpdate();
        }
    }
}

5. Now add full functionality to MainActivity.java . Here also we will create handler so that we can post events on main thread.

package com.example.looper;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.util.Random;

public class MainActivity extends Activity implements DownloadThreadListener,
        OnClickListener {

    private DownloadThread downloadThread;

    private Handler handler;

    private ProgressBar progressBar;

    private TextView statusText;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create and launch the download thread
        downloadThread = new DownloadThread(this);
        downloadThread.start();

        // Create the Handler. It will implicitly bind to the Looper
        // that is internally created for this thread (since it is the UI
        // thread)
        handler = new Handler();

        progressBar = (ProgressBar) findViewById(R.id.progress_bar);
        statusText = (TextView) findViewById(R.id.status_text);

        Button scheduleButton = (Button) findViewById(R.id.schedule_button);
        scheduleButton.setOnClickListener(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        // request the thread to stop
        downloadThread.requestStop();
    }

    // note! this might be called from another thread
    @Override
    public void handleDownloadThreadUpdate() {
        // we want to modify the progress bar so we need to do it from the UI
        // thread
        // how can we make sure the code runs in the UI thread? use the handler!
        handler.post(new Runnable() {
            @Override
            public void run() {
                int total = downloadThread.getTotalQueued();
                int completed = downloadThread.getTotalCompleted();

                progressBar.setMax(total);

                progressBar.setProgress(0); // need to do it due to a
                // ProgressBar bug
                progressBar.setProgress(completed);

                statusText.setText(String.format("Downloaded %d/%d", completed,
                        total));

                // vibrate for fun
                if (completed == total) {
                    ((Vibrator) getSystemService(VIBRATOR_SERVICE))
                            .vibrate(100);
                }
            }
        });
    }

    @Override
    public void onClick(View source) {
        if (source.getId() == R.id.schedule_button) {
            int totalTasks = new Random().nextInt(3) + 1;

            for (int i = 0; i < totalTasks; ++i) {
                downloadThread.enqueueDownload(new DownloadTask());
            }
        }
    }
}

6. Lets add this code to activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/status_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:paddingTop="20dip"
        android:text="Click the button" />

    <ProgressBar
        android:id="@+id/progress_bar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="fill_parent"
        android:layout_height="40dip"
        android:indeterminate="false"
        android:padding="10dip" />

    <Button
        android:id="@+id/schedule_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="20dip"
        android:padding="14dip"
        android:text="Schedule some tasks" />

</LinearLayout>

7. Lets also update AndroidManifest.xml . Add vibrate permission.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.looper"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="3" />

    <uses-permission android:name="android.permission.VIBRATE" />

    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name" >
        <activity
            android:name="com.example.looper.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

8. Now build and run the project.

GuRu

Technology enthusiast. Loves to tinker with things. Always trying to create something wonderful using technology. Loves coding for Android, Raspberry pi, Arduino , Opencv and much more.

You may also like...

1 Response

  1. March 12, 2015

    […] this link for detailed code […]

Leave a Reply

Your email address will not be published. Required fields are marked *