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.
Related
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.
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);
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
I have an intentservice that gets qued by the user and by my app automatically. I need to be able to kill all pending intents that are qued when the user logs out of my application, but I cannot seem to get that to work. I have tried stopService() and stopself(), but the intents continue to fire off the intentservice after the user has logged out. I would try to get the id of the intent but that is difficult as everytime the intentservice starts, the variable holding the intent id's is empty. Here is my intentservice code:
public class MainUploadIntentService extends IntentService {
private final String TAG = "MAINUPLOADINTSER";
private GMLHandsetApplication app = null;
private SimpleDateFormat sdf = null;
public boolean recStops = true;
public MainUploadIntentService() {
super("Main Upload Intent Service");
GMLHandsetApplication.writeToLogs(TAG,
"GMLMainUploadIntentService Constructor");
}
#Override
protected void onHandleIntent(Intent intent) {
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Started");
if (app == null) {
app = (GMLHandsetApplication) getApplication();
}
uploadData(app);
GMLHandsetApplication.writeToLogs(TAG, "onHandleIntent Finished");
}
#Override
public void onDestroy() {
GMLHandsetApplication.writeToLogs(TAG, "onDestroy Started");
app = null;
stopSelf();
GMLHandsetApplication.writeToLogs(TAG, "onDestroy completed");
}
public void uploadData(GMLHandsetApplication appl) {
//All of my code that needs to be ran
}
Unfortunately, I don't think it's possible to accomplish that with the standard IntentService methods since it doesn't offer a way to interrupt it while it's already going.
There are a few options I can think of that you can try to see if they fit your need.
Copy the IntentService code to make your own modifications to it that would allow you to remove pending messages. Looks like someone had some success with that here: Android: intentservice, how abort or skip a task in the handleintent queue
Instead of copying all the IntentService code, you might also be able to Bind to it like a normal Service (since IntentService extends Service) so you can write your own function to remove pending messages. This one is also mentioned in that link.
Rewrite the IntentService as a regular Service instead. With this option, you'd have more control over adding and removing messages.
I had what sounds like a similar situation where I was using an IntentService, and I eventually just converted it to a Service instead. That let me run the tasks concurrently and also cancel them when I needed to clear them.
Here
When should I free the native (Android NDK) handles? is the HangAroundIntentService class that has the method cancelQueue().
The class also has the method
public static Intent markedAsCancelIntent(Intent intent)
that converts an intent into a cancel intent, and
public static boolean isCancelIntent(Intent intent).
The class is based on the open-sourced Google's code.
Just a thought but inside of your onhandleintent can you have an argument that checks to see if app is running if not then don't run the code? example. In the start of your app you could have a static var
boolean appRunning;
Next in your onhandle of the intent, when you set the appRunning to false, after an onPause or onDestroy of activity, you could wrap the onhandleintent code in a boolean:
protected void onHandleIntent(final Intent intent) {
if(MainActivity.appRunning){
...
}
}
Just a thought
i have an activity ("ApplicationActivity") that call an intent service ("DownloadService")
The intentService download files from internet in background, but i want to be able to abort a specific download.........
So let's say that i put 5 files in queue: file 1,2,3,4,5
The intent service start downloading the number 1, then the second and so on....
1) Is there a way to say to the intent service abort what you are doing at the moment in the method handle event (in this case downloading file 1) and start downloading the next one?
2)Is it possible to remove element from the queue, so for example while is downloading file 1, remove the file 4 from the queue so that after the number 3 it goes straight to the 5?
Shortly, i need a way to comunicate with the queue to perform these 2 simple operations, but i didn't find nothing usefull on internet :(
Tnx
Thanks #user280560, I found a solution based on your comment :)
just to give a more specific example, where I wanted to clear the queue in certain cases.
First I copied the IntentService.java source from here to my project (no need to change names, you can keep IntentService.java, just import yours and not android's). Then I added this
public void clearQueue() {
Debug.PrintInfo(TAG, "All requests removed from queue");
mServiceHandler.removeMessages(0);
}
to my IntentService source.
Now, from my service that extends IntentService, I wanted to clear the queue when a certain action (login) was passed to the service, so I override the onStartMethod, like this:
#Override
public void onStart(Intent intent, int startId) {
if(intent.getAction().equals(ACTION_LOGIN)) {
//Login clears messages in the queue
clearQueue();
}
super.onStart(intent, startId);
}
Works like a charm :)
Hope it helps someone...
I create my own MyIntentService class copying the original one that is pretty short and modifying methods for my own purpose........in particular to dequeue an element you can use methods of ServiceHandler in my case
mServiceHandler.removeMessages(appId);
that Remove any pending posts of messages with a certain code 'what' that are in the message queue, this means that you have to label each message you add in the queue adding an identifier inside the "what" field of each message.....for example
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = intent.getIntExtra("appId", 0); \\parameters that come from the outside
Extend the IntentService class and declare a list for cancelled item on it and whenever you want to cancel something add it to this list. finally before handling your intent make sure it has not been cancelled!
public class MyDownloadService extends IntentService {
private static List<String> canceledUrl;
public static void cancelDownload(String url){
canceledUrl.add(url);
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String url = intent.getStringExtra(EXTRA_URL);
if(!canceledUrl.contains(url)) //if download has not been canceled already
downloadFile(url);
else
canceledUrl.remove(url);
}
}
}
I know this code works because i tested it before, But i'm not sure it's a right way to do it!
You can bind to the intentservice and create a method to cancel or de queu a download.
Here is a quick tutorial what you might need
As said by #JeffreyBlattman above, it is better to play safe by assigning your own "what" value to the message like this
#Override
public void onStart(#Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = 0;
mServiceHandler.sendMessage(msg);
}
and clear the queue like mServiceHandler.removeMessages(0).
Hope that helps.