什么是JobScheduler
老习惯,先附上API文档,API才是最权威的指导(不翻墙也可以正常访问)。
https://developer.android.google.cn/reference/android/app/job/JobScheduler.html
官方demo git地址:
https://github.com/googlesamples/android-JobScheduler
一.简介
JobScheduler来自API level 21,是5.0新出的一种后台数据处理模式,
在Android 7.0的新特性中,Google对于新设备功耗要求越来越严格,对于APP的限制也越来越多。
详情可以看我的另外一篇翻译Android7.0新特性:
(简书)
http://www.jianshu.com/p/822699607bc2
(优快云)
http://blog.youkuaiyun.com/aroundme/article/details/55002563
在第六点介绍使用Svelte项目优化后台的时候,提到了推荐使用JobScheduler来是吸纳调度作业,
并且希望将来所有应用都使用它来实现。
(然而对于国内市场情况来说并没有什么卵用)
二.应用场景
当你需要在Android设备满足某种场合才需要去执行处理数据,例如
* 应用具有您可以推迟的非面向用户的工作(定期数据库数据更新)
* 应用具有当插入设备时您希望优先执行的工作(充电时才希望执行的工作备份数据)
* 需要访问网络或 Wi-Fi 连接的任务(如向服务器拉取内置数据)
* 希望作为一个批次定期运行的许多任务
而使用JobScheduler可以很优雅的完成这些情况。
所以相比于其他方式,JobScheduler的好处是显而易见的。
* 避免频繁的唤醒硬件模块,造成不必要的电量消耗。
* 避免在不合适的时间(例如低电量情况下、弱网络或者移动网络情况下的)执行过多的任务消耗电量;
JobScheduler和Android 6.0出现的Doze都一样,总结来说就是限制应用频繁唤醒硬件,从而达到省电的效果。
然而心痛的是搜索引擎的结果:
作为一个开发者我也不想多说什么。
三.使用
大概介绍完之后,重点是如何使用JobScheduler来实现我们需要的功能。
1.创建JobService
首先需要自定义一个类来继承JobService,但是因为JobScheduler是要运行在API>21的情况下的,你需要设置点东西。
- 在build.gradle中设置minSdkVersion为21,但是这样太鸡肋了,难道还5.0以下都不匹配吗
- 使用注解不同版本采取不同策略,保证兼容。
重写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;
}
}
2.Activity中配置JobInfo
JobInfo是控制任务执行的方式,包括时间,延时,状态选择等。
具体API:https://developer.android.google.cn/reference/android/app/job/JobInfo.html
如果不想看可以看Demo中我使用中文注释应该稍微好理解点。
3.开启MyJobService
在官方Demo中,在onStart()方法中开启了Intent注册了这个JobScheduler。
然后就可以愉快的调用API玩耍了。
推荐运行官方的Demo,简单易懂
具体运行推荐下载Github上的Demo运行,跑跑看一目了然。
如果偷偷懒可以看看下面的逻辑层代码,已经写上注释能够更好理解。
Activity代码
import android.app.Activity;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.PersistableBundle;
import android.support.annotation.ColorRes;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.RadioButton;
import android.widget.TextView;
import android.widget.Toast;
import com.example.android.jobscheduler.service.MyJobService;
import java.lang.ref.WeakReference;
import java.util.List;
/**
* Schedules and configures jobs to be executed by a {@link JobScheduler}.
* <p>
* {@link MyJobService} can send messages to this via a {@link Messenger}
* that is sent in the Intent that starts the Service.
*
*
* 计划和配置要由{@link JobScheduler}执行的作业。
*
* {@link MyJobService}可以通过{@link Messenger}向其发送消息
* 在启动服务的Intent中发送。
*/
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
//消息
public static final int MSG_UNCOLOR_START = 0;
public static final int MSG_UNCOLOR_STOP = 1;
public static final int MSG_COLOR_START = 2;
public static final int MSG_COLOR_STOP = 3;
public static final String MESSENGER_INTENT_KEY
= BuildConfig.APPLICATION_ID + ".MESSENGER_INTENT_KEY";
public static final String WORK_DURATION_KEY =
BuildConfig.APPLICATION_ID + ".WORK_DURATION_KEY";
private EditText mDelayEditText;
private EditText mDeadlineEditText;
private EditText mDurationTimeEditText;
private RadioButton mWiFiConnectivityRadioButton;
private RadioButton mAnyConnectivityRadioButton;
private CheckBox mRequiresChargingCheckBox;
private CheckBox mRequiresIdleCheckbox;
private ComponentName mServiceComponent;
private int mJobId = 0;
// Handler for incoming messages from the service.
// 用于来自服务的传入消息的处理程序。
private IncomingMessageHandler mHandler;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_main);
// Set up UI.
// 设置UI
mDelayEditText = (EditText) findViewById(R.id.delay_time);
mDurationTimeEditText = (EditText) findViewById(R.id.duration_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, MyJobService.class);
mHandler = new IncomingMessageHandler(this);
}
@Override
protected void onStop() {
// A service can be "started" and/or "bound". In this case, it's "started" by this Activity
// and "bound" to the JobScheduler (also called "Scheduled" by the JobScheduler). This call
// to stopService() won't prevent scheduled jobs to be processed. However, failing
// to call stopService() would keep it alive indefinitely.
// 服务可以是“开始”和/或“绑定”。 在这种情况下,它由此Activity“启动”
// 和“绑定”到JobScheduler(也被JobScheduler称为“Scheduled”)。
// 对stopService()的调用不会阻止处理预定作业。
// 然而,调用stopService()失败将使它一直存活。
stopService(new Intent(this, MyJobService.class));
super.onStop();
}
@Override
protected void onStart() {
super.onStart();
// Start service and provide it a way to communicate with this class.
// 启动服务并提供一种与此类通信的方法。
Intent startServiceIntent = new Intent(this, MyJobService.class);
Messenger messengerIncoming = new Messenger(mHandler);
startServiceIntent.putExtra(MESSENGER_INTENT_KEY, messengerIncoming);
startService(startServiceIntent);
}
/**
* Executed when user clicks on SCHEDULE JOB.
*
* 当用户单击SCHEDULE JOB时执行。
*/
public void scheduleJob(View v) {
//开始配置JobInfo
JobInfo.Builder builder = new JobInfo.Builder(mJobId++, mServiceComponent);
//设置任务的延迟执行时间(单位是毫秒)
String delay = mDelayEditText.getText().toString();
if (!TextUtils.isEmpty(delay)) {
builder.setMinimumLatency(Long.valueOf(delay) * 1000);
}
//设置任务最晚的延迟时间。如果到了规定的时间时其他条件还未满足,你的任务也会被启动。
String deadline = mDeadlineEditText.getText().toString();
if (!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());
// Extras, work duration.
PersistableBundle extras = new PersistableBundle();
String workDuration = mDurationTimeEditText.getText().toString();
if (TextUtils.isEmpty(workDuration)) {
workDuration = "1";
}
extras.putLong(WORK_DURATION_KEY, Long.valueOf(workDuration) * 1000);
builder.setExtras(extras);
// Schedule job
Log.d(TAG, "Scheduling job");
JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.schedule(builder.build());
//tm.schedule(builder.build())会返回一个int类型的数据
//如果schedule方法失败了,它会返回一个小于0的错误码。否则它会返回我们在JobInfo.Builder中定义的标识id。
}
/**
* Executed when user clicks on CANCEL ALL.
*
* 当用户点击取消所有时执行
*/
public void cancelAllJobs(View v) {
JobScheduler tm = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
tm.cancelAll();
Toast.makeText(MainActivity.this, R.string.all_jobs_cancelled, Toast.LENGTH_SHORT).show();
}
/**
* Executed when user clicks on FINISH LAST TASK.
*
* 当用户点击取消上次任务时执行
*/
public void finishJob(View v) {
JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
List<JobInfo> allPendingJobs = jobScheduler.getAllPendingJobs();
if (allPendingJobs.size() > 0) {
// Finish the last one
int jobId = allPendingJobs.get(0).getId();
jobScheduler.cancel(jobId);
Toast.makeText(
MainActivity.this, String.format(getString(R.string.cancelled_job), jobId),
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(
MainActivity.this, getString(R.string.no_jobs_to_cancel),
Toast.LENGTH_SHORT).show();
}
}
/**
* A {@link Handler} allows you to send messages associated with a thread. A {@link Messenger}
* uses this handler to communicate from {@link MyJobService}. It's also used to make
* the start and stop views blink for a short period of time.
*
*
* {@link Handler}允许您发送与线程相关联的消息。
* {@link Messenger}使用此处理程序从{@link MyJobService}进行通信。
* 它也用于使开始和停止视图在短时间内闪烁。
*/
private static class IncomingMessageHandler extends Handler {
// Prevent possible leaks with a weak reference.
// 使用弱引用防止内存泄露
private WeakReference<MainActivity> mActivity;
IncomingMessageHandler(MainActivity activity) {
super(/* default looper */);
this.mActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity = mActivity.get();
if (mainActivity == null) {
// Activity is no longer available, exit.
// 活动不再可用,退出。
return;
}
View showStartView = mainActivity.findViewById(R.id.onstart_textview);
View showStopView = mainActivity.findViewById(R.id.onstop_textview);
Message m;
switch (msg.what) {
/*
* Receives callback from the service when a job has landed
* on the app. Turns on indicator and sends a message to turn it off after
* a second.
*
* 当作业登录到应用程序时,从服务接收回调。 打开指示灯(上方View闪烁)并发送一条消息,在一秒钟后将其关闭。
*/
case MSG_COLOR_START:
// Start received, turn on the indicator and show text.
// 开始接收,打开指示灯(上方View闪烁)并显示文字。
showStartView.setBackgroundColor(getColor(R.color.start_received));
updateParamsTextView(msg.obj, "started");
// Send message to turn it off after a second.
// 发送消息,一秒钟后关闭它。
m = Message.obtain(this, MSG_UNCOLOR_START);
sendMessageDelayed(m, 1000L);
break;
/*
* Receives callback from the service when a job that previously landed on the
* app must stop executing. Turns on indicator and sends a message to turn it
* off after two seconds.
*
* 当先前执行在应用程序中的作业必须停止执行时,
* 从服务接收回调。 打开指示灯并发送一条消息,
* 在两秒钟后将其关闭。
*
*/
case MSG_COLOR_STOP:
// Stop received, turn on the indicator and show text.
// 停止接收,打开指示灯并显示文本。
showStopView.setBackgroundColor(getColor(R.color.stop_received));
updateParamsTextView(msg.obj, "stopped");
// Send message to turn it off after a second.
// 发送消息,一秒钟后关闭它。
m = obtainMessage(MSG_UNCOLOR_STOP);
sendMessageDelayed(m, 2000L);
break;
case MSG_UNCOLOR_START:
showStartView.setBackgroundColor(getColor(R.color.none_received));
updateParamsTextView(null, "");
break;
case MSG_UNCOLOR_STOP:
showStopView.setBackgroundColor(getColor(R.color.none_received));
updateParamsTextView(null, "");
break;
}
}
/**
* 更新UI显示
* @param jobId jobId
* @param action 消息
*/
private void updateParamsTextView(@Nullable Object jobId, String action) {
TextView paramsTextView = (TextView) mActivity.get().findViewById(R.id.task_params);
if (jobId == null) {
paramsTextView.setText("");
return;
}
String jobIdText = String.valueOf(jobId);
paramsTextView.setText(String.format("Job ID %s %s", jobIdText, action));
}
private int getColor(@ColorRes int color) {
return mActivity.get().getResources().getColor(color);
}
}
}
JobService代码
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.util.Log;
import static com.example.android.jobscheduler.MainActivity.MESSENGER_INTENT_KEY;
import static com.example.android.jobscheduler.MainActivity.MSG_COLOR_START;
import static com.example.android.jobscheduler.MainActivity.MSG_COLOR_STOP;
import static com.example.android.jobscheduler.MainActivity.WORK_DURATION_KEY;
/**
* Service to handle callbacks from the JobScheduler. Requests scheduled with the JobScheduler
* ultimately land on this service's "onStartJob" method. It runs jobs for a specific amount of time
* and finishes them. It keeps the activity updated with changes via a Messenger.
*
*
* 服务处理来自JobScheduler的回调。 使用JobScheduler计划的请求最终将回调在此服务的“onStartJob”方法上。
* 它运行作业特定的时间并完成它们。 它通过Messenger使更改的活动更新。
*/
public class MyJobService extends JobService {
private static final String TAG = MyJobService.class.getSimpleName();
private Messenger mActivityMessenger;
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "Service created");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Service destroyed");
}
/**
* When the app's MainActivity is created, it starts this service. This is so that the
* activity and this service can communicate back and forth. See "setUiCallback()"
*
* 当应用程序的MainActivity被创建时,它启动这个服务。
* 这是为了使活动和此服务可以来回通信。 请参见“setUiCallback()”
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mActivityMessenger = intent.getParcelableExtra(MESSENGER_INTENT_KEY);
return START_NOT_STICKY;
}
@Override
public boolean onStartJob(final JobParameters params) {
// The work that this service "does" is simply wait for a certain duration and finish
// the job (on another thread).
// 该服务做的工作只是等待一定的持续时间并完成作业(在另一个线程上)。
sendMessage(MSG_COLOR_START, params.getJobId());
long duration = params.getExtras().getLong(WORK_DURATION_KEY);
// Uses a handler to delay the execution of jobFinished().
// 使用一个handler处理程序来延迟jobFinished()的执行。
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
sendMessage(MSG_COLOR_STOP, params.getJobId());
jobFinished(params, false);
}
}, duration);
Log.i(TAG, "on start job: " + params.getJobId());
// Return true as there's more work to be done with this job.
// 返回true,很多工作都会执行这个地方
return true;
}
@Override
public boolean onStopJob(JobParameters params) {
// Stop tracking these job parameters, as we've 'finished' executing.
// 停止跟踪这些作业参数,因为我们已经完成工作。
sendMessage(MSG_COLOR_STOP, params.getJobId());
Log.i(TAG, "on stop job: " + params.getJobId());
// Return false to drop the job.
// 返回false来销毁这个工作
return false;
}
private void sendMessage(int messageID, @Nullable Object params) {
// If this service is launched by the JobScheduler, there's no callback Messenger. It
// only exists when the MainActivity calls startService() with the callback in the Intent.
// 如果此服务由JobScheduler启动,则没有回调Messenger。
// 它仅在MainActivity在Intent中使用回调函数调用startService()时存在。
if (mActivityMessenger == null) {
Log.d(TAG, "Service is bound, not started. There's no callback to send a message to.");
return;
}
Message m = Message.obtain();
m.what = messageID;
m.obj = params;
try {
mActivityMessenger.send(m);
} catch (RemoteException e) {
Log.e(TAG, "Error passing service object back to activity.");
}
}
}
后话
吐槽吐槽自己的想法:
从前面两张图延伸过来的一些东西:
为什么我用中文搜索,往往排在第一位的总是防止APP被杀死?如何保活?如何唤醒?
甚至在各种问答上面,APP保活成了一个各路高手冥思苦想想要实现的手段。
平时使用Android手机的人也都知道,各种全家桶,各种唤醒,各种广告铺天盖地。卡,热,流量贵成为了Android用户避免不了的问题。
我想说:各个厂商面对这样大蛋糕不可能不想吃,但是也希望吃相好看点。
凭心而论,各个厂商花着高薪请来工作人员,前期投资这么多,努力把APP做好,优化完善,我们作为用户的也不能白白地使用,也得让厂商们享受回报,用心做的产品用户喜欢就会拥护。而一些厂商仗着名头来一套全家桶,后台全是你们家的东西,谁受得了?
在国外,Google Play市场一家独大,虽然也是一个垄断式的市场,但是制定了较为理智的策略。依托于Google Play的严格权限,大家都还是老老实实遵守规则。
但是在国内,混乱的市场没有统一的管理,更没有一个监督者来监督。就和自己家后花园一样,想要啥就要啥。
双重标准大行其道,国内外双重标准在内部并不少见。
近年来APP越来越多,质量也越来越好,优胜劣汰严重。
作为一个开发者也是一个用户来说,
用户会越来越理性,竞争也越来越强烈,希望APP做的越来越以人为本,希望厂商们静下心来搞好产品,希望国内Android市场越来越规范。
end