Usually an Android app needs to connect to a remote server and invoke webservice to exchange data or the android app has to consume a webservice. We can use IntentService to accomplish this task
Consume Webservice in Android
Restful Webservice
IntentService
ResultReceiver
We know that Android Service has a “longer” life than an Android Activity and if we want a task is completed even if the app UI is no longer available we should use a Service.
Consider for example the download scenario: let’s suppose we have to download some information from a remote server; we could do it in the UI (i.e Activity) but if the user, for example, moves to another app the download will be interrupted. If we want to be sure that the download continues even if the app isn’t anymore active we can use Service.
Android IntentService description
When we want to consume a Webservice, we have to be aware that the time required to contact the server and get the response can be quite long. Even if we execute this logic is a separate service, the service itself runs in the main thread, so that the calling activity can lag, waiting for the response from the service, and the OS can kill it showing the annoying ANR message. We can avoid this problem handling a separate thread inside the service, but Android SDK provides a specialized class, derived from the Service class, called IntentService defined as (from Android API docs):“IntentService is a base class for
Service
s that handle asynchronous requests (expressed as Intent
s) on demand. Clients send requests through startService(Intent)
calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.This "work queue processor" pattern is commonly used to offload tasks from an application's main thread.”Considering that the heavy work is made in a separate thread we don’t know when it will finish and the calling Activity is unaware of it. We will describe how to get back the information as the service retrieved data from the remote Webservice.
Creating the Android IntentService
As example we will use Yahoo! Finance Service to retrieve the current Ask and Bid value for a Stock. At the end we will build an app like this:
The first step is creating a service:
public class QuoteService extends IntentService {
@Override
protected void onHandleIntent(Intent intent) {
// Here we do the heavy work
}
}
At line 4, in
onHandleIntent
we do the “heavy work”, so we call the remote Webservice and parse the response. In this case we can simply use XmlPullParser to parse the data:// Here we retrieve the stock quote using Yahoo! Finance
Log.d("Srv", "Get stock");
String url = YAHOO_FINANCE_URL.replaceAll("#sym#", stock.getSymbol());
try {
HttpURLConnection con = (HttpURLConnection) ( new URL(url)).openConnection();
con.setRequestMethod("GET");
con.connect();
InputStream is = con.getInputStream();
// Start parsing XML
XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setInput(is, null);
int event = parser.getEventType();
String tagName = null;
String currentTag = null;
Stock stockResult = new Stock();
while (event != XmlPullParser.END_DOCUMENT) {
tagName = parser.getName();
Log.d("Srv", "Tag:" + tagName);
if (event == XmlPullParser.START_TAG) {
currentTag = tagName;
}
else if (event == XmlPullParser.TEXT) {
if ("ASK".equalsIgnoreCase(currentTag))
//
else if ("BID".equalsIgnoreCase(currentTag)) {
//
}
}
event = parser.next();
}
} catch (Exception e) {
e.printStackTrace();
}
Then we define our service in the Manifest.xml:
<service android:name=".QuoteService" />
Activity: Webservice client
The next step is creating the client activity that invokes the service. We are using an IntentService then the calling Activity calls the service and then forget it. The activity is quite trivial, because as soon as the user clicks “Get quote!” button we invoke the service using:startService(service_intent);
Anyway, there are two aspects we should consider:
- How we pass the data to the service
- How we get the data back from the service and show the information in UI
To pass data to the service we can create a simple java class that holds the stock symbol and the values we want to be filled by the service as we get the response. Our class should be a Parcelable class:
public class Stock implements Parcelable {
private String symbol;
private double askValue;
private double bidValue;
public Stock() {}
public Stock(String symbol, double askValue, double bidValue) {
this.symbol = symbol;
this.askValue = askValue;
this.bidValue = bidValue;
}
// get and set method
@Override
public void writeToParcel(Parcel dest, int flags) {
// We write the stock information in the parcel
dest.writeString(symbol);
dest.writeDouble(askValue);
dest.writeDouble(bidValue);
}
public static final Creator<Stock> CREATOR = new Creator<Stock>() {
@Override
public Stock createFromParcel(Parcel source) {
Stock stock = new Stock();
stock.setSymbol(source.readString());
stock.setAskValue(source.readDouble());
stock.setBidValue(source.readDouble());
return stock;
}
@Override
public Stock[] newArray(int size) {
return new Stock[0];
}
};
@Override
public int describeContents() {
return 0;
}
}
At line 18 and 25, we implement the two methods required to marshal and unmarshal our class.
Retrieve data from Android service using Android ResultReceiver
Another interesting aspect is how we can get the values back from the IntentService. As we, already, know the calling activity doesn’t wait for the result, so we have to find another way. Generally speaking there are different ways to solve this problem, in this post I will explain how to use ResultReceiver. This method is quite simple and elegant in my opinion.The first step is creating a class that extends ResultReceiver, that we call
QuoteServiceReceiver
:public class QuoteServiceReceiver extends ResultReceiver {
private Listener listener;
public QuoteServiceReceiver(Handler handler) {
super(handler);
}
public void setListener(Listener listener) {
this.listener = listener;
}
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
if (listener != null)
listener.onReceiveResult(resultCode, resultData);
}
public static interface Listener {
void onReceiveResult(int resultCode, Bundle resultData);
}
}
This class is very simple. At line 19 we define an interface that the calling activity has to implement to get notified when the data isavailable. In this interface we define a Bundle that holds the data retrieved. Then at line 14 we override the onReceiveResult and call our callback method defined in the previous interface.
From the client point of view (so our Activity) we simply have to implements the interface defined and pass the QuoteServiceReceiver to the IntentService:
private Intent createCallingIntent(Stock stock) {
Intent i = new Intent(this, QuoteService.class);
QuoteServiceReceiver receiver = new QuoteServiceReceiver(new Handler());
receiver.setListener(this);
i.putExtra("rec", receiver);
i.putExtra("stock", stock);
return i;
}
and when we call the service:
startService(createCallingIntent(stock));
..and in the callback method:
@Override
public void onReceiveResult(int resultCode, Bundle resultData) {
Stock stock = resultData.getParcelable("stock");
Log.d("Srv", "Stock ["+stock+"]");
askValue.setText("" + stock.getAskValue());
bidValue.setText("" + stock.getBidValue());
}
..and here we update the UI, with the data received.
In the service that consumes the remote Webservice:
@Override
protected void onHandleIntent(Intent intent) {
Stock stock = intent.getParcelableExtra("stock");
final ResultReceiver rec = (ResultReceiver) intent.getParcelableExtra("rec");
....
// When data is ready
if ("BID".equalsIgnoreCase(currentTag)) {
stockResult.setBidValue(Double.parseDouble(parser.getText()));
Bundle b = new Bundle();
b.putParcelable("stock", stockResult);
rec.send(0, b);
}
....
}
Source code available @github
0 comments:
Post a Comment