views:

1979

answers:

5

In order to get XML data from a server repeatedly, I'm attempting to use AsyncTask and Timer as per Mark Murphy's suggestion.

I get the following error:

01-07 16:11:26.705: ERROR/AndroidRuntime(729): 
Caused by: java.lang.RuntimeException: 
Can't create handler inside thread that has not 
called Looper.prepare()

I'm using SDK 1.5 with Eclipse on Windows.

I've looked in documentation, on StackOverflow and in the Android Developers group, but I'm not clear what's causing the error or how to fix it.

I can get the data once -- i.e. without Async and Timer -- and parse it via SAX without problems.

Full app code below.

Please excuse any naive errors: I'm quite new to Android.

package com.foo.bar.myactivity;

import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;

public class MyActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

        Timer timer;
        timer = new Timer();
        timer.schedule(new MyTimerTask(), 0, 1000); 
    }

    public class MyAsyncTask extends AsyncTask<String, Integer, MyData> {
        protected MyData doInBackground(String... string) {
            MyData myData = new MyData();
            try {
                URL url = new URL("http://www.example.com/my.xml");
                SAXParserFactory spf = SAXParserFactory.newInstance();
                SAXParser sp = spf.newSAXParser();

                XMLReader xr = sp.getXMLReader();

                MyHandler myHandler = new MyHandler();
                xr.setContentHandler(myHandler);

                System.setProperty("http.proxyHost", "www-cache.example.com");
                System.setProperty("http.proxyPort", "80");

                xr.parse(new InputSource(url.openStream()));

                myData = myHandler.getParsedData();
                return myData;

            } catch (Exception e) {
                Log.e(">>>>>>>>>>>> Error getting myData: ", e.getMessage(), e);
                return myData;
            }

        }

        protected void onProgressUpdate(Integer... progress) {
            // setProgressPercent(progress[0]);
        }

        protected void onPostExecute(MyData myData) {
            Log.d(">>>>>>>>>>>>>My data: ", myData.toString());
        }
    }

    public class MyTimerTask extends TimerTask {
        public void run() {
            try {
                new MyAsyncTask().execute("");
            } catch (Exception e) {
                Log.e(">>>>>>>>>>>> Error executing MyAsyncTask: ", e.getMessage(), e);
            }
        }
    }

}
+1  A: 

The problem seems to be that you haven't called Looper.prepare(). You could have a look at the documentation for Looper.

A Handler needs a message loop in order to process messages and a Thread by default doesn't have one.

It seems that AsyncTask uses a Handler internally so add Looper.prepare() at the top of your run() method in MyTimerTask and that should solve your problem.

Dave Webb
Tried this, but still get the same exception.If I add Looper.prepare() to the top of run() in MyTimerTask, doInBackground() is called once, with no exception, but onPostExecute() is never called.
Sam Dutton
Sam's `MyHandler` doesn't seem to be a subclass of android `Handler` no ??? Otherwise how it can be passed as an argument of a `XmlReader` ? Is there another Thread in your application which create a `android.os.Handler` ?
kosokund
My mistake, sorry. The problem is the `AsyncTask` uses a `Handler` so you need to do a `Looper.prepare()` in your `TimerTask` thread.
Dave Webb
Guys, it's a SAX content handler not a Handler. :\
James
@James - I didn't realise that at first as I didn't read the code carefully enough. As I said above the problem is that `AsyncTask` uses a `Handler` internally so you need to create a message loop in the `TimerTask` thread.
Dave Webb
A: 

I'd use the service with alarm manager

   I    Intent updateIntent = new Intent(ACTION_UPDATE_ALL);
        updateIntent.setClass(this, UpdateService.class);
        PendingIntent pendingIntent = PendingIntent.getService(this, 0, updateIntent, 0);
        AlarmManager alarmManager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC, nextUpdate, pendingIntent);
Alex Volovoy
AlarmManager is definitely **not** for tasks like this. The javadoc for AlarmManager says: "The Alarm Manager is intended for cases where you want to have your application code run at a specific time, even if your application is not currently running. For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use Handler." http://developer.android.com/reference/android/app/AlarmManager.html
Dave Webb
Just to reiterate, as per the comment from Dave Webb: I need to do a background task repeatedly, not at at a specific time.
Sam Dutton
I think my requirements are pretty clear: "to get XML data from a server repeatedly" -- and the code shows that I intend to get updates every second. I take your point that this can be done with AlarmManager, but I agree with Dave Webb that this is probably not the best way to do it.
Sam Dutton
A: 

The problem is you can't create the Handler object in the thread that doInBackground() runs in. I would implement the onPreExecute() method and put your setup code in there. Try moving these lines into the onPreExecute() method:

            URL url = new URL("http://www.example.com/my.xml");
            SAXParserFactory spf = SAXParserFactory.newInstance();
            SAXParser sp = spf.newSAXParser();

            XMLReader xr = sp.getXMLReader();

            MyHandler myHandler = new MyHandler();
            xr.setContentHandler(myHandler);
mbaird
onPreExecute() runs in the UI thread and is so intended for updating the UI not for processing.
Dave Webb
I would think just because it runs on the UI thread that wouldn't mean you should only use it for UI updates. In my opinion doing a bit of quick setup in onPreExecute() would be fine.
mbaird
+4  A: 

The problem is in the use of TimerTask. TimerTask run should post to a handler, something like this:

private Handler mHandler = new Handler(); 

public class MyTimerTask extends TimerTask {
    public void run() {
        mHandler.post(
            new Runnable() { 
                public void run() { 
                    new MyAsyncTask().execute("");
                } 
            };     
        )
    }
}

Of course this is getting a bit ugly, so would recommend taking out the anonymous class.

James
Many thanks, James -- this worked. I've added an answer that uses this.
Sam Dutton
+2  A: 

I got this to work thanks to James's answer.

I've included the code below in case it's useful to anyone.

Caveat developer! The code works for me, but may contain errors.

package com.example.test;

import java.net.URL;
import java.util.Timer;
import java.util.TimerTask;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;

public class MyXmlPoller extends Activity {

    private Handler handler = new Handler();

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

        new Timer().schedule(new MyTimerTask(), 0, 1000);
    }

    private class MyAsyncTask extends AsyncTask<Integer, Integer, MyData> {

        protected MyData doInBackground(Integer... counter) {
            MyData myData = new MyData();

            try {
                URL url = new URL("http://www.example.com/my.xml");
                SAXParserFactory spf = SAXParserFactory.newInstance();
                SAXParser sp = spf.newSAXParser();

                XMLReader xr = sp.getXMLReader();

                MySAXHandler mySAXHandler = new Handler();
                xr.setContentHandler(mySAXHandler);

                xr.parse(new InputSource(url.openStream()));

                myData = mySAXHandler.getParsedData();

                return myData;

            } catch (Exception e) {
                Log.e("!!!!!!!!!! MyAsyncTask doInBackground error", e.getMessage(), e);
                return myData;
            }

        }

        protected void onPostExecute(MyData myData) {
            Log.d("+++++++++++++ MyAsyncTask onPostExecute", myData.toString());
        }
    } // MyAsyncTask

    public class MyTimerTask extends TimerTask {
        private Runnable runnable = new Runnable() {
            public void run() {
                new MyAsyncTask().execute();
            }
        };

        public void run() {
            handler.post(runnable);
        }
    }
}
Sam Dutton