定义
service Android 中的服务,可以理解为一个没有界面的 activity,存在的意义就是运行本应该放在后台执行的代码,比如下载,音乐播放。
activity 也可以执行后台多线程代码,那为啥还要有 service 这个系统组件,就是因为进程优先级啊,运行 service 服务的进程优先级比运行 activity 的进程高,不容易被杀死,可以获得更久,这就是 service 存在的最大意义
注意服务默认是跑在 mian 主线程里的,使用 content 启动服务不用添加 newTask flag,因为服务没有界面的
进程优先级相关看这里:android 进程相关
service 的 xml 设置属性如下:
<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 的情况下服务才会被激活,否则不会激活。
生命周期
@Override
public void onCreate() {
super.onCreate();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
}
上面这5种就是 service 基本的5中生命周期了,根据 service 启动方式的不同,会走不同的生命周期,但是 onCreate / onDestroy是必会执行的,具体的下面跟着启动方式说。
service 全部的生命周期如下:
服务的启动方式
服务的启动方式有2种:
-
startService:直接启动
- 这样启动的服务生命周期比 app 还长,同启动该服务的 activity 没有任何关系了,只要不被系统回收,就会一直存在
- startService 启动的服务所在进程理论上算是服务进程,但是若这个进程含有 activity 页面,那么实际上不算服务进程,只有启动的是前台服务的进程优先级才可以忽略 activity
- 我们无法和 startService 启动的服务通信,因为 startService 方法没有相关的返回值
-
bindService:绑定启动
- 绑定启动的服务生命周期是和启动该服务的 activity 的生命周期一致,activity 关闭了这个 service 服务也就销毁了
- 还有绑定服务就解绑服务是会直接报错的,注意!
- 系统建议我们在页面关闭时解绑这个页面使用绑定方式启动的服务
- bindService 启动的服务,我们可以拿到一个 ibander 类型的对象,这是我们启动的 service 服务返回给我们的,这样就可以和启动的这个服务进行通讯
startService 直接启动方式
startService 启动方式的代码很简单,使用 intent 就可以启动和关闭这个服务,当然我们没法和这个服务通信。因为service 服务是没有界面的,所以使用 content 的 startService 时,不用加 newTask flag 标记。
Intent intent = new Intent(this, MyService.class);
// 启动服务
startService(intent);
// 关闭服务
stopService(intent);
当然了,为啥代码这个简单就可以呢,因为我们启动服务的工作都不是我们来做的,都是我们去和 AMS(ActivityManageService) 通信,由 AMS 来实现的。
注意点:
- 当我们多次 startService 同一个 service 服务,当这个服务已经启动时,我们再去启动同一个服务,那么会触发 服务的 onStartCommand 生命周期
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
所以 startService 方式启动的 service 生命周期如下:
onCreate(Client首次startService(..)) >> onStartCommand >> onStartCommand - optional ... >> onDestroy(Client调用stopService(..))
Started Service Client 与 Service 通信相关:
- 当Client 调用 startService(Intent serviceIntent) 启动Service 时,Client可以将参数通过Intent直接传递给Service
- Service 执行过程中,如果需要将参数传递给Client,一般可以通过借助于发送广播的方式(此时,Client需要注册此广播)
bindService 绑定启动方式
bindService 的方式启动服务有些繁琐,但是是必须的,因为 service 支持 IPC ,跨进程通讯的。
- 创建 MyServiceConnection 专用与于 service 通信的连接对象
// 这个链接类一般声明为内部类即可
public class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myControl = (MyService.IMyControl) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
- 创建链接对象,声明为全局变量,因为我们 接触绑定还是需要这个链接对象的
// 声明全局变量
MyServiceConnection connection;
// 创建链接对象
connection = new MyServiceConnection();
// 开始绑定服务,flag是标记,标记这个 service 的重启模式,我们一般使用 BIND_AUTO_CREATE 这个值,表示系统在因为内存不足 kill 这个 service 后,在内存充足后会重启这个 service
bindService(intent, connection,flag );
- 定义我们具体的 service 服务对外通信接口
interface IMyControl {
void huniyilian();
}
- 在setvice 类中定义继承 Binder类的具体实现类,并实现我们定义的具体对外通信接口。Binder 内部实现了 IBinder这个接口,IBinder 就是 service 默认 IPC 通信的ALDL 接口,Binder 是这个默认的 ALDL 的具体实现类,Binder 内部主要实现了数据的跨进程通讯方法,这就是我们进行跨进程通许的基础。
class MyControl extends Binder implements IMyControl {
public void huniyilian() {
Log.d("AAA", "唬你一脸,哈哈啊");
}
}
- 在 service 类的 onBind 生命周期中返回我们定义的 Binder 实现类
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind...");
return new MyControl();
}
- 在 activity 中接受这个 IBinder 对象,然后强转成我们定义的具体接口类型,注意这个 IBinder 对象是在这个我们定义的服务链接对象中获取的
public class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myControl = (MyService.IMyControl) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
- 使用这个链接对象接触绑定
if (connection != null) {
unbindService(connection);
}
步奏比较多,涉及3个核心类:
- ServiceConnection
- IBinder
- Binder
我来解释下:
这个 ServiceConnection 接口也是android 定义的默认 ALDL 接口,因为在写法上我们看着是和我们绑定的服务直接获得链接,进行通讯,实际上不是这样的。android 所有的组件启动,通讯都是通过 AMS(ActivityManageService) 来完成的,这是android 系统进程中的一个服务,因为4大组件都有复杂的生命周期和复杂的交互,不是我们直接 new 一个对象就好使的,所以有系统来统一管理。所以呢,这个 ServiceConnection 就是一个特殊的和 AMS 进行 IPC 通讯的接口,然后 AMS 会中转我们和目标服务的通讯,作为一个中间件存在,就和电话局管理所有的电话新路一样,我们没必要去记住每根线路都通向谁
IBinder 同上也是一个 android 默认的 ALDL 接口,是 service 服务和 AMS 进行 IPC 通讯的接口, Binder 是实现了这个 IBinder ALDL 接口的类,内部封装具体的数据进行 IPC 通许的方法,为啥要有这个 IBinder 对象呢,因为这个就是给我们用来继承的,利用类的多态性,就可以把这个具体的对象在进程间传递了,注意这个是 伪进程间传递,不是真正把对象引用传过去(对象引用在进程是无法传递的),而是在目标进程中创建一个和我们这个继承了 IBinder 类的相同类型的类,然后我们可以强转成我们定义的通讯类进行调用了,真是是此类非彼类,我们称之为代理类 proxy,详细的可以去看参考文档的文章或是开发艺术探索这个书
全部代码如下:
service 类:
public class MyService extends Service {
String TAG = "AAA";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate..." + Thread.currentThread().getName());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind...");
return super.onUnbind(intent);
}
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind...");
return new MyControl();
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy...");
super.onDestroy();
}
class MyControl extends Binder implements IMyControl {
public void huniyilian() {
Log.d("AAA", "唬你一脸,哈哈啊");
}
}
interface IMyControl {
void huniyilian();
}
}
activity类:
public class MainActivity extends AppCompatActivity {
Intent intent;
MyServiceConnection connection;
MyService.IMyControl myControl;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d("AAA", "pid:" + Process.myPid());
intent = new Intent(this, MyService.class);
connection = new MyServiceConnection();
}
public void startService(View view) {
startService(intent);
}
public void stopService(View view) {
stopService(intent);
}
public void bindService(View view) {
bindService(intent, connection, BIND_AUTO_CREATE);
}
public void unbinfService(View view) {
if (connection != null) {
unbindService(connection);
}
}
public void huniyilian(View view) {
if (myControl != null) {
myControl.huniyilian();
}
}
public class MyServiceConnection implements ServiceConnection {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
myControl = (MyService.IMyControl) service;
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}
}
bindService 的生命周期:
- onCreate -> onBind -> onUnbind -> onDestroy
onStartCommand 方法的返回值
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand...");
return super.onStartCommand(intent, flags, startId);
}
可以看到 onStartCommand 方法的返回值的,返回值的不同表示不同的意思,约束的是系统在内存不足 kill 改 service 之后的操作
具体有4个返回值:
- START_NOT_STICKY
当Service因为内存不足而被系统kill后,接下来未来的某个时间内,即使系统内存足够可用,系统也不会尝试重新创建此Service。除非程序中Client明确再次调用startService(...)启动此Service - START_STICKY
当Service因为内存不足而被系统kill后,接下来未来的某个时间内,当系统内存足够可用的情况下,系统将会尝试重新创建此Service,一旦创建成功后将回调onStartCommand(...)方法,但其中的Intent将是null,pendingintent除外 - START_REDELIVER_INTENT
与 START_STICKY 唯一不同的是,回调 onStartCommand(...) 方法时,其中的Intent将是非空,将是最后一次调用startService(...)中的intent - START_STICKY_COMPATIBILITY
此值一般不会使用,所以注意前面三种情形就好
AndroidManifest.xml中Service元素常见属性
- andorid:name
服务类名。可以是完整的包名+类名。也可使用.代替包名。 - adroid:exported
其他应用能否访问该服务,如果不能,则只有本应用或有相同用户ID的应用能访问。默认为false。 - android:enabled
标识服务是否可以被系统实例化。true--系统默认启动,false--不启动。(默认值为true) - android:label
显示给用户的服务名称。如果没有进行服务名称的设置,默认显示服务的类名。 - android:process
服务所运行的进程名。默认是在当前进程下运行,与包名一致。如果进行了设置,将会在包名后加上设置的集成名。
如果名称设置为冒号 :开头,一个对应用程序私有的新进程会在需要时和运行到这个进程时建立。如果名称为小写字母开头,服务会在一个相同名字的全局进程运行,如果有权限这样的话。这允许不同应用程序的组件可以分享一个进程,减少了资源的使用。 - android:icon
服务的图标。 - android:permission
申请使用该服务的权限,如果没有配置下相关权限,服务将不执行,使用startService()、bindService()方法将都得不到执行。
service 的进程优先级
详细的去看 android 进程相关 这篇博客,这里只说下特别注意点:
- 一个进程要是activity 和 service 都有,进程的优先级要看 activity 的,除非这个 service 是前台 service ,典型例子就是主线程中,我们启动一个 service,然后 home / back,这个按理说应该是一个服务进程,但实际上这是一个后台进程,优先级低容易被系统kill。
service常用启动方式
在项目开发中,我们最常用的service 启动方式就是 先 startService 把服务按长生命周期启动起来,再 bindService 绑定服务,建立和这个服务的通讯链接。这就是最常用的方法了,代码上没有特殊之处,按照上面的代码写法即可
前台进程
一般 service 启动后,这个 service 所在的进程最多是 优先级5的服务进程,有可能随着机型的不同,优先级可能会是8,优先级是8的时候其实是和 activiyt 一样了,很容易就被杀死,优先级是5的时候其实也是会被杀死的,只是比较不容易罢了,这里有一种叫前台服务的服务,是绝对不会被系统 kill 的,除非你手动 kill
前台服务还是也给服务,核心就是在 service 启动后,启动一个 notifacation 通知和这个 service 关联,因为notifacation 是一直在通知栏可见的,所以这个服务就变成了前台服务了,进程优先级会提高至 1 或 3,小于5的都是不会被系统 kill 的。
代码写法:
public class MyService extends Service {
String TAG = "AAA";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate..." + Thread.currentThread().getName());
Notification.Builder builder = new Notification.Builder(this);
// PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
// new Intent(this, MainActivity.class), 0);
// builder.setContentIntent(contentIntent);
builder.setSmallIcon(R.mipmap.ic_launcher);
builder.setTicker("前台进程");
builder.setContentTitle("前台进程......");
builder.setContentText("我是前台进程");
Notification notification = builder.build();
startForeground(1, notification);
}
在 service 的任何生命周期函数里面使用这个方法 startForeground(1, notification) 就可以了。
注意点:
- 这样写,在 SDK 18之前,在通知栏不是显示通知, 18之后就会显示一条通知了,这是系统默认的对用户的友好提示,要是不想让用户看到,要再处理
- SDK 21 之后,新添加了 startForegroundService(intent) 方法,可以直接启动服务为前台进程了。
带通知界面的前台服务和前台通知的交互看这里:
- 【Android】Service前台服务的使用
- Android Service详解(七)---绑定服务BoundService详解之Messenger双向通信的实现
- Android Service详解(八)---前台服务详解
如何启动一个没有Notification的前台服务呢
这是利用系统的 bug 了,据说这个把bug 在7.1修复了,但是还是可以易用不是,具体步奏:
- 编写2个 service ,A/B,B 就是消除通知的,注意 A和B 要使用同一个 通知的 id,核心就在这里
- 先启动 A 服务
- 再启动 B 服务,然后B 再关闭自己,利用前台服务在关闭后会同步关闭通知的特性,来实现没有通知显示的前台服务,这样 A 就达到目的了
具体的代码看这里: 一个没有Notification的前台服务
使用Messenger 实现跨进程通信
使用 Messenger 可以实现跨进程通信,这样不用我们编写 ALDL
- 服务实现一个 Handler,由其接收来自客户端的每个调用的回调
- Handler 用于创建 Messenger 对象(对 Handler 的引用)
- Messenger 创建一个 IBinder,服务通过 onBind() 使其返回客户端
- 客户端使用 IBinder 将 Messenger(引用服务的 Handler)实例化,然后使用Messenger将 Message 对象发送给服务
- 服务在其 Handler 中(在 handleMessage() 方法中)接收每个 Message
具体例子参考:
最后
Android 5.0之后google出于安全的角度禁止了隐式声明Intent来启动Service。如果使用隐式启动Service,会出没有指明Intent的错误
service 的包活除了设置成前台服务之外,还可以在 onDestory 方法中再把自己跑起来,或者2个服务相互守护
具体写法参考:
参考资料
SuperBigLw:
- Android Service详解(一)---概述
- Android Service详解(二)---StartService
- Android Service详解(三)---IntentService
- Android Service详解(四)---绑定服务BoundService详解之扩展Binder类
- Android Service详解(五)---绑定服务BoundService详解之AIDL的使用
- Android Service详解(六)---绑定服务BoundService详解之AIDL的自定义属性使用
- Android Service详解(七)---绑定服务BoundService详解之Messenger双向通信的实现
- Android Service详解(八)---前台服务详解