Grab intent information from a service, only once - android

I am running a service. My ideal process would be to call a method once, get the intent/extras and set parameters, then run my service for a given amount of time.
From my understanding, onCreate() is called only once, while onStartCommand is called every time your service is called. This currently puts me in a backward situation, the method I want called once, doesn't have access to the intent extras, and I don't want to reassign parameters each time the startService is called through the method that has intent access (or should I not care about this happening?).
Is the only way to go about getting what I want to happen the way I want it, to just restrict the ability to start the service if it is already running?
Thanks in advance.

put a static int flag in onCreate and raise it to 1 on first call then check it in OnStartcommand whether this flag is 1 or zero if zero do what ever u want to do else do nothing
static int flag=0;
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
flag=1
}
public int onStartCommand(Intent intent, int flags, int startId) {
if(flag==0){
//run ur code
}
}

Related

Intent is Null all the time in service

I have two Application A and B.In app B I have a service that I can run it from app A. I want to send data to app B with intent but always my intent is null!
I run app B's service from app A with this Code:
try {
String packageName = "app_B_package";
String appService = packageName + ".activity.InternetService";
Intent start = new Intent();
start.setComponent(new ComponentName(packageName, appService));
start.putExtra("LAUNCHER_COMMAND_CLOSE" , true);
G.context.startService(start);
} catch (Exception e) {
e.printStackTrace();
}
But when service of app B will run the intent is null. This is onStart of the service in app B:
#Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
Log.i("LOGO_OFFICE_IN", "onStart");
if (intent != null) {
if (intent.getExtras().getBoolean("LAUNCHER_COMMAND_CLOSE")) {
Tools.clearApplicationData(InternetService.this);
new AppStatus(InternetService.this).isAppRunning(getPackageName(), true);
}
}
}
Why my intent is null all the time? I can't find it out.
Thank you for your help.
It looks like your service is type fire-and-forget - it does one quick thing and should quit immediately because it's done. Correct?
1. Don't leave your idle service running
Documentation says
If a component starts the service by calling startService() (which results in a call to onStartCommand()), the service continues to run until it stops itself with stopSelf() or another component stops it by calling stopService().
so after your workload is done call stopSelf().
When your service is not running there's nothing to restart.
2. Use correct start mode
Unless you stop it, your service is by default automatically restarted after it's killed by system (because system needed resources). The default mode is called START_STICKY and does this:
This mode makes sense for things that will be explicitly started and stopped to run for arbitrary periods of time, such as a service performing background music playback.
Since your service is a quick one-time job, it makes no sense for it do be restarted later at an arbitrary time.
To let Android know, you should return START_NOT_STICKY from onStartCommand.
3. Use current API
Don't use onStart, it was deprecated 9 years ago. It doesn't support start modes mentioned above. Implement onStartCommand instead. Your service would look like this:
#Override
public void onStartCommand(Intent intent, int flags, int startId) {
// No super call.
Log.i("LOGO_OFFICE_IN", "onStart");
// Intent cannot be null.
if (intent.getExtras().getBoolean("LAUNCHER_COMMAND_CLOSE")) {
Tools.clearApplicationData(InternetService.this);
new AppStatus(InternetService.this).isAppRunning(getPackageName(), true);
}
stopSelf(); // Work is done, stop service.
return START_NOT_STICKY; // Don't restart if killed.
}
Now that I think of it, only step 1 is absolutely necessary. Anyway, get into habit of using current APIs and finding out how things work.

onStartCommand() called only once even when Service is started multiple times

The below service is triggered via button click from some other app (by firing pending Intent). The onStartCommand() creates a Messages and dispatches using send() method. Ideally, I expect onStartCommand to be called everytime button is clicked, as a pending intent is used to fire the service on buttonClick.
But onstartCommand() is called only once, for the first time the button is clicked. Subsequent button clicks do not trigger the onStartCommand().
Interestingly if I comment the line
replyTo.send(msg);
onStartCommand gets called each time the button from other app is clicked.
Therefore dispatching the Message using android IPC Messenger from within the service might be causing the issue. I confirmed the Message reaches the destination app successfully. Am I missing some detail about Messages , like blocking send call?
I am returning 'START_STICKY' from onStartCommand(), that also might be the reason.
Any insights on what is happening will be welcome.
//MyService.java
#Override
public void onCreate() {
// create RemoteViews -> rView
Intent intent = new Intent(getBaseContext(), MyService.class);
PendingIntent pendingIntent = PendingIntent.getService(getBaseContext(), 0, intent, 0);
rView.setOnClickPendingIntent(buttonId, pendingIntent);
//On click of the above button, this MyService will be started usingthe given pendingintent
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("debug","Service onStartCommand");
Message msg = Message.obtain(null, UPDATE_REMOTE_VIEW, rView);
try {
replyTo.send(msg);
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return START_STICKY;
}
Bonus Detail: The pendingIntent on the Button (from other app) is set using setOnclickPendingIntent() (RemoteViews class).
What I did in my similar case is to implement onStartCommand as follow:
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
//
// ... HERE support for intent sent by setOnClickPendingIntent ...
//
return super.onStartCommand(intent, flags, startId);
}
And it seems to work. onStartCommand is called multiple times (as many as the number of click on my RemoteViews).
From Docs:
Clients can also use Context.bindService() to obtain a persistent
connection to a service. This likewise creates the service if it is
not already running (calling onCreate() while doing so), but does not
call onStartCommand(). The client will receive the IBinder object that
the service returns from its onBind(Intent) method, allowing the
client to then make calls back to the service. The service will remain
running as long as the connection is established (whether or not the
client retains a reference on the service's IBinder). Usually the
IBinder returned is for a complex interface that has been written in
aidl.
So, it may be because of the use of bindService

Android service stopSelf(int)

Here is my code -
public class BackgroundService extends Service {
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
new ServiceThread(startId);
return START_NOT_STICKY;
}
class ServiceThread extends Thread {
private int startId;
ServiceThread(int startId) {
this.startId = startId;
}
#Override
public void run() {
super.run();
try {
Thread.sleep((long) Math.random());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
stopSelf(startId);
}
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
According to this link "Stopping a service", I can/should call stopSelf with received "startId".
However, if your service handles multiple requests to onStartCommand()
concurrently, then you shouldn't stop the service when you're done
processing a start request, because you might have since received a
new start request (stopping at the end of the first request would
terminate the second one). To avoid this problem, you can use
stopSelf(int) to ensure that your request to stop the service is
always based on the most recent start request. That is, when you call
stopSelf(int), you pass the ID of the start request (the startId
delivered to onStartCommand()) to which your stop request corresponds.
Then if the service received a new start request before you were able
to call stopSelf(int), then the ID will not match and the service will
not stop.
My question is, what will happen, if I invoke stopSelf with last "startId", and still some earlier start is still not finished. In that case, the startId will match, and according to that document the service will die? All all other earlier "starts" will just be killed?
If the answer is "yes", then what is the best practice to achieve that the service will not be killed until all earlier start is not finished.
I just run into exactly the same problem yesterday. And I think I found answer in the javadoc of Service.stopSelfResult(int startId) method.
If you may end up processing IDs out of order (such as by dispatching them on separate threads), then you are responsible for stopping them in the same order you received them.
A solution is to maintain a boolean hash map with the start ID as the key. Then in your worker threads, instead of calling stopSelf(int) call a custom method which does the following:
Set the hash map entry to true using the start ID as the key. Iterate through the keys in ascending order and call stopSelf(key) until you encounter a false entry.
This is a subtle problem - I'm not sure any of the solutions above are adequate/efficient. The solution I use is based on a class called ThreadPoolService, which extends Service and operates as follows:
It defines HashSet<Integer> that stores the startId passed by onStartCommand()
It defines an int field called mLastStartId that stores the most recent startId passed by onStartCommand()
It defines an ExecutorService initialized by either newCachedThreadPool() or newFixedThreadPool()
It defines a beginIntentProcessing() method that adds the startId parameter to the HashSet and records the latest startId in mLastStartId
It defines an endIntentProcessing() method that removes the startId parameter from the HashSet and returns if the HashSet is non-empty. If the HashSet is empty, however, it calls the stopSelf() method on the Service superclass, passing in the mLastStartId.
I've omitted some details of the solution, but it's efficient and solves the underlying problem described above. I'll be covering this topic (and many others) in my upcoming MOOCs on Android concurrent programming, which are described at http://www.coursera.org/course/posaconcurrency and http://www.coursera.org/course/posacommunication.
Doug

Way to share data with service in android

I have an application where user will choose whether he wants Vibrate or Silent mode. And from the Main Activity I am passing the data as part of Intent to the service (the service is always running).
In the onStartCommand method of service, I get the data for first time and everything works fine. But when I exit the application, after some time may be the service's onStartCommand method is again invoked with no data in the Intent (may be Launcher or android OS is doing it).
Since I am setting a local String variable with data from Intent, I get Null Exception when the onStartCommand method is invoked by OS.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(mode == null) {
mode = (String) intent.getExtras().get("mode");
}
return super.onStartCommand(intent, flags, startId);
}
Now my problem is how to get the data from activity for the first time and then refer to it in various member methods of service class.
I have tried making the mode variable as static but same issue. Looks as if OS will unload service class at its discretion and then load it back and invoke onStartCommand with plain Intent.
You must use a SharedPreference to save the state of the variable Vibrate or Silent mode. Here a possible solution:
SharedPreferences preferences = getApplicationContext()
.getSharedPreferences("preferences_name", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("mode", "silent"); // Silent Mode
// editor.putString("mode", "vibrate"); //Vibrate Mode
editor.commit();

How to force stop Intent Service in progress?

I have an intent service which downloads several gigabytes of videos. I have a "Stop" button, to stop the download if accidentally hit "Start" or whatever. I know this has been asked a couple of times but with no working answer for me.
I try to call stopService(), doesn't work. That just calls IntentService.OnDestroy().
I tried to call stopSelf() inside onDestroy, doesn't work either.
I tried to have something like a flag, but onHandleIntent doesn't get called if its already running, it waits till current work is finished and executes then. And even if this would have worked, I would have to have something like a giant if statement, that sucks
Is my only option really to rewrite it to a regular Service?
//Answer
public class SyncService extends IntentService {
boolean isCanceled;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.hasExtra("action")) {
// Set the canceling flag
isCanceled= intent.getStringExtra("action").equals("cancel");
}
return super.onStartCommand(intent, flags, startId);
}
#Override
protected void onHandleIntent(Intent intent) {
// Clean up the possible queue
if (intent.hasExtra ("action")) {
boolean cancel = intent.getStringExtra ("action"). Equals ("cancel");
if (cancel) {
return;
}
}
...
Get your inputStream from HttpUrlConnection or whatever
...
while ((bytesRead = in.read(buffer)) > 0) {
if (isCanceled) {
isCanceled = false;
break;
}
...
}
}
}
And trigger it with
Intent intent = new Intent(context, SyncService.class);
intent.putExtra("action", "cancel");
context.startService(intent);
You have two separate issues, I would think:
How to stop the current download
How to stop queued up downloads, that should execute after the current one completes
The first one is going to have to be "something like a flag", that you check as you download the data. Otherwise, nothing is going to stop your download operation. If you are using a typical HttpUrlConnection recipe, you check that flag in your loop where you read from the HTTP InputStream and write to your FileOutputStream. You set that flag via a call to startService() with a particular Intent structure, identifying it as a "cancel" operation. You would need to override onStartCommand() in your IntentService, look at the Intent, use it to set the flag if it is the cancel Intent, or chain to the superclass for any other sort of Intent.
If you also may have other commands queued up (scenario #2), you would need to check that flag at the top of onHandleIntent() as well.
Given that you haven't posted how you're handling the video download exactly, this may not work (there would be some sort of loop inside onHandleIntent where the downloads are executed). You can use a static class variable inside the IntentService that holds the Stop/Start state of the download, so that it can be set by an Activity. Then, inside onHandleIntent, you would have to routinely check the state so it would know when to cancel the operations.

Categories

Resources