I've read the documentation on Services thoroughly, but I'm still completely confused as to how to use/code a service.
What I'm trying to accomplish:
My main activity:
The user selects all options for a timer and then clicks a button to start up a timer (in a service) with the click. I'm trying to pass the options to the service with putExtra.
The service will collect the variables into new variables for the service's use.
I have an onCreate section that calls my public counter which I've declared inside of the service, but not in the onCreate section. I've also declared all my variables that need to be enumerated from options being passed from the activity here.
Do I need to bind to the service to truely pass the variables with putExtra?
I have an onStart which is where I'm trying to enumerate the variable values by doing a series of gets. This is triggered by the button mentioned earlier in the activity. Inside of the activity - Ex:
mSet.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
//if toggled on
if (mEnableDisable.isChecked()){
getCurrentTime();
//start passing our variables to the service
Intent passvars = new Intent(getBaseContext(),TimerService.class);
passvars.putExtra("mHour24", mHour24);
//start the service now
startService(new Intent(TimerService.class.getName()));
}
}
});
Inside of the service - Ex:
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
//Collecting data from Activity for calcs
Bundle getvars = intent.getExtras();
mHour24 = getvars.getInt("mHour24");
//start the counter now that I have my values
countDown.start();
}
Also if I can get the service to start without crashing I need to pass a value back to the activity. So I imagine I am doing that with putExtra in the service and getExtra in the activity. Once again, will I need to bind to the service to obtain the new values?
To bind if I need to I would do this on the button press. I would also add in a section to the main activity if the program exits, but then resumes to re-bind and collecting that value. The value I'm trying to pull is the time remaining from the counter. So inside of the counter service I would have an onTick put the remaining time into a variable and do a putExtra of that variable, where in the activity I want to collect that value and display it in the UI.
This is my first service attempt and I'm just not sure how to create and use properly. Thanks in advance for all the help and sorry for such a long convoluted post.
UPDATE:
So I have the service working now. I'm just having trouble getting information back from the service to the main activity.
I have added a function to try and retrieve the data from the service which is launched after the button click launches the service:
startService(passvars);
//Init. variable for getDisplayInf() function to update the display with counter strings
Running = 1;
getDisplayInf();
And the getDisplayInf() ex:
public void getDisplayInf() {
//Intent stIntent = new Intent(MainActivity.this,MainActivity.class);
//Intent passvars = new Intent(MainActivity.this,Service.class);
Bundle getvars = getIntent().getExtras();
String Remaining;
String CountDown;
do {
Remaining = getvars.getString("Remaining");
CountDown = getvars.getString("CountDown");
mRemaining.setText(Remaining);
mCountDown.setText(CountDown);
Running = getvars.getInt("Running");
}
while (Running == 1);
};
The timer will set the Running variable to 0 on finish.
As soon as I click the button with this new function in place is crashes the app.
In the service I'm doing this on the counter's onTick:
#Override
public void onTick(long millisUntilFinished) {
Running = 1;
Remaining = "Time Remaining: ";
CountDown = formatTime(millisUntilFinished);
ticker();
}
ticker's code:
public void ticker () {
Intent passvars = new Intent(Service.this,MainActivity.class);
//this is for the activity to keep looping to grab the countdown values while running this timer
//now send it to the activity
passvars.putExtra("Running", Running);
//String Values to be passed to the activity
//now send it to the activity
passvars.putExtra("mRemaining", Remaining);
//now send it to the activity
passvars.putExtra("mCountDown", CountDown);
};
Try starting your service with the passvars Intent rather than creating a new second intent:
Intent passvars = new Intent(MainActivity.this,TimerService.class);
passvars.putExtra("mHour24", mHour24);
//start the service now
startService(passvars);
If your Activity wants to get a value back from your Service you have a couple of choices:
Send an Intent from the Activity to the Service and get the result asynchronously using a ResultReceiver. The link given here by Claszen is excellent.
Use Binder. You can make a synchronous call that returns with the result.
Implement a Content Provider. This choice is for making your own data retrieval and storage component.
I realized I could obtain the amount of awareness that I wanted for the user by using the Notification Manager. So I implemented the notification manager and displayed my countdown information onTick() there.
The reason this worked best for me was because my service is technically a remote service (setup to run in its own process, which is defined in the manifest file.) It is far more complex to connect to a remote service and obtain data synchronously (which is what I wanted.) The notification manager option actually worked best for me here, as the program docks it's icon and updates the notification on each second.
Related
In my Android app there is a Service performing long-running task (e.g. playing music) which can be in one of two states: running or paused. There is a single 'pause/resume' image button to pause/resume the task. And also the task can be paused due to other reasons (not from UI). The button should look differently depending on the current state of the task (running or paused). So I need to sync the button image with the actual state of the task.
Now I've come up with the following solution:
My Service has static pause and resume methods which send intents to itself like this:
public static void pause(Context context) {
Intent intent = new Intent(context, MyService.class);
intent.putExtra("PAUSE", true);
context.startService(intent);
}
public static void resume(Context context) {
Intent intent = new Intent(context, MyService.class);
intent.putExtra("RESUME", true);
context.startService(intent);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.getBooleanExtra("PAUSE", false)) {
doPause();
} else if (intent.getBooleanExtra("RESUME", false)) {
doResume();
}
return START_STICKY;
}
Since doPause() and doResume() can also be called from other places, I can't just set image to the ImageButton when calling MyService.pause() / MyService.resume(): the button image and the actual state of the task may become out of sync. Instead I use LocalBroadcastManager to notify activity when the button should be updated:
public void doPause() {
paused = true;
... // Do some stuff to pause the service
// Notify about state change
Intent intent = new Intent("SERIVCE_STATE_CHANGED");
intent.putExtra("PAUSED", true);
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
Code for doResume() is analogous.
So, my activity registers the receiver and sets the image in onReceive().
The solution seems to work. But my question is whether there is a better/simpler way to achieve the same goal? Any best practices?
I don't think your solution is bad. However, another way that is arguably better would be to use Android data binding.
The way this would work is, make a model object that represents the service state. E.g. Player.isPlaying, representing the service state. Then in the service, you would set the isPlaying state.
Then in your layout, you would use data binding to link that model object isPlaying state to the UI, however you want to set it, for example:
android:background="#{player.isPlaying ? #drawable/pause : #drawable/play}"
The reason I think this might be a better solution is, it links up the UI state directly 1:1 with the service. Conceptually I imagine a broadcast as being able to come from multiple sources. I don't see a problem with only having one source of the broadcast, though, which is why I don't see your current solution as bad.
You can view a detailed video about Data Binding from Google I/O 2016 here: https://www.youtube.com/watch?v=DAmMN7m3wLU
I have received the GCM push Message.
How to decide whether I have to update the UI or post a notification.
If our app is in foreground then we can update the UI. If our app is not running then I need to post a notification. Whether this is the right way. Or else any other way to handle it. And how can I find that my app is in foreground or background.
Thanks in advance.
If you followed that guide at Android Dev (specifically, this section), your app should be posting the notification regardless of whether it is in the foreground or not.
However, if you want to change the UI of the Activity which is in the foreground, modify the PendingIntent inside the sendNotification() method to launch your Activity. You may attach extras in the associated Intent. If your Activity is in the background, it will be started and the extras will be available via getIntent() inside the Activity's lifecycle methods. If it is in the foreground, the Activity's onNewIntent() method will be called, from where you can get your extras again (that you sent from the notification).
Yes I found the answer by myself.
There are 3 different your current activity status.
Resumed - Static reference is available
Stopped - Static reference is available
Destroyed - Static references are garbaged
//Define static variable of your activity instance
public static FleetLocActivity mFleetLocActivity = null;
And write the below code in your GCMIntentService. This class is called whenever there is a new push message.
if (FleetLocActivity.mFleetLocActivity != null) {
**//Activity is in Stopped or Resumed State**
handlePushMessage(pushMessage);
// Start Service and Update UI with the help of Handler
} else {
**//Activity is in Destroyed State**
// Post notification of received message. And Add the action of opening your home activity. When the user clicks the notification it will open the home activity and start the respective service by using activity instance.
mAppUtilInstance.postGcmCommandNotification(
"Command Received : " + pushMessage, mContext);
}
//Class variable updated with the received push message
private String pushMessage = "";
private void handlePushMessage(String pushMsg){
Message msgObj = gcmCommandHandler.obtainMessage();
Bundle bundle = new Bundle();
bundle.putString(Constants.getInstance().GCM_SERVER_MESSAGE,pushMessage);
msgObj.setData(bundle);
gcmCommandHandler.sendMessage(msgObj);
}
/**
* to handle the GCM push message (or) commands using Handler
*/
private Handler gcmCommandHandler = new Handler() {
// Create handleMessage function
public void handleMessage(Message message) {
String pushMessage = message.getData().getString(
Constants.getInstance().GCM_SERVER_MESSAGE);
if (pushMessage != null && pushMessage.length() > 0) {
//Start Service and update UI HERE
}
}
};
I have an alarm clock application I am making. I have one activity where the user sets the time for the alarm. This registers a broadcast receiver class I have made to receive a broadcast at the time of the alarm, using AlarmManager. I then start a new activity in the receivers onReceive(). When this second activity starts, the alarm clock sound is played in onStart(). How can I tell if my activity has been started by a receiver or if the user is just multitasking with the application? I don't want my sound to play when the user silences the alarm, presses the home button, and then renters the app (while still on the sound playing activity).
Just send an extra via the intent you use in your onReceive() method:
Intent intent = new Intent(this, NextActivity.class);
intent.putExtras("playSound", true);
in your "sound playing" activity, you have to play the sound in onCreate():
boolean playSound = getIntent().getBooleanExtra("playSound", false);
This will return false if the intent-extra "playSound" does not exist or is set to false, true if it is set to true.
onCreate() is only called once (when the activity starts), while onStart() gets called everytime a user reenters your activity (i.e. through recent apps). You can see this in the lifecycle:
(diagram source)
Where Paused is called when something draws over you activity (e.g. low battery dialog), Stopped is called if you "exit" your app (e.g. through the home-button).
Start an activity or a service, etc., based on a received broadcast then you need a standalone broadcast receiver and you put that in your android manifest file. If you want your activity itself to respond to broadcasts then you create an instance of a broadcast receiver in your activity and register it there.
public class BRActivity extends Activity {
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver(){
#Override
public void onReceive(Context context, Intent intent) {
..................
..................
}
};
public void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(BROADCAST_ACTION);
this.registerReceiver(this.broadcastReceiver , filter);
}
public void onPause() {
super.onPause();
this.unregisterReceiver(this.broadcastReceiver );
}
}
So, this way the receiver is instantiated when the class is created (could also do in onCreate). Then in the onResume/onPause I handle registering and unregistering the receiver. Then in the reciever's onReceive method I do whatever is necessary to make the activity react the way I want to when it receives the broadcast.
You can do as below:
For each alarm user sets, you put an boolean flag in sharedpreference to true. E.g. you have three alarms then in sharedpreference you will have 3 flags.
Now suppose a alarm broadcast is received for alarm1 and activity2 is started.
Now in activity2 first thing you check is whether the flag for alarm1 which you set in sharedpreference is true or false, if true play sound.
When user silences the alarm or press home button then you can mark this flag to false, so next time if user starts activity from background, flag in sharedpreference will be false and sound will not be played.
Same thing you can achieve using sqlite db, by setting flags in sqlite db table instead of sharedpreference.
For the intent used to launch the sound playing activity use the FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS flag. So if the user moves out of the activity, it cannot be resumed.
Intent intent = new Intent(context, SoundActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
or in manifest
<activity
android:name="SoundActivity"
android:excludeFromRecents="true" >
</activity>
There are couple of solutions:
(1) one approach is to use a Singleton Class which can be shared across activity.
In this approach set a boolean flag of Singleton class in BroadcastReceiver and then check that flag in the activity (which is fired from your BroadcastReceiver) where you play sound. Please reset the flag if it is set. This solution assumes that the Broadcast receiver is part of your Android App package.
(2)
Alternatively, you can also use Intet.putExtra ("Key", Value) method when you start an activity from BroadcastReceiver. You can check this key in the Activity you started from BroadcastReceiver to know who started this activity.
So this will take care of detecting where you come from.
If you are simply trying to set single (one-shot ) alarm then creating another activity for playing the sound is OK. If you set repeat alarm ( alarm plays at a multiple interval), I am not sure how your application will behave.
I prefer to play the sound in the Broadcast receiver itself (registered as remote receiver in manifest) for a specified duration for a given alarm ( like 30 sec of sound or you can ask user to configure it).
So this way you can use the same BroadcastReceiver for playing sound for single-shot & multi-repeat alarm.
I will use the same PendingIntent for setting both Single-shot and multi-repeat alarm.
You can simply set flag or any value in Intent that will determine what's your purpose in that class ..
For Ex: For playing sound set a Boolean value to TRUE in Intent and send same over that class using bundle ..
Else
Set that Boolean value to FALSE if starting alarm class from some other class.
This is the code to find if app is started via broadcast receiver.
public class AlarmReceiver extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
String AlarmTriggerString = (String)intent.getSerializableExtra("AlarmTrigger");
Intent i = new Intent();
i.setClassName("com.prasath.viki.bot","com.prasath.viki.bot.MainActivity");
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT|Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("FromBroacastReceiver",true);
i.putExtra("AlarmTrigger",AlarmTriggerString);
context.startActivity(i);
}
}
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
boolean FromReceiver = (boolean)getIntent().getSerializableExtra("FromBroacastReceiver");
String AlarmTriggerString = (String) getIntent().getSerializableExtra("AlarmTrigger");
if(AlarmTriggerString != null && FromReceiver != null && FromReceiver == true)
{
// do something
}
}
Basically I have created an app into which the user enters a number (in seconds). They then click a button and a service is created which runs a countdown starting at the number of seconds entered. However in my service I reference variables such as:
public EditText text = (EditText)findViewById(R.id.editText1);
int Time = Integer.parseInt(text.getText().toString());
This 'breaks' the service as it is static and can't reference findViewById.
I have tried for hours to work around this but I have no clue, any help much appreciated!
Why does the Service have to be static? A Service should be either started/stopped or bound/unbound (or a combination of the two). You shouldn't have to create a static Android Service.
Put this code into your Activity and not the Service...
public EditText text = (EditText)findViewById(R.id.editText1);
int time = Integer.parseInt(text.getText().toString());
...then just pass the time to the Service in the Intent you use to start it...
Intent i = new Intent(this, MyService.class);
i.putExtra("the_time", time);
startService(i);
Then in the Service onStartCommand(...) method use...
int time = intent.getIntExtra("the_time", 0);
If I create a service in my app's onCreatelike this:
Intent srv = new Intent( this, MyService.class );
startService( srv );
how do I get a reference to the service object and how does the service object reference the app which launched it?
(Yes, I have listed the service in my AndroidManifest).
There are a few ways to handle this. You can bind to the service (bindService) where you will be called back with an IBinder interface.
Another approach is to just keep calling startService() with different intent data as a way of messaging to the service, with intent extra data containing message specifics.
Finally, if you know the service is in the same process, you can share the service instance in some static memory.
Building a Service
First of all, we need to create the Service in the AndroidManifest.xml file. Remember, that every Activity, Service, Content Provider you create in the code, you need to create a reference for here, in the Manifest, if not, the application will not recognize it.
<service android:name=".subpackagename.ServiceName"/>
In the code, we need to create a class that extends from “Service”
public class ServiceName extends Service {
private Timer timer = new Timer();
protected void onCreate() {
super.onCreate();
startservice();
}
}
This is a way to create Services, there are others ways, or the way I use to work with them. Here, we create a Timer, that every X seconds, calls to a method. This is running until we stop it. This can be used, for example, to check updates in an RSS feed. The “Timer” class is used in the startservice method like this
private void startservice() {
timer.scheduleAtFixedRate( new TimerTask() {
public void run() {
//Do whatever you want to do every “INTERVAL”
}
}, 0, INTERVAL);
; }
Where INTERVAL, is the time, every time the run method is executed.
To stop the service, we can stop the timer, for example, when the application is destroyed (in onDestroy())
private void stopservice() {
if (timer != null){
timer.cancel();
}
}
So, this application will be running in the background...