Monday, May 4, 2015

Set requirement of JobScheduler, I am confused!

Last exercise show a "Simple example using JobScheduler to run JobService repeatly". I think I can create a JobScheduler to run JobService repeatly only when power-pluged and connected to WiFi, by intuition. But it seem not the case.

The official document of JobInfo.Builder explain not so clear!

Here is my exercise to test it, with various combination of requirements. You can try it and correct me if I'm wrong.


MainActivity.java
package com.example.androidjobscheduler;

import java.util.List;

import android.support.v7.app.ActionBarActivity;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.SystemClock;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Chronometer;
import android.widget.RadioButton;
import android.widget.Toast;


public class MainActivity extends ActionBarActivity {
 
 Chronometer chronometer;
 Button btnStartJob, btnCancelJobs;
 
 JobScheduler jobScheduler;
 private static final int MYJOBID = 1;
 
 RadioButton optDeadline;
 RadioButton optPeriodic;
 CheckBox optIsPersisted;
 RadioButton optNetworkTypeANY;
 RadioButton optNetworkTypeNONE;
 RadioButton optNetworkTypeUNMETERED;
 CheckBox optRequiresCharging;
 CheckBox optRequiresDeviceIdle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        optDeadline = (RadioButton)findViewById(R.id.optDeadline);
        optPeriodic = (RadioButton)findViewById(R.id.optPeriodic);
        optIsPersisted = (CheckBox)findViewById(R.id.isPersisted);
        optNetworkTypeANY = (RadioButton)findViewById(R.id.optNETWORK_TYPE_ANY);
        optNetworkTypeNONE = (RadioButton)findViewById(R.id.optNETWORK_TYPE_NONE);
        optNetworkTypeUNMETERED = (RadioButton)findViewById(R.id.optNETWORK_TYPE_UNMETERED);
        optRequiresCharging = (CheckBox)findViewById(R.id.requiresCharging);
        optRequiresDeviceIdle = (CheckBox)findViewById(R.id.requiresDeviceIdle);
        
        chronometer = (Chronometer)findViewById(R.id.chronometer);
        btnStartJob = (Button)findViewById(R.id.startjob);
        btnCancelJobs =  (Button)findViewById(R.id.canceljobs);

        jobScheduler = (JobScheduler)getSystemService(JOB_SCHEDULER_SERVICE);
        
        btnStartJob.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    chronometer.setBase(SystemClock.elapsedRealtime());
    chronometer.start();

    ComponentName jobService = 
     new ComponentName(getPackageName(), MyJobService.class.getName());
    
    JobInfo.Builder builder = new JobInfo.Builder(MYJOBID, jobService);
    
    if(optDeadline.isChecked()){
     builder.setMinimumLatency(5000);
     builder.setOverrideDeadline(7000);
    }else{
     builder.setPeriodic(10000);
    }

    /*setPersisted only have an effect if your application 
     * holds the permission RECEIVE_BOOT_COMPLETED. 
     * Otherwise an exception will be thrown.
     */
    builder.setPersisted(optIsPersisted.isChecked());

    if(optNetworkTypeANY.isChecked()){
     builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
    }else if(optNetworkTypeUNMETERED.isChecked()){
     builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
    }else{
     //can skip, it's default
     builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_NONE);
    }
    
    builder.setRequiresCharging(optRequiresCharging.isChecked());
    builder.setRequiresDeviceIdle(optRequiresDeviceIdle.isChecked());

    JobInfo jobInfo = builder.build();

    int jobId = jobScheduler.schedule(jobInfo);
    if(jobScheduler.schedule(jobInfo)>0){
     Toast.makeText(MainActivity.this, 
      "Successfully scheduled job: " + jobId +
      "\ngetId(): " + jobInfo.getId() +
      "\nisPeriodic(): " + jobInfo.isPeriodic() +
      "\nisPersisted(): " + jobInfo.isPersisted() +
      "\ngetNetworkType(): " + jobInfo.getNetworkType() +
      "\nisRequireCharging(): " + jobInfo.isRequireCharging() +
      "\nisRequireDeviceIdle(): " + jobInfo.isRequireDeviceIdle(),
      Toast.LENGTH_LONG).show();
    }else{
     Toast.makeText(MainActivity.this, 
       "RESULT_FAILURE: " + jobId, 
       Toast.LENGTH_SHORT).show();
    }
       
   }});
        
        btnCancelJobs.setOnClickListener(new OnClickListener(){

   @Override
   public void onClick(View v) {
    chronometer.stop();
    
    List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
    String s = "";
    for(JobInfo j : allPendingJobs){
     int jId = j.getId();
     jobScheduler.cancel(jId);
     s += "jobScheduler.cancel(" + jId + " )";
    }
    Toast.makeText(MainActivity.this, 
      s, 
      Toast.LENGTH_SHORT).show();
    
    //or
    //jobScheduler.cancelAll();
    
    
   }});
    }


}

activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.androidjobscheduler.MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:autoLink="web"
        android:text="http://android-er.blogspot.com/"
        android:textStyle="bold" />

    <Chronometer
        android:id="@+id/chronometer"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal" />
    
    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#A0A0A0" >
        <RadioButton
            android:id="@+id/optDeadline"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="setMinimumLatency(5000ms)/setOverrideDeadline(7000ms)" />
        <RadioButton
            android:id="@+id/optPeriodic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="setPeriodic(10000)" 
            android:checked="true" />

    </RadioGroup>
    
    <CheckBox 
        android:id="@+id/isPersisted"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="isPersisted across device reboots" />
    
    <RadioGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#C0C0C0" >
        <RadioButton
            android:id="@+id/optNETWORK_TYPE_NONE"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="NETWORK_TYPE_NONE" 
            android:checked="true" />
        <RadioButton
            android:id="@+id/optNETWORK_TYPE_ANY"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="NETWORK_TYPE_ANY" />
        <RadioButton
            android:id="@+id/optNETWORK_TYPE_UNMETERED"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="NETWORK_TYPE_UNMETERED" />
    </RadioGroup>
    
    <CheckBox 
        android:id="@+id/requiresCharging"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="requiresCharging" />
    
    <CheckBox 
        android:id="@+id/requiresDeviceIdle"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="requiresDeviceIdle" />

    <Button
        android:id="@+id/startjob"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start a Job" />
    
    <Button
        android:id="@+id/canceljobs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Cancel all Jobs" />

</LinearLayout>

MyJobService.java
package com.example.androidjobscheduler;

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;

//Require API Level 21
public class MyJobService extends JobService {

 public MyJobService() {
 }

 @Override
 public boolean onStartJob(JobParameters params) {
  Toast.makeText(this, 
    "MyJobService.onStartJob() - " + 
    params.getJobId(), 
    Toast.LENGTH_SHORT).show();
  /*
   * True - if your service needs to process 
   * the work (on a separate thread). 
   * False - if there's no more work to be done for this job.
   */
  return false;
 }

 @Override
 public boolean onStopJob(JobParameters params) {
  Toast.makeText(this, 
    "MyJobService.onStopJob() - " + 
    params.getJobId(), 
    Toast.LENGTH_SHORT).show();
  return false;
 }

}

uses-permission of "android.permission.RECEIVE_BOOT_COMPLETED" and service of ".MyJobService" are needed in AndroidManifest.xml, for builder.setPersisted().
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.androidjobscheduler"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="21"
        android:targetSdkVersion="22" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            
        </activity>
        <service android:name=".MyJobService"
            android:permission="android.permission.BIND_JOB_SERVICE" />
    </application>

</manifest>



download filesDownload the files.

1 comment:

  1. I'm very confused as well. I want a periodic job but it has a network requirement.
    After so many tries, turns out that the "setPeriodic" sort of overwrites every single requirement. Now, I have no idea how to achieve what I want

    ReplyDelete