一、 什么是Service
第一次明显感觉到Binder的存在实在写Service的时候,虽然那些系统服务都是通过Binder实现的,但是一般来说对开发者并无感知。
像后台音乐播放,后台下载都离不开Service,一般来说Service具有较长时间的运行特性。但是它仍然是Android中的四大组件之一,是Context的子类,他是运行在UI线程中的。
Service可以分为(本文提到的都是本地服务):
- 本地服务:指的是服务和启动服务的activity在同一个进程中
- 远程服务: 指的是服务和启动服务的activity不在同一个进程中。
二、Service的使用
一般来说我们把开启Service的一端叫做客户端,把Service叫做服务端
使用一个Service的步骤
继承Service
类,实现其抽象方法
package me.febsky.aidltest;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
public class MyService extends Service {
public MyService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
从上面代码可以看到,要想实现Service,有个方法必须实现,这个onBind方法的返回值是一个IBinder类型。
在AndroidManifest.xml
中声明上一步创建的Service
<service
android:name=".MyService"
android:enabled="true"
android:exported="true" />
注意这时候,Service和Activity运行在同一个进程中
启动Service
Service的启动有两种方式,一种是通过Context的startService(Intent)
方法,另一种是通过Context的bindService()
的方式打开,这两种不同的启动方式很重要,分别对应后面的不同的生命周期。
三、 Service的生命周期
![](https://img.haomeiwen.com/i797798/9531fd54605d5016.png)
上面这张图是官网给出的,Service的生命周期主要看懂这张图就懂了。
1. 以startService的方式开启Service,也即是上图的左边生命周期
可以写一个Activity,在这个Activity中有个button名字叫做START,button的点击事件如下:
public void startMyService(View view) {
startService(new Intent(MainActivity.this, MyService.class));
}
为了方便观察生命周期,我们把MyService类的一些生命周期函数实现一些,并打印一下log:
public class MyService extends Service {
public MyService() {
}
@Override
public void onCreate() {
super.onCreate();
Log.d("Q_M", "onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("Q_M", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("Q_M", "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
}
当第一次点击START按钮的时候,打印结果调用顺序如下:
onCreate--> onStartCommand
当再次点击START按钮的时候,打印结果调用顺序如下:
onStartCommand
这种service理论上可以无限地运行下去(除非关机,或者系统内存不够,后者用户手动杀死进程),必须Service内部调用stopSelf()方法或者其他组件调用stopService()方法来停止它。
但是startService的开启方式有个小小的缺陷,这种方式下,只能其他组件像MyService发消息,MyService无法直接将结果反馈给调用者(当然可以借助广播发送反馈)。有个非常常见的应用场景,比如我要下载一个文件,这个下载过程最好放到Service中(为啥不放到Activity中,因为有时候用户不想一直盯着这个下载进度看,想关掉当前页面,但是这个时候下载没有完成,这个下载这时候还要后台进行的啊),但是用户还想看到下载进度怎么办,那Service没下载一点,都要向展示下载进度的Activity反馈当前下载的进度。这个时候就引出了Service的另一种开启方式,bindService
2. bindService(intent, connection, flag)对应上图右面的生命周期
这个bindService 只看方法签名就比较麻烦,比上面startService
的方式多了两个参数。
一个Service可以被多个Activity绑定,当所有的activity(客户端)调用unbindService()之后(并且这个MyService没有通过startService的方式开启过),这个Service将自动被系统销毁。要想bind的方式开启Service,必须实现以下几步:
- 客户端Service实现
ServiceConnection
接口 - 服务端实现IBinder接口,并在
onBind
中返回 - 客户端调用bindService,并接收服务端返回的IBinder子类对象
这个地方引出了Binder和AIDL的概念,主要用在上面第二部在Service中的onBind方法中返回一个IBinder的子类。这里先假设Service和他的绑定对应的Activity在同一个进程中,要不然就设计到进程间通信,需要复写Binder的一些方法,比如onTransact等。这些都是重复并且麻烦的工作,Android将其封装成AIDL,然后编译的时候自动生成。【aidl下篇再说】
客户端Activity的实现
package me.febsky.aidltest;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
public class TestActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
//bind button的点击事件
public void bind(View view) {
Intent intent = new Intent(this, MyService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
//unbind button的点击事件
public void unBind(View view) {
unbindService(mServiceConnection);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d("Q_M", "onServiceConnected invoked");
//这里能这么写是因为在同一个进程中
MyService.MyBinder myBinder = (MyService.MyBinder) service;
myBinder.invokeTest();
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.e(getLocalClassName(), "service disconnected");
}
};
}
布局文件就是两个按钮,就不再粘代码了。一个叫bind,一个叫unbind。
服务端Service的实现
package me.febsky.aidltest;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.util.Log;
public class MyService extends Service {
public MyService() {
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d("Q_M", "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onCreate() {
super.onCreate();
Log.d("Q_M", "onCreate");
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d("Q_M", "onDestroy");
}
@Override
public IBinder onBind(Intent intent) {
Log.d("Q_M", "onBind");
return new MyBinder();
}
class MyBinder extends Binder {
public void invokeTest() {
Log.d("Q_M", "MyBinder内的方法被调用");
}
}
}
点击bind按钮,可以根据日志,观察调用顺序:
08-05 13:44:47.763 1213-1213/me.febsky.aidltest D/Q_M: onCreate
08-05 13:44:47.765 1213-1213/me.febsky.aidltest D/Q_M: onBind
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: onServiceConnected invoked
08-05 13:44:47.771 1213-1213/me.febsky.aidltest D/Q_M: MyBinder内的方法被调用
然后,点击unbind按钮:
08-05 13:45:49.164 1213-1213/me.febsky.aidltest D/Q_M: onDestroy
3. 混合开启Service
![](https://img.haomeiwen.com/i797798/9815efb6d6586345.png)
一般来说startService
和bindService
的开启方式并不是单独存在的,一般会混合使用。比如,一个后台音乐service可能因调用 startService()方法而被开启了,稍后,可能用户想要控制播放器或者得到一些当前歌曲的信息,可以通过 bindService()将一个activity和service绑定。这种情况下,stopService()或 stopSelf()实际上并不能停止这个service,除非所有的客户都解除绑定。但是onCreate方法只会执行一次。
网友评论