版权声明:本文出自汪磊的博客,转载请务必注明出处。
一、JobScheduler概述
JobScheduler是安卓5.0版本推出的API,允许开发者在符合某些条件时创建执行在后台的任务。在Android开发中,会存在这些场景:你需要在稍后的某个时间点或者当满足某个特定的条件时执行一个任务,例如当设备接通电源适配器或者连接到WIFI,此时就可以使用JobScheduler了,当一系列预置的条件被满足时,JobScheduler API为你的应用执行一个操作。与AlarmManager不同的是这个执行时间是不确定的。除此之外,JobScheduler API允许同时执行多个任务。
JobSchedule的宗旨就是把一些不是特别紧急的任务放到更合适的时机批量处理。这样做有两个好处:避免频繁的唤醒硬件模块,造成不必要的电量消耗以及避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量。
JobSchedule适用版本为5.0及以上,目前还未发现兼容库支持。
二、JobScheduler使用
使用JobScheduler首先我们需要创建一个类继承自JobService且必须实现两个方法(JobService继承自Service),分别是onStartJob(JobParameters params)
和 onStopJob(JobParameters params),如下:
public class TestJobService extends JobService { @Override
public boolean onStartJob(JobParameters params) { return false;
} @Override
public boolean onStopJob(JobParameters params) { return false;
}
}
当任务开始时会执行onStartJob(JobParameters params)
方法,这是系统用来触发已经被执行的任务。这个方法返回一个boolean值。
如果返回值是false,系统假设这个方法返回时任务已经执行完毕。
如果返回值是true,那么系统假定这个任务正要被执行,执行任务的重担就落在了开发者的肩上,当任务执行完毕时开发者需要自己调用jobFinished(JobParameters params, boolean needsRescheduled)
来通知系统。
当系统接收到一个取消请求时,会调用onStopJob(JobParameters params)
方法取消正在等待执行的任务。很重要的一点是如果onStartJob(JobParameters params)
返回false,那么系统假定在接收到一个取消请求时已经没有正在运行的任务。onStopJob(JobParameters params)
在这种情况下不会被调用。
需要注意的是这个job service运行在你的主线程,这意味着你需要使用子线程,handler, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。我们可以在onStartJob
中利用Handler发送消息,在Handler中处理相关任务。
jobFinished(JobParameters params, boolean needsRescheduled)
的两个参数中的params参数是从JobService的onStartJob(JobParameters params)
的params传递过来的,needsRescheduled参数是告诉系统这个任务如果由于某些原因导致执行失败是否需要重新调度执行,true需要重新调度执行,false不需要。
最后我们需要到AndroidManifest.xml中添加一个service节点让你的应用拥有绑定和使用这个JobService的权限。
<service
android:name=".TestJobService"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
在创建完TestJobService后,我们就该创建JobScheduler对象了,创建JobScheduler对象需要通过getSystemService( Context.JOB_SCHEDULER_SERVICE )来
初始化:
JobScheduler tm =(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
接下来我们使用JobInfo.Builder
来构建一个JobInfo对象。JobInfo.Builder接收两个参数,第一个参数是你要运行的任务的标识符,简单理解为这个任务的ID就可以了,第二个是这个Service组件的类名。
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent);
mServiceComponent初始化如下:
mServiceComponent = new ComponentName(this, TestJobService.class);
builder允许你设置很多不同的选项来控制任务的执行,重要方法说明如下:
方法名 | 说明 |
setPeriodic(long intervalMillis) | 设置任务每隔intervalMillis运行一次 |
setMinimumLatency(long minLatencyMillis) | 这个函数能让你设置任务的延迟执行时间(单位是毫秒),这个函数与setPeriodic(long time) 方法不兼容,如果这两个方法同时调用了就会引起异常 |
setOverrideDeadline(long maxExecutionDelayMillis) | 这个方法让你可以设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。与setMinimumLatency(long time) 一样,这个方法也会与setPeriodic(long time) ,同时调用这两个方法会引发异常 |
setPersisted(boolean isPersisted) | 这个方法告诉系统当你的设备重启之后你的任务是否还要继续执行 |
setRequiredNetworkType(int networkType) | 这个方法让你这个任务只有在满足指定的网络条件时才会被执行。默认条件是JobInfo.NETWORK_TYPE_NONE,这意味着不管是否有网络这个任务都会被执行。另外两个可选类型,一种是JobInfo.NETWORK_TYPE_ANY,它表明需要任意一种网络才使得任务可以执行。另一种是JobInfo.NETWORK_TYPE_UNMETERED,它表示设备不是蜂窝网络( 比如在WIFI连接时 )时任务才会被执行 |
setRequiresCharging(boolean requiresCharging) | 这个方法告诉你的应用,只有当设备在充电时这个任务才会被执行 |
setRequiresDeviceIdle(boolean requiresDeviceIdle) | 这个方法告诉你的任务只有当用户没有在使用该设备且有一段时间没有使用时才会启动该任务 |
需要注意的是setRequiredNetworkType(int networkType)
, setRequiresCharging(boolean requireCharging)
and setRequiresDeviceIdle(boolean requireIdle)
者几个方法可能会使得你的任务无法执行,除非调用setOverrideDeadline(long time)
设置了最大延迟时间,使得你的任务在未满足条件的情况下也会被执行。一旦你预置的条件被设置,你就可以构建一个JobInfo对象,然后通过如下所示的代码将它发送到你的JobScheduler中
if( tm.schedule( builder.build() ) <= 0 ) {
//something wrong
}
schedule方法会返回一个整型。如果schedule方法失败了,它会返回一个小于0的错误码。否则返回我们在JobInfo.Builder中定义的标识id。
停止某个任务,可以调用JobScheduler对象的cancel(int jobId)
来实现;如果想取消所有的任务,可以调用JobScheduler对象的cancelAll()
来实现。
以上就是JobScheduler的介绍,是不是很简单,重要的是理解什么情况下使用JobScheduler。
三、JobScheduler实例举例
本Demo是安卓官方提供的一个例子,我们先看TestJobService类;
public class TestJobService extends JobService {
private static final String TAG = "SyncService"; @Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service created");
} @Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service destroyed");
} @Override
public int onStartCommand(Intent intent, int flags, int startId) {
Messenger callback = intent.getParcelableExtra("messenger");
Message m = Message.obtain();
m.what = MainActivity.MSG_SERVICE_OBJ;
m.obj = this;
try {
callback.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Error passing service object back to activity.");
}
return START_NOT_STICKY;
} @Override
public boolean onStartJob(JobParameters params) {
// We don't do any real 'work' in this sample app. All we'll
// do is track which jobs have landed on our service, and
// update the UI accordingly.
jobParamsMap.add(params);
if (mActivity != null) {
mActivity.onReceivedStartJob(params);
}
Log.i(TAG, "on start job: " + params.getJobId());
return true;
} @Override
public boolean onStopJob(JobParameters params) {
// Stop tracking these job parameters, as we've 'finished' executing.
jobParamsMap.remove(params);
if (mActivity != null) {
mActivity.onReceivedStopJob();
}
Log.i(TAG, "on stop job: " + params.getJobId());
return true;
} MainActivity mActivity;
private final LinkedList<JobParameters> jobParamsMap = new LinkedList<JobParameters>(); public void setUiCallback(MainActivity activity) {
mActivity = activity;
} /** Send job to the JobScheduler. */
public void scheduleJob(JobInfo t) {
Log.d(TAG, "Scheduling job");
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
if(tm.schedule(t)<=0){
Log.i(TAG, "something wrong");
}
} /**
* Not currently used, but as an exercise you can hook this
* up to a button in the UI to finish a job that has landed
* in onStartJob().
*/
public boolean callJobFinished() {
JobParameters params = jobParamsMap.poll();
if (params == null) {
return false;
} else {
jobFinished(params, false);
return true;
}
}
}
上面提到过JobService其实也是Service,同样具有相应生命周期方法。TestJobService先不做详细讲解,但是要看到这个类中封装了一些JobScheduler方法供外界调用。
接下来我们结合MainActivity类一起看,MainActivity源码如下:
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; public static final int MSG_UNCOLOUR_START = 0;
public static final int MSG_UNCOLOUR_STOP = 1;
public static final int MSG_SERVICE_OBJ = 2;
// UI fields.
int defaultColor;
int startJobColor;
int stopJobColor; private TextView mShowStartView;
private TextView mShowStopView;
private TextView mParamsTextView;
private EditText mDelayEditText;
private EditText mDeadlineEditText;
private RadioButton mWiFiConnectivityRadioButton;
private RadioButton mAnyConnectivityRadioButton;
private CheckBox mRequiresChargingCheckBox;
private CheckBox mRequiresIdleCheckbox;
ComponentName mServiceComponent;
TestJobService mTestService;
//
private static int kJobId = 0; @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_main);
Resources res = getResources();
defaultColor = res.getColor(R.color.none_received);
startJobColor = res.getColor(R.color.start_received);
stopJobColor = res.getColor(R.color.stop_received);
// Set up UI.
mShowStartView = (TextView) findViewById(R.id.onstart_textview);
mShowStopView = (TextView) findViewById(R.id.onstop_textview);
mParamsTextView = (TextView) findViewById(R.id.task_params);
mDelayEditText = (EditText) findViewById(R.id.delay_time);
mDeadlineEditText = (EditText) findViewById(R.id.deadline_time);
mWiFiConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_unmetered);
mAnyConnectivityRadioButton = (RadioButton) findViewById(R.id.checkbox_any);
mRequiresChargingCheckBox = (CheckBox) findViewById(R.id.checkbox_charging);
mRequiresIdleCheckbox = (CheckBox) findViewById(R.id.checkbox_idle);
mServiceComponent = new ComponentName(this, TestJobService.class);
// Start service and provide it a way to communicate with us.
Intent startServiceIntent = new Intent(this, TestJobService.class);
startServiceIntent.putExtra("messenger", new Messenger(mHandler));
startService(startServiceIntent);
} Handler mHandler = new Handler(/* default looper */) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_UNCOLOUR_START:
mShowStartView.setBackgroundColor(defaultColor);
break;
case MSG_UNCOLOUR_STOP:
mShowStopView.setBackgroundColor(defaultColor);
break;
case MSG_SERVICE_OBJ:
mTestService = (TestJobService) msg.obj;
mTestService.setUiCallback(MainActivity.this);
}
}
}; private boolean ensureTestService() {
if (mTestService == null) {
Toast.makeText(MainActivity.this, "Service null, never got callback?",
Toast.LENGTH_SHORT).show();
return false;
}
return true;
} /**
* UI onclick listener to schedule a job. What this job is is defined in
* TestJobService#scheduleJob().
*/
@SuppressLint("NewApi")
public void scheduleJob(View v) {
if (!ensureTestService()) {
return;
}
JobInfo.Builder builder = new JobInfo.Builder(kJobId++, mServiceComponent); String delay = mDelayEditText.getText().toString();
if (delay != null && !TextUtils.isEmpty(delay)) {
builder.setMinimumLatency(Long.valueOf(delay) * 1000);
}
String deadline = mDeadlineEditText.getText().toString();
if (deadline != null && !TextUtils.isEmpty(deadline)) {
builder.setOverrideDeadline(Long.valueOf(deadline) * 1000);
}
boolean requiresUnmetered = mWiFiConnectivityRadioButton.isChecked();
boolean requiresAnyConnectivity = mAnyConnectivityRadioButton.isChecked();
if (requiresUnmetered) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
} else if (requiresAnyConnectivity) {
builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
}
builder.setRequiresDeviceIdle(mRequiresIdleCheckbox.isChecked());
builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());
mTestService.scheduleJob(builder.build());
}
/**
* cancel All jobs
* @param v
*/
@SuppressLint("NewApi")
public void cancelAllJobs(View v) {
JobScheduler tm =
(JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.cancelAll();
} /**
* UI onclick listener to call jobFinished() in our service.
*/
public void finishJob(View v) {
if (!ensureTestService()) {
return;
}
mTestService.callJobFinished();
mParamsTextView.setText("");
} /**
* Receives callback from the service when a job has landed
* on the app. Colours the UI and post a message to
* uncolour it after a second.
*/
@SuppressLint("NewApi")
public void onReceivedStartJob(JobParameters params) {
mShowStartView.setBackgroundColor(startJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_START);
mHandler.sendMessageDelayed(m, 1000L); // uncolour in 1 second.
mParamsTextView.setText("Executing: " + params.getJobId() + " " + params.getExtras());
} /**
* Receives callback from the service when a job that
* previously landed on the app must stop executing.
* Colours the UI and post a message to uncolour it after a
* second.
*/
public void onReceivedStopJob() {
mShowStopView.setBackgroundColor(stopJobColor);
Message m = Message.obtain(mHandler, MSG_UNCOLOUR_STOP);
mHandler.sendMessageDelayed(m, 2000L); // uncolour in 1 second.
mParamsTextView.setText("");
} }
对于这个Demo我只想说一个点,MainActivity在启动的时候onCreate()方法中47-49行启动了TestJobService,并且传递了一个new Messenger(mHandler)匿名对象,接下来我们看下TestJobService中onStartCommand方法,此方法上来就获取Messenger对象,并且调用send方法发送一个消息(实际调用的就是Handler的send方法,可自行点进源码查看),此消息obj为TestJobService类实例对象。回到MainActivity中查看mHandler,what为MSG_SERVICE_OBJ的时候:将msg的obj赋值给MainActivity中mTestService变量,然后调用TestJobService中setUiCallback方法,这样MainActivity与TestJobService类就互相持有彼此实例对象了,也就可以调用其内部的方法互相通信了,比如TestJobService中onStartJob方法调用MainActivity中的onReceivedStartJob方法,onReceivedStartJob方法中其实就是发送了Handler消息,最后在Handler中执行相关业务逻辑。好了Demo就说这一点,其余请自行细细查看,很简单,最后附上Demo布局文件:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" > <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp">
<TextView
android:id="@+id/onstart_textview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#999999"
android:gravity="center"
android:text="@string/onstarttask"/>
<TextView
android:id="@+id/onstop_textview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/none_received"
android:gravity="center"
android:text="@string/onstoptask"/>
</LinearLayout>
<Button
android:id="@+id/finished_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="20dp"
android:layout_marginBottom="5dp"
android:onClick="finishJob"
android:text="@string/finish_job_button_text"/> <TextView
android:id="@+id/task_params"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/defaultparamtext"
android:gravity="center"
android:textSize="20dp"
android:padding="15dp"
android:layout_marginBottom="10dp" /> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/constraints"
android:layout_margin="15dp"
android:textSize="18dp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginLeft="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/connectivity"
android:layout_marginRight="10dp"/>
<RadioGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<RadioButton android:id="@+id/checkbox_any"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/any"
android:checked="false" />
<RadioButton android:id="@+id/checkbox_unmetered"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/unmetered"
android:checked="false" />
</RadioGroup> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/timing"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:textSize="17dp"
android:text="@string/delay"/>
<EditText
android:id="@+id/delay_time"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/deadline"
android:textSize="17dp"/>
<EditText
android:id="@+id/deadline_time"
android:layout_width="60dp"
android:layout_height="wrap_content"
android:inputType="number"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/charging_caption"
android:layout_marginRight="15dp"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkbox_charging"
android:text="@string/charging_text"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/idle_caption"
android:layout_marginRight="15dp"/>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/checkbox_idle"
android:text="@string/idle_mode_text"/>
</LinearLayout> </LinearLayout>
<Button
android:id="@+id/schedule_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:onClick="scheduleJob"
android:text="@string/schedule_job_button_text"/>
<Button
android:id="@+id/cancel_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="40dp"
android:layout_marginRight="40dp"
android:onClick="cancelAllJobs"
android:text="@string/cancel_all_jobs_button_text"/>
</LinearLayout>
</ScrollView>
好了,本篇到此结束,希望通过本篇介绍你能正确使用JobScheduler这个强大的工具,有些任务放在适合的时机来做更加适宜。