I want to develop an Android App with three activities and two services.
The first Service, named WebClientService, calls a REST API every 30 seconds, using an Handler, and has to notify the active Activity with the result.
It also has to notify a second Service, named DatabaseService, in order to update a local DB.
The Database Service will be called just once onCreate of the activity (in case of app crash and restart) and just once at onRestart (in this way we have data to show in case there were connectivity issues). The activities will then keep themselves updated thanks to the WebClientService that notifies the "alive" activity every 30 seconds.
Questions are:
What's the best way to notify for an update both the active activity and the background DatabaseService?
My idea is to use sendBroadcast() within WebClientService and a BroadcastReceiver in every activity and within the DatabaseService, is it the right approach?
Should I use the same approach for the communication between AllMeetingRoomActivity and DatabaseService or should I use a Bound Service?
Thanks
UPDATE:
DatabaseService won't be a background service anymore but just a shared instance of the db layer between WebClientService and the activities.
So question now is: is it a good approach to just write my 30 seconds updates to the local db and allow the activities to update themselves every few seconds simply reading from the local db?
Would that affect the performance too much?
Context:
Follows what I've implemented so far but using SettableFutures and thus needs to be re-implemented using Services and Broadcasts once I've clear how to make them communicate effectively:
public class MainActivity extends AppCompatActivity {
private TextView meetingsTextView;
private EditText mEdit, editSubject;
private final ConnectorInitializer clientInitializer = new ConnectorInitializer();
private AppConnector genericClient; // can use OutlookClient or a test client to talk with a mock server
#Override
protected void onCreate(Bundle savedInstanceState) {
// initializes client based on the settings in "config.json"
genericClient = clientInitializer.create(this);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
meetingsTextView = (TextView) findViewById(R.id.NowMeeting);
mEdit = (EditText)findViewById(R.id.editText);
editSubject = (EditText)findViewById(R.id.editSubject);
Futures.addCallback(genericClient.logon(this, scopes), new FutureCallback<Boolean>() {
#Override
public void onSuccess(Boolean result) {
Log.d("APP", "-- Logged in. --");
databaseConnector.synchronouslyGetBackupFromLocalDatabase() // FUTURE
// callback here
// onSuccess, onFailure
}
#Override
public void onFailure(#NonNull Throwable t) {
Log.e("\n ~~~~>> logon \n", t.getMessage());
meetingsTextView.setText(R.string.Login_Failed);
}
});
}
/** At the moment the UI is not updated automatically every 30 seconds
* but manually using a refresh button
*/
public void getBookings(#SuppressWarnings("UnusedParameters") View view){
Log.d("APP", "Retrieve button clicked: "+(DateTime.now())+". Calling async getCalendar.");
meetingsTextView.setText(R.string.retrieving_events);
try{
Futures.addCallback( genericClient.getCalendarEvents(), new FutureCallback<String>(){
#Override
public void onSuccess(final String resultCalendars) {
Log.d("APP", "Success. Result: "+resultCalendars);
runOnUiThread(new Runnable() {
#Override
public void run() {
Log.d("APP", "Calendars SUCCESSFULLY retrieved.");
String meetingsRetrieved = getString(R.string.calendar)+resultCalendars;
meetingsTextView.setText(meetingsRetrieved);
Toast.makeText(getApplicationContext(), "Success!", Toast.LENGTH_LONG).show();
}
});
databaseConnector.asyncUpdateLocalDbWithResults(); // FUTURE
// callback here
// onSuccess, onFailure
}
#Override
public void onFailure(#NonNull Throwable t) {
Log.e( "APP", "Calendar error. Cause: "+t.getLocalizedMessage() );
String retrieveError = "Retrieve error. \n\n\n"+t.getLocalizedMessage();
meetingsTextView.setText(retrieveError);
Toast.makeText(getApplicationContext(), "Fail!", Toast.LENGTH_LONG).show();
}
});
}catch(Exception ex){
Log.e("APP","Something went wrong in your code. Cause:"+ex);
}
}
Best option ever:
Use LocalBroadcastManager. More reference here.
MyService.java:
private LocalBroadcastManager localBroadcastManager;
private final String SERVICE_RESULT = "com.service.result";
private final String SERVICE_MESSAGE = "com.service.message";
#Override
public void onCreate() {
super.onCreate();
// Other stuff
localBroadcastManager = LocalBroadcastManager.getInstance(this);
}
Add below method in service, whenever you want to update data from service to Activity, call method by passing Arguments.
private void sendResult(String message) {
Intent intent = new Intent(SERVICE_RESULT);
if(message != null)
intent.putExtra(SERVICE_MESSAGE, message);
localBroadcastManager.sendBroadcast(intent);
}
HomeActivity.java:
private BroadcastReceiver broadcastReceiver;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.activity_home);
broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String s = intent.getStringExtra(MyService.SERVICE_MESSAGE);
// do something here.
}
};
}
#Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver((broadcastReceiver),
new IntentFilter(MyService.SERVICE_RESULT));
}
#Override
protected void onStop() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver);
super.onStop();
}
Hope this will help you.
I think your approach is ok with BroadCastReceiver. However, BroadCastReceiver should be used for a global purpose (like communicating between 2 applications). If you intend to use BroadCastReceiver for your app only, I prefer using LocalBroadcastManager instead. Using LocalBroadcastManager is faster and more security when it can be caught only by your app.
There's another way to communicate between your activitys and your services is using EventBus. It will be much easier than using BroadCastReceiver (especially in passing data between them).
Update: About your update question:
is it a good approach to just write my 30 seconds updates to the local db and allow the activities to update themselves every few seconds simply reading from the local db? --> Of course NO. You should let your activities update themselves when they need. When you update your local db, you should know that is there any changes or not. If there is any change, use LocalBroadcastmanager to notify your activity to update.
Would that affect the performance too much? --> Yes, that do. The db connection will take time to execute and it will block your UI in some cases. in that case, you should use a thread with ExecutorService for each execute (insert, update...). One more thing to consider is updating that frequently will drain your phone battery very, very fast.
You can bind the services to the activities and update your UI.
Or you can use libraries like Otto or EventBus to create a publisher/subscriber dependency and notify your activities everytime your services publish an update of information.
Use event bus for this communication. EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace traditional Java in-process event distribution using explicit registration.
There are a lot of them:
http://square.github.io/otto/
https://github.com/greenrobot/EventBus
This is an example of Otto usage:
Bus bus = new Bus();
bus.post(new AnswerAvailableEvent(42));
#Subscribe public void answerAvailable(AnswerAvailableEvent event) {
// TODO: React to the event somehow!
}
bus.register(this); // In order to receive events, a class instance needs to register with the bus.
To post from any thread (main or background), in you case a Service and receive events on the main thread:
public class MainThreadBus extends Bus {
private final Handler mHandler = new Handler(Looper.getMainLooper());
#Override
public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
super.post(event);
} else {
mHandler.post(new Runnable() {
#Override
public void run() {
MainThreadBus.super.post(event);
}
});
}
}
Related
My app has activities for the user interaction and a background service which is the only place where the data model is being modified. The background service listens to actions that where made by the user as well as incoming messages from the network. Therefore concurrency issues can arise which I try to prevent by using a handler.
For the event layer I use greenrobots Eventbus.
This is all working well but I wonder if there is a smarter/faster/less code extensive (and therefore less error prone) way to handle this use case?
To be more specific:
Is there a way to ensure serial execution of the onEvent methods
without a handler?
Is there an alternative to having onEvent methods
for each possible event?
Is there a better pattern for what I am
doing here?
This is my approach:
In the oncreate method I do register the service (in case of an activity I do this in onstart)
#Override
public void onCreate() {
super.onCreate();
...
EventBus.getDefault().register(this);
}
And in the onDestroy I do unregister again:
#Override
public void onDestroy() {
super.onDestroy();
....
EventBus.getDefault().unregister(this);
}
Whenever I react to an incoming event I want to ensure serial execution as there can be concurreny issues because there are incoming events from user interactions as well as from other users via networking. So I decided to work with a handler:
private Handler handler = new Handler(){
#Override
public void handleMessage(Message msg) {
Object receivedEvent = msg.obj;
if(receivedEvent instanceof EditUser)
{
processEditUserBroadcast((EditUser)receivedEvent);
}
else if(receivedEvent instanceof JoinParty)
{
processJoinPartyBroadcast((JoinParty)receivedEvent);
}
else if(receivedEvent instanceof LeaveParty)
{
processLeavePartyBroadcast();
}
else if(receivedEvent instanceof SendMessage)
{
processSendMessageBroadcast((SendMessage)receivedEvent);
}
else if(receivedEvent instanceof ReceivedMessage)
{
processReceivedMessageBroadcast((ReceivedMessage)receivedEvent);
}
else if(receivedEvent instanceof Reset)
{
processResetBroadcast();
}
else if(receivedEvent instanceof ImageDownloadFinished)
{
processImageDownloadFinishedBroadcast((ImageDownloadFinished)receivedEvent);
}
}
};
return handler;
}
For each event of interest I do have an onEvent method which is doing nothing but passing the event to the handler to ensure serial execution via a small "passToHandler" helper function
public void passToHandler(Handler handler, Object object)
{
Message message = handler.obtainMessage();
message.obj = object;
handler.sendMessage(message);
}
public void onEvent(EditUser editUser)
{
passToHandler(handler,editUser);
}
public void onEvent(JoinParty joinParty)
{
passToHandler(handler,joinParty);
}
public void onEvent(LeaveParty leaveParty)
{
passToHandler(handler,leaveParty);
}
public void onEvent(SendMessage sendMessage)
{
passToHandler(handler,sendMessage);
}
public void onEvent(ReceivedMessage receivedMessage)
{
passToHandler(handler,receivedMessage);
}
public void onEvent(Reset reset)
{
passToHandler(handler,reset);
}
public void onEvent(ImageDownloadFinished imageDownloadFinished)
{
passToHandler(handler,imageDownloadFinished);
}
The "process.." methods are where the "data magic" happens and shouldn´t be relevant for my question.
And of course for each possible event I did create a class which is usually quite slim like this:
public class JoinParty {
private String partyCode;
public JoinParty(String partyCode) {
super();
this.partyCode = partyCode;
}
public String getPartyCode() {
return partyCode;
}
}
Thank you for posting this Matthias! I think you bring up a very important point about thread safety with GreenRobot EventBus that can easily be missed by users of it.
I think you are quite possibly heading down the right path, though I'm new to GreenRobot EventBus and Android (but not Java). If I read the GreenRobot EventBus source code correctly, one other possible benefit to this approach is that post of the SendMessage event to your onEvent() method immediately returns (after calling sendMessage on the Handler) allowing the EventBus to continue posting it to any other subscribers without delay of the actual processing by your class. This may or may not be what you desire though.
With the approach that you have given, the other thing you need to ensure is that if you take an approach like this that there are no other public methods to your class that has all of your onEvent() methods and the methods such as processEditUserBroadcast(). Otherwise, while you have ensured that all of the processing of the events received from the EventBus are actually handled on a single thread (in a serial manner), some other class might call a public method of this class on a different thread and then cause you thread safety issues again.
If you know that you do need to support other public methods on this class, doing what you have done here at least gets all of the onEvent() methods handling onto a single thread (that of the Looper for the thread that creates the Looper from what I read in the doc for the Looper class) and that simplifies things at least some. You may also then need to apply some synchronization to the public methods and all of the other methods such as processEditUserBroadcast() so as to guarantee safe access to the data members of the class from multiple threads if you are going to have other public methods on this class. Alternatively, depending on what those data members are and what your needs are, you might be able to get by with simply making some of them volatile, atomic, or using the concurrent collections, etc. It all depends on what the read and write access needs are and also the needed granularity of those accesses.
Does this help at all? For those that are well versed with Android, Loopers, Handlers, GreenRobot EventBus, etc. have I misspoken at all?
It's my first question on SO, I hope this question won't be bad.
I have a service, it starts working when user launchs an app and works until user will kill it via task killer or turn off his device.
This service has a background thread which does some work with data. I need to bind activities (from activities, not by service) and sometimes (1-2 times per 30 seconds) send data to binded activities.
Structure of my service:
public class myserv extends Service {
public static boolean started=false;
public class workwithdata extends Thread {
#Override
public synchronized void start() {
super.start();
//.. Not important.
}
#Override
public void run() {
if (running) return;
while (true) {
if(condition) mythread.sleep(30000);
else {
Object data = recieveMyData();
if (!data.isEmpty()) {
//.. Some work with recieved data, not important.
sendDataToBindedActivities(data); //This is what I need.
}
mythread.sleep(10000);
}
}
}
}
#Override
public void onCreate() {
super.onCreate();
this.started=true;
mythread = new workwithdata();
mythread.start();
}
}
Well, I found one question but my problem has a little differences: I don't need to send any data to the service, I need just send some data to all binded activities (which service doesn't know at all).
Structure for which I'm looking for:
public class myact extends Activity {
#Override
public void onCreate(Bundle bun) {
super.onCreate(bun);
if(!myserv.started) {
Intent service = new Intent(getApplicationContext(), myserv.class);
getApplicationContext().startService(service);
}
bindToService(this);
}
#Override
public void onRecievedData(Object data) {
//work with recieved data from service "myserv".
}
}
I also tried to find some solutions in android documentation but I didn't find what I need.
So, main question is: is it possible to work with communications from service to activities?. If no: What should I use for this purpose? If yes, just, sorry, can I ask for some code or class names, because I tried to find and didn't...
Thank you.
You need to use a RemoteCallbackList
When your clients bind to the service, you will need to register them using RemoteCallbackList.register().
When you want to send data to the bound clients, you do something like this:
int count = callbackList.beginBroadcast();
for (int i = 0; i < count; i++) {
try {
IMyServiceCallback client = callbackList.getBroadcastItem(i);
client.onRecievedData(theData); // Here you callback the bound client's method
// onRecievedData() and pass "theData" back
} catch (RemoteException e) {
// We can safely ignore this exception. The RemoteCallbackList will take care
// of removing the dead object for us.
} catch (Exception e) {
// Not much we can do here except log it
Log.e("while calling back remote client", e);
}
}
callbackList.finishBroadcast();
An example can be found here It is kinda complicated, but maybe you don't need everything this offers. In any case, have a look.
is the order of a broadcast intent guaranteed? that is, if i do,
sendBroadcast(intent1);
sendBroadcast(intent2);
are the receivers guaranteed to get intent1 before intent2? i suspect the answer to this is no, but in that case, i'm not quite sure how to solve my problem.
i'm trying to create a "busy" indicator for my app that shows busy when the device is talking on the network, and then goes away when the network communication is done. all network communication happens in an intent service.
my attempt at this was to send a BUSY_START intent when i begin network communication in the service, and a BUSY_STOP when network communication ends. this seems to mostly work, but i'm finding occasionally that i get the stop and start messages out of order.
is there a better way to solve this problem?
i'm thinking of adding an ID to each busy intent, so they can be paired. that way if i receive a start for which i've already received a stop, i can ignore it. or, perhaps more simply, add an integer sequence number into each broadcast. if i ever receive a broadcast for which the sequence of the current intent is less than the sequence of the last received intent, ignore it.
Have you considered using a Handler object to communicate from the background thread in the IntentService? The advantage of a Handler over the BroadcastReciver approach is that the Handler uses a message queue to sequence the Message objects.
(I'm assuming your Service is in the same process as the app's main thread).
At least one viable alternative to intents is to execute messaging through the application class, i.e.,
create a listener interface
Manager a collection of listener objects in the application / provide methods to add / remove listener
Interested entities call the application methods to add / remove themselves as listeners
Add "notify" methods in the application, that call the appropriate listener interface method on each of the registered listeners
Services call the application's notification methods to
For example,
public class MyApplication extends Application {
public interface MyListener {
void onEvent();
}
private Set<MyListener> listeners = new HashSet<Listener>();
public void addListener(MyListener l) {
listeners.add(l);
}
public void removeListener(MyListener l) {
listeners.remove(l);
}
public void sendEvent() {
for (MyListener l: listeners) { l.onEvent(); }
}
}
Now, from your activity (or fragment),
public class MyActivity extends Activity implements MyListener {
...
...
...
#Override
public void onEvent() {
// do something
}
#Override
protected void onResume() {
super.onResume();
((MyApplication)getApplication()).addListener(this);
}
#Override
protected void onPause() {
super.onPause();
((MyApplication)getApplication()).removeListener(this);
}
}
And in your service,
((MyApplication)getApplication()).sendEvent();
This provides synchronous messaging without using intents or static variables.
I am developing an app in Android that performs a background sync with a server (using SyncAdapter and authentication etc).
When the foreground app (with UI) is started, there maybe a background sync in progress, or optionally it may start one via a UI button.
I would like a way to "plug into" an on-going background sync (whether started by the system, or the periodic sync setting or the UI) and show it's progress in the foreground activity.
The ContentResolver documentation (http://developer.android.com/reference/android/content/ContentResolver.html) mentions a mysterious "SyncObserver" that has no link to javadoc and is not documented (that I can find).
There are some other pages around that mention it (http://www.chinaup.org/docs/migrating/m5-0.9/changes/android.content.ContentResolver.html) but I can't find out more about it.
Has anyone implemented this beast?
If not, does anyone have example code or recommendations on tracking the progress of a background sync in a foreground Activity?
Thanks for the answer.
Due to the async nature of the background sync your App (activity) could be started with a background sync already underway, which you detect with the status stored in a preference.
What I have done is to implement a SyncObserver class that implements the SyncStatusObserver interface and create/destroy on suspend/resume.
syncObserverHandle = ContentResolver.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE, new SyncObserver() );
This gets informed of any event related to sync (pending, started, etc) and I also check for status using
ContentResolver.isSyncActive();
The Android APIs for this are pretty basic, and you have to respect rules about what is done on the UI thread and what is not, but if anyone want to see my implementation just post a question and point me to it and I will answer with pleasure.
I had this same problem and ended up implementing it with a combination of 1) a broadcast from the SyncAdapter, and 2) using SharedPreferences to indicate status.
In the SyncAdapter, something like this this:
public static final String START_SYNC = "com.whatever.sync.start";
public static final String STOP_SYNC = "com.whatever.sync.stop";
public static final String SYNC_PROGRESS = "syncProgress";
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
// Add an integer to the shared settings to indicate the status
SharedPreferences settings = mContext.getSharedPreferences(Constants.PREFS, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putInt(SyncAdapter.SYNC_PROGRESS, 0);
editor.commit();
Intent intent = new Intent();
intent.setAction(START_SYNC);
mContext.sendBroadcast(intent);
//... do some stuff, setting SYNC_PROGRESS to other values and
// sending more broadcasts as the state changes
// When we are done, remove the "in progress" setting and store some
// other data
editor.putString(SyncAdapter.LAST_UPDATED, new Date().toString());
editor.remove(SyncAdapter.SYNC_PROGRESS);
editor.commit();
Intent stopIntent = new Intent();
stopIntent.setAction(STOP_SYNC);
mContext.sendBroadcast(stopIntent);
}
In the activity we do two things at resume 1) check the shared preference for whether a sync is currently in progress, 2) register to listen for broadcasts with a receiver.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// .. do some UI stuff
mReceiver = new SyncReceiver(this);
}
#Override
public void onResume() {
super.onResume();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(SyncAdapter.START_SYNC);
intentFilter.addAction(SyncAdapter.STOP_SYNC);
registerReceiver(mReceiver, intentFilter);
showProgress();
}
public void showProgress() {
SharedPreferences settings = getSharedPreferences(Constants.PREFS, 0);
if (settings.contains(SyncAdapter.SYNC_PROGRESS)) {
// ... set the UI to show that a sync is in progress
} else {
// ... set the UI to show that a sync is NOT in progress
}
}
private class SyncReceiver extends BroadcastReceiver {
private MyActivity mActivity;
public SyncReceiver(MyActivity activity) {
mActivity = activity;
}
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(SyncAdapter.START_SYNC)) {
Log.i("#string/app_name", "Started sync");
mActivity.showProgress();
}
else if (intent.getAction().equals(SyncAdapter.STOP_SYNC)) {
Log.i("#string/app_name", "Started sync");
mActivity.showProgress();
}
}
}
This seems to work for me. I must admit I have a feeling that there are some potential issues with this due to the asynchronous nature of the broadcasts. Any input on improving my approach would be appreciated!
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);
}
}
};
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.
You create the intent and call startService.
The operation in the service starts and it sends the activity a message saying it started
The activity processes the message and shows a progress.
The service finishes the operation and sends some data back to your activity.
Your activity processes the data and puts in in a list view
The service sends you a message saying that it is done, and it kills itself.
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);
}
}
}
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
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.
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.
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
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.
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
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.
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.
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.
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.