Service探究系列之基础

一、Service简介

Service 是Android四大组件之一,是一种可以执行长时间操作而不提供界面的应用组件。Service 启动后,当用户切换到其他应用时,其依然可在后台继续运行。此外,组件也可以绑定到 Service 并与之进行交互,甚至是执行进程间通信(IPC)。主要是用来处理一些用户无感知的耗时后台操作,如:网络数据处理、播放音乐、定位信息、执行文件IO操作、数据库读写操作等。

二、创建Service

创建一个 Service 非常简单,只需继承 Service 或其子类即可,以下是一个具体示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class HelloService extends Service {
private final String TAG = "HelloService";

@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "HelloService onCreate");
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "Hello service onStartCommand, intent:" + (intent != null ? intent.toString() : "null") + ", flags:" + flags + ", startId:" + startId);
int count = intent.getIntExtra("count", 0);
Log.i(TAG, "the count is " + count);
return super.onStartCommand(intent, flags, startId);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(TAG, "Hello service onDestroy");
}
}

上面的 HelloService 继承自 Service,并实现了其生命周期方法。其中 onCreate()onDestroy()Activity 的生命周期方法一样,在service创建或销毁时会被系统调用的;onStartCommand()onBind() 方法也属于 Service 的生命周期方法,但是其根据启动方式不同而被回调的方法不同,本例中只关注 onStartCommand() 方法(后续我们在探索service的两种启动方式时再详细说明 onBind() 的使用),当service是以非绑定的方式启动时,在回调完onCreate()方法后,会立即回调onStartCommand()方法,并将附带参数以Intent的方式传递进来,service的重要工作也是在这个方法中实现的。

ServiceActivity 一样也必须在AndroidManifest中声明,之后才能使用,以下为HelloService的清单声明示例:

1
2
3
4
<service
android:name=".service.HelloService"
android:description="@string/service_des"
android:exported="false" />

其中name是必须配置的,其他的根据自己的需要选择性配置,以下是service可配置的所有属性,各个属性的功能及取值说明请参考Service清单配置说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<service android:description="string resource"
android:directBootAware=["true" | "false"]
android:enabled=["true" | "false"]
android:exported=["true" | "false"]
android:foregroundServiceType=["connectedDevice" | "dataSync" |
"location" | "mediaPlayback" | "mediaProjection" |
"phoneCall"]
android:icon="drawable resource"
android:isolatedProcess=["true" | "false"]
android:label="string resource"
android:name="string"
android:permission="string"
android:process="string" >
. . .
</service>

创建完HelloService并在清单文件中声明后,我们就可以启动service了。你可以在ActivityContentProvider中通过Intent来启动服务,如下所示:

1
2
3
Intent intent = new Intent(context, HelloService.class);
intent.putExtra("count", count);
startService(intent);

运行结果如下:

1
2
3
I/HelloService: HelloService onCreate
I/HelloService: Hello service onStartCommand, intent:Intent { cmp=com.summer.servicedemo/.service.HelloService (has extras) }, flags:0, startId:1
I/HelloService: the count is 1

注意:为了确保应用的安全性,请始终使用显示Intent启动服务,且不要为Service声明intent_filter。从Android5.0(API 21)开始,通过隐式Intent调用bindService会抛出系统异常

至此我们的service就启动起来了。service启动后,其生命周期即独立于启动的组件了,即使系统已经销毁启动服务的组件,该服务依然可在后台无限期地运行。因此,当任务完成后应该主动调用并停止服务,可由其他组件在外部调用 stopService() 或在服务内部调用 stopSelf() 方法来停止服务,详细的使用方式我们会在接下来的文章中分析。

默认情况下,服务与服务声明所在的应用运行于同一进程,并且运行在该应用的主线程中。如果在服务内部要执行密集型或阻塞性操作,会降低该应用Activity的性能,同时还有可能引起ANR(服务内任务一般在20s执行不完就会ANR)。因此,如果是密集型或阻塞性操作,请在服务内启动新线程来执行对应任务(IntentService默认封装了工作线程,可直接继承IntentService,将任务放到其工作线程中执行)。

总结下使用service的大致流程:

  1. 继承Service或其子类(IntentService),并实现其生命周期方法,编写任务处理代码;
  2. 在清单文件中声明Service
  3. 在其他组件中启动Service

三、继承IntentService类

通常情况下服务无需同时处理多个请求(实际上,这种多线程操作服务也是危险的),Android提供了一种单一线程并顺序执行任务的Service子类IntentService,其具有以下特点:

  1. 在其内部创建了默认的工作线程,用来处理外部传递给 onStartCommand() 的所有Intent。
  2. 内部维护了工作队列,用于将Intent逐一传递给 onHandleIntent() 方法,避免多线程同步问题。
  3. 在处理完所有的任务后,自动停止服务,不需要你关心何时停止的问题。
  4. 默认实现了 onStartCommand() 方法,并将其接收到的Intent依次发送到工作队列和 onHandleIntent() 处理。
  5. 默认实现了 onBind() 方法(返回null),也就是外部通过 bindService() 方法启动服务是无效的,只能通过 startService() 启动服务。

以下是继承IntentService的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class HelloIntentService extends IntentService {

public HelloIntentService() {
this("HelloIntentService");
}

/**
* @param 工作线程的名称,一般用来测试使用;
*/
public HelloIntentService(String name) {
super(name);
}

@Override
protected void onHandleIntent(@Nullable Intent intent) {
Thread thread = Thread.currentThread();
// 这里获取的线程名称即为在构造方法中设置的name
Log.i(getClass().getSimpleName(), "thread name:" + thread.getName());
// do something
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

@Override
public void onDestroy() {
super.onDestroy();
Log.i(getClass().getSimpleName(), "service onDestroy");
}
}

如上例所示,我们只需要继承IntentService并实现 onHandleIntent() 方法即可,将你要处理的任务放到 onHandleIntent() 方法中,IntentService就会逐个处理传递进来的任务。我们在外部用一个线程池来模拟多线程启动服务的情况,并查看下HelloIntentService是如何处理任务的,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
private void testIntentService() {
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
service.execute(new Runnable() {
@Override
public void run() {
Intent intent = new Intent(MainActivity.this, HelloIntentService.class);
intent.putExtra("index", index);
startService(intent);
}
});
}
}

运行结果如下:

1
2
3
4
5
6
11-08 09:16:37.150  7749  7829 I HelloIntentService: thread name:IntentService[HelloIntentService]
11-08 09:16:38.152 7749 7829 I HelloIntentService: thread name:IntentService[HelloIntentService]
11-08 09:16:39.154 7749 7829 I HelloIntentService: thread name:IntentService[HelloIntentService]
11-08 09:16:40.156 7749 7829 I HelloIntentService: thread name:IntentService[HelloIntentService]
11-08 09:16:41.158 7749 7829 I HelloIntentService: thread name:IntentService[HelloIntentService]
11-08 09:16:42.158 7749 7749 I HelloIntentService: service onDestroy

可以看出任务大约每隔1s执行一次,所有任务都执行完后service也就停止了。

注意:如果你还要重写其他回调方法,如:onCreate()、onStartCommand()或onDestroy(),必须确保调用super实现,以保证 IntentService 能正确处理工作线程的生命周期。

四、总结

Service 作为Android四大组件之一,具有生命周期独立于启动组件而在后台不断运行的特性,主要用来处理一些用户无感知的耗时操作。通过创建服务、清单文件中声明服务、在其他组件中启动服务三个步骤来完成服务的使用,并可由外部组件手动停止服务,也可以在自身内部将服务停止。

如果你的任务需要另开线程处理,且不需要多线程处理,应首选继承 IntentService 实现你的服务,其内置工作线程处理任务,并无需你关心其生命周期。