Android service stopSelf(int) - android

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

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.

Same Intent Service running multiple background tasks parallely (ISSUE)

public class DataManager extends IntentService {
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
}
public DataManager() {
super("DataManager");
setIntentRedelivery(true);
}
#Override
protected void onHandleIntent(final Intent intent) {
// download and parsing task done here
}
}
This is my intent service which i am using to download file and parse it. Now if i get a new request for a file download, i have to clear the ongoing task and start the download for new request cancelling the older one. so i use the below code for doing it :.
private void refreshSync() {
context.stopService(new Intent(context, DataManager.class));
final Intent mServiceIntent = new Intent(context, DataManager.class);
mServiceIntent.putExtras(bundle);
context.startService(mServiceIntent);
}
So the service gets killed and the next request to start service is intented. But the previous tasks starts again running two parallel tasks performing download. Basically the previous task doesnt get killed which i intended to.
Is there any work around to kill the ongoing task of the service and start another fresh task ?
Don't use IntentService. This doesn't match your requirements. IntentService is a simple Service that accepts a queue of work and processes the queue and then shuts itself down when the queue is empty.
You need more intelligence, and you are better off implementing that yourself. Just extend Service instead of IntentService. In onStartCommand() start a background Thread that downloads the data. Keep track of that background Thread in a member variable in the Service. If startService() gets called again, check if you already have a download in progress. If so, stop it and start a new background Thread to download the new file. To stop a background thread, you should provide a boolean variable in the Thread that gets examined every now and then inside the download loop. If that variable's state changes, it means the Thread should stop. This is a standard mechanism for stopping background threads in Java.
You are setting setIntentRedelivery(true);, that force the intents to survive calls of the service if they are not handled completely (if onHandleIntent doesn't manage to return). Taking into account the fact that IntentService has only one working thread (can execute only one task at a time) the behavior of the service completely depends on the onHandleIntent implementation. So you need either analyze implementation and change it according to you goals, or set setIntentRedelivery(false);

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

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.

Registering a ContentObserver in a Android Service

I am working on parental control/adult content filtering application. This app continuously monitors the calls and smses on a child's mobile and logs all the activity onto a server. For this I am starting a service (MyService.java)
on BOOT_COMPLETED and in the onCreate method of the service I register a contentobserver for the callLog and sms uri ( refer to the code snippet below ) .
Now the issue is, Since I want to monitor every outgoing, incoming call s and sms I want the service to be continuously running ( without being stopped/killed) . Moreover this Service is being just used for registering content observers and not doing any other processing(its OnstartCommand method is dummy ) , so android OS kills the service after sometime. How do I ensure that the service runs continuously and keeps the contentobserver object alive ?
public class MyService extends Service {
private CallLogObserver clLogObs = null;
public void onCreate() {
super.onCreate();
try{
clLogObs = new CallLogObserver(this);
this.getContentResolver().registerContentObserver(android.provider.CallLog.Calls.CONTENT_URI, true, clLogObs);
}catch(Exception ex)
{
Log.e("CallLogData", ex.toString());
}
}
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onDestroy() {
if( clLogObs !=null )
{
this.getContentResolver().unregisterContentObserver(clLogObs);
}
super.onDestroy();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
return Service.START_STICKY;
}
#Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
You cannot ensure your service to be running continuously on Android.
For the use-case you mention, it is better to rely on Broadcast receiver like ACTION_NEW_OUTGOING_CALL & SMS_RECEIVED.
If you feel, above supported Broadcast receivers doesn't cover all your use-cases. Use AlaramManager to periodically start your SERVICE and look into CALL_LOGS and SMS table for any change in data and take appropriate action (this may involve check marking the last visited data on CALL_LOGS and SMS table).
you can set the service to run in the foreground . this will give your service a much lower chance of being killed by the OS .
read here for more information .
The content observer get unregistered as soon as your service get killed. So registering once is not an option and your service must be running all the time. to guarantee this you must return START_STICKY from onStartCommand. You can also start the service in foreground.
But what if a new call arrive when the service is killed? To handle this you can store last processed _id in preferences of your app. and each time get all the new call logs with greater id than saved id. You must process the logs on service onCreate as well as on observer onChange.
You cant guarantee it will run continuously, but you can return START_STICKY from onStartCommand which guarantees that Android will re-start your service if it is killed for any reason.

Categories

Resources