Saturday, February 11, 2017

Android Threading: Handler Example

In this post, we will go over a very simple Handler example for Android development. Hopefully this post will explain and demonstrate why and how to use Handler class in Android.

Consider a very simple app, where you have two buttons: task button and increment button.

Upon click, the task button will notify the user that the task is now running, and carry out some heavy task, which may take up to seconds. When complete, it will notify the user that the task is finally done.

Upon click, the increment button will simply increment counter and display its current value.

Let's take a look at a crude attempt to implement this using a simple Thread. Here is the layout file.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_handler_example"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.blogspot.unixnme.handlerexample.HandlerExample">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/text_vew"
android:text="0" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button"
android:text="Ready"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Increment"
android:id="@+id/increment_button"/>
</LinearLayout>

Here is the implementation activity file.

package com.blogspot.unixnme.handlerexample;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class HandlerExample extends Activity {
private static String TAG = HandlerExample.class.getSimpleName();
private Button taskButton;
private Button incrementButton;
private TextView textView;
private int counter = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_example);
textView = (TextView) findViewById(R.id.text_vew);
taskButton = (Button) findViewById(R.id.button);
incrementButton = (Button) findViewById(R.id.increment_button);
// when the taskButton is pressed, do some long task, simulated by sleep
taskButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
taskButton.setText("Busy");
Thread task = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (Throwable t) {
Log.d(TAG, t.getMessage());
}
}
});
task.start();
try {
task.join();
} catch (Throwable t) {
Log.d(TAG, t.getMessage());
}
taskButton.setText("Ready");
}
});
// when the incrementButton is pressed, it will simply increment the counter
incrementButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(Integer.toString(++counter));
}
});
}
}

Here, upon the task button click, it launches the task, which is simulated by sleep(3000), on a separate thread so that we don't slow down the main UI thread. However, the problem is that we must wait for this task thread to complete because we need to update the button's text upon completion. Thus, we use join() method to wait for the task to complete. With this implementation, we find that the UI thread becomes unresponsive while waiting. This is NOT a good way of implementing the task.

The core problem is that we ask the task thread to carry out some heavy task, and we need to make sure that the main UI thread does not just sit and wait, but rather do its jobs on its own. The task thread then must communicate with the UI thread and let it know the task is complete, at which point, the UI thread can update the UI accordingly.

The Handler class in Android achieves exactly this, and below is modification that makes use of the Hander class.

package com.blogspot.unixnme.handlerexample;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class HandlerExample extends Activity {
private static String TAG = HandlerExample.class.getSimpleName();
private Button taskButton;
private Button incrementButton;
private TextView textView;
private int counter = 0;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
// task is complete; update the UI
taskButton.setText("Ready");
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_example);
textView = (TextView) findViewById(R.id.text_vew);
taskButton = (Button) findViewById(R.id.button);
incrementButton = (Button) findViewById(R.id.increment_button);
// when the taskButton is pressed, do some long task, simulated by sleep
taskButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
taskButton.setText("Busy");
new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (Throwable t) {
Log.d(TAG, t.getMessage());
}
// notify that the task is complete
handler.sendEmptyMessage(0);
}
}).start();
}
});
// when the incrementButton is pressed, it will simply increment the counter
incrementButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(Integer.toString(++counter));
}
});
}
}

As you can see, the task thread will do its job, and when the task is complete, it notifies the main UI thread that the job is done through handler. The handler is created in the main activity, so it is bound to the main UI thread. When the handler receives a message from the task thread that the job is complete, it will then update the button's text. In the meantime, the main UI task carries out its own tasks, such as incrementing the counter when the increment button is pressed.

Of course, one can implement this without using the Handler class, shown below. However, this solution is possible because we are communicating with the UI thread in this example. If, however, you have two different task threads that must communicate with each other, then you need to use the Handler.

package com.blogspot.unixnme.handlerexample;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class HandlerExample extends Activity {
private static String TAG = HandlerExample.class.getSimpleName();
private Button taskButton;
private Button incrementButton;
private TextView textView;
private int counter = 0;
private HandlerExample instance;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler_example);
instance = this;
textView = (TextView) findViewById(R.id.text_vew);
taskButton = (Button) findViewById(R.id.button);
incrementButton = (Button) findViewById(R.id.increment_button);
// when the taskButton is pressed, do some long task, simulated by sleep
taskButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
taskButton.setText("Busy");
Thread task = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (Throwable t) {
Log.d(TAG, t.getMessage());
}
instance.runOnUiThread(new Runnable() {
public void run() {
taskButton.setText("Ready");
}
});
}
});
task.start();
}
});
// when the incrementButton is pressed, it will simply increment the counter
incrementButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView.setText(Integer.toString(++counter));
}
});
}
}

No comments:

Post a Comment