In my Android application, I have a simple list view with adapter. There's a heavy query which is to fill the list view with data. So I put it to an IntentService that runs in another thread.
The IntentService is normally running separately, on its own, just to query some data and insert it into the SQLite database.
But now I would like to have the following possibility:
The activity starts the IntentService with startService().
The IntentService does its heavy work.
When the IntentService is finished, it should inform the activity about the result so that the activity can be refreshed to show the new data.
Is this possible? I read a lot of questions here on Stack Overflow on this topic. But in every question, there was another solution. So I want to ask you all: Which solution is the best for my purpose?
Binding the IntentService to the Activity does not seem to be the best solution as there might be conflicts with configuration changes of the activity etc. Correct?
This blog post suggests using AIDL with Parcelables - which sounds very complex to me. There is an easier way, isn't it?
One could set up a broadcast receiver in the activity and fire this broadcast in the IntentService when it is finished.
Some people say you should use createPendingResult() to pass a PendingIntent to the IntentService. If the IntentService finds that PendingIntent in its extras, it uses this to trigger off onActivityResult() in the Activity. Is this the way to choose?
As an example, I use a ResultReceiver to call notifyDataSetChanged() on the adapter of my Activity (which extends ListActivity). It can be adapted to do whatever you need.
ResultReceiver code:
public class MyResultReceiver extends ResultReceiver {
private Context context = null;
protected void setParentContext (Context context) {
this.context = context;
}
public MyResultReceiver(Handler handler) {
super(handler);
}
#Override
protected void onReceiveResult (int resultCode, Bundle resultData) {
// Code to process resultData here
((BaseAdapter) ((ListActivity)context).getListAdapter()).notifyDataSetChanged();
}
}
MyActivity code:
public class MyActivity extends ListActivity {
private MyResultReceiver theReceiver = null;
...
private void callService () {
theReceiver = new MyResultReceiver(new Handler());
theReceiver.setParentContext(this);
Intent i = new Intent("com.mycompany.ACTION_DO_SOMETHING");
// Code to define and initialize myData here
i.putExtra("someData", myData);
i.putExtra("resReceiver", theReceiver);
startService(i);
}
}
IntentService code:
Bundle resultBundle = new Bundle();
ResultReceiver resRec = intent.getParcelableExtra("resReceiver");
// Do some work then put some stuff in resultBundle here
resRec.send(12345, resultBundle);
When the IntentService completes, it should use LocalBroadcastManager to send an intent to any registered activity.
The IntentService will contain code like this:
private void sendBroadcast() {
Intent intent = new Intent("myBroadcastIntent");
intent.putExtra("someName", someValue);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
The activity receiving the notification will contain code like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String someValue = intent.getStringExtra("someName");
// ... do something ...
}
};
LocalBroadcastManager.getInstance(this)
.registerReceiver(receiver, new IntentFilter("myBroadcastIntent"));
}
For more depth, see the blog post Using LocalBroadcastManager In Service To Activity Communications.
None of the other answers references the official android documentation
https://developer.android.com/training/run-background-service/report-status.html
that states clearly that for the Activity-IntentService communication "The recommended way to send and receive status is to use a LocalBroadcastManager, which limits broadcast Intent objects to components in your own app"!
I would suggest using a Broadcast Receiver in the The Activity waiting for the result.
Your Service would just use sendBroadcast with a custom Intent.
I think the event bus is the way to go. Simple and effective interprocess communication.
http://square.github.io/otto/
https://github.com/greenrobot/EventBus
Related
I'm trying to get push notification working in my Android app, using parse.com's push notification service. They implement a Broadcast receiver, which I am extending in my own class:
public class MyPushBroadcastReceiver extends ParsePushBroadcastReceiver
{
#Override
protected void onPushReceive(Context context, Intent intent)
{
JSONObject data = getDataFromIntent(intent);
[...]
super.onPushReceive(context, intent);
}
#Override
protected void onPushOpen(Context context, Intent intent)
{
ParseAnalytics.trackAppOpenedInBackground(intent);
Intent i = new Intent(context, MyActivity.class);
i.putExtras(intent.getExtras());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
I'm over-riding two methods:
onPushReceive - this is called when a notification arrives
onPushOpen - this is called when the user clicks on a notification in the notification area
There are three things I need to do:
When a notification arrives, I need to save it
When the user clicks on a notification, I need to open the app to an activity that displays the notifications I've saved
If a notification arrives while I have the app open to the display notifications activity, I need to update the UI to include the new activity.
The first part was easy. I'm just writing some JSON to a file.
The second I've had no trouble with. I create an intent, and that opens my activity. It reads the JSON from the file, and Bob's your uncle.
I've not been able to find a clean way of handling the third part.
I think where I'm stuck is that I haven't a clear understanding of the lifecycle of Activities or BroadcastServices. I'm not creating either, in my app, they're declared in the manifest, and constructed whenever.
Does the Android framework create one of each, as it processes the manifest? Is it possible for an activity to find the instance of the BroadcastReceiver? If I could, it'd be easy enough for me to wire up a callback between them.
Or do I need to define my own BroadcastService, that the ParsePushBroadcastReceiver would use to publish events, and that the activity would consume? The examples I've seen on doing that seem excessively complicated for what should be a fairly simple thing.
Help would be appreciated.
The suggestion on using a static variable got me thinking, and I think I've found a workable solution.
There may be multiple instances of an Activity, but only one can be active at any time.
I spent some time playing around with setting various flags on the Intent I'd pass to startActivity(), and didn't like any of the behaviors I'd see. (Some combinations would crash, some would create multiple entries in the stack so that the back button returned you to an older instance of the activity, all of them created visual effects as the old activity was replaced by the new.)
So, why not create a static field that points to the currently active Activity?
public class MyActivity extends Activity implements ReceiveNotifications
{
public static ReceiveNotifications notificationReceiver = null;
#Override
protected void onResume()
{
super.onResume();
NotificationsActivity.notificationReceiver = this;
updateMessages();
}
#Override
protected void onPause()
{
NotificationsActivity.notificationReceiver = null;
super.onPause();
}
#Override
public void notificationReceived()
{
updateMessages();
}
private void updateMessages()
{
[...]
}
}
Whenever an instance of MyActivity is active, the static variable notificationReceiver will point to it. Of course, I'm using an interface to control how much of MyActivity is visible through that variable:
public interface ReceiveNotifications
{
void notificationReceived();
}
Then, when we receive a notification, if notificationReceiver is not null, we call notificationReceived():
public class MyPushBroadcastReceiver extends ParsePushBroadcastReceiver
{
#Override
protected void onPushReceive(Context context, Intent intent)
{
JSONObject data = getDataFromIntent(intent);
[...]
super.onPushReceive(context, intent);
if (MyActivity.notificationReceiver != null)
MyActivity.notificationReceiver.notificationReceived();
}
#Override
protected void onPushOpen(Context context, Intent intent)
{
ParseAnalytics.trackAppOpenedInBackground(intent);
Intent i = new Intent(context, MyActivity.class);
i.putExtras(intent.getExtras());
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
}
}
You can use the onNewIntent() (docs) method of the Activity to send the new info about something happened and then display some prompt.
I had to implement a feature to this app which consists of an Activity and a Service working on the background (it implements Service, not IntentService).
I went through a few tutorials on the Internet that are supposed to work, and they all use LocalBroadcastManager, which by the way is the recommended by Android:
If you don't need to send broadcasts across applications, consider
using this class with LocalBroadcastManager instead of the more
general facilities described below.
I literally lost a day to find out the problem why it wouldn't work for me: it only works if I use Context.sendBroadcast(). and Context.registerReceiver() instead of the LocalBroadcastManager methods.
Now my app is working, but I feel I am going against the best practices, and I don't know why.
Any ideas why it could be happening?
EDIT:
After I wrote this question I went further on the problem. LocalBroadcastManager works through a Singleton, as we should call LocalBroadcastManager.getInstance(this).method(). I logged both instances (in the Activity and in the Service) and they have different memory addresses.
Now I came to another question, shouldn't a Service have the same Context as the Activity that called it? From this article a Service runs on the Main Thread, hence I'd think the Context would be
the same.
Any thoughts on that? (sorry for the long post)
Code samples:
MyService
public class MyService extends Service {
...
// When an event is triggered, sends a broadcast
Intent myIntent = new Intent(MainActivity.MY_INTENT);
myIntent.putExtra("myMsg","msg");
sendBroadcast(myIntent);
// Previously I was trying:
// LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(myIntent);
}
MyActivity
public class MainActivity {
...
private BroadcastReceiver messageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("onReceive", "received!");
// TODO something
}
};
#Override
protected void onResume() {
super.onResume();
registerReceiver(messageReceiver, new IntentFilter(MY_INTENT));
// Previously I was trying:
// LocalBroadcastManager.getInstance(getApplicationContext()).registerReceiver(messageReceiver, new IntentFilter(MY_INTENT));
}
}
I've never used LocalBroadcastManager, but it sounds like you have to register your receiver on there (i.e. lbm.registerReceiver(...), not mycontext.registerReceiver(...)). Are you doing that?
Now I came to another question, shouldn't a Service have the same Context as the Activity that called it? From this article a Service runs on the Main Thread, hence I'd think the Context would be the same.
The Context class is not related to threads. In fact, both Service and Activity are (indirect) subclasses of Context -- so they're their own Contexts! That's why you can use "this" as a Context.
But regardless of which context you send into LocalBroadcastManager.getInstance(), you should be getting the exact same LBM instance out. I can't think of any reason that you wouldn't -- except if you're running the Activity and Service in different processes?
Declaration:
private BroadcastReceiver receiver;
Initialization:
receiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
{
//todo
}
};
Registration:
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, new IntentFilter("RECEIVER_FILTER"));
context can be any type of Context, you can use the application context.
Unregister:
LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver);
Broadcast:
Intent intent = new Intent("RECEIVER_FILTER");
intent.putExtra("EXTRA", someExtra);
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
check out if your Service and Activity are run in different process, LocalBroadcastManager can't apply in different process.(you should see it in AndroidManifest.xml file)
I have a ContentProvider which fetches data from sqlite database and loads it via Loader. It's updated by a Service which runs an AsyncTask to download data from server and update it in the ContentProvider. I hope it's clear but to be sure:
ListFragment takes data from ContentProvider via Loader,
ContentProvider gets updated with a Service.
Now, when my local sqlite database is empty the first time I launch the app, it shows that it has no events, even though they're being currently downloaded via Service. I would rather have a ProgressBar shown at this moment (the infinite spinning wheel, not a bar). But if I show a ProgressBar when there are no results from database, it would be there even after fetching data from sever in this specific case when there are no records in the external database (and it occurs quite often in my case). So:
When the data is downloaded for the first time by the Service I
would like to show a ProgressBar until ContentProvider gives
non-empty result OR the Service finished it's job.
When ContentProvider returned nothing AND Service finished
it's job (and fetched empty result) I would like the app to show
"no results found".
My problem is probably: how to notify the ListFragment that the Service is still running or that it finished ts job. I mean - I shouldn't store any reference to the calling Fragment inside the Service. It goes against the idea of ContentProviders, doesn't it? So how?
Note: I don't really know which fragment of code would be helpful here, so if you feel that you need to see some specific frag, just tell me in comments. Thanks!
Since you're not so much interested in posting actual progress back to the UI, the simplest way to implement this would be using a pair of custom broadcasts, and maybe a static boolean to show run state as well.
Basically, your service can notify any component of your application that's interested when it is beginning a download and when it has finished it. So you can define two custom action strings:
public static final String ACTION_DOWNLOADSTART = "com.mypackage.intent.ACTION_DOWNLOADSTART";
public static final String ACTION_DOWNLOADCOMPLETE = "com.mypackage.intent.ACTION_DOWNLOADCOMPLETE";
Then have your service broadcast them at the proper points in the code:
Intent start = new Intent(ACTION_DOWNLOADSTART);
sendBroadcast(start);
//...Service does its work
Intent finish = new Intent(ACTION_DOWNLOADCOMPLETE);
sendBroadcast(finish);
You can register for these callbacks anywhere in your application with a BroadcastReceiver and act accordingly (i.e. check the status of the ContentProvider and show/hide progress if necessary).
Another common practice, if you want to be able to check if a Service is running at any given point, is simply to include a private static boolean member that you can toggle when the Service is active (perhaps between onCreate()/onDestroy() but perhaps elsewhere) and an accessor method like isRunning(). Then your application can also check at any time if the Service is running by just calling that method.
There are various techniques how to communicate between Fragment / Activity and a Service.
One of them is using ResultReceiver and sending it to IntentService in Intent extra.
You create custom receiver ServiceResultReceiver extending ResultReceiver.
public class ServiceResultReceiver extends ResultReceiver {
private Receiver mReceiver;
public ServiceResultReceiver(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);
}
}
}
Make your Fragment implement ServiceResultReceiver.Receiver interface. Create receiver
and initialize it to your Fragment. You than pass the receiver to service and in service just get the receiver from intent and call receiver.send() to send anything back to the receiver.
public class MyFragment extends ListFragment implements ServiceResultReceiver.Receiver {
private ServiceResultReceiver mReceiver;
....
#Override
public void onCreate(Bundle savedInstanceState) {
...
mReceiver = new ServiceResultReceiver(new Handler());
mReceiver.setReceiver(this);
}
public void startMyService() {
final Intent intent = new Intent(getActivity(), MyService.class);
intent.putExtra("receiver", mReceiver);
getActivity().startService(intent);
}
#Override
public void onReceiveResult(int resultCode, Bundle resultData) {
// service finished
}
}
public class MyService extends IntentService {
...
#Override
protected void onHandleIntent(Intent intent) {
// download data and update database
....
final ResultReceiver receiver = intent.getParcelableExtra("receiver");
if (receiver != null) {
receiver.send(0, null);
}
}
}
I have a main activity that launches:
1.- A network prone thread that writes into a socket.
2.- A network prone service that is supposed to read from a socket.
So far I'm done with 1. but I want the information read from the socket to be shown in the main activity. I know I can pass information between the activity and the service using extras but how can I tell the activity to update and get the new data?
I guess that you could use broadcasting intents combined with a BroadcastReceiver in your main activity in order to achieve background communication.
Here's a snippet that can achieve this.
(CODE PUT IN THE ACTIVITY):
class MyActivity extends Activity{
CustomEventReceiver mReceiver=new CustomEventReceiver();
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
/*YOUR ONCREATE CODE HERE*/
/*Set up filters for broadcast receiver so that your reciver
can only receive what you want it to receive*/
IntentFilter filter = new IntentFilter();
filter.addAction(CustomEventReceiver.ACTION_MSG_CUSTOM1);
filter.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(mReceiver, filter);
}
#Override
public void onDestroy(){
super.onDestroy();
/*YOUR DESTROY CODE HERE*/
unregisterReceiver(mReceiver);
}
/*YOUR CURRENT ACTIVITY OTHER CODE HERE, WHATEVER IT IS*/
public class CustomEventReceiver extends BroadcastReceiver{
public static final String ACTION_MSG_CUSTOM1 = "yourproject.action.MSG_CUSTOM1";
#Override
public void onReceive(Context context, Intent intent){
if(intent.getAction().equals(ACTION_MSG_CUSTOM1)){
/*Fetch your extras here from the intent
and update your activity here.
Everything will be done in the UI thread*/
}
}
}
}
Then, in your service, you simply broadcast an intent (with whatever extras you need)... Say with something like this:
Intent tmpIntent = new Intent();
tmpIntent.setAction(CustomEventReceiver.ACTION_MSG_CUSTOM1);
tmpIntent.setCategory(Intent.CATEGORY_DEFAULT);
/*put your extras here, with tmpIntent.putExtra(..., ...)*/
sendBroadcast(tmpIntent);
One option could be to write the output of the socket reader to a stream - a file stored in the app's internal storage for example, and then periodically poll that file in the activity thread.
I've a simple Main Activity which has to stop till an SMS is received... How can I launch a method from the MainActivity within the BroadcastReceiver's onReceive() Method?
Is there away with Signal and Wait? Can I pass something with a pending Intent, or how can I implement this communication?
Communication from BroadcastReceiver to Activity is touchy; what if the activity is already gone?
If I were you I'd set up a new BroadcastReceiver inside the Activity, which would receive a CLOSE message:
private BroadcastReceiver closeReceiver;
// ...
closeReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
//EDIT: receiving parameters
String value = getIntent().getStringExtra("name");
//... do something with value
finish();
}
};
registerReceiver(closeReceiver, new IntentFilter(CLOSE_ACTION));
Then from the SMS BroadcastReceiver you can send out this action:
Intent i = new Intent(CLOSE_ACTION);
i.putExtra("name", "value"); //EDIT: this passes a parameter to the receiver
context.sendBroadcast(i);
I hope this helps?
I had the exact same problem, I tried using intent but i was unsuccessful
The easiest way to use it would be using static methods and static variables
MainActivity.java
public static void stopsms()
{
/*
some code to stop the activity
*/
}
SMSReceiver.java
at the end call this function
MainActivity.stopsms();
It works amazing if your code does not affect when you use static methods and variables. Let me know if you need any help.
The problem with registering a second receiver within the activity, however, is that it will not be persistent like registering in the manifest... thus, although, this solution may work, will only work if the activity is running in background.
it's easy, use interface like that:
1) in your broadcast receiver create an interface.
public interface ChangeListener{
public void functionWhoSendData(String type);
public void etc();
}
and instantiate that interface in your broadcast receiver, use it:
public void onReceive(....
String data=functionWhereYouReceiveYouData();
ChangeListener.functionWhoSendData(data);
}
And in your activity, make it implements your interface