开发时,会遇到这样的情况:想在将来的某个时间或在一定条件下运行任务,如上传图片、下载歌曲等操作想在接入电源或连接到WIFI时执行。Android L提供了JobScheduler API。
当一组预定义的条件得到满足时,JobScheduler API的应用程序执行一项操作。不像AlarmManager类,JobScheduler的时间测定是不准确的,但是AlarmManager手机关机后就被取消了,而JobScheduler可以设置关机重启后依然能运行。此外,该API能够一同批处理各种工作。
对比AlarmManager:时间比较准确,手机关机后就被取消了,无法再特定情况下执行,如没有网络去情况下,没必要执行唤醒,并且需要稍后再试。
JobScheduler:时间测定是不准确,可以设置关机重启后依然能运行,可以在特定情况下才触发,不需要重试。
google为啥要给出job方案,按照我们传统的做法,要保证任务的按时执行,需要后台常住:
1.使用fork进程创建子进程。
2.定时检测服务。
当一组预定义的条件得到满足时,JobScheduler API的应用程序执行一项操作。不像AlarmManager类,JobScheduler的时间测定是不准确的,但是AlarmManager手机关机后就被取消了,而JobScheduler可以设置关机重启后依然能运行。此外,该API能够一同批处理各种工作。
对比AlarmManager:时间比较准确,手机关机后就被取消了,无法再特定情况下执行,如没有网络去情况下,没必要执行唤醒,并且需要稍后再试。
JobScheduler:时间测定是不准确,可以设置关机重启后依然能运行,可以在特定情况下才触发,不需要重试。
google为啥要给出job方案,按照我们传统的做法,要保证任务的按时执行,需要后台常住:
1.使用fork进程创建子进程。
2.定时检测服务。
但是这些都很耗性能的。并且多个APP都采取这种方式,手机很卡。为了解决这个问题。google陆续提出了采用JobScheduler来接管这些所谓的后台服务Service。也就是说service以后会在后续的版本中逐渐的被移除。从5.0上看,我们看到了google移 除了httpclient,而采用了okhttp为主的通讯框架。在图片内存管理上,采取了LRU的管理方式进行管理。这些都为google的android用户体验以及开发人员的开发方式上逐渐形成了一个开发规范。
为此,google在全新一代操作系统上,采取了Job的方式,即每个需要后台的业务处理为一个job,通过系统管理job,来提高资源的利用率,从而提高性能,节省电源。这样又能满足APP开发商的要求,又能满足系统性能的要求。
JobScheduler是Android Lollipop新增的特性,用于定义满足某些条件下执行的任务。例如可用于当设备连接到充电器后,调度程序将唤醒那些需要处理器工作的程序,让他们进行工作,或者在设备连接至WiFi网络的时候上传下载照片,更新内容等。JobScheduler的优势相当巨大,可以帮助手机节省电量。
JobScheduler特性:
1、支持在一个任务上组合多个条件;
2、内置条件:设备待机、设备充电和连接网络;
3、支持持续的job,这意味着设备重启后,之前被中断的job可以继续执行;
4、支持设置job的最后执行期限
5、根据你的配置,可以设置job在后台运行还是在主线程中运行
可实现:后台数据上送,推送应用等。
适配:android 5.0
兼容5.0之前版本: android Trigger
作业调度在下列情况下非常有用:
* 应用具有您可以推迟的非面向用户的工作。
* 应用具有当插入设备时您希望优先执行的工作。
* 应用具有需要访问网络或 Wi-Fi 连接的任务。
* 应用具有您希望作为一个批次定期运行的许多任务。
使用 JobInfo.Builder 类配置调度的任务应当如何运行。您可以将任务调度为在特定的条件下运行,例如:
* 当设备充电时启动
* 当设备连接到不限流量网络时启动
* 当设备空闲时启动
* 在特定的截止期限之前或以最小的延迟完成
你可以按需求指定这一个或者多个限制,但调度程序只会当所以这些限制都满足的时候才会执行,除非是你设置了一个最后必须执行的时间,比如一个小时后,当这个时间到
达以后,它会覆盖现有的所有约束条件,去执行你的任务。
例如,您可以添加如下代码以在不限流量网络上运行您的任务:
JobInfo uploadTask = new JobInfo.Builder(mJobId,
mServiceComponent /* JobService component */)
.setRequiredNetworkCapabilities(JobInfo.NetworkType.UNMETERED)
.build();
JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(uploadTask);
如果设备具有稳定的电源(也就是说,它已插入了 2 分钟以上并且电池处于健康水平),则系统将运行任何已就绪可运行的已调度作业,即使作业的截止期限尚未到期也是如此。
命名一个service为JobSchedulerService ,并扩展jobservice 类,这就需要创建两个方法:onStartJob(JobParameters params) 和onStopJob(JobParameters params)。
public class JobSchedulerService 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, 或者一个异步任务来运行耗时的操作以防止阻塞主线程。因为多线程技术已经超出了我们这篇文章的范围,让我们简单实现一个Handlder来执行我们在JobSchedulerService定义的任务吧。
private Handler mJobHandler = new Handler( new Handler.Callback() {
@Override
public boolean handleMessage( Message msg ) {
Toast.makeText( getApplicationContext(),
"JobService task running", Toast.LENGTH_SHORT )
.show();
jobFinished( (JobParameters) msg.obj, false );
return true;
}
} );
在Handler中,你需要实现handleMessage(Message msg)方法来处理你的任务逻辑。在这个例子中,我们尽量保证例子简单,因此我们只在handleMessage(Message msg)中显示了一个Toast,这里就是你要写你的任务逻辑( 耗时操作 )的地方,比如同步数据等。
当任务执行完毕之后,你需要调用jobFinished(JobParameters params, boolean needsRescheduled)来让系统知道这个任务已经结束,系统可以将下一个任务添加到队列中。如果你没有调用jobFinished(JobParameters params, boolean needsRescheduled),你的任务只会执行一次,而应用中的其他任务就不会被执行。
jobFinished(JobParameters params, boolean needsRescheduled)的两个参数中的params参数是从JobService的onStartJob(JobParameters params)的params传递过来的,needsRescheduled参数是让系统知道这个任务是否应该在最初的条件下被重复执行。这个boolean值很有用,因为它指明了你如何处理由于其他原因导致任务执行失败的情况,例如一个失败的网络请求调用。
创建了Handler实例之后,你就可以实现onStartJob(JobParameters params) 和onStopJob(JobParameters params)方法来控制你的任务了。你可能已经注意到在下面的代码片段中onStartJob(JobParameters params)返回了true。这是因为你要通过Handler实例来控制你的操作,
这意味着Handler的handleMessage方法的执行时间可能比onStartJob(JobParameters params)更长。返回true,你会让系统知道你会手动地调用jobFinished(JobParameters params, boolean needsRescheduled)方法。
你也会注意到,数字1被传递到Handler示例。这是相关引用工作中你将用到的标识符。
@Override
public boolean onStartJob(JobParameters params) {
mJobHandler.sendMessage( Message.obtain( mJobHandler, 1, params ) );
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
mJobHandler.removeMessages( 1 );
return false;
}
简单的案例:
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class StepCounterJobService extends JobService {
@Override
public boolean onStartJob(JobParameters jobParameters) {
new StepCounterTask(this).execute(jobParameters);
return true;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
private static class StepCounterTask extends AsyncTask<JobParameters, Void, JobParameters> {
private final JobService mJobService;
public StepCounterTask(JobService jobService) {
this.mJobService = jobService;
}
@Override
protected JobParameters doInBackground(JobParameters... jobParameterses) {
return jobParameterses[0];
}
@Override
protected void onPostExecute(JobParameters jobParameters) {
//do something
mJobService.jobFinished(jobParameters, false);
}
}
}
一旦你使用Java部分的JobSchedulerService类完成,你需要研究AndroidManifest.xml,并增加服务节点,因此,你的应用程序有权限绑定和使用JobService类。
<service android:name=".JobSchedulerService"
android:permission="android.permission.BIND_JOB_SERVICE" />
随着JobSchedulerService构建完毕,我们可以开始研究你的应用如何与JobScheduler API进行交互了。第一件要做的事就是你需要创建一个JobScheduler对象,在实例代码的MainActivity中我们通过getSystemService( Context.JOB_SCHEDULER_SERVICE )初始化了一个叫做mJobScheduler的JobScheduler对象。
mJobScheduler = (JobScheduler)
getSystemService( Context.JOB_SCHEDULER_SERVICE );
当你想创建你的计划任务时,你可以使用来JobInfo.Builder创建一个JobInfo对象,字符指针传递到你的服务中。为了创建一个JobInfo对象,JobInfo.Builder 接受两个参数。 第一个参数是你要运行的任务的标识符,第二个是这个Service组件的类名即是你将与JobScheduler API.一同使用的服务的组件名称。
JobInfo.Builder builder = new JobInfo.Builder( 1,
new ComponentName( getPackageName(),
JobSchedulerService.class.getName() ) );
当你的工作将执行时,这个设置可以让你设置许多不同的控制选项。下面的代码片段显示了你如何设置你的任务每三秒定期运行一次。
builder.setPeriodic( 3000 );
如:
JobScheduler mJobScheduler = (JobScheduler)getSystemService(Context.JOB_SCHEDULER_SERVICE);
// 注销之前的job
mJobScheduler.cancel(Constants.JOB_ID_STEPCOUNTER);
JobInfo.Builder jobBuilder = new JobInfo.Builder(Constants.JOB_ID_STEPCOUNTER, new ComponentName(DeviceInfoUtil.getPackageName(), StepCounterJobService.class.getName()));
jobBuilder.setPeriodic(FLUSH_INTERVAL);
// 设置手机重启后依旧执行任务
jobBuilder.setPersisted(true);
mJobScheduler.schedule(jobBuilder.build());
其他设置方法 :
* 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( mJobScheduler.schedule( builder.build() ) <= 0 ) {
//If something goes wrong
}
你会注意到,mJobScheduler.schedule操作返回一个整数。如果schedule 失败,它会返回一个小于0的错误码。否则它会是我们在JobInfo.Builder中定义的标识id。
如果你的应用程序需要你停止特定或所有工作,你可以通过对JobScheduler 对象调用cancel(int jobId)或cancelAll()实现。
mJobScheduler.cancelAll();
你现在应该能够使用JobScheduler API以及你自己的应用程序进行批处理工作和运行后台操作。
JobService:
1. public class MyJobService extends JobService {
2. private static final String TAG = "MyJobService";
3.
4. /**
5. * false: 系统假设任何任务运行不需要很长时间并且到方法返回时已经完成。
6. * true: 系统假设任务是需要一些时间并且当任务完成时需要手动调用jobFinished()告知系统。
7. */
8. @Override
9. public boolean onStartJob(JobParameters params) {
10. Log.i(TAG, "Totally and completely working on job " + params.getJobId());
11. if (isNetworkConnected()) {
12. new SimpleDownloadTask().execute(params);
13. return true;
14. } else {
15. Log.i(TAG, "No connection on job " + params.getJobId() + "; sad face");
16. }
17. return false;
18. }
19.
20. /**
21. * 当收到取消请求时,该方法是系统用来取消挂起的任务的。
22. * 如果onStartJob()返回false,则系统会假设没有当前运行的任务,故不会调用该方法。
23. */
24. @Override
25. public boolean onStopJob(JobParameters params) {
26. Log.i(TAG, "stop job " + params.getJobId());
27. return false;
28. }
29.
30. private boolean isNetworkConnected() {
31. ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
32. NetworkInfo info = manager.getActiveNetworkInfo();
33. return (info != null && info.isConnected());
34. }
35.
36. private class SimpleDownloadTask extends
37. AsyncTask<JobParameters, Void, String> {
38.
39. private JobParameters mJobParam;
40.
41. @Override
42. protected String doInBackground(JobParameters... params) {
43. mJobParam = params[0];
44. try {
45. InputStream is = null;
46. int len = 50;
47. URL url = new URL("http://www.baidu.com");
48. HttpURLConnection conn = (HttpURLConnection) url
49. .openConnection();
50. conn.setReadTimeout(10000);
51. conn.setConnectTimeout(15000);
52. conn.setRequestMethod("GET");
53. conn.connect();
54. int responseCode = conn.getResponseCode();
55. Log.i(TAG, "response code is : " + responseCode);
56. is = conn.getInputStream();
57. Reader reader = null;
58. reader = new InputStreamReader(is, "UTF-8");
59. char[] buffer = new char[len];
60. reader.read(buffer);
61. return new String(buffer);
62. } catch (Exception e) {
63. return "unable to retrieve web page";
64. }
65. }
66.
67. @Override
68. protected void onPostExecute(String result) {
69. jobFinished(mJobParam, false);
70. Log.i(TAG, "获取结果:" + result);
71. }
72. }
73. }
调用:
1. public class MainActivity extends Activity {
2.
3. private TextView result;
4. private ComponentName jobService;
5.
6. @Override
7. protected void onCreate(Bundle savedInstanceState) {
8. super.onCreate(savedInstanceState);
9. setContentView(R.layout.activity_main);
10. jobService = new ComponentName(this, MyJobService.class);
11. Intent service = new Intent(this, MyJobService.class);
12. startService(service);
13.
14. result = (TextView) findViewById(R.id.result_tv);
15. Button btn = (Button) findViewById(R.id.button);
16. btn.setOnClickListener(new OnClickListener() {
17.
18. @Override
19. public void onClick(View v) {
20. pollServer();
21. }
22. });
23. }
24.
25. private void pollServer() {
26. JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
27. int jobId;
28. for (int i = 0; i < 10; i++) {
29. jobId = i;
30. JobInfo jobInfo = new JobInfo.Builder(jobId, jobService)
31. .setMinimumLatency(5000)// 设置任务运行最少延迟时间
32. .setOverrideDeadline(60000)// 设置deadline,若到期还没有达到规定的条件则会开始执行
33. .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED)// 设置网络条件
34. .setRequiresCharging(true)// 设置是否充电的条件
35. .setRequiresDeviceIdle(false)// 设置手机是否空闲的条件
36. .build();
37. result.append("scheduling job " + i + "!\n");
38. scheduler.schedule(jobInfo);
39. }
40. }
41.
42. }
注册:
1. <service
2. android:name="com.example.jobschedulerdemo.MyJobService"
3. android:permission="android.permission.BIND_JOB_SERVICE" >
4. </service>
如果应用程序需要停止特定或所有工作,可以通过对JobScheduler对象调用cancel(int jobId)或cancelAll()实现。
主配置文件:
1. <?xml version="1.0" encoding="utf-8"?>
2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3. package="com.jobschedule.durian.durianschedule" >
4.
5. <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
6. <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
7. <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
8. <uses-permission android:name="android.permission.INTERNET" />
9.
10. <application
11. android:allowBackup="true"
12. android:icon="@drawable/ic_launcher"
13. android:label="@string/app_name"
14. android:theme="@style/AppTheme" >
15. <activity
16. android:name=".DurianMainActivity"
17. android:label="@string/app_name" >
18. <intent-filter>
19. <action android:name="android.intent.action.MAIN" />
20.
21. <category android:name="android.intent.category.LAUNCHER" />
22. </intent-filter>
23. </activity>
24.
25. <service
26. android:name=".MyJobService"
27. android:enabled="true"
28. android:exported="true"
29. android:permission="android.permission.BIND_JOB_SERVICE"/>
30. </application>
31.
32. </manifest>
运行之前建议先将设备连接到网络.
运行程序后,通过for循环将work全部先加入计划表中,然后服务根据时间限制,再对要求的条件进行判断,如果在限定的时间内,条件也会满足,服务就会向百度发送异步请求.
这种工作处理方式可以很大程度上改善APP的性能,不需要APP一直在后台运行"死守",这里面只需要规定时间内条件满足以后才会触发程序执行.相比定时器Timer,这个更加人性化,对性能优化更加充分.
接下来介绍一个利用JobScheduler保活的方法
就是将JobService运行在想保护的进程,里面不需要运行任何代码,这样每当系统执行job的时候会唤起想要保护的进程,这样在5.0以上的系统基本上是杀不死的。
代码如下
public class JobServiceUtil {
private static final String TAG = "JobServiceUtil";
private static final int INTERVALMILLS = 2 * 60 * 1000; // 2分钟
private JobScheduler mJobScheduler;
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public void startJobServiceForKeepLive() {
mJobScheduler = (JobScheduler) Util.context().getSystemService(Context.JOB_SCHEDULER_SERVICE);
// 注销之前的job
mJobScheduler.cancel(Constants.JOB_ID_KEEPLIVE);
JobInfo.Builder builder = new JobInfo.Builder(Constants.JOB_ID_KEEPLIVE, new ComponentName(DeviceInfoUtil.getPackageName(), MyJobService.class.getName()));
if (DeviceInfoUtil.isHuawei() || DeviceInfoUtil.isVivo()) {
// 华为和vivo手机5秒,解决后台保护和降频问题
builder.setPeriodic(5000);
} else {
builder.setPeriodic(INTERVALMILLS);
}
builder.setPersisted(true);
mJobScheduler.schedule(builder.build());
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void cancelJobSchedule() {
if (mJobScheduler != null)
mJobScheduler.cancel(Constants.JOB_ID_KEEPLIVE);
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public class MyJobService extends JobService {
private static final String TAG = "MyJobService";
@Override
public boolean onStartJob(JobParameters params) {
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}