IntentService and Threadpool - android

i have an IntentService that should act like a manager and create Tasks in a queue (Runnable) that are submitted to a ThreadPool.
Im a little bit confused of the lifecycle of an IntentService:
The method protected abstract void onHandleIntent (Intent intent) runs already on a separated Thread. In the onHandleIntent I would create a new Runnable instance and submit it to the ThreadPool. My Service looks like this:
public class SyncService extends IntentService {
private final ThreadPoolExecutor threadPool;
public SyncService() {
super("SyncService");
BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
threadPool = new ThreadPoolExecutor(1, 1, 20, TimeUnit.SECONDS, queue);
}
#Override
public void onCreate() {
super.onCreate();
EventBus.getInstance().register(this);
}
#Override
public void onDestroy() {
super.onDestroy();
EventBus.getInstance().unregister(this);
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent.getAction().equals("sync")){
threadPool.submit(new SyncRunnable());
}else
if(intent.getAction().equals("delete")){
threadPool.submit(new DeleteRunnable());
} else
if(intent.getAction().equals("register")){
threadPool.submit(new RegisterRunnable())
}
}
}
My questions:
Is it a good idea to use a ThreadPool in a IntentService?
If I use a ThreadPool, than the IntentService will be destroyed if the Threadpool has no more Runnables to execute or queued, right?
Is IntentService already something that I want to achieve and should I simply execute my (long running) Runnable code in the
onHandleIntent() because this method alread runs on the
IntentService worker Thread? If yes, is there a queue limit for
intent, since onHandleIntent() could run up to 30 seconds before
finishing and handling the next Intent.

Is it a good idea to use a ThreadPool in a IntentService?
Not really. IntentService is already a single threaded (serial) variant of what you try to achieve. I would derive directly from Service.
If I use a ThreadPool, than the IntentService will be destroyed if the Threadpool has no more Runnables to execute or queued, right?
No. IntentService can go into the destroyed state once you return from onHandleIntent - i.e. immediately because threadPool.submit is non-blocking. Within the source it calls stopSelf(int) with the startId it got when the service was started.
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
A Service will go into destroyed state if you call stopSelf with the latest (highest) startId. It will keep running if a newer start is in the queue.
If the service goes into destroyed state it will not kill your thread pool because it has no knowledge about it. The problem is that Android now thinks that your service is dead and it will no longer count as a reason to keep your app process. The service running vs destroyed state is essentially just a way to tell Android that there is something going on and you don't want to get destroyed.
If you want to do it the right way you have to keep the service state in sync with what is actually going on.
Is IntentService already something that I want to achieve and should I simply execute my (long running) Runnable code in the onHandleIntent() because this method alread runs on the IntentService worker Thread?
If you are happy with single threaded serial execution yes. That's what onHandleIntent does for you.
If yes, is there a queue limit for intent, since onHandleIntent() could run up to 30 seconds before finishing and handling the next Intent.
There is no limit (it's a linked list as far as I can tell). But there is also nothing that stops you from producing more tasks than it can handle which will ultimately lead to some kind of overflow.

Related

Android Oreo - Job Intent Service is running on main Thread causing app to freeze for couple of seconds

I am trying to download some file in background. Earlier i was doing with intent service, and my app wont get freezed in using intent service. But as with oreo and above versions onward Intent service is getting destroyed as soon as app get closed from background. Same processing i did it in a Job service but it seem its running on main thread. What should i do for background processing that shouldn't be running on main thread ?
Below is code for JOB scheduling i did:
public class Util {
// schedule the start of the service every 10 - 30 seconds
#TargetApi(Build.VERSION_CODES.M)
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public static void scheduleJob(Context context) {
ComponentName serviceComponent = new ComponentName(context, MyService.class);
JobInfo.Builder builder = new JobInfo.Builder(0, serviceComponent);
builder.setMinimumLatency(1 * 1000); // wait at least
builder.setOverrideDeadline(3 * 1000); // maximum delay
//builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED); // require unmetered network
//builder.setRequiresDeviceIdle(true); // device should be idle
//builder.setRequiresCharging(false); // we don't care if the device is charging or not
JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
jobScheduler.schedule(builder.build());
}
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class MyService extends JobService {
private static final String TAG = "SyncService";
public Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
public SharedPreferences sharedPreferences;
public ComplexObject complexObject;
private Context context;
public static final String NOTIFICATION_CHANNEL_ID = "10001";
#Override
public boolean onStartJob(JobParameters params) {
System.out.println("RUnning this Job.......");
context = this;
this.sharedPreferences = context.getSharedPreferences(context.getResources().getString(R.string.shared_preference_key), Context.MODE_PRIVATE);
//30-40 HTTP call to process the data
return true;
}
#Override
public boolean onStopJob(JobParameters params) {
return true;
}
Not sure if this will help you since it's kinda old but:
Your title reads JobIntentService and you use JobService, the answer by
TheWanderer refers to your used class (JobService), but if you will use JobIntentService, its method onHandleWork runs on a background thread:
This method is called on a background thread, so you can do long blocking operations here.
According to the docs:
This service executes each incoming job on a Handler running on your application's main thread. This means that you must offload your execution logic to another thread/handler/AsyncTask of your choosing. Not doing so will result in blocking any future callbacks from the JobManager - specifically onStopJob(android.app.job.JobParameters), which is meant to inform you that the scheduling requirements are no longer being met.
So, you'll need to use an AsyncTask or some other form of asynchronous method to execute your logic. You should use something cancellable, though, since, when onStopJob() is called, you're supposed to stop whatever you're doing.
Also remember to call jobFinished() when your logic has been completed.
You can use JobIntentService as a solution to download/upload some file or even communicate with server. One another good even better approach is WorkManager. Note that IntentService runs on UI thread that is the onHandleIntent(Intent intent) runs on UI thread. So we should use a separate Thread or use of for example AsyncTask. But JobIntentService has the function onHandleWork(#NonNull Intent intent) which is run on a background thread and so we just do the work on it. In WorkManager approach, the work is done in doWork() method of Worker and this function also runs on a background thread and so we should write the main code inside it. I think JobIntentService and WorkerManager are both good for such purpose. JobIntentService just runs the work once, but by WorkManager we can do a work once or repeat it periodically (the interval time between periods has a minimum of 15 minutes).

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);

Make Asynchronous Calls to a Service (Android)

Right now I have an Activity and a local Service. I need to be able to call a service method asynchronously from the activity. The only ways I know of communicating between a service and an activity is through Intents, binding, and AIDL.
I tried binding, but bound service calls are synchronous.
Using intents (modifying the intent passed to startService) doesn't work either because onStartCommand is called on the main thread.
I'd rather not resort to AIDL because it seems rather complicated. Any alternatives I'm missing?
btw Making Asynchronous Service calls in Android does not answer my question
PROLOGUE
Following CommonWare's answer I decided to use a service with a ThreadPoolExecuter
The only ways I know of communicating between a service and an activity is through Intents, binding, and AIDL.
AIDL is binding. AIDL makes it possible to bind between apps.
I tried binding, but bound service calls are synchronous.
Make the call in a background thread, then.
Using intents (modifying the intent passed to startService) doesn't work either because onStartCommand is called on the main thread.
The service can use a background thread, then. For example, an IntentService supplies a background thread, passing the Intent delivered originally to onStartCommand() on to your onHandleIntent() method.
Any alternatives I'm missing?
None that avoid the background thread.
Turns out, an IntentService did not work for me because I needed persistent objects and IntentServices close after all jobs are finished. Instead, I used a service with an Executor, which I made to run all my service requests on a single background thread.
handleMessageHelper handles all requests on a background thread. I use a Messenger to recieve requests, and use a custom Runnable to pass the msg to my handleMessageHelper method
MyService.java (snippet)
ExecutorService background = Executors.newSingleThreadExecutor();
//handles messages from client
class IncomingHandler extends Handler {
#Override
public void handleMessage(Message msg) {
background.execute(new MessengerRunnable(msg));
super.handleMessage(msg);
}
class MessengerRunnable implements Runnable {
public Message msg;
public MessengerRunnable(Message m) {
super();
msg = m;
}
#Override
public void run() {
handleMessageHelper(msg);
}
}
}
//called from handleMessage in IncomingHandler
//should be run on `background` ExecutorService
public void handleMessageHelper(Message msg) {
Log.d("CASE: ","" + msg.what);
switch (msg.what) {
case SET_ACCOUNT:
Bundle b = msg.getData();
String accountName = b.getString("accountName");
if (accountName != null) {
setAccount(accountName);
}
break;
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());

make an IntentService not sleep until it executes a handler.postDelayed runnable

In the onHandleIntent of my my IntentService class, I created handle containing a runnable which should be done after 20 seconds. Unfortunatly my service sleeps or is destroyed before this period.
I tried also with the CountDownTimer, but i had the same problem.
Do someone have any idea can I make the onHnadleIntent waiting? Thank you!
This is the code:
public class MyService extends IntentService {
//...
#Override
protected void onHandleIntent(Intent workIntent) {
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
Log.i("20 seconds later","I am here");
}
}, 20000);
//...
}
//...
}
Don't use an IntentService. It is not designed for your scenario. Use a regular Service. Put your code in onStartCommand(). At the bottom of your run() method, call stopSelf() on the Service instance to shut it down.
You need to stop onHandleIntent from returning until the Runnable has completed. This can be achieved by using a CountdownLatch, which awaits at the end of your onHandleIntent method, and gets released at the end of the run method.
NOTE: this will cause the intent service to not process other Intents you sent it until the previous one has completed (after 20 seconds).
You may also want to obtain a partial wakelock at the start of onHandleIntent and release it at the end.

Does stopService kill a Android service or does it wait untill it's idle?

I currently have a service that processes some stuff, and it is started with startService.
I was wondering, can I call `stopService immediately after I start the service and expect it to stop the service after the processing is done?
Or does Android kill the service when I call that command?
One hopes that "processes some stuff" is done in a background thread, assuming that it will take more than a couple of milliseconds.
Android is largely oblivious to such a background thread. stopService() will trigger onDestroy() of the service, and the service will go away. The thread, however, will continue to run, until it terminates on its own, or until the process is terminated.
can I call `stopService immediately after I start the service and expect it to stop the service after the processing is done?
Only if "the processing" is done on the main application thread (e.g., in the body of onStartCommand()), which, again, is not a good idea if such work will take more than a couple of milliseconds. And, if that indeed is the case, there's no good reason for having a service in the first place.
If you want to have a service that:
Has a background thread, and
Automatically shuts down when the work is complete (avoiding the need for stopService())
then use an IntentService.
Android can't kill just a single Service. All it can do is to kill the whole process and everything running within. Most apps will have just 1 process so this usually means Android kills everything or nothing. Most of the times nothing.
The lifecycle of a Service or Activity tells Android whether it may kill the process safely or not. The Processes and Threads describes the order in which processes are kill if there is demand for memory.
It is important to know that a Thread started from a Service / Activity it is not affected at all by onDestroy etc. It just keeps running. Android simply does not know about that thread and won't stop it for you.
That means if you want to do some background processing you have link the lifecycle of such threads to the lifecycle of your Activity / Service or Android may just kill the process and thus your thread.
Quick example of a Service that prints to logcat every second while running. Not based on IntentService since that's more or less intended for tasks with an end.
public class MyService extends Service {
public static void start(Context context) {
context.startService(new Intent(context, MyService.class));
}
public static void stop(Context context) {
context.stopService(new Intent(context, MyService.class));
}
private final ExecutorService mBackgroundThread = Executors.newSingleThreadExecutor();
private Future<?> mRunningTask;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// startService -> start thread.
if (mRunningTask == null) {
// prevents task from being submitted multiple times.
// actually not necessary when using a single thread executor.
mRunningTask = mBackgroundThread.submit(mRunnable);
}
return START_STICKY;
}
private Runnable mRunnable = new Runnable() {
#Override
public void run() {
while (!Thread.interrupted()) {
try {
// Do something
Log.d("Service", "I'm alive");
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.d("Service", "Got interrupted", e);
Thread.currentThread().interrupt();
}
}
}
};
#Override
public void onDestroy() {
// stopService > kill thread.
mBackgroundThread.shutdownNow();
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
According to the documentation:
stopService(Intent service)
Request that a given application service be stopped. If the service is
not running, nothing happens. Otherwise it is stopped. Note that calls
to startService() are not counted -- this stops the service no matter
how many times it was started.
Note that if a stopped service still has ServiceConnection objects
bound to it with the BIND_AUTO_CREATE set, it will not be destroyed
until all of these bindings are removed. See the Service documentation
for more details on a service's lifecycle.

Categories

Resources