So I'm looking into the feasibility of changing from callback interfaces to local broadcasts for some long-running network operations. Since the Activity lifecycle creates all sorts of complication for asynchronous requests that need to modify the UI (disconnect the Activity from the callback in onDestroy(), don't modify FragmentTransactions after onSaveInstanceState(), etc.), I'm thinking that using local broadcasts makes more sense, and we can just register/unregister the receiver at the lifecycle events.
However, when the Activity is destroyed and recreated during a configuration change, there's this small window of time when the broadcast receiver would not be registered (in between onPause()/onResume() for example). So if, for example, we start an asynchronous request in onCreate() if savedInstanceState == null (e.g. for the first launch of the Activity), isn't it possible that the broadcast sent upon completion would be lost if the user changes their device orientation right before the operation completes? (i.e. the receiver is unregistered on onPause(), then the operation completes, then the receiver is re-registered in onResume())
If that's the case, then it adds a lot of extra complexity we would need to add support for, and it's probably not worth the switch. I've looked into other things such as the Otto EventBus library but I'm not sure whether or not it has the same concerns to worry about.
As documented in the onRetainNonConfigurationInstance() method of the Activity, the system disables the message queue processing in the main thread while the Activity is in the process of being restarted. This ensures that events posted to the main thread will always be delivered at a stable point in the lifecycle of the Activity.
However, there seems to be a design flaw in the sendBroadcast() method of LocalBroadcastManager, in that it evaluates the registered BroadcastReceivers from the posting thread before queuing the broadcast to be delivered on the main thread, instead of evaluating them on the main thread at the time of broadcast delivery. While this enables it to report the success or failure of the delivery, it does not provide the proper semantics to allow BroadcastReceivers to be safely unregistered temporarily from the main thread without the possibility of losing potential broadcasts.
The solution to this is to use a Handler to post the broadcasts from the main thread, using the sendBroadcastSync() method so that the broadcasts are delivered immediately instead of being reposted. Here's a sample utility class implementing this:
public class LocalBroadcastUtils extends Handler {
private final LocalBroadcastManager manager;
private LocalBroadcastUtils(Context context) {
super(context.getMainLooper());
manager = LocalBroadcastManager.getInstance(context);
}
#Override
public void handleMessage(Message msg) {
manager.sendBroadcastSync((Intent) msg.obj);
}
private static LocalBroadcastUtils instance;
public static void sendBroadcast(Context context, Intent intent) {
if (Looper.myLooper() == context.getMainLooper()) {
// If this is called from the main thread, we can retain the
// "optimization" provided by the LocalBroadcastManager semantics.
// Or we could just revert to evaluating matching BroadcastReceivers
// at the time of delivery consistently for all cases.
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
} else {
synchronized (LocalBroadcastUtils.class) {
if (instance == null) {
instance = new LocalBroadcastUtils(context);
}
}
instance.sendMessage(instance.obtainMessage(0, intent));
}
}
}
To overcome this issue you need a component which stays alive even when activity gets re-created on configuration change. You can either use Application singleton or a retained Fragment.
If you use Otto or EventBus, then you can create an instance of event bus as a field of Application, and it will stay decoupled from device configuration changes like orientation change. Your activity will need to register event listener in onStart() and it will receive latest events.
If you use a retained Fragment, then fragment will stay alive until activity is not finished. Configuration changes will not release the instance of retained fragment either. It is also good practice to make retained Fragment invisible (return null from onCreateView() method). In onStart() of your activity you can always puck up latest state from that Fragment.
You can use LocalBroadcastManager with one of these approaches, but it doesn't really addresses the issue. It's just like any other event bus, but with ugly and inconvenient API ;)
I found android loaders is extremly helpful in this case.
In my case I need to receive broadcasts from another application and manage fragment transitions in my application.
So i did like below.
/**
* LoaderManager callbacks
*/
private LoaderManager.LoaderCallbacks<Intent> mLoaderCallbacks =
new LoaderManager.LoaderCallbacks<Intent>() {
#Override
public Loader<Intent> onCreateLoader(int id, Bundle args) {
Logger.v(SUB_TAG + " onCreateLoader");
return new MyLoader(MyActivity.this);
}
#Override
public void onLoadFinished(Loader<Intent> loader, Intent intent) {
Logger.i(SUB_TAG + " onLoadFinished");
// Display our data
if (intent.getAction().equals(INTENT_CHANGE_SCREEN)) {
if (false == isFinishing()) {
// handle fragment transaction
handleChangeScreen(intent.getExtras());
}
} else if (intent.getAction().equals(INTENT_CLOSE_SCREEN)) {
finishActivity();
}
}
#Override
public void onLoaderReset(Loader<Intent> loader) {
Logger.i(SUB_TAG + " onLoaderReset");
}
};
/**
* Listening to change screen commands. We use Loader here because
* it works well with activity life cycle.
* eg, like when the activity is paused and we receive command, it
* will be delivered to activity only after activity comes back.
* LoaderManager handles this.
*/
private static class MyLoader extends Loader<Intent> {
private Intent mIntent;
BroadcastReceiver mCommadListner;
public MyLoader(Context context) {
super(context);
Logger.i(SUB_TAG + " MyLoader");
}
private void registerMyListner() {
if (mCommadListner != null) {
return;
}
Logger.i(SUB_TAG + " registerMyListner");
mCommadListner = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null || action.isEmpty()) {
Logger.i(SUB_TAG + " intent action null/empty returning: ");
return;
}
Logger.i(SUB_TAG + " intent action: " + action);
mIntent = intent;
deliverResult(mIntent);
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(INTENT_CHANGE_SCREEN);
getContext().registerReceiver(mCommadListner, filter);
}
#Override
protected void onStartLoading() {
Logger.i(SUB_TAG + " onStartLoading");
if (mIntent != null) {
deliverResult(mIntent);
}
registerMyListner();
}
#Override
protected void onReset() {
Logger.i(SUB_TAG + "Loader onReset");
if (mCommadListner != null) {
getContext().unregisterReceiver(mCommadListner);
mCommadListner = null;
}
}
}
Activity#onCreate or Fragment#onActivityCreated()
#Override
protected void onCreate(Bundle savedInstanceState) {
// Listening to change screen commands from broadcast listner. We use Loader here because
// it works well with activity life cycle.
// eg, like when the activity is paused and we receive intent from broadcast, it will delivered
// to activity only after activity comes back. LoaderManager handles this.
getLoaderManager().initLoader(0, null, mLoaderCallbacks);
}
Normal broadcast will be lost if your activity will be paused or recreated. You can use sticky broadcast but it doesn't work with LocalBroadcastManager and you have to remember to manually remove sticky broadcast by calling Context.removeStickyBroadcast(). Sticky broadcast will be kept by system(even if your activity is paused) until you decide to delete it.
EventBus offer postSticky() method which works similar to sticky broadcast.
Related
I'm trying to start a service from one activity to make an api call and obtain its result in another activity. I use a BroadcastReceiver to receive the data but how do I make sure that the activity is created and the receiver is attached before sending the broadcast from the service. Is there something wrong with the way that I'm designing this?
Thanks in advance.
Edit: To simply this, I'm starting a 3 second animation when the app is called. I do not want to waste that time and so I'm trying to get data from network and then display it on the activity called after the animation ends. I assumed IntentService would be the way to go but if its not please suggest me how to go about this.
You can use Sticky Events from EventBus library. Basically it will cache your data in memory before broadcasting events. The data can be delivered to subscribers. Thus, you don’t need any special logic to consider already available data.
First you need to declare a class to hold data which you get from network.
public class MyDataEvent {
String token;
// Write more properties here
}
In the service, after getting the data from network, post event which contains data to subscribers.
MyDataEvent data = new MyDataEvent();
data.token = "123456789abcxyz";
EventBus.getDefault().postSticky(data);
In activity which you want to receive the data
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
// UI updates must run on MainThread
#Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
public void onMyDataEvent(MyDataEvent data) {
// Process the data here
Log.i("TAG", data.token);
}
#Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
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);
}
});
}
}
I have a Service like this (this is not the actual Service, it's just for describing my problem).
public class UploadService {
private BlockingQueue<UploadData> queue = null;
private UploadInfoReceiver receiver = null;
public void onStart(...) {
queue = new LinkedBlockingQueue<UploadData>();
(new Processor()).start();
// creating and reigtering receiver
}
public void onDestroy() {
queue.add(new ServiceDestroyedData());
// unregistering the receiver
}
private class Processor extends Thread() {
public void run() {
while (true) {
UploadData data = queue.take();
if (data instanceof ServiceDestroyedData) {
return;
}
// processing data
}
}
}
private class UploadInfoReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
queue.add(new UploadData(/* getting data from intent */));
}
}
}
And my problem is that if I do something like this in my App:
if (!isUploadServiceRunning()) {
// start the Service
}
Then it starts the Service, but when I move my App to the background and open task manager (android 4.2.2), and kill the app, Android restart my Service, and I can see that it creates a whole new instance of it, and I can see that onDestroy never gets called for the previous Service instance. And I also can see that the instance of the previous Processor Thread is no longer running. How can this be? If onDestroy never gets called how does Android know that it should stop my Thread?
Thanks for your answers.
Android will kill off anything that it finds that is attached to your apps classloader when you select force stop from the menu. Think kill -9 on Linux. There will be no nice callbacks to any onDestroy methods, the system will just end everything.
Now for your service:
while(true) should really NEVER be used. It will instantly kill the battery and will not do any work 99% of the time anyway.
You area already using a receiver, you can just put your while logic into there and once the upload is done call the next upload and so on. There is absolutely no need for the loop.
By using EventBus, I need to post an event(MyEvent) in an Activity and receive the event in another Activity in Android. I tried the greenrobot EventBus performance test project but could not get how to do it.
I tried in ActivitySubscriber
MyEvent event = new MyEvent();
EventBus.getDefault().post(event);
and tried to receive the event in ActivityReceiver as
EventBus.getDefault().register(this);
public void onEvent(MyEvent event){
....
}
but I am unable to receive the event. Can anyone let me know where am I doing wrong?
Since they are two activities, ActivitySubscriber posts the event while ActivityReceiver is still not created, or is in stall mode (onStop()). You need to use sticky events, i.e.
ActivitySubscriber.postSticky(...)
And for ActivityReceiver you have two options:
EventBus.getDefault().register(this) and somewhere after that EventBus.getDefault().getStickyEvent()
EventBus.getDefault().registerSticky() and then using regular EventBus.getDefault().onEvent(...)
Updated:
EventBus 3.0 changes the way to subscribe.
There is no need of method names which end up with specific suffixes but rather annotations.
How to use version 3:
//// in your build.gradle
compile 'de.greenrobot:eventbus:3.0.0-beta1'
// alternatively you can target latest whatever currently
// compile 'de.greenrobot:eventbus:+'
//// from a class which needs to dispatch an event
// posting an event is as before, no changes
// here we dispatch a sticky event
EventBus.getDefault().postSticky(myStickyEvent);
//// from your class which needs to listen
// method name can be any name
// any of subscribe params is optional, i.e. can use just #Subscribe
#Subscribe(threadMode = ThreadMode.MainThread, sticky = true, priority = 1)
public void onEventBusEvent(#Nullable final StickyEvent stickyEvent) {
if (stickyEvent != null) {
...
// optionally you can clean your sticky event in different ways
EventBus.getDefault().removeAllStickyEvents();
// EventBus.getDefault().removeStickyEvent(stickyEvent);
// EventBus.getDefault().removeStickyEvent(StickyEvent.class);
}
}
For more details and comparison of version 3:
http://androiddevblog.com/eventbus-3-droidcon/
http://androiddevblog.com/wordpress/wp-content/uploads/EventBus3.pdf
Some details extracted from the sources:
ThreadMode.PostThread
Subscriber will be called in the same thread, which is posting the event. This is the default. Event delivery implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for simple tasks that are known to complete is a very short time without requiring the main thread. Event handlers using this mode must return quickly to avoid blocking the posting thread, which may be the main thread.
ThreadMode.MainThread
Subscriber will be called in Android's main thread (sometimes referred to as UI thread). If the posting thread is the main thread, event handler methods will be called directly. Event handlers using this mode must return quickly to avoid blocking the main thread.
ThreadMode.BackgroundThread
Subscriber will be called in a background thread. If posting thread is not the main thread, event handler methods will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single background thread, that will deliver all its events sequentially. Event handlers using this mode should try to return quickly to avoid blocking the background thread.
ThreadMode.Async
Event handler methods are called in a separate thread. This is always independent from the posting thread and the main thread. Posting events never wait for event handler methods using this mode. Event handler methods should use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number of long running asynchronous handler methods at the same time to limit the number of concurrent threads. EventBus uses a thread pool to efficiently reuse threads from completed asynchronous event handler notifications.
default values for #Subscribe
threadMode = ThreadMode.PostThread
sticky = false - If true, delivers the most recent sticky event (posted with de.greenrobot.event.EventBus.postSticky(Object) to this subscriber (if event available)
priority = 0 - Subscriber priority to influence the order of event delivery. Within the same delivery thread, higher priority subscribers will receive events before others with a lower priority. The default priority is 0. Note: the priority does NOT affect the order of delivery among subscribers with different thread modes.
Edit 2
There is a dedicated site now for any Greenrobot EventBus questions from the creator of the lib:
http://greenrobot.org/eventbus/
Add
dependencies {
..
compile 'org.greenrobot:eventbus:3.0.0'
..
}
into dependencies section of Modules Build gradle
Create a MessageEvent class like
public final class MessageEvent {
private MessageEvent() {
throw new UnsupportedOperationException("This class is non-instantiable");
}
public static class Message1{
public String str1;
public Message1(String str) {
str1 = str;
}
}
public static class Message2{
public String str2;
public Message2(final String str) {
str2 = str;
}
}
}
// so on
Assume that we have Fragment1 and there is a button that suppose to send messages to MainActivity
public class Fragment1 extends Fragment {
private View frView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup
container, Bundle savedInstanceState) {
frView = inflater.inflate(R.layout.fragment1,
container, false);
btn = (Button) frView.findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
frView.setBackgroundColor(Color.RED);
EventBus.getDefault().post(new MessageEvent.Message1("1st message"));
EventBus.getDefault().post(new MessageEvent.Message2("2nd message"));
}
});
return frView;
}
End finally MainActivity to listen and do action
public class MainActivity extends AppCompatActivity {
#Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
protected void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment1 Fragment1 = new Fragment1();
getFragmentManager().beginTransaction().replace(
R.id.activity_main, Fragment1,
"Fragment 1").commit();
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage1(MessageEvent.Message1 event) {
Toast.makeText(getApplication(), event.str1,
Toast.LENGTH_LONG).show();
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessage2(MessageEvent.Message2 event) {
Toast.makeText(getApplication(), event.str2,
Toast.LENGTH_LONG).show();
}
}
Inside ActivityReceiver class, replace
EventBus.getDefault().register(this);
with
EventBus.getDefault().register(this, MyEvent.class);
It really depends when and where this code exists. Remember that you must register for the events before you can receive them, and registering takes place at runtime, not compile time.
So, you must ensure that you are posting the event after you have registered the second activity. I would simply put some breakpoints on the following lines and ensure that the debugger stops here:
EventBus.getDefault().register(this);
before you get to here:
EventBus.getDefault().post(event);
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.