简介
- 服务 是 Android 应用的后台。
-
服务 可以不依赖
Activity
独自生存。 - 直接使用
Service
创建的 服务 不会拥有 后台线程 ,但是大多数 服务 都需要 后台线程 来完成某些任务,因此需要手动创建 后台线程 ,而IntentService
又对这些操作进行了封装,所以对于大多数 服务 推荐IntentService
。 - 重启设备会使原来在运行的 服务 失效。
服务的生命周期回调方法
服务 生命周期回调方法会在 UI主线程 上运行。
startService()
启动的服务
-
onCreate(...)
: 服务 创建时调用。 -
onStartCommand(Intent, int, int)
: 服务 启动时调用。对于IntentService
,不应该重写此方法。- 参数说明:
-
Intent
:发送给 服务 的Intent
。 -
Intent
标识符集 :可以用来判断当前Intent
发送是一次重新发送,还是从来没有成功过的发送。 - 启动ID :可用于区分不同的命令。
-
- 返回 服务 的类型。有以下几种:
Service.START_NOT_STICKY
Service.START_REDELIVER_INTENTL
Service.START_STICKY
- 参数说明:
-
onDestroy()
: 服务 停止时调用。
bindService()
启动的服务
相比startService()
启动的 服务 ,增加了两个生命周期回调方法。
-
onBind(Intent)
:绑定 服务 时调用。 -
onUnbind(Intent)
: 服务 绑定终止时调用。
non-sticky服务与sticky服务
non-sticky服务
- non-sticky服务 在 服务 自己认为已完成任务时停止。
-
non-sticky服务 的
Service.onStartCommand(...)
会返回START_NOT_STICKY
或START_REDELIVER_INTENTL
。 - 可以通过调用
stopSelf()
或stopSelf(int)
方法,停止 服务 。区别:-
stopSelf()
:无条件方法,不管Service.onStartCommand(...)
调用多少次,该方法总是会成功停止 服务 。 -
stopSelf(int)
:有条件方法,需要来自Service.onStartCommand(...)
的 启动ID ,只有接收到最新的 启动ID ,该方法才会停止 服务 。
-
-
START_NOT_STICKY
和START_REDELIVER_INTENTL
的区别是:在因为系统资源吃紧关闭 服务 时,START_REDELIVER_INTENTL
会在资源不再吃紧时尝试再次启动 服务 。 -
IntentService
是一种 non-sticky服务 ,也是START_NOT_STICKY
类型,可以通过调用IntentService.setIntentRedelivery(true)
方法,改为START_REDELIVER_INTENTL
。
sticky服务
-
sticky服务 会持续运行,直到外部组件调用
Context.stopService(Intent)
方法。 -
sticky服务 的
Service.onStartCommand(...)
会返回START_STICKY
。 - 可传入一个
null
intent
给Service.onStartCommand(...)
方法实现 服务 重启。 - sticky服务 适合音乐播放器这种启动后一直保持运行状态,直到用户主动停止的 服务 。即使是这样也要考虑使用 non-sticky服务 替代,因为 sticky服务 的管理很不方便,难以判断 服务 是否已启动。
启动服务
1. startService()
启动
简介
-
startService()
启动的 服务 的生命周期:一旦 服务 启动了,即便 启动者 退出了, 服务 还会在后台长期的运行,直到外部调用stopService(...)
。 - 启动者 不能调用 服务 里面的方法。
简单示例
Intent i = PollService.newIntent(getActivity());
getActivity().startService(i);
getActivity().stopService(i);
2. bindService()
启动
简介
-
bindService()
启动的 服务 的生命周期: 服务 和 被绑定者 的生命绑定在一起,共存亡。 -
被绑定者 可以通过
Binder
对象调用 服务 里面的方法。
本地服务绑定简单示例
1. 定义ServiceConnection
接口对象
在Activity
或Fragment
中:
private PollService.MyBinder mMyBinder;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
// service为服务里面onBind(...)方法返回的对象。
mMyBinder = (PollService.MyBinder) service;
}
@Override
public void onServiceDisconnected(ComponentName name) { }
};
2. 定义和返回IBinder
对象
在Service
中:
// 返回IBinder对象
@Nullable
@Override
public IBinder onBind(Intent intent) {
return new MyBinder();
}
// 定义IBinder对象
public class MyBinder extends Binder {
public PollService getServeice() {
return PollService.this;
}
}
3. 绑定与解绑服务
在Activity
或Fragment
的onCreate(...)
中
Intent i = PollService.newIntent(getActivity());
getActivity().bindService(i, mServiceConnection, Context.BIND_AUTO_CREATE);
在Activity
或Fragment
的onDestroy(...)
中
getActivity().unbindService(mServiceConnection);
4. 通过IBinder
对象调用服务方法
在Activity
或Fragment
中
mMyBinder.getServeice().function();
远程服务绑定示例【Todo】
3. 定时器AlarmManager
启动
在Service
中:
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = PollService.newIntent(context);
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (isOn) {
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);
} else {
alarmManager.cancel(pi);
pi.cancel();
}
}
在Activity
或Fragment
中:
PollService.setServiceAlarm(getActivity(), true);
IntentService
简介
- 创建
Intent
,并封装为PendingIntent
,并发送给IntentService
来告知IntentService
处理 任务 。 - 在
onHandleIntent(Intent)
中获取传送过来的Intent
,并执行相关 任务 。 -
onHandleIntent(Intent)
位于 后台线程 。 - 任务 执行完毕后, 服务 也随之停止和销毁。
-
IntentService
是一种 non-sticky服务 ,也是START_NOT_STICKY
类型,可以通过调用IntentService.setIntentRedelivery(true)
方法,改为START_REDELIVER_INTENTL
。
简单示例
1. AndroidManifest.xml
<service android:name=".PollService"/>
2. 定义IntentService
public class PollService extends IntentService {
private static final String TAG = "PollService";
private static final long POLL_INTERVAL = AlarmManager.INTERVAL_FIFTEEN_MINUTES;
// 静态构造方法
public static Intent newIntent(Context context) {
return new Intent(context, PollService.class);
}
// 判断是否服务是否在运行
public static boolean isServiceAlarmOn(Context context) {
Intent i = PollService.newIntent(context);
PendingIntent pi = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_NO_CREATE);
return pi != null;
}
// 服务定时启动
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = PollService.newIntent(context);
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (isOn) {
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);
} else {
alarmManager.cancel(pi);
pi.cancel();
}
}
// 构造方法
public PollService() {
super(TAG);
}
// 【后台线程】
// 服务该要处理的任务
@Override
protected void onHandleIntent(Intent intent) {
// 先判断情形,如网络连接,失败重试,设备充电时等。
// Todo: 处理Intent。
}
}
3. 定时器AlarmManager
启动服务
在Service
中:
public static void setServiceAlarm(Context context, boolean isOn) {
Intent i = PollService.newIntent(context);
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
if (isOn) {
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);
} else {
alarmManager.cancel(pi);
pi.cancel();
}
}
在Activity
或Fragment
中:
PollService.setServiceAlarm(getActivity(), true);
JobService和JobScheduler
简介
- 节能 :把时间点相近的任务同时进行。
- 强大 :能在满足网络、电量、时间等一定预定条件下触发执行。
- 持续化 :可以在设备重启后继续运行。
简单示例
1. AndroidManifest.xml
<service
android:name=".service.MyJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="false"/>
-
android:permission
:设置启动这个 服务 的 启动者 需要有什么 权限 。 -
android:exported
:设置是否导出,使得其他应用也能调用这个 服务 。
2. 定义JobService
public class MyJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
// UI主线程
// Todo: 在此处创建后台线程执行任务
return false;
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}
-
onStartJob()
:- 任务 启动时被调用。
- 在 UI主线程 中执行,有可能会卡UI。
- 返回值:
-
false
:执行完毕。 -
true
:有其他 异步任务 没有执行完毕, 异步任务 执行完时需要手动调用jobFinished(JobParameters, boolean)
方法。
-
-
onStopJob()
:- 任务 因为 执行完毕 、 被取消 或者 没有满足运行条件 而被放弃时调用。
- 返回值:
-
false
:不重新计划 任务 。 -
true
:重新计划 任务 。
-
3. 构建JobInfo
简单示例
JobInfo
被用于配置JobService
,使得 任务 可以在满足一定条件下执行:
ComponentName jobService = new ComponentName(getActivity(), MyJobService.class.getName());
JobInfo jobInfo = new JobInfo.Builder(/* 保证为整个系统中唯一的ID */, jobService).setPeriodic(10000).build();
详解
-
setRequiredNetworkType(int)
: 联网状态 。-
NETWORK_TYPE_NONE
:默认值。对联网状态没有要求,离线也可以。 -
NETWORK_TYPE_ANY
:只要联网就可以。 -
NETWORK_TYPE_UNMETERED
:要求非付费网络。 -
NETWORK_TYPE_NOT_ROAMING
:要求非漫游网络。
-
-
setRequiresCharging(boolean)
:要求设备处于 充电状态 ,默认关闭。 -
setRequiresDeviceIdle(boolean)
:要求设备处于 闲置状态 ,默认关闭。 -
addTriggerContentUri(TriggerContentUri)
:使用ContentObserver
监听指定URI
,URI
内容发生变动时触发 任务 。 -
setTriggerContentUpdateDelay(long)
:在监听的URI
发生变动之后延迟一段时间才执行 任务 。 -
setTriggerContentMaxDelay(long)
:在监听的URI
发生变动后最多等待指定的时间,如果系统一直没有调度到这次变动的 任务 ,就在超时时强制执行。 -
setPeriodic(long interval)
:循环执行 任务 。 -
setPeriodic(long interval, long flexMillis)
:循环执行 任务 ,区别在于可以把 任务 执行的时间范围限制在指定时间段内的倒数flexMillis
毫秒里。相比起上面的方法,可以做到更接近定时器的效果。 -
setMinimumLatency(long)
:设置最小延时,满足条件后等待指定时间才执行 任务 。 -
setOverrideDeadline(long)
:设置等待超时,超时后即使任何条件都没满足也会执行 任务 。 -
setBackoffCriteria(long initialBackoffMillis, int backoffPolicy)
:设置 任务 执行失败时的重试策略。不能和setRequiresDeviceIdle(boolean)
同时设置。-
initialBackoffMillis
:间隔时间,默认30秒。 -
backoffPolicy
:默认为BACKOFF_POLICY_EXPONENTIAL
。-
BACKOFF_POLICY_LINEAR
:每次重试的间隔相同。 -
BACKOFF_POLICY_EXPONENTIAL
:每次重试的间隔都是上一次的两倍。
-
-
-
setExtras(PersistableBundle)
:携带额外信息,只允许携带基本类型。 -
setPersisted(boolean)
:是否在重启后继续执行,默认为false
。需要RECEIVE_BOOT_COMPLETED
权限。
4. 启动JobService
开启调用
JobScheduler jobScheduler = (JobScheduler) getActivity().getSystemService(Context.JOB_SCHEDULER_SERVICE);
jobScheduler.schedule(jobInfo);
取消调用
jobScheduler.cancel(/* 保证为整个系统中唯一的ID */);
jobScheduler.cancelAll();
PendingIntent
简介
-
PendingIntent
是一种token
对象。 -
PendingIntent
是对Intent
的封装,相比Intent
,不会立即被调用。 - 保存有当前 App 的
Context
,即便当前 App 已经消失,其他 App 也能通过PendingIntent
作为当前 App 调用被封装的Intent
。 - 同一个
Intent
获取两次PendingIntent
,得到的PendingIntent
会是同一个。
简单示例
Intent i = /* 服务Intent */
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
Intent i = /* Activity Intent */;
PendingIntent pi = PendingIntent.getActivity(context, 0, i, 0);
Intent i = /* Broadcast Intent */;
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
判断PendingIntent
是否存在
PendingIntent
也可以用作判断 服务 是否存在。
public static boolean isServiceAlarmOn(Context context) {
Intent i = PollService.newIntent(context);
// 若不使用FLAG_NO_CREATE标识,会创建PendingIntent对象。
PendingIntent pi = PendingIntent.getService(context, 0, i, PendingIntent.FLAG_NO_CREATE);
return pi != null;
}
AlarmManager
简介
-
AlarmManager
可用于重复定时启动PendingIntent
。
简单示例
1. 创建PendingIntent
Intent i = /* new Service Intent */;
PendingIntent pi = PendingIntent.getService(context, 0, i, 0);
2. 获取AlarmManager
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
3. 设置定时任务
- 优先采用
AlarmManager.setInexactRepeating(...)
,相对于AlarmManager.setRepeating(...)
不精确,但是节能。 -
AlarmManager.setInexactRepeating(...)
参数说明:- 1.时间基准值:
- 【推荐】
ELAPSED_REALTIME
:设备启动至今的时间,不依赖时钟时间。锁屏时不会触发。 -
ELAPSED_REALTIME_WAKEUP
:同上。锁屏时也会触发。 -
RTC
:时钟时间,使用 UTC时间 ,没有经过本地时间换算,要手动处理。锁屏时不会触发。 -
RTC_WAKEUP
:同上。锁屏时也会触发。
- 【推荐】
- 2.时间起始值,与时间基准值相关:
-
ELAPSED_REALTIME
或ELAPSED_REALTIME_WAKEUP
对应SystemClock.elapsedRealtime()
。 -
RTC
或RTC_WAKEUP
对应System.currentTimeMillis()
。
-
- 3.时间间隔,以毫秒为单位,定时器到达时间起始值+时间间隔时触发,若已经超过,则马上触发。有几个预定义值:
INTERVAL_FIFTEEN_MINUTES
INTERVAL_HALF_HOUR
INTERVAL_HOUR
INTERVAL_HALF_DAY
INTERVAL_DAY
- 4.
PendingIntent
对象。
- 1.时间基准值:
alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), POLL_INTERVAL, pi);
4. 关闭定时器
关闭定时器时通常需要同时关闭PendingIntent
对象。
alarmManager.cancel(pi);
pi.cancel();