Android JobService的使用及源码分析
Google在Android 5.0中引入JobScheduler来执行一些需要满足特定条件但不紧急的后台任务,APP利用JobScheduler来执行这些特殊的后台任务时来减少电量的消耗。本文首先介绍JobSerice的使用方法,然后分析JobService的源码实现。
JobService的使用
使用JobScheduler的时候需要把待执行的后台任务封装到JobService中提交。下面就来介绍JobService的使用,首先看一下JobService是什么东东。
从上面的截图,可以看出JobService继承自Service,并且是一个抽象类。在JobService中有两个抽象方法onStartJob(JobParameters)和onStopJob(JobParameters)。onStartJob在JobService被调度到的时候会执行,我们只需要继承JobService然后重写onStartJob方法,并在里面执行我们的后台任务就可以了。
下面给出一个JobService的使用实例。
首先,定义一个JobService的子类,如:
public class MyJobService extends JobService {
public static final String TAG = MyJobService.class.getSimpleName();
@Override
public boolean onStartJob(JobParameters params) {
Log.i(TAG, "onStartJob:" + params.getJobId());
Toast.makeText(MyJobService.this, "start job:" + params.getJobId(), Toast.LENGTH_SHORT).show();
jobFinished(params, false);//任务执行完后记得调用jobFinsih通知系统释放相关资源
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
Log.i(TAG, "onStopJob:" + params.getJobId());
return false;
}
}
在MyJobService中,onStartJob里面的逻辑非常简单:弹出一个Toast。定义完JobService之后,剩下的工作就是提交Job了,这里我们在Activity中实现,用户点击button来提交任务。Activity的代码如下:
public class MainActivity extends Activity {
public static final String TAG = MainActivity.class.getSimpleName();
private int mJobId = ;
private EditText mDelayEditText;
private EditText mDeadlineEditText;
private RadioButton mWiFiConnectivityRadioButton;
private RadioButton mAnyConnectivityRadioButton;
private CheckBox mRequiresChargingCheckBox;
private CheckBox mRequiresIdleCheckbox;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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);
}
public void onBtnClick(View view) {
JobScheduler scheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
ComponentName componentName = new ComponentName(MainActivity.this, MyJobService.class);
JobInfo.Builder builder = new JobInfo.Builder(++mJobId, componentName);
String delay = mDelayEditText.getText().toString();
if (delay != null && !TextUtils.isEmpty(delay)) {
//设置JobService执行的最小延时时间
builder.setMinimumLatency(Long.valueOf(delay) * );
}
String deadline = mDeadlineEditText.getText().toString();
if (deadline != null && !TextUtils.isEmpty(deadline)) {
//设置JobService执行的最晚时间
builder.setOverrideDeadline(Long.valueOf(deadline) * );
}
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());//是否要求设备为idle状态
builder.setRequiresCharging(mRequiresChargingCheckBox.isChecked());//是否要设备为充电状态
scheduler.schedule(builder.build());
Log.i(TAG, "schedule job:" + mJobId);
}
//......
}
这里重点看一下26----55行,在button的单击事件响应中,先通过getSystemService拿到系统的JobScheduler,然后使用JobInfo.Buidler来构造一个后台任务,具体看28----55行。在设置后台任务的参数时,需要特别注意的是:以下五个约束条件我们需要至少指定其中的一个,否则调用JobInfo.Buidler的build方法时会抛异常,导致后台任务构造失败。五个约束条件如下:
1)最小延时
2)最晚执行时间
3)需要充电
4)需要设备为idle(空闲)状态(一般很难达到这个条件吧)
5)联网状态(NETWORK_TYPE_NONE--不需要网络,NETWORK_TYPE_ANY--任何可用网络,NETWORK_TYPE_UNMETERED--不按用量计费的网络)
其实仔细想一想也有道理,其实约束条件决定了JobService在什么时候执行,如果都没指定,系统就不知道在什么来执行我们的JobService了。如果我们的后台任务满足以上的一个或多个条件,就可以考虑是不是应该用JobService来执行。
运行效果如下:
JobService源码分析
JobService内部的运行机制究竟是怎样的?既然继承子Service,那么它至少要重写onStartCommand或者onBind。实际上JobService选择的是重写onBind。为什么使用bind方式呢?上面有提到,JobService是通过JobScheduler来调度,很明显这里会涉及到跨进程通信,如果使用AIDL(当然也可以使用Messenger)就可以很容易实现了。看一下源码:
/** @hide */
public final IBinder onBind(Intent intent) {
return mBinder.asBinder();
}
很明显,这里采用的是AIDL方式。在看一下mBinder的定义:
/** Binder for this service. */
IJobService mBinder = new IJobService.Stub() {
@Override
public void startJob(JobParameters jobParams) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_EXECUTE_JOB, jobParams);
m.sendToTarget();
}
@Override
public void stopJob(JobParameters jobParams) {
ensureHandler();
Message m = Message.obtain(mHandler, MSG_STOP_JOB, jobParams);
m.sendToTarget();
}
}; /** @hide */
void ensureHandler() {
synchronized (mHandlerLock) {
if (mHandler == null) {
mHandler = new JobHandler(getMainLooper());
}
}
}
从这里可以看到,JobService定义了一个IJobService接口,在这个接口里面定义了startJob和stopJob两个方法来让JobScheduler调度我们的后台任务的执行。这两个方法的实现也很简单,分别发送了MSG_EXECUTE_JOB和MSG_STOP_JOB两个Message。ensureHandler从名字上看,应该就是用来初始化一个Handler吧。看一下源码就知道了:
/** @hide */
void ensureHandler() {
synchronized (mHandlerLock) {
if (mHandler == null) {
mHandler = new JobHandler(getMainLooper());
}
}
}
从这里可以看到,在JobService里面定义了一个JobHandler。注意下这里使用的是getMainLooper(),因此,消息是在主线程中处理。继续看JobHandler是怎么处理这两个消息的:
class JobHandler extends Handler {
JobHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
final JobParameters params = (JobParameters) msg.obj;
switch (msg.what) {
case MSG_EXECUTE_JOB:
try {
boolean workOngoing = JobService.this.onStartJob(params);
ackStartMessage(params, workOngoing);
} catch (Exception e) {
Log.e(TAG, "Error while executing job: " + params.getJobId());
throw new RuntimeException(e);
}
break;
case MSG_STOP_JOB:
try {
boolean ret = JobService.this.onStopJob(params);
ackStopMessage(params, ret);
} catch (Exception e) {
Log.e(TAG, "Application unable to handle onStopJob.", e);
throw new RuntimeException(e);
}
break;
case MSG_JOB_FINISHED:
final boolean needsReschedule = (msg.arg2 == );
IJobCallback callback = params.getCallback();
if (callback != null) {
try {
callback.jobFinished(params.getJobId(), needsReschedule);
} catch (RemoteException e) {
Log.e(TAG, "Error reporting job finish to system: binder has gone" +
"away.");
}
} else {
Log.e(TAG, "finishJob() called for a nonexistent job id.");
}
break;
default:
Log.e(TAG, "Unrecognised message received.");
break;
}
}
......//省略部分代码
}
从源码中,可以很清楚的看到,在第10----18行,处理在startJob中发出的消息,这里会调用JobService.this.onStartJob(params)来执行任务,在第19----27调用JobService.this.onStopJob(params)来通知我们需要停止任务了。如果我们的后台任务需要在wifi可用的时候才执行的话,如果在任务执行的过程中wifi断开了,那么系统就调用onStopService来通知我们停止运行。
再次强调一下,JobService中的后台任务是在主线程中执行,这里一定不能执行耗时的任务。虽然在JobService中使用了Binder,但是最后还是通过Handler将任务调度到主线程中来执行。
在上面的例子用,有提到在JobInfo.Builder中配置JobService的时候需要指定至少一个约束(触发)条件,否则会抛出异常,这里我们也看一下JobInfo.Builder的build方法:
public JobInfo build() {
// Allow jobs with no constraints - What am I, a database?
if (!mHasEarlyConstraint && !mHasLateConstraint && !mRequiresCharging &&
!mRequiresDeviceIdle && mNetworkType == NETWORK_TYPE_NONE) {
throw new IllegalArgumentException("You're trying to build a job with no " +
"constraints, this is not allowed.");
}
mExtras = new PersistableBundle(mExtras); // Make our own copy.
// Check that a deadline was not set on a periodic job.
if (mIsPeriodic && (mMaxExecutionDelayMillis != 0L)) {
throw new IllegalArgumentException("Can't call setOverrideDeadline() on a " +
"periodic job.");
}
if (mIsPeriodic && (mMinLatencyMillis != 0L)) {
throw new IllegalArgumentException("Can't call setMinimumLatency() on a " +
"periodic job");
}
if (mBackoffPolicySet && mRequiresDeviceIdle) {
throw new IllegalArgumentException("An idle mode job will not respect any" +
" back-off policy, so calling setBackoffCriteria with" +
" setRequiresDeviceIdle is an error.");
}
return new JobInfo(this);
}
从第3----7行,可知,如果5个约束条件都没有指定的时候,会抛出IllegalArgumentException。其实仔细想一想也有道理,其实约束条件决定了JobService在什么时候执行,如果都没指定,系统就不知道在什么来执行我们的JobService了。
总结
最后,总结一下JobService的使用:
1)先继承JobService,并重写startJob和stopJob
2)在manifest.xml中声明JobService的时候,记得一定要加上
android:permission=”android.permission.BIND_JOB_SERVICE”
3)后台任务不能执行耗时任务,如果一定要这么做,一定要再起一个线程去做,使用 thread/handler/AsyncTask都可以。
4)JobService一定要设置至少一个执行条件,如有网络连接、充电中、系统空闲...
5)任务执行完后记得调用jobFinish通知系统释放相关资源
如果我们的后台任务满足JobService的一个或多个约束条件,就可以考虑是不是应该用JobService来执行。
Android JobService的使用及源码分析的更多相关文章
- Android Small插件化框架源码分析
Android Small插件化框架源码分析 目录 概述 Small如何使用 插件加载流程 待改进的地方 一.概述 Small是一个写得非常简洁的插件化框架,工程源码位置:https://github ...
- 【Android】Handler、Looper源码分析
一.前言 源码分析使用的版本是 4.4.2_r1. Handler和Looper的入门知识以及讲解可以参考我的另外一篇博客:Android Handler机制 简单而言:Handler和Looper是 ...
- Android View事件分发-从源码分析
View事件分发-从源码分析 学习自 <Android开发艺术探索> https://blog.csdn.net/qian520ao/article/details/78555397?lo ...
- Android线程间异步通信机制源码分析
本文首先从整体架构分析了Android整个线程间消息传递机制,然后从源码角度介绍了各个组件的作用和完成的任务.文中并未对基础概念进行介绍,关于threadLacal和垃圾回收等等机制请自行研究. 基础 ...
- Android服务之PackageManagerService启动源码分析
了解了Android系统的启动过程的读者应该知道,Android的所有Java服务都是通过SystemServer进程启动的,并且驻留在SystemServer进程中.SystemServer进程在启 ...
- Android 7.0 Gallery图库源码分析3 - 数据加载及显示流程
前面分析Gallery启动流程时,说了传给DataManager的data的key是AlbumSetPage.KEY_MEDIA_PATH,value值,是”/combo/{/local/all,/p ...
- Android图片处理神器BitmapFun源码分析
作为一名Android开发人员,相信大家对图片OOM的问题已经耳熟能详了,关于图片缓存和解决OOM的开源项目也是相当的多,被大家熟知的就是Universal_image_loader和Volley了, ...
- Android ViewManger解析 从ViewRoot 源码分析invalidate
转载请标明出处:http://blog.csdn.net/sk719887916/article/details/48443429,作者:skay 通过学习了AndroidUI之绘图机基础知道 ...
- android的消息处理机制(图文+源码分析)—Looper/Handler/Message[转]
from:http://www.jb51.net/article/33514.htm 作为一个大三的预备程序员,我学习android的一大乐趣是可以通过源码学习google大牛们的设计思想.andro ...
随机推荐
- 实际感受美丽的Linux(多组视频)
1.Fedora 上使用google earth 2.体验桌面-GNOME 2.2.9 3.体验桌面-KDE4.4 永中Office使用演示(和MS Office 2003很相似哦) 4.在Fedor ...
- ftp实现图片上传,文件也类似
本来用得是easyui得控件 点击按钮实现选择图片得 ,但是老板非得要双击图片框实现上传代码....做个简单得记录 前端代码: 首先,<form>表单要加上 enctype="m ...
- x64系统下,InpOutx64有数字签名而WinIO3.0无数字签名
参考文档 http://www.highrez.co.uk/Downloads/InpOut32/ //可以下载InpOutx64的驱动程序及DLL,还有驱动主板硬件IO的例程 https://www ...
- .net core 分布式性能计数器的实现
1.特别鸣谢张善友老师的指点; 2.分布式性能计数器链接地址:https://mp.weixin.qq.com/s/hPV_bNZD4XmjP0QTE54pWA
- Shell中反引号(`)与$()用法的区别
今天有人提问: echo `echo \\\\\\\w` echo $(echo \\\\\\\w) 为什么输出的不一样? 这就引申出了另一个问题:反引号与$()有没有区别? 这是一个非常有意思的问题 ...
- 82.管道实现cgi内存多线程查询
总体思路就是客户端写入要查询的数据到管道中,服务器端从管道读取,然后写入随机文件,再把文件名写入管道,然后客户端再读取文件 服务器端 设置缓冲区大写,设置管道名字,以及标识有多少个线程等 //设置缓存 ...
- Mahjong tree (hdu 5379 dfs)
Mahjong tree Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others) Tot ...
- angular6添加material-svgIcon
1. app/assets/util/util.svg.ts 统一管理svg字体库,避免各个模块分散加载.所以使用公共文件统一处理 再到core.module.ts中引入.在core模块下的所有组价都 ...
- Java与模式:装饰(Decorator)模式
装饰模式使用被装饰类的一个子类的实例.把client的调用委派到被装饰类,装饰模式的关键在于这样的扩展是全然透明的. 装饰模式在Java种使用也非常广泛,比方我们在又一次定义button.对话框等 ...
- 4、linux开发中常用指令
1.cat /proc/device 可以查看各个全部字符设备和块设备,在register_chrdev中设置的名字在打印出来的信息中可以看到:2.top 可以看各个应用程序占用CPU量及PID等信息 ...