By Martyn


2010-07-07 17:34:27 8 Comments

I'm looking to make a service which I can use to make calls to a web-based REST API.

Basically I want to start a service on app init then I want to be able to ask that service to request a url and return the results. In the meantime I want to be able to display a progress window or something similar.

I've created a service currently which uses IDL, I've read somewhere that you only really need this for cross app communication, so think these needs stripping out but unsure how to do callbacks without it. Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

Currently I have a service with post and get http methods inside, a couple of AIDL files (for two way communication), a ServiceManager which deals with starting, stopping, binding etc to the service and I'm dynamically creating a Handler with specific code for the callbacks as needed.

I don't want anyone to give me a complete code base to work on, but some pointers would be greatly appreciated.

Code in (mostly) full:

public class RestfulAPIService extends Service  {

final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();

public void onStart(Intent intent, int startId) {
    super.onStart(intent, startId);
}
public IBinder onBind(Intent intent) {
    return binder;
}
public void onCreate() {
    super.onCreate();
}
public void onDestroy() {
    super.onDestroy();
    mCallbacks.kill();
}
private final IRestfulService.Stub binder = new IRestfulService.Stub() {
    public void doLogin(String username, String password) {

        Message msg = new Message();
        Bundle data = new Bundle();
        HashMap<String, String> values = new HashMap<String, String>();
        values.put("username", username);
        values.put("password", password);
        String result = post(Config.getURL("login"), values);
        data.putString("response", result);
        msg.setData(data);
        msg.what = Config.ACTION_LOGIN;
        mHandler.sendMessage(msg);
    }

    public void registerCallback(IRemoteServiceCallback cb) {
        if (cb != null)
            mCallbacks.register(cb);
    }
};

private final Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        // Broadcast to all clients the new value.
        final int N = mCallbacks.beginBroadcast();
        for (int i = 0; i < N; i++) {
            try {
                switch (msg.what) {
                case Config.ACTION_LOGIN:
                    mCallbacks.getBroadcastItem(i).userLogIn( msg.getData().getString("response"));
                    break;
                default:
                    super.handleMessage(msg);
                    return;

                }
            } catch (RemoteException e) {
            }
        }
        mCallbacks.finishBroadcast();
    }
    public String post(String url, HashMap<String, String> namePairs) {...}
    public String get(String url) {...}
};

A couple of AIDL files:

package com.something.android

oneway interface IRemoteServiceCallback {
    void userLogIn(String result);
}

and

package com.something.android
import com.something.android.IRemoteServiceCallback;

interface IRestfulService {
    void doLogin(in String username, in String password);
    void registerCallback(IRemoteServiceCallback cb);
}

and the service manager:

public class ServiceManager {

    final RemoteCallbackList<IRemoteServiceCallback> mCallbacks = new RemoteCallbackList<IRemoteServiceCallback>();
    public IRestfulService restfulService;
    private RestfulServiceConnection conn;
    private boolean started = false;
    private Context context;

    public ServiceManager(Context context) {
        this.context = context;
    }

    public void startService() {
        if (started) {
            Toast.makeText(context, "Service already started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.startService(i);
            started = true;
        }
    }

    public void stopService() {
        if (!started) {
            Toast.makeText(context, "Service not yet started", Toast.LENGTH_SHORT).show();
        } else {
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.stopService(i);
            started = false;
        }
    }

    public void bindService() {
        if (conn == null) {
            conn = new RestfulServiceConnection();
            Intent i = new Intent();
            i.setClassName("com.something.android", "com.something.android.RestfulAPIService");
            context.bindService(i, conn, Context.BIND_AUTO_CREATE);
        } else {
            Toast.makeText(context, "Cannot bind - service already bound", Toast.LENGTH_SHORT).show();
        }
    }

    protected void destroy() {
        releaseService();
    }

    private void releaseService() {
        if (conn != null) {
            context.unbindService(conn);
            conn = null;
            Log.d(LOG_TAG, "unbindService()");
        } else {
            Toast.makeText(context, "Cannot unbind - service not bound", Toast.LENGTH_SHORT).show();
        }
    }

    class RestfulServiceConnection implements ServiceConnection {
        public void onServiceConnected(ComponentName className, IBinder boundService) {
            restfulService = IRestfulService.Stub.asInterface((IBinder) boundService);
            try {
            restfulService.registerCallback(mCallback);
            } catch (RemoteException e) {}
        }

        public void onServiceDisconnected(ComponentName className) {
            restfulService = null;
        }
    };

    private IRemoteServiceCallback mCallback = new IRemoteServiceCallback.Stub() {
        public void userLogIn(String result) throws RemoteException {
            mHandler.sendMessage(mHandler.obtainMessage(Config.ACTION_LOGIN, result));

        }
    };

    private Handler mHandler;

    public void setHandler(Handler handler) {
        mHandler = handler;
    }
}

Service init and bind:

// this I'm calling on app onCreate
servicemanager = new ServiceManager(this);
servicemanager.startService();
servicemanager.bindService();
application = (ApplicationState)this.getApplication();
application.setServiceManager(servicemanager);

service function call:

// this lot i'm calling as required - in this example for login
progressDialog = new ProgressDialog(Login.this);
progressDialog.setMessage("Logging you in...");
progressDialog.show();

application = (ApplicationState) getApplication();
servicemanager = application.getServiceManager();
servicemanager.setHandler(mHandler);

try {
    servicemanager.restfulService.doLogin(args[0], args[1]);
} catch (RemoteException e) {
    e.printStackTrace();
}

...later in the same file...

Handler mHandler = new Handler() {
    public void handleMessage(Message msg) {

        switch (msg.what) {
        case Config.ACTION_LOGIN:

            if (progressDialog.isShowing()) {
                progressDialog.dismiss();
            }

            try {
                ...process login results...
                }
            } catch (JSONException e) {
                Log.e("JSON", "There was an error parsing the JSON", e);
            }
            break;
        default:
            super.handleMessage(msg);
        }

    }

};

11 comments

@Robby Pond 2010-07-07 17:53:07

If your service is going to be part of you application then you are making it way more complex than it needs to be. Since you have a simple use case of getting some data from a RESTful Web Service, you should look into ResultReceiver and IntentService.

This Service + ResultReceiver pattern works by starting or binding to the service with startService() when you want to do some action. You can specify the operation to perform and pass in your ResultReceiver (the activity) through the extras in the Intent.

In the service you implement onHandleIntent to do the operation that is specified in the Intent. When the operation is completed you use the passed in ResultReceiver to send a message back to the Activity at which point onReceiveResult will be called.

So for example, you want to pull some data from your Web Service.

  1. You create the intent and call startService.
  2. The operation in the service starts and it sends the activity a message saying it started
  3. The activity processes the message and shows a progress.
  4. The service finishes the operation and sends some data back to your activity.
  5. Your activity processes the data and puts in in a list view
  6. The service sends you a message saying that it is done, and it kills itself.
  7. The activity gets the finish message and hides the progress dialog.

I know you mentioned you didn't want a code base but the open source Google I/O 2010 app uses a service in this way I am describing.

Updated to add sample code:

The activity.

public class HomeActivity extends Activity implements MyResultReceiver.Receiver {

    public MyResultReceiver mReceiver;

    public void onCreate(Bundle savedInstanceState) {
        mReceiver = new MyResultReceiver(new Handler());
        mReceiver.setReceiver(this);
        ...
        final Intent intent = new Intent(Intent.ACTION_SYNC, null, this, QueryService.class);
        intent.putExtra("receiver", mReceiver);
        intent.putExtra("command", "query");
        startService(intent);
    }

    public void onPause() {
        mReceiver.setReceiver(null); // clear receiver so no leaks.
    }

    public void onReceiveResult(int resultCode, Bundle resultData) {
        switch (resultCode) {
        case RUNNING:
            //show progress
            break;
        case FINISHED:
            List results = resultData.getParcelableList("results");
            // do something interesting
            // hide progress
            break;
        case ERROR:
            // handle the error;
            break;
    }
}

The Service:

public class QueryService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        final ResultReceiver receiver = intent.getParcelableExtra("receiver");
        String command = intent.getStringExtra("command");
        Bundle b = new Bundle();
        if(command.equals("query") {
            receiver.send(STATUS_RUNNING, Bundle.EMPTY);
            try {
                // get some data or something           
                b.putParcelableArrayList("results", results);
                receiver.send(STATUS_FINISHED, b)
            } catch(Exception e) {
                b.putString(Intent.EXTRA_TEXT, e.toString());
                receiver.send(STATUS_ERROR, b);
            }    
        }
    }
}

ResultReceiver extension - edited about to implement MyResultReceiver.Receiver

public class MyResultReceiver implements ResultReceiver {
    private Receiver mReceiver;

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    public void setReceiver(Receiver receiver) {
        mReceiver = receiver;
    }

    public interface Receiver {
        public void onReceiveResult(int resultCode, Bundle resultData);
    }

    @Override
    protected void onReceiveResult(int resultCode, Bundle resultData) {
        if (mReceiver != null) {
            mReceiver.onReceiveResult(resultCode, resultData);
        }
    }
}

@idolize 2010-07-08 02:16:13

Good answer, although the pattern is a little hard to completely wrap my head around. Would you mind adding some example code? That would be really great!

@Martyn 2010-07-08 13:47:08

Excellent - thank you! I ended up going through the Google iosched application and wow...it's pretty complex but I have something which is working, now I just need to work out why it works! But yes, this is the basic pattern that I have working. thank you very much.

@FrVaBe 2011-02-24 09:46:29

Can not find any ResultReceiver interface which my activity could implement. Instead it is a concrete class which has to be extended and used. Nevertheless good and helpful post!

@Robby Pond 2011-02-24 13:20:04

@K. Claszen You are correct I was doing this from memory and in the sdk api it calls ResultReceiver an interface when it is actually a class. I added my ResultReceiver extension to make the example more complete and correct. I actually use this pattern in a few of my successful apps. Let me know if you have any other questions.

@u2ix 2011-08-10 19:04:15

But in case the user closes the app during service call the UI won't be refreshed on resuming. How to solve that?

@Robby Pond 2011-08-11 16:12:26

@u2ix Then the question is if the user goes to another app do they care about the results? If the answer is yes then the service should save the results somewhere (db) that the app would load when the user restarted it.

@u2ix 2011-08-12 15:42:27

@Robby Pond The user might go to another app during the loading process. During this I show a ProgressDialog, which won't disappear because the activity isn't notified about new results.

@Sam 2011-09-11 17:34:32

Excellent post +1, just thinking what is the different if used Broadcast Receiver, sample link"mobile.tutsplus.com/tutorials/android/…"

@Nemanja Kovacevic 2012-01-19 19:52:09

@Robby Pond Thank you for this answer this is how complex it should be for simple apps and not that Google IO session overkill. However I've run into a little mess with this. It's working perfectly well but I want to provide a user with a chance to cancel request. So you click on a button, service is started, progress wheel is spinning and you press back key. What happens in the code then? Is it possible to kill an intent service and how? How do you deal with this kind of situation?

@Ben 2012-02-02 17:22:03

Is there any way to use this on a service that has already been started? i.e. App A has a service that is started before App B is launched. App B is then launched and then wants to make a call and get a result from the service running in App A.

@Vincent Mimoun-Prat 2012-02-24 08:35:06

One small addition to the answer: as you do the mReceiver.setReceiver(null); in the onPause method, you should do the mReceiver.setReceiver(this); in the onResume method. Else you might not receive the events if your activity is resumed without being re-created

@Mbrevda 2012-08-28 07:14:47

I'm trying to wrap my head around all these different parts... does anyone have a link to this same content/pattern/idea, just a bit more verbose?

@Mikael Ohlson 2012-09-13 21:19:08

Don't the docs say that you don't have to call stopSelf, since IntentService does that for you?

@quietmint 2012-09-17 21:01:12

@MikaelOhlson Correct, you should not call stopSelf if you subclass IntentService because if you do, you'll lose any pending requests to the same IntentService.

@james 2012-09-24 20:41:30

what about when the receiver is null? would need to send a notification to the activity or something... arg!

@kontinuity 2012-11-01 21:38:08

Thanks for the great answer. Can the resultant logic be done directly in the MyResultReceiver since the handler is also available? Or is it simply good design to delegate control back to the activity?

@Maragues 2013-01-08 16:09:48

What if the Activity is destroyed? What happens when invoking receiver.send? If the handler has no queue to post to, it'll throw a RuntimeException. I guess I'm missing something.

@Andy Res 2013-01-21 12:10:37

If I need to show the progress of download process in a notification bar lets say, and update it every 1 second so I can see its current progress. Then, is it OK to use the ResultReceiver to send the progress to notification every 1 second? Or should I use in this case a BroadcastReceiver, or it's the same?

@rmirabelle 2013-03-22 03:39:12

even this simplified structure is pretty 'heady' - but I've got it up and running - thanks for your efforts on this

@coderek 2013-04-02 09:13:35

so the up-to-date pattern to do this is AsyncTask?

@Pierre Criulanscy 2013-06-13 09:19:38

It's a very good pattern, you can find a library that's implements this pattern (and more generally the Service Pattern in Google's IO video) : RESTDroid

@turtleboy 2013-07-19 12:37:14

@RobbyPond Hi i sort of understand you code and would like to implement in lieu of AsyncTask. I have put MyResultReceiver as a public inner class of my activity but where does the variable of type Receive come from? Thanks in advance

@Snicolas 2013-07-25 20:57:36

RoboSpice is actually the easiest way to implement a REST client inside a service on Android. (I can tell, I am one of the contributors ;))

@Robby Pond 2013-09-07 16:27:12

@Aerovistae Yes this still works. Another approach now used are Non-UI fragments that can live beyond the current Activity.

@S.D. 2014-05-08 09:49:34

You can do the same Via Messengers, but the whole command pattern is too much when you are just calling a local service.

@Euporie 2014-06-26 06:20:28

IntentService will kill itself when task is complete, so this.stopSelf()is unnecessary.

@Martin Konecny 2015-05-05 05:45:25

Maybe I'm missing something here, but I am dumbfounded why we are going through all this effort to make an API call? What's wrong with a simple AsyncTask? Why do we need to launch a service to do this?

@snafu109 2016-05-17 16:53:10

@MartinKonecny if the request is one that MUST complete, AsyncTask won't be sufficient - if the app is killed (e.g. low memory and user takes a call) the request won't necessarily complete. If the request is run from a service, the process is less likely to be killed when the application moves to the background. Service processes have higher priority than background processes. For pulling data for UI display it doesn't matter if request is incomplete, but if you're posting an update from the app to a server then it would be since the data might be lost when the app is killed.

@Rohit Singh 2017-10-06 12:59:48

@RobbyPond what is the significance of having a Receiver inside a MtResultReceiver ? Can't activity just implement ResultReceiver ? Can u pls explain

@Terrance 2011-09-20 12:35:27

Developing Android REST client applications has been an awesome resource for me. The speaker does not show any code, he just goes over design considerations and techniques in putting together a rock solid Rest Api in android. If your a podcast kinda person or not, I'd recommend giving this one at least one listen but, personally I've listened to it like 4 or five times thus far and I'm probably going to listen to it again.

Developing Android REST client applications
Author: Virgil Dobjanschi
Description:

This session will present architectural considerations for developing RESTful applications on the Android platform. It focuses on design patterns, platform integration and performance issues specific to the Android platform.

And there are so many considerations I really hadn't made in the first version of my api that I've had to refactor

@Thomas Ahle 2011-09-21 14:29:57

+1 This contains the kinds of considerations you would never think of when you start out.

@Terrance 2011-09-21 14:33:30

Yeah my first foray into developing a Rest client was almost exactly his description of what exactly not to do (Thankfully I realized much of this was wrong before I watched this). I lold at that a bit.

@Flávio Faria 2011-10-19 20:06:17

I've seen this video more than once and I'm implementing the second pattern. My problem is that I need to use transactions in my complex database model to update local data from fresh data that comes from the server, and the ContentProvider interface doesn't provide me a way to do it. Do you have any suggestion, Terrance?

@Donal Lafferty 2015-07-02 16:14:31

NB: Comments by Dobjanschi on HttpClient no longer hold. See stackoverflow.com/a/15524143/939250

@RonR 2015-10-11 13:12:44

Yes, HttpURLConnection is now preferred. Further, with the release of Android 6.0, support for the Apache HTTP Client has officially been removed.

@Pete 2014-09-27 10:04:52

I would highly recommend the REST client Retrofit.

I have found this well written blog post extremely helpful, it also contains simple example code. The author uses Retrofit to make the network calls and Otto to implement a data bus pattern:

http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html

@Andrew Halloran 2013-04-08 18:06:20

Robby provides a great answer, though I can see you still looking for more information. I implemented REST api calls the easy BUT wrong way. It wasn't until watching this Google I/O video that I understood where I went wrong. It's not as simple as putting together an AsyncTask with a HttpUrlConnection get/put call.

@zim 2014-04-03 20:23:04

Dead link. Here is the updated one - Google I/O 2010 - Android REST client applications

@StlTenny 2012-07-11 18:01:09

Just wanted to point you all in the direction of an standalone class I rolled that incorporates all of the functionality.

http://github.com/StlTenny/RestService

It executes the request as non-blocking, and returns the results in an easy to implement handler. Even comes with an example implementation.

@Jose L Ugia 2012-07-05 14:59:01

There is another approach here which basically helps you to forget about the whole management of the requests. It is based on an async queue method and a callable/callback based response. The main advantage is that by using this method you'll be able to make the whole process (request, get and parse response, sabe to db) completely transparent for you. Once you get the response code the work is already done. After that you just need to make a call to your db and you are done. It helps as well with the problematic of what happens when your activity is not active. What will happen here is that you'll have all your data saved in your local database but the response won't be processed by your activity, that's the ideal way.

I wrote about a general approach here http://ugiagonzalez.com/2012/07/02/theres-life-after-asynctasks-in-android/

I'll be putting specific sample code in upcoming posts. Hope it helps, feel free to contact me for sharing the approach and solving potential doubts or issues.

@jk7 2017-03-14 16:54:06

Dead link. The domain has expired.

@Aakash 2011-11-04 03:49:13

Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

In this case its better to use asynctask, which runs on a different thread and return result back to the ui thread on completion.

@panchicore 2011-09-12 19:38:47

I know @Martyn does not want full code, but I think this annotation its good for this question:

10 Open Source Android Apps which every Android developer must look into

Foursquared for Android is open-source, and have an interesting code pattern interacting with the foursquare REST API.

@TjerkW 2011-04-28 08:15:11

Note that the solution from Robby Pond is somehow lacking: in this way you only allow todo one api call at a time since the IntentService only handles one intent at a time. Often you want to perform parallel api calls. If you want todo this you have to extend Service instead of IntentService and create your own thread.

@Viren 2011-07-25 08:05:32

You can still do multiple calls on the IntentService by delegating the webservice api calls to an executor-thread-service, present as a member variable of your IntentService-derived class

@Nikhil_Katre 2011-03-17 09:49:29

Lets say I want to start the service on an event - onItemClicked() of a button. The Receiver mechanism would not work in that case because :-
a) I passed the Receiver to the service (as in Intent extra) from onItemClicked()
b) Activity moves to the background. In onPause() I set the receiver reference within the ResultReceiver to null to avoid leaking the Activity.
c) Activity gets destroyed.
d) Activity gets created again. However at this point the Service will not be able to make a callback to the Activity as that receiver reference is lost.
The mechanism of a limited broadcast or a PendingIntent seems to be more usefull in such scenarios- refer to Notify activity from service

@DArkO 2011-09-05 06:31:12

there is a problem with what you are saying. that is that when the activity moves to background it isn't destroyed... so the receiver still exists and the activity context also.

@jk7 2017-03-14 17:37:42

@DArkO When an Activity is paused or stopped it can be killed by the Android system in low memory situations. Refer to Activity Lifecycle.

@Soumya Simanta 2010-07-07 18:34:35

Also when I hit the post(Config.getURL("login"), values) the app seems to pause for a while (seems weird - thought the idea behind a service was that it runs on a different thread!)

No you have to create a thread yourself, a Local service runs in the UI thread by default.

Related Questions

Sponsored Content

35 Answered Questions

[SOLVED] What exactly is RESTful programming?

  • 2009-03-22 14:45:39
  • hasen
  • 1661910 View
  • 3996 Score
  • 35 Answer
  • Tags:   http rest definition

12 Answered Questions

[SOLVED] Call a REST API in PHP

34 Answered Questions

[SOLVED] PUT vs. POST in REST

  • 2009-03-10 14:25:20
  • alex
  • 2415488 View
  • 5442 Score
  • 34 Answer
  • Tags:   http rest post put

10 Answered Questions

[SOLVED] SOAP vs REST (differences)

2 Answered Questions

Android AlarmManager for periodical sensor reading

10 Answered Questions

[SOLVED] Understanding REST: Verbs, error codes, and authentication

  • 2010-01-04 19:55:36
  • Pekka
  • 133035 View
  • 604 Score
  • 10 Answer
  • Tags:   web-services rest

Sponsored Content