LeoYan Blog

技术分享,生活记录。

0%

Android 之 Service 相关知识

转载请注明出处:www.leoyanblog.com

本文出自 LeoYan 的博客

本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 LeoYan 即可关注。

什么是 Service?  

  Service 是 Android 系统的主要组件之一,Service 的职责是进行一些不需要UI存在的操作,比如网络请求、本地磁盘IO、执行定时任务等。Service 是没有图形界面的,只会在后台默默地运行。

注:Service 只是在后台运行的一个组件,默认情况下,Service 也是运行在 app 的主线程中的,它并不会开启新的线程或者进程(当然,这个可以做到)。所以,假如在 Service 中执行有耗时的操作的话,最好在子线程中执行。

Service 的特点

  没有可视的UI、后台运行,运行不阻塞前台UI、拥有服务的进程具有较高的优先级。

Service 与 Thread 的区别

  • Thread

  Thread 是程序执行的最小单元,它是分配 CPU 的基本单位。可以用 Thread 来执行一些异步的操作。

  • Service

  Service 是 Android 的一种机制,当它运行的时候如果是 Local Service,那么对应的 Service 是运行在主进程的 main 线程上的。如:onCreate,onStart 这些函数在被系统调用的时候都是在主进程的 main 线程上运行的。如果是 Remote Service,那么对应的 Service 则是运行在独立进程的 main 线程上。因此请不要把 Service 理解成线程,它跟线程半毛钱的关系都没有!

  既然这样,那么我们为什么要用 Service 呢?其实这跟 Android 的系统机制有关,我们先拿 Thread 来说。Thread 的运行是独立于 Activity 的,也就是说当一个 Activity 被 finish 之后,如果你没有主动停止 Thread 或者 Thread 里的 run 方法没有执行完毕的话,Thread 也会一直执行。因此这里会出现一个问题:当 Activity 被 finish 之后,你不再持有该 Thread 的引用。另一方面,你没有办法在不同的 Activity 中对同一 Thread 进行控制。

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

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

Service 的分类

按运行分类

  • 前台服务

  前台服务是指那些经常会被用户关注的服务,因此内存过低时它不会成为被杀的对象。 前台服务必须提供一个状态栏通知,并会置于“正在进行的”(“Ongoing”)组之下。这意味着只有在服务被终止或从前台移除之后,此通知才能被解除。

  例如,用服务来播放音乐的播放器就应该运行在前台,因为用户会清楚地知晓它的运行情况。 状态栏通知可能会标明当前播放的歌曲,并允许用户启动一个 Activity 来与播放器进行交互。

  要把你的服务请求为前台运行,可以调用 startForeground() 方法。此方法有两个参数:唯一标识通知的整数值、状态栏通知 Notification 对象。例如:

1
2
3
4
5
6
7
8
9
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),System.currentTimeMillis());

Intent notificationIntent = new Intent(this,ExampleActivity.class);

PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);

notification.setLatestEventInfo(this, getText(R.string.notification_title), getText(R.string.notification_message), pendingIntent);

startForeground(ONGOING_NOTIFICATION, notification);

  要从前台移除服务,请调用 stopForeground() 方法,这个方法接受个布尔参数,表示是否同时移除状态栏通知。此方法不会终止服务。不过,如果服务在前台运行时被你终止了,那么通知也会同时被移除。

  • 后台服务

按使用分类  

  • 本地服务

  用于应用程序内部,实现一些耗时任务,并不占用应用程序比如 Activity 所属线程,而是单开线程后台执行。

  调用 Context.startService() 启动,调用 Context.stopService() 结束。在内部可以调用 Service.stopSelf() 或 Service.stopSelfResult() 来自己停止。

  • 远程服务

  用于 Android 系统内部的应用程序之间,可被其他应用程序复用,比如天气预报服务,其他应用程序不需要再写这样的服务,调用已有的即可。可以定义接口并把接口暴露出来,以便其他应用进行操作。客户端建立到服务对象的连接,并通过那个连接来调用服务。调用 Context.bindService() 方法建立连接,并启动,以调用 Context.unbindService() 关闭连接。多个客户端可以绑定至同一个服务。如果服务此时还没有加载,bindService() 会先加载它。

Service生命周期

  Service生命周期

  Android Service 的生命周期并不像 Activity 那么复杂,它只继承了 onCreate(), onStart(), onDestroy() 三个方法,当我们第一次启动 Service 时,先后调用了 onCreate(), onStart() 这两个方法,当停止 Service 时,则执行 onDestroy() 方法,这里需要注意的是,如果 Service 已经启动了,当我们再次启动 Service 时,不会在执行 onCreate() 方法,而是直接执行 onStart() 方法,具体的可以看下面的实例。

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
30
31
32
33
34
public class ExampleService extends Service {
int mStartMode; // 标识服务被杀死后的处理方式
IBinder mBinder; // 用于客户端绑定的接口
boolean mAllowRebind; // 标识是否使用onRebind

@Override
public void onCreate() {
// 服务正被创建
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// 服务正在启动,由startService()调用引发
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// 客户端用bindService()绑定服务
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// 所有的客户端都用unbindService()解除了绑定
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// 某客户端正用bindService()绑定到服务,
// 而onUnbind()已经被调用过了
}
@Override
public void onDestroy() {
// 服务用不上了,将被销毁
}
}

请注意 onStartCommand() 方法必须返回一个整数。这个整数是描述系统在杀死服务之后应该如何继续运行。onStartCommand() 的返回值必须是以下常量之一:

START_NOT_STICKY

  如果系统在 onStartCommand() 返回后杀死了服务,则不会重建服务了,除非还存在未发送的 intent。 当服务不再是必需的,并且应用程序能够简单地重启那些未完成的工作时,这是避免服务运行的最安全的选项。 

START_STICKY

  如果系统在 onStartCommand() 返回后杀死了服务,则将重建服务并调用 onStartCommand(),但不会再次送入上一个 intent, 而是用 null intent 来调用 onStartCommand()。除非还有启动服务的 intent 未发送完,那么这些剩下的 intent 会继续发送。 这适用于媒体播放器(或类似服务),它们不执行命令,但需要一直运行并随时待命。

START_REDELIVER_INTENT

  如果系统在 onStartCommand() 返回后杀死了服务,则将重建服务并用上一个已送过的 intent 调用 onStartCommand()。任何未发送完的 intent 也都会依次送入。这适用于那些需要立即恢复工作的活跃服务,比如下载文件。

  服务的生命周期与 Activity 的非常类似。不过,更重要的是你需密切关注服务的创建和销毁环节,因为后台运行的服务是不会引起用户注意的。

  通过实现这些方法,您可以监控服务生命周期的两个嵌套循环:

  • 服务的整个生命周期从调用 onCreate() 开始起,到 onDestroy() 返回时结束。与 Activity 类似,服务也在 onCreate() 中完成初始设置,并在 onDestroy() 中释放所有剩余资源。例如,音乐播放服务可以在 onCreate() 中创建用于播放音乐的线程,然后在 onDestroy() 中停止该线程。

  无论服务是通过 startService() 还是 bindService() 创建,都会为所有服务调用 onCreate() 和 onDestroy() 方法。

  • 服务的有效生命周期从调用 onStartCommand() 或 onBind() 方法开始。每种方法均有 Intent 对象传入,该对象分别传递到 startService() 或 bindService()。

  对于启动服务,有效生命周期与整个生命周期同时结束(即便是在 onStartCommand() 返回之后,服务仍然处于活动状态)。对于绑定服务,有效生命周期在 onUnbind() 返回时结束。

注:尽管启动服务是通过调用 stopSelf() 或 stopService() 来停止,但是该服务并无相应的回调(没有 onStop() 回调)。因此,除非服务绑定到客户端,否则在服务停止时,系统会将其销毁 — onDestroy() 是接收到的唯一回调。

在manifest中声明服务

  如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。

  要声明服务,请添加 元素作为 元素的子元素。例如:

1
2
3
4
5
6
7
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>

Service 元素的属性有:

android:name  ————-  服务类名

android:label  ————–  服务的名字,如果此项不设置,那么默认显示的服务名则为类名

android:icon  ————–  服务的图标

android:permission  ——-  申明此服务的权限,这意味着只有提供了该权限的应用才能控制或连接此服务

android:process  ———-  表示该服务是否运行在另外一个进程,如果设置了此项,那么将会在包名后面加上这段字符串表示另一进程的名字

android:enabled  ———-  如果此项设置为 true,那么 Service 将会默认被系统启动,不设置默认此项为 false

android:exported  ———  表示该服务是否能够被其他应用程序所控制或连接,不设置默认此项为 false 

  android:name 是唯一必需的属性——它定义了服务的类名。与 activity 一样,服务可以定义 intent 过滤器,使得其它组件能用隐式 intent 来调用服务。如果你想让服务只能内部使用(其它应用程序无法调用),那么就不必(也不应该)提供任何 intent 过滤器。

  此外,如果包含了 android:exported 属性并且设置为 “false”, 就可以确保该服务是你应用程序的私有服务。即使服务提供了 intent 过滤器,本属性依然生效。 

Service 启动方式

startService()

  从 Activity 或其它应用程序组件中可以启动一个服务,调用 startService() 并传入一个Intent(指定所需启动的服务)即可。

1
2
	Intent intent = new Intent(this, MyService.class);
startService(intent);

服务类:

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

/**
* onBind 是 Service 的虚方法,因此我们不得不实现它。
* 返回 null,表示客服端不能建立到此服务的连接。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}

@Override
public void onCreate() {
super.onCreate();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId)    {
//接受传递过来的intent的数据
return START_STICKY;
};

@Override
public void onDestroy() {
super.onDestroy();
}

}

  一个 started 服务必须自行管理生命周期。也就是说,系统不会终止或销毁这类服务,除非必须恢复系统内存并且服务返回后一直维持运行。 因此,服务必须通过调用 stopSelf() 自行终止,或者其它组件可通过调用 stopService() 来终止它。

bindService()  

  当应用程序中的 Activity 或其它组件需要与服务进行交互,或者应用程序的某些功能需要暴露给其它应用程序时,你应该创建一个 bound 服务,并通过进程间通信(IPC)来完成。

方法如下:

1
2
Intent intent=new Intent(this,BindService.class); 
bindService(intent, ServiceConnection conn, int flags)

注意 bindService 是 Context 中的方法,当没有 Context 时传入即可。

在进行服务绑定的时,其 flags 有:

  • Context.BIND_AUTO_CREATE

  表示收到绑定请求的时候,如果服务尚未创建,则即刻创建,在系统内存不足需要先摧毁优先级组件来释放内存,且只有驻留该服务的进程成为被摧毁对象时,服务才被摧毁 

  • Context.BIND_DEBUG_UNBIND  

  通常用于调试场景中判断绑定的服务是否正确,但容易引起内存泄漏,因此非调试目的的时候不建议使用

  • Context.BIND_NOT_FOREGROUND  

  表示系统将阻止驻留该服务的进程具有前台优先级,仅在后台运行。

服务类:

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 BindService extends Service {

// 实例化MyBinder得到mybinder对象;
private final MyBinder binder = new MyBinder();

/**
* 返回Binder对象。
*/
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return binder;
}

/**
* 新建内部类MyBinder,继承自Binder(Binder实现IBinder接口),
* MyBinder提供方法返回BindService实例。
*/
  public class MyBinder extends Binder{

public BindService getService(){
return BindService.this;
}
}


@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
}

启动服务的 Activity 代码:

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
public class MainActivity extends Activity {

/** 是否绑定 */
boolean mIsBound = false;
BindService mBoundService;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
doBindService();
}
/**
* 实例化ServiceConnection接口的实现类,用于监听服务的状态
*/
private ServiceConnection conn = new ServiceConnection() {

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BindService mBoundService = ((BindService.MyBinder) service).getService();

}

@Override
public void onServiceDisconnected(ComponentName name) {
mBoundService = null;

}
};

/** 绑定服务 */
public void doBindService() {
bindService(new Intent(MainActivity.this, BindService.class), conn,Context.BIND_AUTO_CREATE);
mIsBound = true;
}

/** 解除绑定服务 */
public void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(conn);
mIsBound = false;
}
}

@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();

doUnbindService();
}
}

注意 在AndroidMainfest.xml 中对 Service 进行显式声明

判断 Service 是否正在运行:

1
2
3
4
5
6
7
8
9
10
private boolean isServiceRunning() {
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);

 {
if ("com.example.demo.BindService".equals(service.service.getClassName()))   {
return true;
}
}
return false;
}