Service使用详解
Service是Android系统中的四大组件之一,主要有两个应用场景:后台运行和跨进程访问。Service可以在后台执行长时间运行操作而不提供用户界面,除非系统必须回收内存资源,否则系统不会停止或销毁服务。服务可由其他应用组件启动,而且即使用户切换到其他应用,服务仍将在后台继续运行。 此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)
需要注意的是,Service是在主线程里执行操作的,可能会因为执行耗时操作而导致ANR
一、基础知识
Service可以分为以下三种形式:
- 启动
当应用组件通过调用 startService() 启动服务时,服务即处于“启动”状态。一旦启动,服务即可在后台无限期运行,即使启动服务的组件已被销毁也不受影响。 已启动的服务通常是执行单一操作,而且不会将结果返回给调用方 - 绑定
当应用组件通过调用 bindService() 绑定到服务时,服务即处于“绑定”状态。绑定服务提供了一个客户端-服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至是利用进程间通信 (IPC) 跨进程执行这些操作。多个组件可以同时绑定服务,服务只会在组件与其绑定时运行,一旦该服务与所有组件之间的绑定全部取消,系统便会销毁它 - 启动且绑定
服务既可以是启动服务,也允许绑定。此时需要同时实现以下回调方法:onStartCommand()和 onBind()。系统不会在所有客户端都取消绑定时销毁服务。为此,必须通过调用 stopSelf() 或 stopService() 显式停止服务
无论应用是处于启动状态还是绑定状态,或者处于启动且绑定状态,任何应用组件均可像使用 Activity 那样通过调用 Intent 来使用服务(即使此服务来自另一应用),也可以通过清单文件将服务声明为私有服务,阻止其他应用访问
要使用服务,必须继承Service类(或者Service类的现有子类),在子类中重写某些回调方法,以处理服务生命周期的某些关键方面并提供一种机制将组件绑定到服务
- onStartCommand()
当组件通过调用 startService() 请求启动服务时,系统将调用此方法(如果是绑定服务则不会调用此方法)。一旦执行此方法,服务即会启动并可在后台无限期运行。 在指定任务完成后,通过调用 stopSelf() 或 stopService() 来停止服务 - onBind()
当一个组件想通过调用 bindService() 与服务绑定时,系统将调用此方法(如果是启动服务则不会调用此方法)。在此方法的实现中,必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信 - onCreate()
首次创建服务时,系统将调用此方法来执行初始化操作(在调用 onStartCommand() 或 onBind() 之前)。如果在启动或绑定之前Service已在运行,则不会调用此方法 - onDestroy()
当服务不再使用且将被销毁时,系统将调用此方法,这是服务接收的最后一个调用,在此方法中应清理占用的资源
仅当内存过低必须回收系统资源以供前台 Activity 使用时,系统才会强制停止服务。如果将服务绑定到前台 Activity,则它不太可能会终止,如果将服务声明为在前台运行,则它几乎永远不会终止。或者,如果服务已启动并要长时间运行,则系统会随着时间的推移降低服务在后台任务列表中的位置,而服务也将随之变得非常容易被终止。如果服务是启动服务,则必须将其设计为能够妥善处理系统对它的重启。 如果系统终止服务,那么一旦资源变得再次可用,系统便会重启服务(这还取决于 onStartCommand() 的返回值)
二、声明Service
如同其他组件一样,想要使用Service,必须在清单文件中对其进行声明
声明方式是添加 < service > 元素作为 < application > 元素的子元素
例如
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<service android:name=".MyService" />
</application>
android:name 属性是唯一必需的属性,用于指定服务的类名,还可将其他属性包括在 < service > 元素中以定义一些特性
为了确保应用的安全性,最好始终使用显式 Intent 启动或绑定 Service,且不要为服务声明 Intent 过滤器。 启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 Intent 中排除相应的组件名称,但随后必须使用 setPackage() 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性
此外,还可以通过添加 android:exported 属性并将其设置为 “false“,确保服务仅适用于本应用。这可以有效阻止其他应用启动本应用内的服务,即便在使用显式 Intent 时也是如此
- Service包含的属性有
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
</service>
属性 | 说明 |
---|---|
description | 对服务进行描述,属性值应为对字符串资源的引用,以便进行本地化 |
directBootAware | 设置是否可以在用户解锁设备之前运行,默认值为“false” |
enabled | 设置是否可以由系统来实例化服务。< application >元素有自己的enabled属性,适用于包括服务在内的所有应用程序组件。要启用服务,< application >和< service >属性必须都为“true”(默认情况下都为true)。如果其中一个是“false”,则服务被禁用 |
exported | 设置其他应用程序的组件是否可以调用本服务或与其交互,如果可以,则为“true”。当值为“false”时,只有同一个应用程序或具有相同用户ID的应用程序的组件可以启动该服务或绑定到该服务。该属性的默认值取决于服务是否包含Intent filters。没有任何过滤器意味着它只能通过指定其确切的类名来调用,这意味着该服务仅用于应用程序内部使用(因为其他人不知道类名)。所以在这种情况下,默认值为“false”。 另一方面,如果存在至少一个过滤器,意味着该服务打算供外部使用,因此默认值为“true” |
icon | 服务的图标,属性值应是对drawable资源的引用。如果未设置,则将使用应用程序图标 |
isolatedProcess | 设置该服务是否作为一个单独的进程运行,如果设置为true,此服务将在与系统其余部分隔离的特殊进程下运行,并且没有自己的权限,与它唯一的通信是通过服务API(绑定和启动) |
label | 可以向用户显示的服务的名称,属性值应是对字符串资源的引用 |
name | 服务类的完全限定名 |
permission | 设定组件必须具有的权限,得以启动服务或绑定服务。如果startService(),bindService()或stopService()的调用者没有被授予此权限,则该方法将不会工作,并且Intent对象不会传递到服务中 |
process | 用来运行服务的进程的名称。通常,应用程序的所有组件都运行在应用程序创建的默认进程中,它与应用程序包名具有相同的名称。 < application >元素的process属性可以为所有组件设置不同的默认值,但组件可以使用自己的进程属性覆盖默认值,从而允许跨多个进程扩展应用程序 |
三、启动Service
启动服务由组件通过调用 startService() 启动,服务启动之后,其生命周期即独立于启动它的组件,并且可以在后台无限期地运行,即使启动服务的组件已被销毁也不受影响。因此,服务应通过调用 stopSelf() 来自行停止运行,或者由另一个组件调用 stopService() 来停止
可以通过扩展两个类来创建启动服务:
- Service
这是所有服务的父类。扩展此类时,如果要执行耗时操作,必须创建一个用于执行操作的新线程,因为默认情况下服务将运行于UI线程 - IntentService
这是 Service 的子类,它使用工作线程逐一处理所有启动请求。如果应用不需要同时处理多个请求,这是最好的选择。IntentService只需实现构造函数与 onHandleIntent() 方法即可,onHandleIntent()方法会接收每个启动请求的 Intent
3.1、继承Service
这里举一个音乐播放器的例子
继承Service类实现自定义Service,提供在后台播放音乐、暂停音乐、停止音乐的方法
public class MyService extends Service {
private final String TAG = "MyService";
private MediaPlayer mediaPlayer;
private int startId;
public enum Control {
PLAY, PAUSE, STOP
}
public MyService() {
}
@Override
public void onCreate() {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(this, R.raw.music);
mediaPlayer.setLooping(false);
}
Log.e(TAG, "onCreate");
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.startId = startId;
Log.e(TAG, "onStartCommand---startId: " + startId);
Bundle bundle = intent.getExtras();
if (bundle != null) {
Control control = (Control) bundle.getSerializable("Key");
if (control != null) {
switch (control) {
case PLAY:
play();
break;
case PAUSE:
pause();
break;
case STOP:
stop();
break;
}
}
}
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
Log.e(TAG, "onDestroy");
if (mediaPlayer != null) {
mediaPlayer.stop();
mediaPlayer.release();
}
super.onDestroy();
}
private void play() {
if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
}
private void pause() {
if (mediaPlayer != null && mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
}
private void stop() {
if (mediaPlayer != null) {
mediaPlayer.stop();
}
stopSelf(startId);
}
@Override
public IBinder onBind(Intent intent) {
Log.e(TAG, "onBind");
throw new UnsupportedOperationException("Not yet implemented");
}
点击播放音乐后,状态栏就出现了一个通知
当中,提供给 startForeground() 的整型参数不得为 0。要从前台移除服务,需调用 stopForeground()方法,此方法不会停止服务。 但是,如果前台服务被停止,则通知也会被移除
六、Service的生命周期
服务生命周期从创建到销毁可以遵循两条不同的路径:
- 启动服务
该服务在其他组件调用 startService() 时创建,然后无限期运行,必须通过调用 stopSelf() 来自行停止运行或通过其他组件调用 stopService() 来停止服务。服务停止后,系统会将其销毁 - 绑定服务
服务在另一个组件(客户端)调用 bindService() 时创建。然后,客户端通过 IBinder 接口与服务进行通信。客户端可以通过调用 unbindService() 关闭连接。多个客户端可以绑定到相同服务,而且当所有绑定全部取消后,系统即会销毁该服务(服务不必自行停止运行)
这两条路径并非完全独立。也就是说,可以绑定到已经使用 startService() 启动的服务。例如,可以通过使用 Intent(标识要播放的音乐)调用 startService() 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 bindService() 绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 stopService() 或 stopSelf() 不会实际停止服务