I have a Service that frequently updates the Main Activity UI by passing values via a LocalBroadCastManager. The following method is triggered within the Service to pass the value to the Main Activity:
private void updateUI(String statusValue){
broadcastIntent.putExtra("status", statusValue);
LocalBroadcastManager.getInstance(this).sendBroadcast(broadcastIntent);
}
Within the Main Activity I added a BroadcastReceiver to pick up the value and update the UI accordingly:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String brStatus = intent.getStringExtra("status");
if(brStatus != null){
//Update UI
}
}
}
};
When the user navigates to another activity the receiver for the Broadcasts is unregistered as the user wont see the UI. Then onResume() when the user returns to the activity the receiver is reregistered:
LocalBroadcastManager.getInstance(this)
.registerReceiver(mMessageReceiver, new IntentFilter("speed-stats"));
UPDATE:
Whilst the activity is paused, the user can make actions (such as 'Pause') by clicking on Pending Intents on the ongoing notification. This action is handled within onStartCommand() of the Service:
case PAUSE_SERVICE :
Log.i(LOG_TAG, "Pause Foreground service.");
startForeground(NOTIF_ID,makeNotification(isRunning = false));
updateUI("paused");
stopSpeed();
break;
This works fine, however i have noticed that the UI is not updated as the receiver is unregistered whilst the activity is paused.
Is it possible to continue these UI updates despite pausing? Or is it possible to apply the UI updates as soon as the activity is resumed?
When the user navigates to another activity the receiver for the
Broadcasts is unregistered as the user wont see the UI.
This is the reason why your Activity's data is not up to date. Since the broadcast receiver is unregistered, data sent by the service won't be received.
Since its not a great idea keep the receiver registered, one solution would be:
Bind the service in on onResume() an unBind() it in onStop().
Inside service maintain data object, which will hold the latest data.
After service is binded, call the service method through Binder which
will return the the data Object with latest data.
Update the data in your Activity accordingly.
You can refer this SO for binding/unbinding service
Related
Is it possible to send an intent from a service to an Application class? Not Activity?
I wouldn't know what activity would be running at a particular time, so I am adding a boolean flag in the activity class that detects the activity and sends the appropriate data based on the broadcast received.
If your Service is active, then your Application class is active as well.
Otherwise you wouldn't be able to use getApplicationContext().
Although I'm skeptic about a service that runs forever there is a very clean way to make the Service communicate with a certain Activity, should the last one be currently active.
Such clean way is called LocalBroadcastManager.
The Activity meant to receive the data should register a BroadcastReceiver in onResume() and unregister it in onPause().
You instantiate your BroadcastReceiver in your Activity's onCreate()
this.localBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Do what you have to do here if you receive data from the Service.
}
}
You create a Filter so your Activity only listens to a certain type of signals.
private IntentFilter notifIntentFilter new IntentFilter("com.you.yourapp.MY_SIGNAL");
in onResume()
LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(this.localBroadcastReceiver, notifIntentFilter);
in onPause()
LocalBroadcastManager.getInstance(getApplicationContext()).unregisterReceiver(this.localBroadcastReceiver);
Now whenever you want to send data to your Activity, your Service can call:
final Intent intent = new Intent();
intent.setAction("com.you.yourapp.MY_SIGNAL");
// put your data in intent
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent);
If your Activity is awake, it will respond to the signal. Otherwise, if it's in the background, or it is not instantiated it won't.
You can apply this pattern to as many Activities as you wish.
Still, I have never used this inside the Application class. But you can try to register your receiver there. It might work, since if the Application class is destroyed, the BroadcastReceiver is destroyed too and thus probably unregistered as well.
The point is, if your Application gets destroyed, your Service will be killed as well. Unless you launched it in another process. But then it will have it's own instance of Application; and this is a complex thing you probably do not want to get into details now...
Important: since the Application class is not tied to any UI component, you can do whatever you need directly inside your service. If you need to manipulate the UI, then the pattern described above will work for you.
Please read about new Android's background limitations.
Edit:
Oh yeah right, if you need your Service to call a function declared in your Application class, you can just do
((MyApplication) getApplication()).myFunctionToHandleData(Intent intent);
I didn't really understand your question though, but either of the methods described above should work for you.
I have a simple BroadcastReceiver that I setup in my onResume method of my Activity.
#Override
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mNotificationReceiver, new IntentFilter(QuickstartPreferences.NEW_NOTIFICATION));
}
I unregister the receiver on onPause:
#Override
protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mNotificationReceiver);
}
I have a background service that sends the broadcast message to this:
Intent newNotification = new Intent(QuickstartPreferences.NEW_NOTIFICATION);
newNotification.putExtra("title", data.getString("title"));
LocalBroadcastManager.getInstance(this).sendBroadcast(newNotification);
My question is, if the background service broadcasts a message when the Activity is not opened and now when the Activity is resumed, will it be able to pick up the broadcasted messages? If no, how to maintain a queue to store broadcasted messages when Activity is not open?
LocalBroadcast is queued but not persisted. In your activity onPause() you unregistered receiver so you can not receive further broadcast. the sendBroadcast() will check receiver, if there is no reciever it will return false to indicate failure.
If you want always get notification, try to register broadcast at application level rather than activity level.
if the activity is not running then it'll never receive it. what you can do is:
check if your activity is running before/after you send the broadcast, if its not running then you could store a sharedPreference flag that tells your activity when it runs again to do whatever task that it has to do when it receives that broadcast.
I have an activity that starts a background service. Once it is started, it runs forever.
Lets say the background service needs the activity that started it to update something. Then how can I start the activity again "If it is not started" however if it is already started then send a broadcast?
Thanks
You would have to bind to the service in your activity. Then, in the service, implement the onBind and onUnbind methods to set a boolean "bound". Check the boolean to see whether the activity is active.
With activity you mean the service's process. If the service is started forever, then its process its started foerever (except when the system kill its for recovering memory purpose and recreates it later). That doesnt mean the rest of activities/fragments/services are not kill. A service is just an entry point for your application and it gives your process a position into the process priority ladder.
Its hard to say, since I don't know the details of your app, but I think that you may want to consider a bit of a redesign.
As you have noticed, Activities are ephemeral. Generally speaking, a service should not (cannot) depend on a particular Activity being active.
In fact, a well-designed service should not depend on any particular activity at all.
I'm not sure I completely follow your question. However, when communicating from a Service to an Activity I use a broadcast receiver in the Activity class.
This can be created as follows:
// register the BroadcastReceiver in your activity class
this.receiver = new NotificationReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.example.LISTENER");
registerReceiver(receiver, filter);
// insert in your activity class
class MyReceiver extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
if(intent.hasExtra("command")) {
if(intent.getStringExtra("command").equals("userRegistered")) {
// insert code to do something when this intent is received
}
}
}
}
// insert in your service class to send message
Intent i = new Intent("com.example.LISTENER");
i.putExtra("command", "userRegistered");
sendBroadcast(i);
Tl;dr How to know when an IntentService has finished downloading upon returning to the Activity which listens to its result using a BroadcastReceiver?
I'm moving to implementing data downloading to IntentServices, and notifying when the task has finished using BroadcastReceivers.
I generally start the service in my Activity:
IntentFilter intentFilter = DownloadDataService.startDownloadData(this);
getLocalBroadcastManager().registerReceiver(mBroadcastReceiver, intentFilter);
The part that starts the Service:
/**
* Starts the service and returns an IntentFilter to subscribe a BroadcastReceiver on.
* When the task has finished, a broadcast for returned IntentFilter is sent,
* containing downloaded data.
*/
public static IntentFilter startDownloadData(final Context context) {
Intent intent = new Intent(context, DownloadDataService.class);
intent.setAction(ACTION_DOWNLOAD_DATA);
context.startService(intent);
return new IntentFilter(ACTION_DOWNLOAD_DATA);
}
And of course, onHandleIntent(Intent) (simplified):
#Override
protected void onHandleIntent(final Intent intent){
Data data = downloadData();
Intent intent = new Intent(ACTION_DOWNLOAD_DATA);
intent.putExtra(DATA, data);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
This all works well, and I can keep states in my Activity to know for example after an orientation change whether I was waiting for a download data result:
#Override
public void onResume(){
super.onResume();
if (mState == State.DOWNLOADING) {
Log.v(TAG, "Registering receiver for existing download data");
IntentFilter intentFilter = DownloadDataService.getIntentFilter();
getLocalBroadcastManager().registerReceiver(mBroadcastReceiver, intentFilter);
}
}
Great, now I can also handle orientation changes. Only one problem left:
Activity starts the DownloadDataService
User moves away from the Activity
DownloadDataService broadcasts its done message (which is not received by the Activity due to unregisterReceiver in onStop())
User moves back into the Activity
Activity still thinks it's waiting for the DownloadDataService, and does nothing.
How can I compensate for this?
Note that I do not have any persistence like databases for storing the downloaded data. The Activity retrieves the data from the broadcasted Intent.
Note #2: There is this answer to the question of how to know whether a Service is running. Although this might work, it is explicitly stated that that method is for debugging or implementing service management type user interfaces.
use sendStickyBroadcast to send a sticky broadcast. This broadcast is held by the system.
I wasn't really convinced by using SharedPreferences, static variables and other 'hacky' solutions.
I did find however that you can supply a ResultReceiver - which is parcelable - which you can use to notify your task is finished. It receives a Handler to specify the thread the result is handled on.
The advantage of this, is that you can save the ResultReceiver during onSaveInstanceState. Using some clever tricks you can certainly make this work. I have created an experimental library which facilitates this tactic: https://github.com/nhaarman/Ergo
I have a Base Activity class to implement common behaviour for all the Activities. All of them extend this BaseActivity.
I'm binding to a service in in the BaseActivity's onStart method and unbinding conditionally in the onStop method. With conditionally, I mean that depending on some option selected by the user, the service should or should not keep running in the background when the app goes to background.
The problem is that sometimes the service keeps running when it shouldn't (this is, when the option for killing it is enabled, and unbindService() is effectively called).
I'm thinking that on every Activity change the service is unbound and bound again. Since bound services are reference counted, maybe my service is bound more times than unbound, so that's why it keeps running at the end, even when I call unbindService().
Additionally, the documentation says something about that:
You should usually pair the binding and unbinding during matching bring-up and tear-down moments of the client's lifecycle. For example:
If you only need to interact with the service while your activity is visible, you should bind during onStart() and unbind during onStop().
If you want your activity to receive responses even while it is stopped in the background, then you can bind during onCreate() and unbind during onDestroy(). Beware that this implies that your activity needs to use the service the entire time it's running (even in the background), so if the service is in another process, then you increase the weight of the process and it becomes more likely that the system will kill it.
Since I'm kind of trying to implement both options, what should be the best approach to implement this?
Finally I changed my approach and decided to use only startService() and communicate with the service using a Local Broadcast Receiver.
I start the service in the onCreate() and stop it in the onDestroy() methods of the Base Activity.
Then, to send a message from the Service to the activity, I use this:
private void sendBroadcastMessage(String msg) {
Log.d(LOG_TAG, "send broadcast message: " + msg);
Intent intent = new Intent(MyService.class.getSimpleName());
// Add data
intent.putExtra("message", msg);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Then, to be notified in the activity:
// handler for the events launched by the service
private BroadcastReceiver mMyServiceReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Extract data included in the Intent
String message = intent.getStringExtra("message");
Log.d(LOG_TAG, "Got message: " + message);
// Do stuff...
}
};
And to register the Receiver in the activity:
#Override
public void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mMonitorReceiver,
new IntentFilter(MyService.class.getSimpleName()));
}