5.3.2 服务组件的生命周期

服务组件的使用方式可分为两种,分别是调用服务和绑定服务。在不同的使用方式下,服务组件的生命周期模型既有相同之处,也有不同之处。

如图5-10所示,不论在何种使用模式下,组件的生命周期都是从Service.onCreate函数开始,至Service.onDestroy函数结束。因此,在服务组件的开发中,开发者可以选择在Service.onCreate函数中做数据加载等初始化工作,而在Service.onDestroy函数中做数据销毁、线程终止等清理工作。

5.3.2 服务组件的生命周期 - 图1

图 5-10 界面组件的状态保存

在绑定模式下,当Service.onBind函数被调用时,说明服务组件已被前台界面组件绑定,服务组件应该根据调用者传递的Intent对象,在该函数内加载资源,构建通信对象,等待绑定者的调用。当界面组件完成相关操作,会解除与服务组件的绑定,此时,服务组件的Service.onUnbind函数会被调用,可以在该函数内做一些统计或资源清理工作。

被绑定服务组件的进程状态,与绑定该服务的界面组件密切相关。如果其绑定组件为前台界面组件,那么服务所处的进程即为前台进程;如果绑定该服务的界面组件是一个可视界面组件,那么服务所在的进程则为可视进程。

Android系统不会轻易回收前台进程或可视进程,因此,处于绑定状态的服务组件通常也不会被强制停止。对于开发者而言,绑定服务后一定不要忘记选择合适的时机解除绑定,否则将使服务组件停留在前台或可视状态无法回收,从而浪费系统资源。

在调用模式下,当服务组件执行Service.onStartCommand函数时[1],服务所在的进程为前台进程,拥有最高的优先级。当Service.onStartCommand函数执行完成后,如果没有显性调用Service.stopSelf等相关函数来停止服务,那么服务组件将会成为后台组件继续提供服务,直至调用Service.stopSelf函数显性停止,或者被系统强行回收。

当系统资源紧张时,后台服务组件会被系统强行终止回收,而此时,服务组件进行的操作可能尚未完成。从Android 2.0开始,系统提供了Service.onStartCommand函数,取代原有的Service.onStart函数。onStartCommand函数中增加了返回值和控制参数,用于指定后台服务组件的运行方式,其中最重要的返回值有三个。

❑Service. START_STICKY

如果Service.onStartCommand函数返回Service.START_STICKY,那么系统会对该服务组件“负责到底”,在强行回收该组件后,在资源宽裕的时候还会调用Service.onStartCommand函数去重新启动该服务。直到该服务组件执行完成所有操作后主动调用Service.stopSelf函数,才能最终关闭服务。

返回Service.START_STICKY的服务组件,是一个“不死”的组件。只要资源有空余,系统就会执行该服务,因此,此类服务往往与用户体验紧密结合。对于开发者而言,编写返回值为Service.START_STICKY的函数,一定要在合适的时机调用Service.stopSelf函数主动关闭服务,否则会无限期地消耗系统资源。

Android原生的音乐播放服务组件,在播放音乐时就是需要返回Service.START_STICKY来保持持续运行不被系统回收,使用户不会听着听着歌就没了:


//在消息循环中不断检查音乐播放状况,一旦发现音乐已经停止,就关闭服务

private Handler mDelayedStopHandler=new Handler(){

@Override

public void handleMessage(Message msg){

//检查音乐播放状况

if(!isPlaying()){

stopSelf();

}

}

};

public int onStartCommand(Intent intent, int flags, int startId){

//发送一个延迟消息,稍后检查音乐的播放状况

Message msg=mDelayedStopHandler.obtainMessage();

mDelayedStopHandler.sendMessageDelayed(msg, IDLE_DELAY);

//返回Service.START_STICKY,保持音乐播放器的运行状态

Return START_STICKY;

}


❑Service. START_NOT_STICKY

如果Service.onStartCommand函数返回Service.START_NOT_STICKY,则说明系统可以无条件地回收该组件,而无需关注服务是否完成,也不需要负责服务的重新启动。

通常,返回START_NOT_STICKY的服务,都是需要长时间运行服务组件,服务的时效性相对较低。为了在合适的时机重新启动服务,服务组件可以注册定时事件来保持长时间的运行。比如,一个后台邮件推送服务的示例如下:


/启动邮件推送服务PushService/

@Override

public int onStartCommand(Intent intent, int flags, int startId){

//开始监听新邮件

if(!wasStartListenMails()){

startListenMails();

}

//注册定时事件,每过3分钟自动唤醒服务,使得服务得以长期运行

final AlarmManager alarmManager=(AlarmManager)getSystemService(Context.

ALARM_SERVICE);

final PendingIntent weakupIntent=PendingIntent.getService(this,0,new

Intent(this, PushService.class),0);

alarmManager.set(

AlarmManager.ELAPSED_REALTIME_WAKEUP,

SystemClock.elapsedRealtime()+180000,

weakupIntent);

//返回

return START_NOT_STICKY;

}

/停止邮件推送服务/

@Override

public void onDestroy(){

//停止监听,释放占用的线程等资源

stopListenMails();

super.onDestroy();

}


对于后台服务而言,如果与用户体验没有非常密切的联系,最好采取上述模式来维持其运行,而不要通过返回Service.START_STICKY来强制系统恢复,以免消耗过多的系统资源。

❑Service. START_REDELIVER_INTENT

如果Service.onStartCommand函数返回了Service.START_REDELIVER_INTENT,则意味着需要保证该服务组件能够完整地处理每个Intent对象。如果在服务的后台处理过程中,系统强行终止了该服务,则要求系统在资源允许的情况下重新传入该Intent对象,直到服务组件处理完成,显性地调用Service.selfStop来关闭服务为止。

返回Service.START_REDELIVER_INTENT的服务,关注的是Intent对象中的请求和数据,确保它们不会丢失。比如,如果调用组件传递给服务组件的Intent对象包含一些关键数据(比如用户输入的帐号和密码等),需要服务组件进行处理并存储,那么服务组件就要保证能够完整地执行整个操作,否则就会导致数据的丢失。

如果返回Service.START_REDELIVER_INTENT的服务组件,在后台执行的过程中被系统回收,则系统会在不久后重新调用Service.onStartCommand函数,再次传入原来的Intent对象,同时将flags参数标记为Service.START_FLAG_REDELIVERY,以区别首次调用与重新调用。

Android原生的日历应用的提醒服务(AlertService),就是一个返回Service.START_REDELIVER_INTENT的服务组件。当用户日历上有事件触发时,日历应用会向提醒服务发送Intent,提醒用户有事件需要处理。为了确保用户在任何时候下都能够收到该事件提醒,就需要返回Service.START_REDELIVER_INTENT使得系统不会丢弃任何一个发往该服务的Intent请求:


mServiceHandler=new Handler{

@Override

public void handleMessage(Message msg){

//处理提醒消息,根据消息内容来提示用户

processMessage(msg);

//如果没有更多的消息需要处理,主动关闭服务

TryToStopService();

}

};

public int onStartCommand(Intent intent, int flags, int startId){

if(intent!=null){

//从Intent对象中提取需要提醒的内容,并发送到消息循环中

Message msg=mServiceHandler.obtainMessage();

msg.arg1=startId;

msg.obj=intent.getExtras();//需要提醒的内容

mServiceHandler.sendMessage(msg);

}

//返回Service.START_REDELIVER_INTENT,保证可靠性

return START_REDELIVER_INTENT;

}


[1]在2.0以上的版本中,onStart函数已被onStartCommand函数所取代。