Service全面总结
Posted 渡口一艘船
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Service全面总结相关的知识,希望对你有一定的参考价值。
service
本篇博文主要介绍Service相关知识,具体目录如下
0x00 什么是Service
- Service是一个应用程序组件,可以在后台长时间运行的操作,不提供用户界面;
- 一个应用程序可以启动一个服务,它将继续在后台运行,即使用户切换到另外一个应用
- 一个组件可以绑定到一个服务与它交互,甚至执行进程间通信(IPC),如处理网络传输、音乐播放、执行文件I/O,与content provider进行交互等。
0x01 服务的分类
- 按照运行地点分类
类别 | 区别 | 优点 | 缺点 | 应用 |
---|---|---|---|---|
本地服务(Local Service) | 该服务依附在主进程上 | 服务依附在主进程上而不是独立的进程,这样在一定程度上节约了资源,另外Local服务因为是在同一进程因此不需要IPC,也不需要AIDL。相应bindService会方便很多。 | 主进程被Kill后,服务便会终止。 | 如:音乐播放器播放等不需要常驻的服务。 |
远程服务(Remote Service) | 该服务是独立的进程 | 服务为独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程提供服务具有较高的灵活性。 | 该服务是独立的进程,会占用一定资源,并且使用AIDL进行IPC稍微麻烦一点。 | 一些提供系统服务的Service,这种Service是常驻的。 |
- 按运行类型分类
类别 | 区别 | 应用 |
---|---|---|
前台服务 | 会在通知栏显示onGoing的 Notification | 当服务被终止的时候,通知一栏的 Notification 也会消失,这样对于用户有一定的通知作用。常见的如音乐播放服务。 |
后台服务 | 默认的服务即为后台服务,即不会在通知一栏显示 onGoing的 Notification。 | 当服务被终止的时候,用户是看不到效果的。某些不需要运行或终止提示的服务,如天气更新,日期同步,邮件同步等。 |
- 按使用方式分类
类别 | 区别 |
---|---|
startService启动的服务 | 主要用于启动一个服务执行后台任务,不进行通信。停止服务使用stopService。 |
bindService启动的服务 | 方法启动的服务要进行通信。停止服务使用unbindService。 |
同时使用startService、bindService 启动的服务 | 停止服务应同时使用stopService与unbindService。 |
0x02 生命周期
如使用方式分类所提,service使用常分为两大类,start、bind
如果一个应用程序组件(比如一个activity)通过调用startService()来启动服务,则该服务就是被“started”了。一旦被启动,服务就能在后台一直运行下去,即使启动它的组件已经被销毁了。
通常,started的服务执行单一的操作并且不会向调用者返回结果。比如,它可以通过网络下载或上传文件。当操作完成后,服务应该自行终止。如果一个应用程序组件通过调用bindService()绑定到服务上,则该服务就是被“bound”了。bound服务提供了一个客户端/服务器接口,允许组件与服务进行交互、发送请求、获取结果,甚至可以利用进程间通信(IPC)跨进程执行这些操作。绑定服务的生存期和被绑定的应用程序组件一致。
多个组件可以同时与一个服务绑定,不过所有的组件解除绑定后,服务也就会被销毁。
二者的生命周期如下:
对应的方法解释如下:
- 4个手动调用的方法
手动调用方法 | 作用 |
---|---|
startService() | 启动服务 |
stopService() | 关闭服务 |
stopSelf() | 关闭服务 |
bindService() | 绑定服务 |
unbindService() | 解绑服务 |
- 5个内部调用的方法
内部调用的方法 | 作用 |
---|---|
onCreat() | 创建服务 |
onStartCommand() | 开始服务 |
onDestroy() | 销毁服务 |
onBind() | 绑定服务 |
onUnbind() | 解绑服务 |
其中需要注意以下几点:
- startService()和stopService()只能开启和关闭Service,无法操作Service;bindService()和unbindService()可以操作Service
- startService开启的Service,调用者退出后Service仍然存在;BindService开启的Service,调用者退出后,Service随着调用者销毁。
0x03 如何使用
建议参照demo学习https://github.com/xsfelvis/ServiceAIDLStudyDemo.git
本地Service(startService)
通过start启动的service一旦被启动,服务一般会在后台一直运行即使启动它的的组件已经销毁了,而且不会像调用者返回结果
,如可以通过它进行网络下载或者上传文件,当操作完成后,该服务自行终止。
Step 1 在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 |
step 2 新建子类继承自Service类
需要重写onCreate()、onStartCommand()、onDestroy()和onBind()方法
step 3 构建用于启动Service的intent对象
step 4 调用startService启动,调用stopService/stopSelf停止服务
@Override
public void onCreate()
//这里配置一些信息
//启动运行服务的线程。
//请记住我们要创建一个单独的线程,因为服务通常运行于进程的主线程中,可我们不想阻塞主线程。
//我们还要赋予它后台运行的优先级,以便计算密集的工作不会干扰我们的UI。
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
//获取handlerThread的loop队列并用于Handler
mServiceLooper = handlerThread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
Log.d(TAG, "onCreate");
@Override
public int onStartCommand(Intent intent, int flags, int startId)
msgStr = intent.getStringExtra("startService");
Log.d(TAG, "onStartCommand getExtraString = " + msgStr);
//对于每一个启动请求,都发送一个消息来启动一个处理
//同时传入启动ID,以便任务完成后我们知道该终止哪一个请求。
Message message = mServiceHandler.obtainMessage();
message.arg1 = 1;
mServiceHandler.sendMessage(message);
//如果我们被杀死了,那从这里返回之后被重启
return START_STICKY;
由于Service也是运行在主线程中,如果需要执行一些耗时操作需要放到相应的子线程中处理,谷歌内置了一个IntentService(异步处理服务)
,
它会新开一个线程:handlerThread在线程中发消息,然后接受处理完成后,会清理线程,并且关掉服务。
IntentService有以下特点:
(1) 它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。
(2) 创建了一个工作队列,来逐个发送intent给onHandleIntent()。
(3) 不需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。
(4) 默认实现的onBind()返回null
(5) 默认实现的onStartCommand()的目的是将intent插入到工作队列中
其中intentService在5.0系统中需要显示启动
在之前的例子中我们自己手动维护了一个handleThread去处理耗时操作,intentService已经自带了,然后用户只要是实现onHandleIntent去处理新的业务即可
/**
* IntentService从缺省的工作线程中调用本方法,并用启动服务的intent作为参数。
* 本方法返回后,IntentService将适时终止这个服务。
*/
@Override
protected void onHandleIntent(@Nullable Intent intent)
//根据Intent的不同进行不同的事务处理
String taskName = intent.getExtras().getString("taskName");
switch (taskName)
case "task1":
Log.d(TAG, "do task1");
break;
case "task2":
Log.d(TAG, "do task2");
break;
default:
break;
以上两种均在demo中有所实现
可通信Service(bind)
如果一个应用程序组件通过调用bindService()绑定到服务上,bound服务提供了一个客户端/服务器接口,
允许组件与服务进行交互、发送请求、获取结果,甚至可以利用进程间通信(IPC)跨进程执行这些操作
。绑定服务的生存期和被绑定的应用程序组件一致。多个组件可以同时与一个服务绑定,不过所有的组件解除绑定后,服务也就会被销毁。
使用场景
当客户端和服务位于同一个应用程序的进程中,(如一个音乐应用需要把Activity绑定到它自己的后台音乐播放上)使用步骤
在你的服务中创建一个Binder的实例,通常需要实现以下3点之一
- 包含了可供客户端调用的公共方法,如demo中的
getHelloBoundService()
方法 - 返回当前Service实例,其中包含了可供客户端调用的公共方法,如demo中的
getRandomNumber
- 或者,返回内含服务类的其它类的一个实例,服务中包含了可供客户端调用的公共方法,
- 包含了可供客户端调用的公共方法,如demo中的
从回调方法onBinder()返回Binder的实例
- 在客户端中,在回调方法onServiceConnected()中接收Binder并用所提供的方法对绑定的服务进行调用,不过服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(cast)返回的对象并调用对象的API。
服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。
Tips
- 在没有bind时执行unbind,会报Service not registered crash,可以增加标志位控制bind、unbind可以参看demo中的unbind操作
- ServiceConnection中重写2个方法
onServiceConnected
、onServiceDisconnected
,其中bindService会触发onServiceConnected,而unbinderService不会触发onServiceDisconnected;onServiceDisconnected
在系统在内存不足的时候可以优先杀死这个服务
前台service
前台service和后台service最大的区别在于
- 前台service在下来通知栏有显示通知,但是后台service没有
- 前台service优先级较高,不会由于系统内存不足而被回收,而后台service优先级比较低,当系统出现内存不足情况时有可能被回收
与普通service使用类似,核心是增加构建通知部分的处理,具体可以查看demo中代码
public int onStartCommand(Intent intent, int flags, int startId)
//API11之后构建Notification的方式
Notification.Builder builder = new Notification.Builder(this);
Intent frontServiceIntent = new Intent(this, MainActivity.class);
PendingIntent frontServicePeningIntent = PendingIntent.getActivity(this, 0, frontServiceIntent, 0);
builder.setContentIntent(frontServicePeningIntent)
.setContentTitle("下拉列表中的Title")
.setContentText("要显示的内容")
.setSmallIcon(R.mipmap.ic_front_small)
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_front_big))
.setWhen(System.currentTimeMillis());
Notification notification = builder.getNotification();
notification.defaults = Notification.DEFAULT_SOUND;
// 参数一:唯一的通知标识;参数二:通知消息。
startForeground(110, notification);// 开始前台服务
return START_STICKY;
远程service
主要是为了让Service与多个应用程序的组件进行跨进程通信(IPC),这里涉及到两个概念
- IPC:Inter-Process Communication,即跨进程通信
- AIDL:Android Interface Definition Language,即Android接口定义语言;用于让某个Service与多个应用程序组件之间进行跨进程通信,从而可以实现多个应用程序共享同一个Service的功能。
AIDL(Android 接口定义语言)与您可能使用过的其他 IDL 类似。 您可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。 在 Android 上,一个进程通常无法访问另一个进程的内存。 尽管如此,进程需要将其对象分解成操作系统能够识别的原语,并将对象编组成跨越边界的对象。 编写执行这一编组操作的代码是一项繁琐的工作,因此 Android 会使用 AIDL 来处理。
并且谷歌特意注明了AIDL使用的场景
注:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,请您务必理解绑定服务。
首先新建一个aidl文件
interface IAidlService
void aidlService();
然后make一下,在build/generated/source/aidl文件下生成一个接口文件
在使用到通信的地方使用这个接口文件中的api,AIDLService1.Stub.asInterface()
mAidlServiceConnection = new ServiceConnection()
//重写onServiceConnected()方法和onServiceDisconnected()方法
//在Activity与Service建立关联和解除关联的时候调用
@Override
public void onServiceDisconnected(ComponentName name)
//在Activity与Service建立关联时调用
@Override
public void onServiceConnected(ComponentName name, IBinder service)
//使用AIDLService1.Stub.asInterface()方法将传入的IBinder对象传换成了mServerAidlService对象
mServerAidlService = IAidlService.Stub.asInterface(service);
try
//通过该对象调用在MyAIDLService.aidl文件中定义的接口方法,从而实现跨进程通信
mServerAidlService.aidlService();
catch (RemoteException e)
e.printStackTrace();
;
可以看出二者不在一个线程中
具体代码在demo中~
0x04 一些容易混淆的点
Service和Thread的区别
官方有两点描述
- 1.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. - 2.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).
第二点清楚提到不是一个thread,只是有些时候二者均工作在后台而已。
service和调用者之间的通讯都是同步的(不论是远程service还是本地service),它跟线程一点关系都没有!
service和intentService之间区别
- 1.Service:依赖于应用程序的主线程不要误以为是独立的进程 or 线程,因此不能处理耗时操作,否则就会报ANR(Activity—–>5秒
Broadcast—–>10秒,Service—–>20秒),而intentService内部启动一个HandleThread工作线程来去处理耗时任务 -
- Service需要主动调用stopSelf()来结束服务,而IntentService不需要(在所有intent被处理完后,系统会自动关闭服务,内部调用了stopself()方法)
IntentService与线程的区别
- intentService内部采用了HandlerThread实现,作用类似于后台线程;与后台线程相比,IntentService是一种后台服务,优势是:优先级高(不容易被系统杀死),从而保证任务的执行
- 在应用中,如果是长时间的在后台运行,而且不需要交互的情况下,使用服务。
- 同样是在后台运行,不需要交互的情况下,如果只是完成某个任务,之后就不需要运行,而且可能是多个任务,需要长时间运行的情况下使用线程。
- 如果任务占用CPU时间多,资源大的情况下,要使用线程。
0x05 小结
service是Android 四大组件之一,掌握好它对平时的开发有着莫大益处,本文只是介绍了service的常见用法和一些容易混淆的点,还有一些深入的点比如aidl数据传递、自定义notification定制化和在不同android版本的坑,这些都需要在实际开发中遇到再去针对性处理了~
参考文档
- https://developer.android.com/guide/components/services.html?hl=zh-cn
- https://developer.android.com/guide/components/bound-services.html?hl=zh-cn
- https://developer.android.com/guide/components/aidl.html?hl=zh-cn
- http://www.jianshu.com/p/1e49e93c3ec8
- http://www.jianshu.com/p/8d0cde35eb10
- http://blog.csdn.net/luoyanglizi/article/details/51980630
以上是关于Service全面总结的主要内容,如果未能解决你的问题,请参考以下文章