Android Service的使用

前言

Thread和Service

如果你的 Thread 需要不停地隔一段时间就要连接服务器做某种同步的话,该 Thread 需要在 Activity 没有start的时候也在运行。这个时候当你 start 一个 Activity 就没有办法在该 Activity 里面控制之前创建的 Thread。因此你便需要创建并启动一个 Service ,在 Service 里面创建、运行并控制该 Thread,这样便解决了该问题(因为任何 Activity 都可以控制同一 Service,而系统也只会创建一个对应 Service 的实例)。

因此你可以把 Service 想象成一种消息服务,而你可以在任何有 Context 的地方调用 Context.startServiceContext.stopServiceContext.bindServiceContext.unbindService,来控制它,你也可以在 Service 里注册 BroadcastReceiver,在其他地方通过发送 broadcast 来控制它,当然这些都是 Thread做不到的。

service可以通过两种方式创建:startService()bindService().

  • startService():一般用于在后台上传文件或者下载文件等,不跟其他组件通信,就算启动它的应用被销毁了,它仍然会欢快的在后台执行,直到完成任务的时候自刎(自己调用stopSelf())或者被其他人下黑手(调用stopService()).
  • bindService():允许其他组件跟它进行通信,允许多个客户端绑定到同一个service上,当所有的客户端都解除绑定后,该service就销毁了。

Service在清单文件中的声明
前面说过Service分为启动状态和绑定状态两种,但无论哪种具体的Service启动类型,都是通过继承Service基类自定义而来,也都需要在AndroidManifest.xml中声明,那么在分析这两种状态之前,我们先来了解一下Service在AndroidManifest.xml中的声明语法,其格式如下:

1
2
3
4
5
6
7
8
9
<service 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>

配置项

  • android:exported:代表是否能被其他应用隐式调用,其默认值是由service中有无intent-filter决定的,如果有intent-filter,默认值为true,否则为false。为false的情况下,即使有intent-filter匹配,也无法打开,即无法被其他应用隐式调用。

  • android:name:对应Service类名

  • android:permission:是权限声明

  • android:process:是否需要在单独的进程中运行,当设置为android:process=”:remote”时,代表Service在单独的进程中运行。注意“:”很重要,它的意思是指要在当前进程名称前面附加上当前的包名,所以remote:remote不是同一个意思,前者的进程名称为:remote,而后者的进程名称为:App-packageName:remote

  • android:isolatedProcess :设置 true 意味着,服务会在一个特殊的进程下运行,这个进程与系统其他进程分开且没有自己的权限。与其通信的唯一途径是通过服务的API(bind and start)。

  • android:enabled:是否可以被系统实例化,默认为 true因为父标签 也有 enable 属性,所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。

方式一(startService)

startService开启服务和结束服务稍微简单一点。

1
<service android:name=".DBService"/>

服务类

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
public class DBService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.e("call", "onBind");
return null;
}

@Override
public void onCreate() {
Log.e("call", "onCreate");
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.e("call", "onStartCommand");
String command = intent.getStringExtra("command");

if ("eat".equals(command)) {
}
return super.onStartCommand(intent, flags, startId);
}

@Override
public void onDestroy() {
Log.e("call", "onDestroy");
super.onDestroy();
}
}

开启服务

1
2
3
//开启服务
Intent service = new Intent(this, DBService.class);
startService(service);

结束服务

1
2
//结束服务
stopService(service);

开启服务时,调用一次startService,生命周期执行的方法依次是:

onCreate() ==> onStartCommand();

调用多次startServiceonCreate只有第一次会被执行,而onStartCommand会执行多次。
结束服务时,调用stopService,生命周期执行onDestroy方法,并且多次调用stopService时,onDestroy只有第一次会被执行。

方式二(bindService)

定义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
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.widget.Toast;

public class DBService extends Service {
private DBBinder mBinder = new DBBinder();

public DBService() {
}

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

public List<ZWord> loadWenziByPinyin(String pinyin) {
List<ZWord> word_list = new ArrayList<>();
return word_list;
}

public class DBBinder extends Binder {
public List<ZWord> callLoadWenziByPinyin(String pinyin) {
return loadWenziByPinyin(pinyin);
}
}
}

定义的DBBinder就是能让Activity和Service交互

bingService开启服务时,根据生命周期里onBind方法的返回值是否为空,有两种情况。

  1. onBind返回值是null;
    调用bindService开启服务,生命周期执行的方法依次是:
    onCreate() ==> onBind();
    调用多次bindService,onCreate和onBind也只在第一次会被执行。
    调用unbindService结束服务,生命周期执行onDestroy方法,并且unbindService方法只能调用一次,多次调用应用会抛出异常。使用时也要注意调用unbindService一定要确保服务已经开启,否则应用会抛出异常。
  2. onBind返回值不为null;
    看一下android对于onBind方法的返回类型IBinder的介绍,字面上理解是IBinder是android提供的进程间和跨进程调用机制的接口。而且返回的对象不要直接实现这个接口,应该继承Binder这个类。
    那么我们就在自己写的DBService里创建一个内部类DBBinder,让他继承Binder,并在onBind方法里返回DBBinder的对象。

Activity绑定

bindService()

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MyApplication extends Application {
public static Context mContext;

private DBService.DBBinder dbBind;
private DBConn dbConn;

@Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
startDBService();
}

public void click(View v) {
//调用service的方法
dbBind.callLoadWenziByPinyin("nihao");
}

private void startDBService(){
if(null == dbConn){
dbConn = new DBConn();
}
Intent intent = new Intent(this,DBService.class);
bindService(intent,dbConn,BIND_AUTO_CREATE);
}

private void stopDBService(){
if(null != dbConn){
unbindService(dbConn);
}
}

private class DBConn implements ServiceConnection {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("bindLog","bind");
dbBind= (DBService.DBBinder) service;
}

@Override
public void onServiceDisconnected(ComponentName name) {
Log.d("bindLog","unbind");
}
}
}

绑定成功后就会调用onServiceConnected

这时候调用bindService开启服务,生命周期执行的方法依次是:

onCreate() ==> onBind() ==> onServiceConnected();

可以发现我们自己写的DBConn类里的onServiceConnected方法被调用了。调用多次bindService,onCreate和onBind都只在第一次会被执行,onServiceConnected会执行多次。
并且我们注意到onServiceConnected方法的第二个参数也是IBinder类型的,不难猜测onBind()方法返回的对象被传递到了这里。打印一下两个对象的地址可以证明猜测是正确的。
也就是说我们可以在onServiceConnected方法里拿到了DBService服务的内部类DBBinder的对象,通过这个内部类对象,只要强转一下,我们可以调用这个内部类的非私有成员对象和方法。
调用unbindService结束服务和上面相同,unbindService只能调用一次,onDestroy也只执行一次,多次调用会抛出异常。

区别

接下来我们说一下startService和bindService开启服务时,他们与activity之间的关系。

  1. startService开启服务以后,与activity就没有关联,不受影响,独立运行。
  2. bindService开启服务以后,与activity存在关联,退出activity时必须调用unbindService方法,否则会报ServiceConnection泄漏的错误。

注意

最后还有一点,同一个服务可以用两种方式一同开启,没有先后顺序的要求,DBService的onCreate只会执行一次。
关闭服务需要stopService和unbindService都被调用,也没有先后顺序的影响,DBService的onDestroy也只执行一次。

但是如果只用一种方式关闭服务,不论是哪种关闭方式,onDestroy都不会被执行,服务也不会被关闭。这一点需要注意。