前言
- Service 是一个可以在后台执行长时间运行操作而不提供用户界面的应用组件。
- Service 可由其他应用组件启动,而且即使用户切换到其他应用,Service 仍将在后台继续运行。 此外,组件可以绑定到 Service,以与之进行交互,甚至是执行进程间通信 (IPC)。
- Service 可以处理网络事务、播放音乐,执行文件 I/O 或与内容提供程序交互,而所有这一切均可在后台进行。
- Service 是运行在主线程里的,也就是说如果你在 Service 里编写了非常耗时的代码,程序必定会出现ANR的。
- 不要把后台和子线程联系在一起,这是两个完全不同的概念。
- Android的后台就是指,它的运行是完全不依赖UI的,即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与 Service 器之间始终保持着心跳连接,就可以使用Service来实现。对于耗时操作可以在 Service 中再创建一个子线程,然后在这里去处理耗时逻辑。
- 既然在 Service 里也要创建一个子线程,那为什么不直接在 Activity 里创建呢?这是因为Activity很难对 Thread 进行控制,当 Activity 被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity 中创建的子线程,另一个 Activity 无法对其进行操作。但是 Service 就不同了,所有的 Activity 都可以与Service 进行关联,然后可以很方便地操作其中的方法,即使 Activity 被销毁了,之后只要重新与 Service 建立关联,就又能够获取到原有的 Service 中 Binder 的实例。因此,使用 Service 来处理后台任务,Activity 就可以放心地
finish()
,完全不需要担心无法对后台任务进行控制的情况。 - Thread 是程序执行的最小单元,它是分配CPU的基本单位,可以用 Thread 来执行一些异步的操作。
一、Service有两种状态
“启动” 状态、“绑定” 状态。
startService()
当应用组件(如 Activity)通过调用 startService()
启动 Service 时, Service 即处于“启动”状态。
一旦启动, Service 即可在后台无限期运行,即使启动 Service 的组件已被销毁也不受影响。
已启动的 Service 通常是执行单一操作,而且不会将结果返回给调用方。例如,它可能通过网络下载或上传文件。 操作完成后, Service 会自行停止运行。
如果 Service 同时处理多个 onStartCommand()
请求,则您不应在处理完一个启动请求之后停止 Service ,因为您可能已经收到了新的启动请求(在第一个请求结束时停止 Service 会终止第二个请求)。为了避免这一问题,您可以使用 stopSelf(int)
确保 Service 停止请求始终基于最近的启动请求。也就说,在调用 stopSelf(int)
时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 onStartCommand(int startId)
的 startId
)。然后,如果在您能够调用 stopSelf(int)
之前 Service 收到了新的启动请求,ID 就不匹配, Service 也就不会停止。
bindService()
当应用组件通过调用 bindService()
绑定到 Service 时, Service 即处于“绑定”状态。
绑定 Service 提供了一个 客户端-Service
接口(ServiceConnection),允许组件与 Service 进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。
仅当与另一个应用组件绑定时,绑定 Service 才会运行。 多个组件可以同时绑定到该 Service ,但全部取消绑定后,该 Service 即会被销毁。
注意事项
为了确保应用的安全性,启动 Service 时,请始终使用显式 Intent,且不要为 Service 声明 Intent 过滤器。使用隐式 Intent 启动 Service 存在安全隐患,因为您无法确定哪些 Service 将响应 Intent,且用户无法看到哪些 Service 已启动。从 Android 5.0
(API 级别 21)开始,如果使用隐式 Intent 调用 bindService()
,系统会引发异常。
这两种状态并非完全独立。也就是说,可以绑定到已经使用 startService()
启动的 Service 。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService()
来启动后台音乐 Service 。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService()
绑定到 Service 。在这种情况下,除非所有客户端均取消绑定,否则 stopService()
或 stopSelf()
不会实际停止 Service 。
二、生命周期
如何使用Service
创建 Service 的子类(或使用它的一个现有子类,如 IntentService)。
在实现中,需要重写一些回调方法,以处理 Service 生命周期的某些关键方面并提供一种机制将组件绑定到 Service (如适用)。
生命周期方法
生命周期方法 | 描述 |
---|---|
onCreate() |
首次创建 Service 时,系统将调用此方法来执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果 Service 已在运行,则不会调用此方法。 |
onStartCommand() |
当另一个组件(如 Activity)通过调用 startService() 请求启动 Service 时,系统将调用此方法。一旦执行此方法, Service 即会启动并可在后台无限期运行。 如果您实现此方法,则在 Service 工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止 Service 。(如果您只想提供绑定,则无需实现此方法。) |
onBind() |
当另一个组件想通过调用 bindService() 与 Service 绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与 Service 进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null 。 |
onRebind() |
在onUnbind() 之后再次调用bindService() 时,执行该方法而不是执行onBind() 方法。 |
onUnbind() |
当最后一个绑定的组件解绑时或者调用unbindService() 方法时,执行该方法。注意该方法返回值默认为false ,表示不允许重复绑定。如果返回false ,再次执行bindService() 依然能够成功,但是unbindService() 就无效果了,所以如果想允许重复绑定一定要返回true
|
onDestroy() |
当 Service 不再使用且将被销毁时,系统将调用此方法。 Service 应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是 Service 接收的最后一个调用。 |
onStartCommand()
返回值
-
START_STICKY
,如果 service 进程被 kill 掉,可恢复但不保留递送的 intent 对象。kill 掉以后系统会尝试重新创建 service。创建后会执行该方法onStartCommand(Intent,int,int)
。如果在此期间没有任何启动命令被传递到service,那么参数 Intent 将为null
。用startService(intent)
传递命令参数。api5包括api5之后默认类型。 -
START_NOT_STICKY
,使用这个返回值时,如果在执行完onStartCommand()
后, Service 被异常kill掉,系统不会自动重启该 Service 。 -
START_REDELIVER_INTENT
,重传 Intent。使用这个返回值时,如果在执行完onStartCommand()
后, Service 被异常kill掉,系统会自动重启该 Service ,并将Intent的值重新传入。 -
START_STICKY_COMPATIBILITY
,START_STICKY
的兼容版本,但不保证 Service 被kill后一定能重启。api5之前默认类型,现在基本没用了
四、IntentService
Service
这是适用于所有 Service 的基类。扩展此类时,必须创建一个用于执行所有 Service 工作的新线程,因为默认情况下, Service 将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。
IntentService
这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求 Service 同时处理多个请求,这是最好的选择。 您只需实现 onHandleIntent()
方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。
IntentService已经做了如下工作:
- 创建默认的工作线程,用于在应用的主线程外执行传递给
onStartCommand()
的所有Intent
。 - 创建工作队列,用于将
Intent
逐一传递给onHandleIntent()
实现,这样您就永远不必担心多线程问题。 - 在处理完所有启动请求后停止 Service ,因此您永远不必调用
stopSelf()
。 - 提供
onBind()
的默认实现(返回null
)。 - 提供
onStartCommand()
的默认实现,可将Intent
依次发送到工作队列和onHandleIntent()
实现。
五、Service 与 Activity交互
启动服务状态可以通过广播的形式与Activity交互。
绑定服务通过 IBinder 接口方式交互。
要提供服务绑定,必须实现 onBind()
回调方法。该方法返回的 IBinder 对象定义了客户端用来与服务进行交互的编程接口。可以通过三种方法定义接口:
继承 Binder 类
如果服务是供您的自有应用专用,并且在与客户端相同的进程中运行(常见情况),则应通过继承 Binder 类并从 onBind() 返回它的一个实例来创建接口。客户端收到 Binder 后,可利用它直接访问 Binder 实现中乃至 Service 中可用的公共方法。如果服务只是您的自有应用的后台工作线程,则优先采用这种方法。 不以这种方式创建接口的唯一原因是,您的服务被其他应用或不同的进程占用。
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
//客户端关键代码
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
使用 Messenger
如需让接口跨不同的进程工作,则可使用 Messenger 为服务创建接口。服务可以这种方式定义对应于不同类型 Message 对象的 Handler。此 Handler 是 Messenger 的基础,后者随后可与客户端分享一个 IBinder,从而让客户端能利用 Message 对象向服务发送命令。此外,客户端还可定义自有 Messenger,以便服务回传消息。
这是执行进程间通信 (IPC) 的最简单方法,因为 Messenger 会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
//客户端关键代码
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
使用 AIDL
AIDL(Android 接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 IPC。 之前采用 Messenger 的方法实际上是以 AIDL 作为其底层结构。 如上所述,Messenger 会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。 不过,如果您想让服务同时处理多个请求,则可直接使用 AIDL。 在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。
如需直接使用 AIDL,您必须创建一个定义编程接口的 .aidl
文件。Android SDK 工具利用该文件生成一个实现接口并处理 IPC 的抽象类,您随后可在服务内对其进行扩展。
注:大多数应用“都不会”使用 AIDL 来创建绑定服务,因为它可能要求具备多线程处理能力,并可能导致实现的复杂性增加。因此,AIDL 并不适合大多数应用,本文也不会阐述如何将其用于您的服务。如果您确定自己需要直接使用 AIDL,请参阅 AIDL 文档。
参考 AIDL语法和使用详解
六、前台 Service
前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 前台服务必须为状态栏提供通知,放在“正在进行”标题下方,这意味着除非服务停止或从前台移除,否则不能清除通知。
例如,应该将通过服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。
获取 Service 存活状态
public static boolean serviceIsRunning(Context context, Class<?extends Service> clazz){
//判断 Service 是否已经启动,该方法也支持独立进程的 Service 检测,可以检测到系统上所有正在运行的 Service 程序
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//得到运行的 Service 列表
List<ActivityManager.RunningServiceInfo> list = am.getRunningServices(Integer.MAX_VALUE);
//遍历已启动的 Service 列表
for (ActivityManager.RunningServiceInfo info : list) {
//获取 Service 的ComponentName对象
ComponentName service = info.service;
String className = service.getClassName();
if (className.equals(clazz.getName())) {
return true;
}
}
return false;
}
设置前台 Service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.createNotificationChannel(new NotificationChannel(CHANNEL_ID, "MyService", NotificationManager.IMPORTANCE_DEFAULT));
}
Notification notification = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID).build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
附:参考
官方文档
深入理解Android内核设计思想.林学森
SDK源码
网友评论