Service是 Android 四大组件之一。主要有两个使用场景:后台运行和跨进程访问。
Service的运行不依赖与任何用户界面,可以在后台长期运行,即使程序被切换到后台甚至打开另一个应用程序,Service 仍然可以继续保持正常运行,但是需要注意 Service 默认是运行在 UI 线程的,所以如果需要在 Service 中运行耗时操作需要开一个新线程,否则可能会引发 ANR。
此外,组件可以绑定到服务,以与之进行交互,甚至是执行进程间通信 (IPC)。
Intent intent = new Intent(this, MyService.class);
startService(intent);
在 MainActivity中点击 START SERVICE ACTIVITY 按钮进入ServiceActivity
在ServiceActivity中点击 STSRT SERVICE 启动服务可以看到
服务已经启动并在后台运行。现在我们点击返回键回到 MainActivity,再看下回到 MainActivity
我们看到Service依然在后台运行。也就验证了我们上面所说的。
startService()启动的服务通常执行单一的操作,且不会将结果返回给调用方。
绑定
当组件通过bindService()绑定到服务时,服务处于“绑定”状态。绑定服务提供了一个客户端-服务的接口,允许客户端和服务端进行交互、发送请求、获取结果,甚至可以通过IPC跨进程进行这些操作。仅当与组件绑定时绑定服务才运行,多个组件可以绑定到同一个服务,一旦服务与所有组件取消绑定时,系统便会销毁该服务。具体验证在后面讲到服务到生命周期时会进行代码验证。
启动并绑定
服务既可以启动也可以绑定,此时当服务与所有组件取消绑定时,服务并不会停止,具体如何停止服务后面会说到。
官方说明图
1、通过 startService()启动的服务的生命周期
1)、首先我们建一个 MyService 类继承 Service
public class MyService extends Service {
private final static String TAG = MyService.class.getName();
public MyService() {
super();
Log.i(TAG, "MyService Constructor");
}
@Override
public void onCreate() {
super.onCreate();
Log.i(TAG, "MyService onCreate");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
System.out.println("intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
Log.i(TAG, "MyService onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "MyService onBind");
return null;
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "MyService onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
Log.i(TAG, "MyService onDestroy");
super.onDestroy();
}
}
2)、新建两个 Activity(MainActivity,ServiceActivity),分别在两个 Activity 中加入一个两个按钮,一个开启服务,一个停止服务。
//启动服务
findViewById(R.id.btnService).setOnClickListener(view -> {
Intent intent = new Intent(this, MyService.class);
startService(intent);
});
//停止服务
findViewById(R.id.btn_stop_service).setOnClickListener(view -> {
Intent intent = new Intent(this, MyService.class);
stopService(intent);
});
3)、首先在 MainActivity 中点击 START SERVICE按钮,看下日志输出
我们看到依次调用的方法 分别为构造函数=>onCreate=>onStartCommand。
我们在进入到ServiceActivity再次点击START SERVICE 按钮,看下日志输出
从上面我们可以看到当在ServiceActivity再次启动服务时发现只走了 onStartCommand 方法,并没有再走构造方法和 onCreate方法。但是可以看到每个 Activity启动服务时onStartCommand返回的是 startId 都不一样,这个 startId 其实相当于每个申请该服务的组件的一个标识,具体后面会介绍到这个方法时再详细说明。
4)、通过 stopService 停止服务
在 ServiceActivity 中停止发现走了onDestroy 方法
这时候我们回到 MainActivity 后再点击 MainActivity 中的 停止服务按钮,发现并没有日志输出,说明此时没有找到对应的服务,也就是说服务已经由上个页面停止销毁了。也从侧面说明一个Service同时只能有一个实例
2、绑定Service的生命周期
应用组件通过调用 bindService()绑定服务,绑定是异步的。系统随后调用服务的 onBind()方法,该方法返回用于与服务进行交互的 IBinder实例。要接受IBinder 实例,调用的组件必须提供一个 ServiceConnection 对象用于监控与服务的连接,并将其传递给 bindService()。当组件与 Service 连接成功之后回调ServiceConnection的onServicesConnectioned()方法,该方法会返回 IBinder 实例。
下面我们通过代码来看一下:
首先我们新建一个 Service:
public class BindService extends Service {
private final static String TAG = "BindService";
private MyBinder mMyBinder = new MyBinder();
public BindService() {
Log.i(TAG, "BindService constructor");
}
@Override
public void onCreate() {
Log.i(TAG, "BindService onCreate");
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
Log.i(TAG, "BindService onBind");
return mMyBinder;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.i(TAG, "BindService onStartCommand");
System.out.println("intent = [" + intent + "], flags = [" + flags + "], startId = [" + startId + "]");
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onRebind(Intent intent) {
Log.e(TAG, "BindService onRebind");
super.onRebind(intent);
}
@Override
public boolean onUnbind(Intent intent) {
Log.i(TAG, "BindService onUnbind");
return super.onUnbind(intent);
}
public void callService() {
Log.i(TAG, "BindService callService");
}
public class MyBinder extends Binder {
BindService bindService = BindService.this;
/**
* 返回当前绑定的 Service
* @return
*/
public BindService getBindService() {
return bindService;
}
}
}
然后我们在创建一个 BindServiceActivity ,BindServiceActivity里面有两个按钮,一个绑定服务,一个解绑服务:
private static final String TAG = "BindServiceActivity";
private ServiceConnection mServiceConnection;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
initServiceConnection();
//绑定 Service
findViewById(R.id.btn_bind_service).setOnClickListener(view -> {
Intent intent = new Intent(this, BindService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
});
//解绑 Service
findViewById(R.id.btn_unbind_service).setOnClickListener(view -> {
unbindService(mServiceConnection);
});
findViewById(R.id.btn_nav_service).setOnClickListener(view -> {
Intent intent = new Intent(this, BindService2Activity.class);
startActivity(intent);
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mServiceConnection);
}
private void initServiceConnection() {
mServiceConnection = new ServiceConnection() {
/**
* 绑定关系已经建立
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.i(TAG, "onServiceConnected");
BindService.MyBinder binder = (BindService.MyBinder) service;
binder.getBindService().callService();
}
/**
* 绑定关系解除
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
}
};
}
1)、我们先点击绑定按钮,看下各个生命周期的执行顺序:
再点击解绑按钮,看下各个生命周期的执行顺序:
从上面我们可以看出当点击绑定服务时走了 构造函数->onCreate->onBind->onServiceConnection,同时我们看到打印出了 callService 说明我们在 activity 中获取到了 service 实例。
当点击解绑时走了 onUnbind->onDestory 方法。
2)、那么我们重复点击绑定按钮Service 的各个生命周期方法又是什么样的呢?
第一次点击:
第二次点击:
发现没有生命周期函数输出。也就是说在同一个组件中重复调用 bindService 函数时,如果之前已经建立了与服务的连接,就不会重复去绑定了。
3)、上面我们验证了在同一个组件中重复绑定服务的情况,那么在不同组件中点击 bindService 函数呢?我们现在再创建一个 BindService2Activity,代码与BindServiceActivity 一样。同时我们在BindServiceActivity中加个按钮跳转到BindService2Activity。
BindServiceActivity 中点击后:
然后跳转到BindService2Activity,再次点击绑定服务:
我们在BindService2Activity中并没有走 onCreate,甚至连 onBind 都没有走。
通过上面三个验证我们可以总结一下:
a)、当调用 bindService 时,Service 的生命周期为 onCreate->onBind,同时回调了 ServiceConnection 的 onServiceConnected 方法;
b)、当调用 unBindService时,Service 的生命周期为 onUnbind->onDestroy(注意:这里并没有回调onServiceDisconnected,onServiceDisconnected在连接正常解绑当时候时不会调用的。该方法只在Service 被意外销毁时才会调用,例如内存不足,需要释放一些Service,而当前Service正好被释放)。
c)、同一个组件重复调用 bindService并不会有任何生命周期函数的输出。
d)、多个组件可以同时连接到一个服务。不过只有在第一个组件绑定的时候,系统才会调用 onBind 方法来检索 IBinder。之后系统无需调用 onBind方法便可将同一个 IBinder传递给其它调用该服务的组件。
另外只有 Activity、Service、content provider 可以绑定到服务,Receiver 无法绑定到服务。
4)、unBindService
(1)、其实上面的 BindServiceActivity 的代码有一个问题。比如:我们先点击绑定服务按钮进行服务绑定,然后点击返回,会发现报了一个错。
一般出现这种错的原因是Activity销毁的时候没有unBindService。此时只要在 Activity 的 onDestroy 中加入 unBindService 方法即可。
2)、除了上面一种情况会报错以外,我们再来看一种情况:
点击绑定服务->点击解绑服务->点击解绑服务(或者点击绑定服务->解绑服务->返回键) 结果发现又报了一个错,
这个错的意思就是在进行解绑的时候发现服务并没有与当前组件进行绑定或者已经与当前组件解绑过了。那么我们怎么解决这个错误呢。第一反应肯定是要用一个变量来判断这个服务什么时候与组件绑定了,什么时候与组件解绑了。话不多说,直接上代码:
public class BindServiceActivity extends AppCompatActivity {
private static final String TAG = "BindServiceActivity";
private ServiceConnection mServiceConnection;
private boolean isServiceBounded = false;//用于判断是否与 Service 成功连接
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bind_service);
initServiceConnection();
//绑定 Service
findViewById(R.id.btn_bind_service).setOnClickListener(view -> {
Intent intent = new Intent(this, BindService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
});
//解绑 Service
findViewById(R.id.btn_unbind_service).setOnClickListener(view -> {
safeUnbindService();
});
findViewById(R.id.btn_nav_service).setOnClickListener(view -> {
Intent intent = new Intent(this, BindService2Activity.class);
startActivity(intent);
});
}
@Override
protected void onDestroy() {
super.onDestroy();
safeUnbindService();
}
private void safeUnbindService() {
if (isServiceBounded) {
unbindService(mServiceConnection);
isServiceBounded = false;
}
}
private void initServiceConnection() {
mServiceConnection = new ServiceConnection() {
/**
* 绑定关系已经建立
* @param name
* @param service
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
isServiceBounded = true;
Log.i(TAG, "onServiceConnected");
BindService.MyBinder binder = (BindService.MyBinder) service;
binder.getBindService().callService();
}
/**
* 绑定关系解除
* @param name
*/
@Override
public void onServiceDisconnected(ComponentName name) {
Log.i(TAG, "onServiceDisconnected");
isServiceBounded = false;
}
};
}
}
这样的话重复点击 unbindService 就不会有问题了。
几点建议:
如果你需要在组件的生命周期的进入和退出时对服务进行绑定和解绑的话,需要注意一下几点:
a)、如果你只需要在 Activity 可见时与服务进行交互,那么你应该在 onStart()中对服务进行绑定,在 onStop()中对服务进行解绑。
b)、如果你希望在 Activity在后台运行的状态下仍可以接受响应,你可以在 onCreate 中进行绑定,在 onDestory 中进行解绑。
c)、通常情况下,切勿在 onResume 和 onPause 中进行服务的绑定和解绑,因为每一次 Activity 生命周期的转换都会调用 onResume 和 onPause,应该尽量减少这些转换处理。另外,如果有多个 Activity 绑定到同一个服务时,并且两个 Activity 发生了生命周期的转换,那么,在当前的Activity在下一个 Activity 绑定服务之前解除了服务绑定,那么可能会销毁并重建服务。
3、startService 和 bindService 混合使用
使用场景:在activity中要得到service对象进而能调用对象的方法,但同时又不希望activity finish的时候service也被destroy了,startService和bindService混合使用就派上用场了。
1)、先 start再 bind,生命周期函数调用顺序如下
2)、先 bind 再 start,生命周期函数调用顺序如下
3)、当我们通过上述两种情况之后,先点击停止服务,再点击解绑服务,生命周期如下,
我们会发现当我们点击 stop 的时候并没有生命周期函数调用,当我们再点击解绑的时候会走 onUnbind->onDestroy
4)、先解绑服务,再停止服务,生命周期如下
当我们点击解绑的时候走的是 onUnbind,再点击 停止服务走的是 onDestroy。
通过3和4我们可以知道,当服务同时被 startService 和 bindService的时候,只有当解绑和 stop 都执行过(被系统回收除外),service 才会销毁。