Access to a Service object through different Activities / Classes - android

I've got a Service which is playing music. It has different functions like 'play' ,'pause' , 'stop' etc. I'm creating the object for this Service in my MainActivity with bindService(). Everything works fine!
But how can I use this object through other classes (BroadcastReceiver, Widget) or another Activity while the MainActivity is not running ?
If I declare it static I only can access it when MainActivity is running.
How can I save/hold this Service object without it being deleted because the Activity finishes ?

You do not have to use a bound service for media playback. Just use
startService(myPlaybackIntent);
and before that supply the information the service needs (play/pause/skip/...) as an intent extra.
EDIT
If for example you use int constants like ACTION_PLAY = 0, ACTION_PREVIOUS = 1,..., then you can write
myPlaybackIntent.putExtra("your.package.name.MEDIA_ACTION", ACTION_PLAY);
before calling 'startService()'
and evaluate the intent extra at the beginning of the service's 'onStartCommand()' method:
if(intent.hasExtra("your.package.name.MEDIA_ACTION"))
{
switch(intent.getIntExtra("your.package.name.MEDIA_ACTION", -1))
// -1 = some value you don't use for your actions
{
case ACTION_PLAY: // start playback
case ACTION_PREVIOUS: // jump to last song
...
}
}
End of EDIT
There is a very nice guide to media playback using a service .
This way, you can keep your service from being stopped/deleted by returning with "START_STICKY" from the 'onStartCommand()' method in your service class.
The other good thing about using the 'onStartCommand()' method is that you can call it from a Widget. The only difference is that you first have to define a pending intent for each action you want to take.
Let's say your RemoteViews object (I assume you're familiar with widgets, if not - I learned a lot from the guide at developer.android.com) is called 'updateViews' and you have different integer constants for indicating which action to take, like 'ACTION_PLAY' for starting playback. Then for the 'play'-button (R.id.btn_play) you could write:
Intent iPlay = new Intent(this, SVC.class);
iPlay.putExtra("your.package.name.MEDIA_ACTION", ACTION_PLAY);
PendingIntent piPlay = PendingIntent.getService(context, ACTION_PLAY,
iPlay, 0);
updateViews.setOnClickPendingIntent(R.id.btn_play, piPlay);
Please make sure to use different values for the second parameter of the 'getService()' method for the different pending intents you are going to need (play, pause, previous,...).
Because if you set up another intent like
Intent iPrevious = new Intent(this, SVC.class);
then - no matter what extras you put to iPrevious - if you code
PendingIntent piPrevious = PendingIntent.getService(context, ACTION_PREVIOUS,
iPrevious, 0);
the system will know the difference between the pending intents only by comparing ACTION_PLAY to ACTION_PREVIOUS.

Related

IntentService to start intent or broadcast message?

I currently have an intent service that is managing my geofence transitions and I am hoping to use the geofence ID to trigger a sequence of events either by starting an activity through another intent or by broadcasting the relevant string to my main activity so that I can do the process there. currently my code inside my intentservice contains this
String fenceID = fences.get(0).getRequestId();
Log.i( fenceID, "fenceID is");
if (fenceID == Home){
Intent broadcastIntent = new Intent();
broadcastIntent.setAction(PlayerActivity.MyWebRequestReceiver.PROCESS_RESPONSE);
broadcastIntent.addCategory(Intent.CATEGORY_DEFAULT);
broadcastIntent.putExtra(fenceID, fenceID);
broadcastIntent.putExtra(RESPONSE_MESSAGE, "respone");
sendBroadcast(broadcastIntent);
/* Intent mpdIntent = new Intent(this, PlayerActivity.class)
.setData(Uri.parse("http://maxmarshall.ddns.net/segments/TEST_dash.mpd"))
.putExtra(PlayerActivity.CONTENT_ID_EXTRA, "my test")
.putExtra(PlayerActivity.CONTENT_TYPE_EXTRA, 0);
startActivity(mpdIntent);*/
}
You can see the commented out code is my original idea or end goal (whether it is completed through this set up or in another activity
However even though fenceID is Home I am not getting any response to that effect with this setup. Can anyone shed some light on what I am doing wrong here? I can update the question with more code if anyone needs. perhaps I am not registering my broadcast receiver properly if theres something I need to put in the manifest? anyway thanks very much for any and all help!
You cannot start a new Activity from a Service context in the same task. The only way you can start an Activity from there is to use the flag FLAG_ACTIVITY_NEW_TASK. Make sure you really need it before you implement this.
Your code for sending a broadcast message looks fine. Just make sure you register your receiver properly and it's IntentFilter matches.
Also, never do this:
String fenceID = fences.get(0).getRequestId();
Log.i( fenceID, "fenceID is");
if (fenceID == Home){
The fenceId is a String, i.e. it is a an Object. Comparing with == will give you true only if both references point to the same object. Is this really what you want in this case? Use fenceId.equals(Home) if you want to compare characters instead of objects.
My guess is that your code is never executed because of this mistake in the condition.

After rotation change second instance of service

My app is a video player which streams videos from a nas. Therefore the video is also playing in the background, I have running a startforeground service where the media player is hold.
So every time the activity starts I have to bind to the service, to be able to show the video. This is also required when rotation changed. Then when I want to bind to it, sometimes I don't bind to the already running service but It creates a new instance. So there are two instances of the service.
Yes, normally services should only be able to be instantiated one time, but in my case there are sometimes definitly 2 instances... :/
How can I prevent this? Had anybody already the same problem?
EDIT:
service gets started and bound with following code:
Intent serviceIntent = new Intent(getApplicationContext(), MediaPlayerService.class);
getApplicationContext().startService(serviceIntent);
Intent serviceIntent = new Intent(getApplicationContext(), MediaPlayerService.class);
getApplicationContext().bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE);
Sorry I wasn't clear because I was interested in which method in the activity life cycle you were doing this in. There are certain methods that are called on-orientation change. This other answer should point you in the right direction.I hope that helps.
Which activity method is called when orientation changes occur?

Service being re-Created by AlarmManager

I have a fairly standard Service which I wish to trigger using an alarm. Here's the initiation section of the service:
class MyService extends Service {
private Context context;
private AlarmManager alarmManager = null;
private final String startReason = "com.stuff.myreason";
private final int REASON_NO_INTENT = 0;
private final int REASON_ALARM = 1;
private final int REASON_X = 2; // and so on.
#Override
void onCreate() {
super.onCreate();
context = getApplicationContext();
alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
// do onCreate stuff
}
#Override
int onStartCommand (Intent intent, int flags, int startId) {
int reason = REASON_NO_INTENT;
if (intent != null) {
reason = intent.getExtra(startReason, REASON_NO_INTENT);
}
switch(reason) {
// handle the different reasons we may have been "started"
}
return START_STICKY;
}
}
When I trigger it using context.startService from an activity, it starts absolutely normally. In particular, if it is already running it doesn't (re)start from scratch but simply enters the existing instantiation via onStartCommand(). This is the expected behaviour. However, when I trigger it using the AlarmManager:
Intent intent = new Intent(context, MyService.class);
intent.putExtra(purposeOfStartCode, REASON_ALARM);
PendingIntent pi = PendingIntent.getService(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.set(AlarmManager.RTC_WAKEUP, /* A time in the future */, pi);
When the alarm is due it seems to restart the service from scratch: it starts a new instantiation, calls onCreate() and then onStartCommand() rather than just calling onStartCommand() in the already running instantiation.
I have already tried changing the PendingIntent flag to FLAG_ONE_SHOT and replacing context with MyService.this with no improvement.
I am rather bemused by this - can anyone explain this behaviour and suggest ways to get it to behave as expected?
EDIT - The collection of actions that resulted in a solution are in my answer below.
After some investigation and work, I've discovered a number of things. Having done all of them, this problem looks like it's disappeared:
If you override onStart and onStartCommand in a service (to allow for older devices) and you put super.onStartCommand in the latter, it will call onStart, meaning you get every intent coming in twice!
As per one of the other answers (and comments on it), the AlarmManager is designed and specified to deliver Broadcast intents, not other types. However, in practice, it isn't picky and seems to honour other forms. I think that this was one of the keys in resolving the issue.
If the service is in the same process as other activites etc, the service sometimes seems to "just get restarted". This may be the actual cause of the issue noted in this question. See Android service onCreate is called multiple times without calling onDestroy.
Things seem to be more stable when solely using intents to communicate with the Service rather than binding and using a Messenger or binding and accessing methods. Whilst both of these are correct, they are quite complex to manage (although you could use this approach: What is the preferred way to call an Android Activity back from a Service thread and Using the Android Application class to persist data). Whilst I fully appreciate that the android docs disagree with me, in my observation moving to broadcast intent only communication seemed key. If you go for the separate process approach you'll have to do this anyway.
It pays to be consistent in how you declare and address your classes. It's a bit messy, but, because it sometimes seems to pay to use full names ("com.company.superapp.CleverService") rather than short ("CleverService" or ".CleverService"). So, it's probably better to always use full names.
The rule of thumb floating around out there about contexts ("use getApplicationContext") isn't really the right way to do it. See When to call activity context OR application context?; in essence use this, unless you really need to use a broader context, and manage your variables well.
It's possible for the garbage collector to clear up something still in use if it was created in an Activity, Service, Thread, AsyncTask, etc that is no longer around. If the application is based around a service, it may be wise to make a copy of classes coming in so that they don't get cleared up later.
A neater way to start a service than is often suggested is to give the service an intentFilter with it's full name as the action. You can then create the intent to start it with just the class name as a string. This means you don't have to worry about context. See Issue in Calling Service.
Well, I'm actually surprised that it runs your Service at all! The PendingIntent that you pass to the AlarmManager needs to be a broadcast Intent. So you need to rearchitect your code a bit. The AlarmManager will trigger a BroadcastReceiver and the BroadcastReciever can then call startService().
See the description of AlarmManager.set()
I got this to work by using the following code:
AlarmManager almgr = (AlarmManager)MyContext.getSystemService(Context.ALARM_SERVICE);
Intent timerIntent = new Intent(MyUniqueLabel);
timerIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
PendingIntent pendingOffLoadIntent = PendingIntent.getBroadcast(MyContext, 1, timerIntent, 0);
you MUST do these things for it to work.
1.) Call addFlags and the intent and pass it in FLAG_RECEIVER_FORGROUND
2.) Use a non-zero request code in PendingIntent.getBroadcast
If you leave any of those steps out it will not work.

Android how to replicate info from one activity to other concurrently

I would like to know if it is possible to replicate in my visible Activity changes which are ocurring in a not visible activity.
For example, I have a loop with an integer incrementing in Activity A, then I invoke Activity B passing the integer as an extra.
Is there a way to see reflected the increments (which are ocurring in A) in B?
Thanks in advance.
Since int is a primitive type, a variable with int type will not be changed when you pass it into activity B.
I would suggest you pass in a wrapper of int type (i.e. class IntWrapper { int value; })
The change will be reflected when you increase value.
You can also pass data using broadcast recievers:
In the listening activity/fragment create the reciever and register it (do this in oncreate or onresume or something, maybe even onrestart). The activity will now listen for a broadcast and you test if it's a broadcast meant for that reciever using a built in keyword. Because a the broadcast is done using an intent, you can also get and use other data stored in that intent (like your int):
my_broadcast_reciever = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {String action = intent.getAction();
if (action.equals("finish_buy_activity"))
//do whatever
}
}
};
registerReceiver(my_broadcast_reciever, new IntentFilter("theKeywordThisBCRRespondsTo"));
Dont forget to unregister any recievers in onpause or ondestroy (whatever makes most sense depending on where you register the reciever):
{unregisterReceiver(my_broadcast_reciever);
Now, in the activity/fragment which you want to use to send the data, fire off the intent like so (you might want to create and keep the intent at a higher level so you don't keep on creating a new object every update):
Intent sintent = new Intent("theKeywordThisBCRRespondsTo");
//maybe also do: sintent.addInt(int); or something
sendBroadcast(sintent);
Keep in mind, this might be a little overkill in certain cases, but it is a great way to communicate and transmit data between activities/fragments.

stopService(intent_with_extras) - how do you read those extras from within the service to stop?

I have a Service that can be stopped in multiple ways. Whenever I call stopService(Intent), I pass an intent with some extras. How do you retrieve those extras?
Thanks.
You need to override onStartCommand() in your Service this is how you get a reference to the incoming intent from startService.
In this case you would have a special action in your intent to tell the service to stop itself. You add extras to this intend which can be read in the onStartCommand() method.
Sample Code
public class MyService extends Service {
#Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
final String yourExtra = intent.getStringExtra(YOUR_EXTRA_ID);
// now you can e.g. call the stopSelf() method but have the extra information
}
}
Explanation
Every time you call context.startService(Intent) onStartCommand() will be called. If the service is already running a new service isn't created but onStartCommand is still called. This is how you can get a new intent with extras to a running service.
I found that the extras are not passed with the intent when stopService is called. As a workaround, simply call startService(Intent) and stopService(Intent) right after one another.
Example code from Activity:
Intent j = new Intent(SocketToYa.this, AccelerometerDataSocket.class);
j.putExtra("com.christophergrabowski.sockettoya.DestroyService", true);
startService(j);
stopService(j);
In Service.onStartCommand,
intent.hasExtra("com.christophergrabowski.sockettoya.DestroyService")
will evaluate to true, and you will destroy your service the way intended by the API (i.e., by calling stopService, which will in turn call OnDestroy after onStartCommand is called).
My suggetion is that use static member in class that extends Activity for passing information to service & it in service as normal static member access in outside class
Please don't do this unless you have no other option. You should try to use the mechanisms built into the framework for passing data, and not use public static fields unless there is no other choice. Read the Service documentation for examples.
Are you able to use an Intent with a "shutdown" action with Context.startService()?
That is, send an Intent with a shutdown action and extras to Service.onStartCommand(), decide how to shutdown based on the extras, then use Service.stopSelf() to stop the service.
I agree this isn't a great solution, since it potentially starts the service in order to shut it down. I would still like to hear of the "correct" way (if one exists) of doing this with Context.stopService().
You can not write
getIntent()
method in a class extending Service. So I think using getExtra() won't work.
My suggetion is that use static member in class that extends Activity for passing information to service & it in service as normal static member access in outside class i.e.
Classname.yourobject
.
see this link for other option
http://developer.android.com/resources/faq/framework.html#3

Categories

Resources