第一章 Service介绍
service服务是一个应用程序的四大组件之一,可以再后台执行长时间运行的操作,不提供用户界面。一个应用程序组件可以启动一个服务,它将继续在后台运行,即使用户切到另一个应用程序。此外,一个组件可以绑定到一个服务与它交互,甚至执行进程间的通信(IPC)。
1.1 基础介绍
Service中比较重要的方法有以下几个:
-
onStartCommand()
当其他组件,如Activity请求服务启动的时候,系统会调用这个方法。一旦这个方法执行,服务就开始执行。如果实现这个方法,当服务完成任务后,需要你调用stopSelf()
或者stopService()
来停止服务。如果只想提供绑定,不需要自己实现这个方法。 -
onBind()
当有其他组件想通过bindService()
方法绑定这个服务时系统就会调用此方法。在实现的方法里面,必须添加一个供客户端使用的接口通过返回IBinder
来与服务通信,这个方法必须实现。当然想禁止绑定的话,返回null
即可。 -
onCreate()
服务第一次建立的时候会调用这个方法,执行一次性设置程序,在上面2个方法执行前调用。如果服务已存在,则不执行该方法。 -
onDestory()
服务不再使用则调用该方法。服务应该事先这个方法来清理诸如线程,注册的监听器等资源。这是最后调用的方法。
Android系统只会在内存占用很高,必须回复系统资源供当前运行程序的情况下强制停掉一个运行中的服务。如果服务绑定在当前的运行程序中,就几乎不会被kill,如果服务声明了在前台运行(其实在后台,只是给系统一个错误的信息来提高优先级),就几乎不会被kill。另外,如果一个服务正在运行,且运行了很久,系统就会根据运行时间把其排在后台任务列表的后面,则这个服务很容易被杀掉。根据onStartCommand()
的返回值设置,服务被杀掉后仍然可以再资源充足的条件下立即重启。
1.2启动服务的两种方式:
- started启动:
started
形式的服务是指当一个应用组件(比如activity
)通过startService()
方法开启服务。一旦开启,该服务就可以永久的在后台运行,哪怕开启它的组件被销毁掉。通常开启的服务执行一个单独的操作并且不向调用者返回一个结果。比如,从网络下载文件,当文件下载完成,服务就应该自己停止。
关闭服务则需要服务自己调用方法stopSelf()
或者由启动服务的地方调用stopService(Intent)
方法来关闭。 - Bound绑定:
bound
形式的服务是指一个应用组件通过调用bindService()
方法与服务绑定。一个绑定的服务提供一个接口,允许组件与服务交互,发送请求、获得结果、甚至进行进程间通信。一个绑定的服务只和与其绑定的组件同时运行。多个组件可以同时绑定到一个服务,当全部解除绑定后,服务就会被销毁。
虽然分为两类,但是一个服务可以同时使用这两种方式-使用started
永久运行,同时允许绑定。只要在服务中实现两个回调方法:onStartCommand()
允许组件开启服务,onBind()
允许绑定。
不论引用程序是怎么起服务的,任何应用程序都可以用这个服务。同样的,任何组件可以使用一个Activity
通过传递Intent
开启服务。你也可以在配置文件设置服务为私有来防止其他应用访问该服务。
注意:一个服务在进程中的主线程运行,服务不会自己创建线程和进程(除非特别指定或者开启一个线程)。这意味着,如果服务需要做一些频繁占用CPU的工作或者会发生阻塞的操作,需要在服务另外开启线程。
1.3 Service生命周期
- 启动服务:
startService()->onCreate()->onStartCommand()->running->stopService()/stopSelf()->onDestroy()->stopped
,其中,服务未运行时会调用一次onCreate()
,运行时不会调用。 - 绑定服务:
bindService()->onCreate()->onBind()->running->onUnbind()->onDestory()->stopped
。
在上面两个过程中,只有onStart
第二章 实现Service
实现服务有两种方式,继承service
或者IntentService
。后者是前者的子类。前者包含上一章节中Service的几个重要的方法,用于普通的服务。后者可以自己开一个工作线程,串行的处理多个请求。
2.1 继承Service
继承Service
就可以实现对请求多线程的处理,前面介绍了Service
的生命周期,可以按照生命周期实现方法,就不放示例了。
2.2 继承IntentService
大多数服务不需要同时处理多个请求,继承IntentService
是最好的选择。
IntentService处理流程:
- 创建按默认的一个
worker
线程处理传递给onStartCommand()
所有的intent
,在非UI线程中区工作。 - 创建一个工作队列依次传递一个
intent
到你实现的onHandleIntent()
方法,避免了多线程。 - 在所有启动请求被处理后自动关闭服务,不需要调用
stopSelf()
- 默认提供
onBind()
的实现,并返回null
- 默认提供
onStartCommand()
的实现,实现发送intent
到工作队列再到你的onHandleIntent()
方法实现。
这些都加入到IntentService
中了,你需要做的就是实现构造方法和onHandleIntent(),如下:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
long endTime = System.currentTimeMillis() + 5*1000;
while (System.currentTimeMillis() < endTime) {
synchronized (this) {
try {
wait(endTime - System.currentTimeMillis());
} catch (Exception e) {
}
}
}
}
}
第三章 如何保证服务不被杀死
在有些特定的情况下,服务需要保持开启不能被杀死。主要有以下几种方法保持Service不被杀死。
3.1 onStartCommand
方法中,返回START_STICKY
在StartCommand()
几个常量:
-
START_STICKY
系统重新创建服务并且调用onStartCommand()
方法,但并不会传递最后一次传递的intent
,只是传递一个空的intent
。除非存在将要传递来的intent
,那么就会传递这些intent
。这个适合播放器一类的服务,不需要执行命令,只需要独自运行,等待任务。 -
START_NOT_STICKY
系统不重新创建服务,除非有将要传递来的intent
。这是最安全的选项,可以避免在不必要的时候运行服务。 -
START_REDELIVER_INTENT
系统重新创建服务并且调用onStartCommand()
方法,传递最后一次传递的intent
。其余存在的需要传递的intent
会按顺序传递进来。这适合像下载一样的服务,立即恢复,积极执行。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
flags = START_STICKY;
return super.onStartCommand(intent, flags, startId);
}
手动返回START_STICKY,亲测当service因内存不足被kill,当内存又有的时候,service又被重新创建,比较不错,但是不能保证任何情况下都被重建,比如进程被干掉了....
3.2 提升Service优先级
前台服务是被认为用于已知的正在运行的服务,当系统需要释放内存时不会优先杀掉该进程。前台进程必须发一个notification
在状态栏中显示,知道进程被杀死。因为前台服务一直消耗一部分资源,但不像一般服务那样会在需要的时候被杀掉,所以为了节约资源,保护电池寿命,一定要在建前台服务的时候发送notification
,提示用户。当然系统提供的方法就必须有notification
参数的,所以不要想着怎么把notification
隐藏掉。
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// TODO Auto-generated method stub
Intent notificationIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
Notification noti = new Notification.Builder(this)
.setContentTitle("Title")
.setContentText("Message")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
startForeground(123456,noti);
return Service.START_STICKY;
}
startForeground()
方法就是将服务设置为前台服务,参数123456就是这个通知的唯一的id,只要不为0即可。
3.3 在onDestory()中发送广播开启自己
service+broadcast方式,就是当service调用到ondestory()
的时候,发送一个自定义的广播,当收到广播的时候,重新启动service;
// 这个是自定义的action
在service中的ondestroy()
时候:
@Override
public void onDestroy(){
stopForeground(true);
Intent intent = new Intent("com.example.demo.destroy");
sendBroadcast(intent);
super.onDestroy();
}
在MyReceiver中
public class MyReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals("com.example.demo.destroy")){
Intent sevice = new Intent(this, MyService.class);
this.startService(sevice);
}
}
}
当然,从理论上来讲这个方案是可行的,实验一下结果也是可行的。但是有些情况下,发送的广播在消息队列中排的靠后,就有可能服务还没有接收到广播就销毁了(只是猜想)。所以为了能让这个机制完美运行,可以开启两个服务,相互监听,相互启动。服务A监听B的广播来启动B,服务B监听A的广播来启动A。经过实验,这个方案是可行的。