Android中后台的劳动者“服务”

前言

作为四大组件之一的Service,想必不少开发者都是了解的,那具体熟悉吗?是不是对Service中的每个知识点是否了解,它与Activity的关系又是什么样的,我们所理解的后台服务跟Service是否一样,那跟Thread有什么区别呢,带着这些问题,就可以写一篇文章来一一展开了。

目录

  • Service是什么
  • Service生命周期
  • Service基本用法
  • Service与Activity的关系
  • Service与Thread的区别
  • 小结

Service是什么

从官网中,我们可以看到这么一句:

Most confusion about the Service class actually revolves around what it is not:

  • A Service is not a separate process. The Service object itself does not imply it is running in its own process; unless otherwise specified, it runs in the same process as the application it is part of.
  • A Service is not a thread. It is not a means itself to do work off of the main thread (to avoid Application Not Responding errors).

Thus a Service itself is actually very simple, providing two main features:

  • A facility for the application to tell the system about something it wants to be doing in the background (even when the user is not directly interacting with the application). This corresponds to calls to Context.startService(), which ask the system to schedule work for the service, to be run until the service or someone else explicitly stop it.
  • A facility for an application to expose some of its functionality to other applications. This corresponds to calls to Context.bindService(), which allows a long-standing connection to be made to the service in order to interact with it.

简单来说,Service不是一个独立的进程,除非它被特殊指定,否则它也是我们应用程序的一部分,它需要依赖于创建服务时所在的应用程序。同时Service也不是一个线程,它其实是在主线程工作的,所以不能在Service中处理耗时的操作,不然就会出现ARN现象,如果要处理耗时的操作,可以新开一个子线程,单独处理。

更进一步讲,Service是 Android中实现程序后台运行的解决方案,它非常适合用于去执行那 些不需要和用户交互而且还要求长期运行的任务。服务的运行不依赖于任何用户界面,即使 当程序被切换到后台,或者用户打开了另外一个应用程序,服务仍然能够保持正常运行。

Service有两种启动方式,一种是通过startService()方式,一种是通过bindService()方式,那这两种具体有什么区别呢,我们可以看下它们的生命周期来看出端倪。

Service生命周期

看张图:

服务生命周期

从图中我们可以看到两种不同的启动方式,分别是context.startService()和context.bindService()。

startService()启动的生命周期:

当我们第一次使用startService启动一个服务时,系统实例化一个Service实例,然后依次调用onCreate()和onStartCommand()方法,然后运行,需要注意的是,再次使用startService方法时,不会在创建一个新的服务对象了,但还是会再次执行onStartCommand()方法,如果我们想要停掉一个服务,可以用stopService方法,此时,onDestroy()方法就会被调用,不管前面使用了多少次的startService,stopService方法调用一次,就可停掉服务。

bindService()启动的生命周期:

当调用者首次使用bindService绑定一个服务时,系统会实例化一个Service实例,并一次调用其onCreate()方法和onBind()方法,然后调用者就可以和服务进行交互了,此后,如果再次使用bindService绑定服务,系统不会创建新的Service实例,也不会再调用onBind方法;如果我们需要解除与这个服务的绑定,可使用unbindService方法,此时onUnbind方法和onDestroy方法会被调用。

这两个启动方式的不同,导致生命周期也不同。startService与调用者没有必然的联系,即调用者结束了自己的生命周期,只要没有使用stopService方法停止这个服务,服务仍会运行。而bindService需要有个寄宿的对象,就相当于bind到某个宿主中去,谁绑定了,谁就要负责,负责它的生命周期,从开始到结束,如果宿主自己的生命周期结束了,bindService模式就要先把服务给销毁掉。

值得注意的一点是,如果调用者首先是先用startService方式启动服务,然后再用bindService方式绑定某个服务的话,一定要先用unbindService方式解绑,然后才用stopService方式销毁服务对象,不然的话,仅仅只是stopService是不够的,没解绑的对象还是在的,就容易造成内存泄露了。

Service基本用法

首先要定义个类,继承Service类,比如我们可以定义一个叫做MyService的类,具体如下:

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
public class MyService extends Service {

private static final String TAG = MyService.class.getSimpleName();
private MyBinder mBinder = new MyBinder();

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

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

@Override
public boolean onUnbind(Intent intent) {
TLog.i(TAG,"onUnbind()");
return super.onUnbind(intent);
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
TLog.i(TAG,"onStartCommand()");
return super.onStartCommand(intent, flags, startId);
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
TLog.i(TAG,"onBind()");
return mBinder;
}

public class MyBinder extends Binder {

public void startDownload() {
Log.d("TAG", "startDownload()");
// 执行具体的下载任务
}
}
}

然后写个界面来控制服务的启动和关闭

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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="启动服务"
android:id="@+id/recommend_btn_start"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="停止服务"
android:id="@+id/recommend_btn_stop"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="绑定服务"
android:id="@+id/recommend_btn_bind"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解绑服务"
android:id="@+id/recommend_btn_unbind"/>


</LinearLayout>

相应的后台处理类,给按钮增加事件处理。

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class RecommendFragment extends BaseFragment implements OnTabReselectListener {
@BindView(R.id.recommend_btn_start)
Button mStart;

@BindView(R.id.recommend_btn_stop)
Button mStop;

@BindView(R.id.recommend_btn_bind)
Button mBind;

@BindView(R.id.recommend_btn_unbind)
Button mUnBind;

private Unbinder unbinder;

private MyService.MyBinder myBinder;

private ServiceConnection connection = new ServiceConnection() {

@Override
public void onServiceDisconnected(ComponentName name) {
}

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
myBinder.startDownload();
}
};

@Override
protected int getLayoutId() {
return R.layout.fragment_recommend;
}

@Override
protected void initView(View view, Bundle savedInstanceState) {
unbinder = ButterKnife.bind(this,view);
mStart.setOnClickListener(this);
mStop.setOnClickListener(this);
mBind.setOnClickListener(this);
mUnBind.setOnClickListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.recommend_btn_start:
Intent startIntent = new Intent(getHoldingActivity(), MyService.class);
getHoldingActivity().startService(startIntent);
break;
case R.id.recommend_btn_stop:
Intent stopIntent = new Intent(getHoldingActivity(), MyService.class);
getHoldingActivity().stopService(stopIntent);
break;
case R.id.recommend_btn_bind:
Intent bindIntent = new Intent(getHoldingActivity(), MyService.class);
getHoldingActivity().bindService(bindIntent,connection, Context.BIND_ADJUST_WITH_ACTIVITY);
break;
case R.id.recommend_btn_unbind:
getHoldingActivity().unbindService(connection);
break;
}
}

@Override
public void onTabReselect() {

}

@Override
public void onDestroyView() {
super.onDestroyView();
unbinder.unbind();
}
}

我们首先用startService方式来启动服务

当点击启动服务时,日志打印如下:

启动状态

当点击停止服务时,日志打印如下:

停止状态

我们再用bindService方式来启动服务

此时我们可以看到一个ServiceConnection的一个实例,构成了Service与调用者的通信,在Activity里具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
private ServiceConnection connection = new ServiceConnection() {

@Override
public void onServiceDisconnected(ComponentName name) {
}

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myBinder = (MyService.MyBinder) service;
myBinder.startDownload();
}
};

然后在调用的时候开始启动绑定服务,使用这么一句:

1
getHoldingActivity().bindService(bindIntent,connection, Context.BIND_ADJUST_WITH_ACTIVITY);

当点击绑定服务时,日志打印如下:

绑定服务

当点击解绑服务时,日志打印如下:

解绑服务

Service与Activity的关系

其实Service与Activity都是属于四大组件之一,可能有几个关键区别是:

1,Activity是相当于应用程序的门面,用户看到最多的交互界面,可以点击滑动之类的,然而Service则是默默的后台运行,默默的服务着,可以说它是没有界面的Activity,也是有自己的生命周期。

2,Service与Activity都是属于UI线程中运行,都不能做耗时操作,如果需要耗时,比如网络请求,I/O读写之类的,都需要重新开个异步线程,然后进行回调更新UI内容。

3,如果Service中是用bindService方式的话,一般同一个Service对象可以被多个Activity对象使用,可以共享Service实例,所以在Activity对象销毁之前,先解绑Service对象。

4,在Service的onDestroy()方法里去清理掉那些不再使用的资源,防止在Service被销毁后还会有一些不再使用的对象仍占用着内存。

5,一个Service对象在被启动后,如果startService方法调用多次,返回的Service对象还是之前建立过的,不会启动一个新的对象,这跟Activity是有点不一样,Activity是有四种的标准模式。

Service与Thread的区别

说实话,Service与Thread这两个是没有什么关系的。因为我们知道Service是运行在UI线程中,那么当需要耗时操作的时候,就需要Thread帮助,不是说Service因为是在后台运行,就跟Thread等同了。Thread是用于开启一个子线程,在这里去执行一些耗时操作就不会阻塞主线程的运行。而Service我们最初理解的时候,总会觉得它是用来处理一些后台任务的,一些比较耗时的操作也可以放在这里运行,这就会让人产生混淆了。

Android的后台就是指,它的运行是完全不依赖UI的。即使Activity被销毁,或者程序被关闭,只要进程还在,Service就可以继续运行。比如说一些应用程序,始终需要与服务器之间始终保持着心跳连接,就可以使用Service来实现。

既然在Service里要创建一个子线程,那为什么不直接在Activity里创建呢?这是因为Activity很难对Thread进行控制,当Activity被销毁之后,就没有任何其它的办法可以再重新获取到之前创建的子线程的实例。而且在一个Activity中创建的子线程,另一个Activity无法对其进行操作。但是Service就不同了,所有的Activity都可以与Service进行关联,然后可以很方便地操作其中的方法,即使Activity被销毁了,之后只要重新与Service建立关联,就又能够获取到原有的Service中Binder的实例。因此,使用Service来处理后台任务,Activity就可以放心地finish,完全不需要担心无法对后台任务进行控制的情况。

小结

经过上面的讲诉,不知大家是否清楚点,可以说Service与Activity都是属于四大组件之一,只不过我们平时用的多都是Activity,Service用的比较少。这跟所开发的应用程序类型有关吧,比如常见的听歌应用,听书应用的,当我们锁屏的时候,一般都是在后台播放,此时Service就派上用场了,还有一种场景是远程Service的用法,进程之间的通信,多个APP共用一个Service进程对象,具体可以参考这篇文章: Android Service完全解析,关于服务你所需知道的一切(下)

,