Skip to content

Instantly share code, notes, and snippets.

@MohammadSamandari
Created April 4, 2020 23:42
Show Gist options
  • Save MohammadSamandari/94a118a3326a24f7201748fa234a61f0 to your computer and use it in GitHub Desktop.
Save MohammadSamandari/94a118a3326a24f7201748fa234a61f0 to your computer and use it in GitHub Desktop.
Android - Jobschedule

Efficient data transfer

Monitor connectivity state

You can use the ConnectivityManager to determine the active wireless radio and modify your prefetching routines depending on network type:

ConnectivityManager cm =
   (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
TelephonyManager tm =
    (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);

NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
int PrefetchCacheSize = DEFAULT_PREFETCH_CACHE;

switch (activeNetwork.getType()) {
    case (ConnectivityManager.TYPE_WIFI):
        PrefetchCacheSize = MAX_PREFETCH_CACHE; 
        break;
    case (ConnectivityManager.TYPE_MOBILE): {
        switch (tm.getNetworkType()) {
           case (TelephonyManager.NETWORK_TYPE_LTE |
                  TelephonyManager.NETWORK_TYPE_HSPAP):
                PrefetchCacheSize *= 4;
                break;
            case (TelephonyManager.NETWORK_TYPE_EDGE |
                  TelephonyManager.NETWORK_TYPE_GPRS):
               PrefetchCacheSize /= 2;
               break;
            default: break;
        }
        break;
      }
  default: break;
  }

Monitor battery state

The BatteryManager broadcasts all battery and charging details in a broadcast Intent that includes the charging status.

To check the current battery status, examine the broadcast intent:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
// Are we charging / charged?
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING ||
                     status == BatteryManager.BATTERY_STATUS_FULL;

// How are we charging?
int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
boolean usbCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;
boolean acCharge = chargePlug == BatteryManager.BATTERY_PLUGGED_AC;

If you want to react to changes in the battery charging state, use a BroadcastReceiver to register for the battery status actions in your code:

public void registerBatteryChargingStateReceiver()
 {

     // Create Receiver Object
     BroadcastReceiver receiver = new MyPowerReceiver();

     //Create Intent Filter
     IntentFilter intentFilter = new IntentFilter();

     intentFilter.addAction(Intent.ACTION_POWER_CONNECTED);
     intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);

     // Register broadcast receiver
     this.registerReceiver(receiver, intentFilter);

 }

JobScheduler

Introduced in API level 21, JobScheduler allows you to schedule a task around specific conditions, rather than a specific time as with AlarmManager.

JobScheduler has three components:

  1. JobInfo uses the builder pattern to set the conditions for the task.
  2. JobService is a wrapper around the Service class where the task is actually completed.
  3. JobScheduler schedules and cancels tasks.

JobInfo

Set the job conditions by constructing a JobInfo object using the JobInfo.Builder class.

JobScheduler scheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
ComponentName serviceName = new ComponentName(getPackageName(),
NotificationJobService.class.getName());
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName);
builder.setRequiredNetworkType(NETWORK_TYPE_UNMETERED);
JobInfo jobInfo = builder.build();

The JobInfo.Builder class has many set() methods that allow you to determine the conditions of the task. Below is a list of few available constraints with their respective set() methods and class constants:

  1. Minimum Latency: The minimum amount of time to wait before completing the task. Set this condition using the setMinimumLatency() method, which takes a single argument: the amount of time to wait in milliseconds.
  2. Override Deadline: The maximum time to wait before running the task, even if other conditions aren't met. Set this condition using the setOverrideDeadline() method, which is the maximum time to wait in milliseconds.
  3. Periodic: Repeats the task after a certain amount of time. Set this condition using the setPeriodic() method, passing in the repetition interval. This condition is mutually exclusive with the minimum-latency and override-deadline conditions: setting setPeriodic() with one of them results in an error.
  4. Required Network Type: The kind of network type your job needs. If the network isn't necessary, you don't need to call this function, because the default is NETWORK_TYPE_NONE. Set this condition using the setRequiredNetworkType() method, passing in one of the following constants: NETWORK_TYPE_NONE, NETWORK_TYPE_ANY, NETWORK_TYPE_NOT_ROAMING, NETWORK_TYPE_UNMETERED.
  5. Required Charging State: Whether the device needs to be plugged in to run this job. Set this condition using the setRequiresCharging() method, passing in a boolean. The default is false.
  6. Requires Device Idle: Whether the device needs to be in idle mode to run this job. "Idle mode" means that the device isn't in use and hasn't been for some time, as loosely defined by the system. When the device is in idle mode, it's a good time to perform resource-heavy jobs. Set this condition using the setRequiresDeviceIdle()method, passing in a boolean. The default is false.

JobService

Once the conditions for a task are met, the framework launches a subclass of JobService, which is where you implement the task itself. The JobService runs on the UI thread, so you need to offload blocking operations to a worker thread.

Declare the JobService subclass in the Android Manifest, and include the BIND_JOB_SERVICE permission:

<service android:name="MyJobService"
        android:permission="android.permission.BIND_JOB_SERVICE" >

In your subclass of JobService, override two methods, onStartJob() and onStopJob().

onStartJob()

The system calls onStartJob() and automatically passes in a JobParameters object, which the system creates with information about your job. If your task contains long-running operations, offload the work onto a separate thread. The onStartJob() method returns a boolean: true if your task has been offloaded to a separate thread (meaning it might not be completed yet) and false if there is no more work to be done.

Use the jobFinished() method from any thread to tell the system that your task is complete. This method takes two parameters: the JobParameters object that contains information about the task, and a boolean that indicates whether the task needs to be rescheduled, according to the defined backoff policy.

onStopJob()

If the system determines that your app must stop execution of the job, even before jobFinished() is called, the system calls onStopJob(). This happens if the requirements that you specified when you scheduled the job are no longer met.

Examples:

  • If your app requests Wi-Fi with setRequiredNetworkType() but the user turns off Wi-Fi while your job is executing, the system calls onStopJob().
  • If your app specifies setRequiresDeviceIdle() but the user starts interacting with the device while your job is executing, the system calls onStopJob().

You're responsible for how your app behaves when it receives onStopJob(), so don't ignore it. This method returns a boolean, indicating whether you'd like to reschedule the job based on the defined backoff policy, or drop the task.

JobScheduler

The final part of scheduling a task is to use the JobScheduler class to schedule the job. To obtain an instance of this class, call getSystemService(JOB_SCHEDULER_SERVICE). Then schedule a job using the schedule() method, passing in the JobInfo object you created with the JobInfo.Builder. For example:

mScheduler.schedule(myJobInfo);

To cancel a job, call cancel(), passing in the job ID from the JobInfo.Builder object, or call cancelAll(). For example:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mohammad.samandari.notificationscheduler">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".NotificationJobService"
android:permission="android.permission.BIND_JOB_SERVICE"/>
</application>
</manifest>
package mohammad.samandari.notificationscheduler;
import androidx.appcompat.app.AppCompatActivity;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity {
private static final int JOB_ID = 0;
private JobScheduler mScheduler;
// Switches for setting job options.
private Switch mDeviceIdleSwitch;
private Switch mDeviceChargingSwitch;
// Override deadline seekbar.
private SeekBar seekBar;
@Override
protected void onCreate (Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDeviceChargingSwitch = findViewById(R.id.chargingSwitch);
mDeviceIdleSwitch = findViewById(R.id.idleSwitch);
seekBar = findViewById(R.id.seekBar);
final TextView seekbarTextview = findViewById(R.id.seekbarTextview);
mScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
// Updates the TextView with the value from the seekbar.
seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged (SeekBar seekBar, int progress, boolean fromUser) {
if (progress > 0) {
seekbarTextview.setText("Override Deadline: " + progress + " s");
} else {
seekbarTextview.setText("Override Deadline: Not Set");
}
}
@Override
public void onStartTrackingTouch (SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch (SeekBar seekBar) {
}
});
}
/**
* onClick method that schedules the jobs based on the parameters set.
*/
public void scheduleJob (View view) {
RadioGroup networkOptions = findViewById(R.id.networkOptions);
int selectedNetworkId = networkOptions.getCheckedRadioButtonId();
int selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
int seekBarInteger = seekBar.getProgress();
boolean seekBarSet = seekBarInteger > 0;
switch (selectedNetworkId) {
case R.id.noNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
break;
case R.id.anyNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_ANY;
break;
case R.id.wifiNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_UNMETERED;
break;
}
ComponentName serviceName = new ComponentName(getPackageName(),
NotificationJobService.class.getName());
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName)
.setRequiredNetworkType(selectedNetworkOption)
.setRequiresDeviceIdle(mDeviceIdleSwitch.isChecked())
.setRequiresCharging(mDeviceChargingSwitch.isChecked());
if (seekBarSet) {
builder.setOverrideDeadline(seekBarInteger * 1000);
}
boolean constraintSet = selectedNetworkOption
!= JobInfo.NETWORK_TYPE_NONE
|| mDeviceChargingSwitch.isChecked()
|| mDeviceIdleSwitch.isChecked()
|| seekBarSet;
if (constraintSet) {
JobInfo myJobInfo = builder.build();
mScheduler.schedule(myJobInfo);
Toast.makeText(this, "Job Scheduled, job will run when " +
"the constraints are met.", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Please set at least one constraint",
Toast.LENGTH_SHORT).show();
}
}
/**
* onClick method for cancelling all existing jobs.
*/
public void cancelJob (View view) {
if (mScheduler != null) {
mScheduler.cancelAll();
mScheduler = null;
Toast.makeText(this, "Jobs cancelled", Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(this, "Nothing To Cancel", Toast.LENGTH_SHORT).show();
}
}
}
package mohammad.samandari.notificationscheduler;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import androidx.core.app.NotificationCompat;
public class NotificationJobService extends JobService {
NotificationManager mNotificationManager;
private static final String PRIMARY_CHANNEL_ID = "primary_notification_channel";
private static final int NOTIFICATION_ID = 0;
@Override
public boolean onStartJob (JobParameters params) {
createNotificationChannel();
//Set up the notification content intent to launch the app when clicked
Intent notifyIntent = new Intent(this, MainActivity.class);
PendingIntent notifyPendingIntent = PendingIntent.getActivity(this, NOTIFICATION_ID, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT);
//construct and deliver a notification
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, PRIMARY_CHANNEL_ID)
.setContentTitle("Job Service")
.setContentText("Your Job is running!")
.setSmallIcon(R.drawable.ic_job_running)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(notifyPendingIntent)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setAutoCancel(true);
mNotificationManager.notify(NOTIFICATION_ID, mBuilder.build());
return false;
}
@Override
public boolean onStopJob (JobParameters params) {
//Make sure that onStopJob() returns true, because if the job fails, you want the job to be rescheduled instead of dropped.
return false;
}
private void createNotificationChannel () {
// Define notification manager object.
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Notification channels are only available in OREO and higher.
// So, add a check on SDK version.
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
// Create the NotificationChannel with all the parameters.
NotificationChannel notificationChannel = new NotificationChannel(PRIMARY_CHANNEL_ID, "Job Service Notification", NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableVibration(true);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.setDescription("Notifications from Job Service");
mNotificationManager.createNotificationChannel(notificationChannel);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment