17

I would like to use Androids new JobScheduler in my app but right now I don't know how to pass my object which contains the data (byte array) that should be sent via network by a job. I searched for an answer but so far found none I'm afraid.

I have a JobService:

public class MyJob extends JobService {

    @Override
    public boolean onStartJob(JobParameters jobParameters) {
        new JobTask(this).execute(jobParameters);
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }

    private static class JobTask extends AsyncTask<JobParameters, Void,       JobParameters> {
        private final JobService jobService;

        public JobTask(JobService jobService) {
            this.jobService = jobService;
        }

        @Override
        protected JobParameters doInBackground(JobParameters... params) {
            AnotherClass.post(myObject); // where does myObject come from?
            return params[0];
        }

        @Override
        protected void onPostExecute(JobParameters jobParameters) {
            jobService.jobFinished(jobParameters, false);
        }
    }
}

... am building a job like this:

PersistableBundle bundle = new PersistableBundle();
JobInfo job = new JobInfo.Builder(jobID, new ComponentName(context, AshServiceJob.class))
              .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)
              .setPersisted(true)
              .setExtras(bundle)  // could bundle somehow contain myObject?
              .build();

OtherClass.addJobInBackground(job);

... and am scheduling the job:

public void addJobInBackground(final JobInfo job) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        jobScheduler.schedule(job);
    }

Sooo ... I can't call any methods in MyJob directly, right? I then thought I could use .setExtras(bundle) to pass my object to the job, but as I found out you can only use a PersistableBundle which wouldn't take a serialized object like Bundle. Setting keys and values doesn't work, because you can only put booleans, ints, strings, etc. but not byte[], which is what I need.

Has anyone any idea? I'm quite stuck.

Thanks in advance, a-m

PS: Sorry, if I probably didn't use the right Code-Tags.

Mike
  • 8,055
  • 1
  • 30
  • 44
a-m
  • 241
  • 1
  • 2
  • 7
  • This is not tied specifically to passing objects to `JobInfo`. It is about using an `Intent` to pass objects between any two android components, **options available have been discussed in detail in [this question](https://stackoverflow.com/q/2139134/2666212)**. – Mike Jun 03 '17 at 10:02

4 Answers4

16

I know it is an old question, but I'll leave my solution for whoever finds it useful.

First I turn my object into json using the Google Gson library

MyCustomObject myObj = new MyCustomObject("data1", 123);
Gson g = new Gson();
String json = g.toJson(myObj);

PersistableBundle bundle = new PersistableBundle();
bundle.putString("MyObject", json);

Then you retrieve the string from the bundle and deserialize

String json = params.getExtras().getString("MyObject");
Gson g = new Gson();
MyCustomObject myObj = g.fromJson(json, MyCustomObject.class);
Tolio
  • 1,023
  • 13
  • 30
  • 1
    Your answer is perfect. This is just a warning for readers. I now only use `putString` and `getString` and I won't use the variants like `putLong`. I've had issues on one device with version 5.1.1 that was causing crashes related to job extras using `putLong` after calling `getAllPendingJobs`. – g2server Mar 16 '18 at 22:31
  • Just a note on why we have to use this work around instead of passing an object - "The set of types supported by this class is purposefully restricted to simple objects that can safely be persisted to and restored from disk." – C. Skjerdal Oct 01 '19 at 15:49
7

Ok - I will answer myself.

It seems that passing more complex objects than String, ints, etc. to a JobService is not possible due to persistence reasons (code could have changed and not classes will not be able to work with old data). That of course makes sense but I think with that restriction it is very difficult to use the JobScheduling.

My "solution" now is saving my object in a File and pass the filename via

PersistableBundle bundle = new PersistableBundle();
bundle.putString("filename", object.getFilename());

In my JobService I am reading that file and am sending it's data via network. Maybe it is not really elegant, but I cannot think of another (better) way. Maybe this helps someone.

a-m
  • 241
  • 1
  • 2
  • 7
0

I tried that but with the FirebaseJobDispatcher:

  1. I made my CustomObject implements Parcelable
  2. I converted my CustomObject to a String
  3. I put it as an extra in a Bundle

To convert CustomObject that extends Parcelable to String:

    private String encodeToString(CustomObject customObject) {
        Parcel parcel = Parcel.obtain();
        List< CustomObject > temp = new ArrayList<>();
        temp.add(customObject);
        parcel.writeList(temp);
        byte[] byt = parcel.marshall();
        String data = Base64.encodeToString(byt, 0, byt.length, 0);
        parcel.recycle();

        return data;
    }

To retrieve CustomObject that extends Parcelable from String:

       private CustomObject decodeFromString (String data) {
            byte[] byt = Base64.decode(data, 0);

            Parcel parcel = Parcel.obtain();
            parcel.unmarshall(byt, 0, byt.length);
            parcel.setDataPosition(0);

            List<CustomObject> temp = new ArrayList<>();
            parcel.readList(temp, getClass().getClassLoader());
            parcel.recycle();

            if (temp.size()>0) {
                return temp.get(0);
            } else {
                return null;
            }
        }
0

Class loader information will be missing if custom object is passed through the intent in job service.

It can be solved by setting the extra class loader.

intent.setExtrasClassLoader(<CUSTOM CLASS>.class.getClassLoader());
4b0
  • 21,981
  • 30
  • 95
  • 142