Thursday, October 21, 2010

Message handlers

I was facing a runtime exception like below. I couldnt figure out what was the mistake am doing. Then i went and did some google results and found out how to avoid this and why this exception happened.



E/AndroidRuntime( 60): *** FATAL EXCEPTION IN SYSTEM PROCESS: Timer-0
E/AndroidRuntime( 60): android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
E/AndroidRuntime( 60): at android.view.ViewRoot.checkThread(ViewRoot.java:2802)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChild(ViewRoot.java:607)
E/AndroidRuntime( 60): at android.view.ViewRoot.invalidateChildInParent(ViewRoot.java:633)
E/AndroidRuntime( 60): at android.view.ViewGroup.invalidateChild(ViewGroup.java:2505)
E/AndroidRuntime( 60): at android.view.View.invalidate(View.java:5139)
E/AndroidRuntime( 60): at android.widget.TextView.checkForRelayout(TextView.java:5364)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2688)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2556)
E/AndroidRuntime( 60): at android.widget.TextView.setText(TextView.java:2531)
E/AndroidRuntime( 60): at com.android.server.status.StatusBarService$1$1.run(StatusBarService.java:403)
E/AndroidRuntime( 60): at java.util.Timer$TimerImpl.run(Timer.java:289)


Reason :

Every activity/application when started it creates a main UI thread to show the UI elements. When a new thread is created from main UI thread, and when UI elements where tried to access from this thread, then this exception will be thrown,
Only the original thread that created a view hierarchy can touch its views

So how do we acheive this,??

This scenario is common, we create a thread in main thread to do some light weight work, like fetching some data from net, to wait for some event from other process, and when the event is successful, we would like to update the UI elements.

Message handlers are used to achieve this.

http://developer.android.com/reference/android/os/Handler.html



"When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler. This is done by calling the same post or sendMessage methods as before, but from your new thread. The given Runnable or Message will than be scheduled in the Handler's message queue and processed when appropriate."



The above explanation makes it clear.

Each handler instance when created will be linked to the thread where it is created.Each thread has its own messageQueue and using this handler we can deliver the messages, runnables to this thread.

So by keeping the message handler object global, it can use used in other thread apart from main UI thread to deliver messages to main UI thread.

The below picture portrays what it is in high level picture.




I have written a simple example to explain the message handler. The application has one textview and one button.

On Click of the button, it creates a new thread() and fetches data from google local search about "pubs london" and when the response is received, this thread uses message handler to deliver the message event to main thread.(UI thread ).

Lets see in detail in code:

Layout.xml
--------------



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Fetch Data" />
</LinearLayout>



handler.java
-------------



package com.android.handler;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class handler extends Activity {
private String responseText;
private TextView content;
private ProgressDialog MyDialog;
private Context mContext;
private final int UPDATE_TEXT = 0;
private Handler messageHandler = new Handler() {

public void handleMessage(Message msg) {
super.handleMessage(msg);

if(msg.what == UPDATE_TEXT)
{
MyDialog.dismiss();
content.setText(responseText);
}
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mContext = getApplicationContext();
content = (TextView)findViewById(R.id.text);
MyDialog = new ProgressDialog(this);
MyDialog.setMessage("Fetching...");
MyDialog.setIndeterminate(true);
MyDialog.setCancelable(true);

Button b = (Button)findViewById(R.id.button1);
b.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v)
{

MyDialog.show();
new Thread() {
public void run() {
try {
sendSearchRequest();
} catch (Exception e) {
}

}
}.start();

}

});
}
public void sendSearchRequest()
{
HttpClient client = new DefaultHttpClient();
String query = "http://ajax.googleapis.com/ajax/services/search/local?hl=en&v=1.0&rsz=8&q=pubs london&start=0";
try {
URL url = new URL(query);
URI uri = new URI(url.getProtocol(), url.getHost(), url.getPath(), url.getQuery(), null);
HttpGet request = new HttpGet(uri);
HttpResponse response = client.execute(request);
Userrequest(response);
}catch (URISyntaxException e){

}
catch(Exception ex){
content.setText("Failed expcetion");
}

}
public void Userrequest(HttpResponse response){

try{
InputStream in = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder str = new StringBuilder();
String line = null;
while((line = reader.readLine()) != null){
str.append(line + "\n");
}
in.close();
responseText = str.toString();
messageHandler.sendEmptyMessage(UPDATE_TEXT);

}catch(Exception ex){
responseText = "Error";
}

}
}

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete

  2. Inside the button onClick handler... I had created a new thread and executed search request.

    new Thread() {
    public void run() {
    try {
    sendSearchRequest();
    } catch (Exception e) {

    }

    } // End of run()

    }.start();

    ReplyDelete