5

I didn't see example of using jobFinshed of JobService, seems like we have to track changes when some condition meet we have to call jobFinished() method, am I right?

Asad Ali Choudhry
  • 4,985
  • 4
  • 31
  • 36
blackHawk
  • 6,047
  • 13
  • 57
  • 100

4 Answers4

9

The difficulty of calling jobFinished() from another class like an IntentService seems to be getting an instance of your class that extends JobService to use to call jobFinished(). You can get info on the scheduled job, but not on the JobService (at least, I can't find a way). I can think of 3 ways to call jobFinished().

If you don't care if your work is successful or not, or if your work takes very little time.

In one of my JobService classes, I'm doing periodic work. I'm not worried about handling failures. The task will execute again soon enough. If this is the case, you can do this.

    public boolean onStartJob(JobParameters params) {
        startService(new Intent(this, MyIntentServiceThatDoesTheWork.class));

        // job not really finished here but we assume success & prevent backoff procedures, wakelocking, etc.
        jobFinished(params, false);
        return true;
    }

This is also the way you want to do it if your work is short enough it's no problem to do it on the UI thread. In this case, do all your work in onStartJob() then return false.

Use a BroadcastReceiver to send a message from the IntentService to the JobService (a separate file for each class).

    // common Strings
    public static final String IS_SUCCESS = "isSuccess";
    public static final String MY_BC_RCVR = "MyBroadcastRcvr";

Your JobService

    public class MyJobService extends JobService {
        JobParameters mParams;

        public boolean onStartJob(JobParameters params) {
            mParams = params;
            LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver,
                    new IntentFilter(MY_BC_RCVR));
            startService(new Intent(this, MyIntentServiceThatDoesTheWork.class));
            return true;
        }

        private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                boolean isSuccess = false;
                if(intent.hasExtra(IS_SUCCESS)) {
                    isSuccess = intent.getBooleanExtra(IS_SUCCESS, false);
                }
                LocalBroadcastManager.getInstance(context).unregisterReceiver(this);
                jobFinished(mParams, !isSuccess);
            }
        };
    }

& your IntentService

    public class MyIntentServiceThatDoesTheWork extends IntentService {
        @Override
        protected void onHandleIntent(Intent intent) {

            boolean isSuccess = methodToDoAllMyWork();
            Intent bcIntent = new Intent(MY_BC_RCVR);
            bcIntent.putExtra(IS_SUCCESS, isSuccess);
            LocalBroadcastManager.getInstance(this).sendBroadcast(bcIntent);
        }
    }

Nest your worker thread class in the JobService class.

I've given an example of an AsyncTask based on this Medium post (also referenced by Arseny Levin) from a Google Developer Advocate but it should also be possible to use an IntentService (see this SO post for nesting IntentService).

    public class MyJobService extends JobService {
        JobParameters mParams;

        public boolean onStartJob(JobParameters params) {
            mParams = params;
            new MyAsyncTaskThatDoesTheWork().execute();
            return true;
        }

        private class MyAsyncTaskThatDoesTheWork extends AsyncTask<Void, Void, Boolean> {
            @Override
            protected Boolean doInBackground(Void... params) {
                return methodToDoAllMyWork();
            }

            @Override
            protected void onPostExecute(Boolean isSuccess) {
                if(mParams != null) {
                    jobFinished(mParams, !isSuccess);
                }
            }
        }
    }
Gary99
  • 1,750
  • 1
  • 19
  • 33
  • approach with InentService + LocalBroadcastManager works fine. But your first code sample where you start a service and instantly call jobFinished(params, false); is not a good idea. It may shut down execution of worker thread in the middle of something which may result to unexpected state or/and behavior. If your job is quick go with AsyncTask, if takes some time use IntentService + LocalBroadcasting to finish the job properly. – Kirill Karmazin Feb 28 '18 at 11:04
  • Is it necessary to call `LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver)` in the `onStopJob` method and `BroadcastReceiver::onReceive` method? – Jsyntax Sep 06 '18 at 03:02
  • Update: I tried the `startService` (2nd approach) in the JobService, and when my app went into the background, the job failed from starting the service (IllegalStateException) - I am confused as I thought you can do anything inside the jobservice (async, start a new background service, etc), but apparently you cannot. I ended up starting a `JobIntentService` from the `JobService`. The `JobService` is redundant at this point, but I wanted to preserve the constraint checking as provided the job scheduler. – Jsyntax Sep 06 '18 at 17:22
5

If your onStartJob() method returns true, that means that you are doing work in the background in support of this job. That background thread needs to call jobFinished() when that work has been completed or if the job needs to be rescheduled.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • What is the best approach to establish a network connection with a jobservice? Will the connection stay established when we call jobfinished? – neoexpert Jan 11 '19 at 19:32
  • @neoexpert: "Will the connection stay established when we call jobfinished?" -- you should not count on it. AFAIK, your service will be destroyed when there are no active jobs. "What is the best approach to establish a network connection with a jobservice?" -- get all your network I/O done in the confines of a job and its ~10 minute maximum runtime. Or, be in position to pick up where you left off with a fresh connection. Or, don't use `JobService`. – CommonsWare Jan 11 '19 at 19:41
2

A simple approach is to start a new thread from onStartJob as shown below:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
    @Override
    public boolean onStartJob(final JobParameters params) {
      new Thread(new Runnable() {
        @Override
        public void run() {

            // do your job here

            jobFinished(params, true);
        }
      }).start();

      return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
      return true;
    }
}
Ashwin
  • 7,277
  • 1
  • 48
  • 70
1

As @CommonsWare explained - inside onStartJob you'll decide if further work is required on a background thread. Only if that's the case, then you should call jobFinished from that background thread. Just to answer your specific question - no condition tracking is required on your behalf. JobService will call onStopJob if the conditions you asked are no longer true.

Example of jobFinished can be found here: https://medium.com/google-developers/scheduling-jobs-like-a-pro-with-jobscheduler-286ef8510129

Arseny Levin
  • 664
  • 4
  • 10