谨以文章记录学习历程,如有错误还请指明。
前言
记得我的第一部手机是Nokia的,当时的Symbian系统相较于其他手机最突出的一点就是,Symbian系统支持后台功能,毕竟这使得我们可以一边听着音乐一边聊着qq,在当时这可是很酷的一件事,毕竟Symbian系统出现以前,只能用一部mp3听音乐,另外再拿一部手机打电话。
Android显然看到了这个发光点,自始至终就支持后台功能,IOS随着时间的推移也发现这个功能的重要性,在后续版本加入了后台功能。
本文就将针对这一炫酷的后台功能(Service
)开始讲解,深入总结Service
的方方面面的知识。
简介
- 服务(
Service
) 是Android实现程序后台运行的解决方案,是Android四大组件之一。 - 适合于执行不需要和用户交互而且需要长期运行的任务。
生命周期
首先放上Google官方文档给出的生命周期示意图:
(单独使用startService()
和单独使用bindService()
时的生命周期)
回调方法介绍
回调方法 | 说明 |
---|---|
onCreate() | 首次创建服务时调用,执行一次性设置程序(在调用 onStartCommand() 或 onBind() 之前)。如果服务已在运行,则不会调用此方法。 |
onStartCommand() | 当另一个组件(如 Activity )通过调用 startService() 请求启动服务时,系统将调用此方法。一旦执行此方法,服务即会启动并可在后台无限期运行。 如果您实现此方法,则在服务工作完成后,需要由您通过调用 stopSelf() 或 stopService() 来停止服务。(如果您只想提供绑定,则无需实现此方法。) |
onDestroy() | 当服务不再使用且将被销毁时,系统将调用此方法。服务应该实现此方法来清理所有资源,如线程、注册的侦听器、接收器等。 这是服务接收的最后一个调用。 |
onBind() | 当另一个组件想通过调用 bindService() 与服务绑定(例如执行 RPC )时,系统将调用此方法。在此方法的实现中,您必须通过返回 IBinder 提供一个接口,供客户端用来与服务进行通信。请务必实现此方法,但如果您并不希望允许绑定,则应返回 null 。 |
onUnbind() | 解绑服务 |
特别事项
-
onCreate()
只调1次 -
startService()
调用次数=onStartCommand()
调用次数。 -
Service
通过bindService()
被绑定启动后,将一直运行,直到调用unBindService()
或者调用bindService()
的Context
被销毁(即 使用bindService()
建立的连接断开)。
尽管
Context
销毁时,Service
会自动停止,但只要调用bindService()
进行绑定,仍然还是需要在某处调用unBindService()
解除绑定
-
onStartCommand()
方法必须返回一个整数,描述系统在杀死服务后如何继续运行。- START_NOT_STICKY:
不会重新创建服务,除非有未发送的intent
。当应用程序可以简单地重新启动任何未完成的工作时,这是避免在不必要的情况下运行服务的最安全的选项。 - START_STICKY
重新创建服务并调用onStartCommand()
,但不会再次送入上一个intent
。相反除非有未发送完的启动服务的intent
,否则使将用null intent
调用onStartCommand()
。这适用于不执行命令的媒体播放器(或类似的服务),但它们会持续运行并随时待命。 - START_REDELIVER_INTENT
重新创建服务并调用onStartCommand()
,并将上一个intent
交付给服务。任何未处理的intent
都将依次传递。这适用于需要立即恢复工作的活跃服务,例如下载文件。
- START_NOT_STICKY:
手机屏幕旋转时,Activity会销毁 & 重新创建,因此使用
bindService()
建立的连接会断开
startService()与bindService()混合使用时的生命周期:
我们给出两个例子:
startService()
->bindService()
或者bindService()
->startService()
,这二者顺序仅仅影响onBind()
和onStartCommand()
的顺序- 多次重复
startService()
或bindService()
也同样遵循上述 特别事项
- 按顺序1,2,3,4执行
(1)startService()
:调用onCreate()
->onStartCommand()
(2)bindService()
:调用onBind()
(3)stopService()
:没有调用onDestory()
,Service
仍然在运行!
(4)unbindService()
:调用onUnbind()
->onDestory()
,此时Service关闭!
官方解释:若被停止的服务依然有ServiceConnection
与其绑定,则服务不能销毁,直至我们把所有ServiceConnection
解绑
- 将上述3,4调换
(1)startService()
:调用onCreate()
->onStartCommand()
(2)bindService()
:调用onBind()
(3)unbindService()
:调用onUnbind()
,Service
仍然在运行!
(4)stopService()
:调用onDestory()
,此时Service关闭!
官方解释:
当所有ServiceConnection
解绑后,系统会自动销毁服务(不包括同时用startService()
启动的情况)。此时,我们不得不再调用一次stopService()
来销毁它
给出Google官方的图,方便直观的理解:
分类
按启动方式:
(上面生命周期已经介绍的很详细了,只简要概括)
类别 | 区别 | 应用场景 |
---|---|---|
Started Service ( startService() 启动的服务) |
停止服务使用stopService() |
不需要与Service 通信服务长期运行 |
Bound Service ( bindService() 启动的服务) |
停止服务使用unbindService() |
需要与Service 通信 |
混合服务Service ( startService() 同时也 bindService() 启动的服务) |
停止服务应同时使用stopService(() 与unbindService() |
需要与Service 通信服务长期运行 |
(官方的分类只有前两种,上面的混合服务Service只是我个人的看法,也是为了后续方便介绍如何使用)
按运行地点
类别 | 区别 | 优点 | 缺点 | 应用 |
---|---|---|---|---|
本地服务(Local) | 该服务依附在主进程上 | 节约了资源 通信方便不需要IPC,也不需要AIDL |
主进程被Kill后,服务便会终止。 | 我们大部分写的Service就是这种服务,如音乐后台播放 |
远程服务(Remote) | 该服务是独立的进程 | 有较高的灵活性,Activity所在进程被Kill的时候,该服务依然在运行 | 独立进程,占用一定资源 使用AIDL进行IPC麻烦 |
一些提供系统服务的Service,这种Service是常驻的。 |
按运行类型
类别 | 区别 | 应用 |
---|---|---|
前台服务 | 通知栏显示Notification | 当服务被终止的时候,通知栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如音乐播放服务。 |
后台服务 | 默认的服务即为后台服务,用户不可见 | 当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。 |
具体实现
实际上,在上述不同场景下的分类多多少少都有所重叠,比如:
- Bound Service包括 本地(
Binder
) 和 远程服务(Message
或AIDL
)- Started Service/Bound Service都可以在位于前台,也都可以在后台
Demo地址
GitHub:DemoForService
1. Started Service
- 继承
Service
类 - 复写
onCreate()
,onStartCommand()
,onDestroy()
,onBind()
方法 - 在
AndroidManifest.xml
中注册 - 通过
startService()
,stopService()
启动和停止服务 - 前台通知只需要再合适的地方调用
startForeground()
,Demo 通知栏适配Android8.0
下面以Demo具体说明:
MainActivity.java
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private Button mButton1;
private Button mButton2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton1 = findViewById(R.id.button1);
mButton2 = findViewById(R.id.button2);
mButton1.setOnClickListener(this);
mButton2.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.button1:
//启动服务
startService(new Intent(MainActivity.this,StartedService.class));
break;
case R.id.button2:
//停止服务
stopService(new Intent(MainActivity.this,StartedService.class));
break;
}
}
}
StartedService.java
//继承Service类
public class StartedService extends Service {
//TAG标记
private static final String TAG = "StartedService";
//复写onCreate()方法,输出一段内容
@Override
public void onCreate() {
Log.d(TAG, "onCreate: "+ TAG);
super.onCreate();
}
//复写onStartCommand(方法),输出一段内容
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: "+ TAG);
//适配Android 8.0的通知
Intent notificationIntent = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//实例化通道channel
final String ID = "channalID";
final String name = "name";
NotificationChannel channel = new NotificationChannel(ID,name, NotificationManager.IMPORTANCE_HIGH);
//调用该方法之后,该channel的ID才能被通知使用
manager.createNotificationChannel(channel);
//添加建立好的通道的ID
Notification notification = new NotificationCompat.Builder(this,ID)
.setContentTitle("前台服务标题")
.setContentText("前台服务内容")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setChannelId(ID)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build();
//开启前台服务
startForeground(1,notification);
return super.onStartCommand(intent, flags, startId);
}
//复写onDestroy()方法,输出一段内容
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: "+TAG);
stopForeground(true);
super.onDestroy();
}
//不需要通信,返回空即可
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidMenifest.xml
前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收前台Service优先级较高,不会由于系统内存不足而被回收;后台Service优先级较低,当系统出现内存不足情况时,很有可能会被回收
Androidmanifest里Service的常见属性说明
属性 | 说明 | 备注 |
---|---|---|
android:name | Service的类名 | |
android:label | Service的名字 | 若不设置,默认为Service类名 |
android:icon | Service的图标 | |
android:permission | 申明此Service的权限 | 有提供了该权限的应用才能控制或连接此服务 |
android:process | 表示该服务是否在另一个进程中运行(远程服务) | 不设置默认为本地服务 remote则设置成远程服务 |
android:enabled | 系统默认启动 | true:Service 将会默认被系统启动 不设置则默认为false |
android:exported | 该服务是否能够被其他应用程序所控制或连接 | 不设置默认此项为 false |
结果展示
2. Bound Service
2.1 本地服务-Binder实现
- 继承
Service
类,并新建子类继承Binder
类,写入关联方法,创建实例 - 调用方通过
ServiceConnection
实例与Service
子类建立联系 - 复写
onCreate()
,onBind()
,onUnBind()
,onDestroy()
下面给出部分Demo代码作介绍:
MainActivity.java
- 在Activity通过调用MyBinder类中的public方法来实现Activity与Service的联系
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final String TAG = "MainActivity";
private LocalService.LocalBinder mBinder;
private LocalService mLocalService;
...
private Button mButton3;
private Button mButton4;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
mButton3 = findViewById(R.id.button3);
mButton4 = findViewById(R.id.button4);
...
mButton3.setOnClickListener(this);
mButton4.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
...
//调用bindService()
case R.id.button3:
//这里传入BIND_AUTO_CREATE表示在Activity和Service建立关联后自动创建Service
//这会使得LocalService中的onCreate()方法得到执行,但onStartCommand()方法不会执行
bindService(new Intent(MainActivity.this,LocalService.class),mConnection,BIND_AUTO_CREATE);
break;
//调用unBindService()
case R.id.button4:
unbindService(mConnection);
break;
}
}
//创建ServiceConnection的匿名类
private ServiceConnection mConnection = new ServiceConnection() {
//在建立关联的时候调用
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//得到MyBinder的实例,可以调用MyBinder中的方法
mBinder = (LocalService.LocalBinder) service;
//得到LocalService的实例,可以调用其中方法
mLocalService = mBinder.getService();
//调用MyBinder类中的方法
mBinder.showBinderInfo();
//调用LocalService服务中的方法
mLocalService.showServiceInfo();
}
//注意:调用unBindService()方法时,不会调用该方法!!!
//该方法只有在系统回收该服务时候才会调用!!!
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
}
LocalService
- 在新建子类继承Service类,并新建一个子类继承自Binder类、写入与Activity关联需要的方法、创建实例
//继承Service类
public class LocalService extends Service {
private static final String TAG = "LocalService";
//创建LocalBinder实例
private final LocalBinder mBinder = new LocalBinder();
//新建LocalBinder类继承自Binder类
public class LocalBinder extends Binder{
//返回当前服务对象的方法
LocalService getService(){
return LocalService.this;
}
//LocalBinder中的公开方法,供建立联系的Activity访问
void showBinderInfo(){
Log.d(TAG, "showInfo: 我是来自Binder的方法");
}
}
//复写方法
@Override
public void onCreate() {
Log.d(TAG, "onCreate: "+ TAG);
super.onCreate();
}
//复写方法
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: "+TAG);
return super.onStartCommand(intent, flags, startId);
}
//复写方法
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
//复写方法
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: "+TAG);
super.onDestroy();
}
//返回当前LocalBinder实例
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
//服务中的公开方法,供建立联系的Activity访问
public void showServiceInfo(){
Log.d(TAG, "showServiceInfo: 我是来自LocalService的方法");
}
}
AndroidManifest.xml
结果展示
注意:多次解绑会报错(
java.lang.IllegalArgumentException
),原因大概是解绑之后,找不到该注册的服务。详细需要深入源码找原因。
2.2远程服务-IPC&AIDL
具体见文章:Android多线程基础解析
总结
- 本文对Android中的服务Service的方方面面做出详细的总结。
- 笔者水平有限,如有错漏,欢迎指正。
- 接下来我也会将所学的知识分享出来,有兴趣可以继续关注whd_Alive的Android开发笔记
欢迎关注whd_Alive的
- 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。