- Service的使用场景
- Service的后台工作对比Thread的后台工作
- Start型与Bound型启动模式的理解以及使用场景
- onBinder()方法返回的IBinder的含义,如何简单的获得IBinder对象
- Android UniversalMusicPlayer中的Service实例
概念
Service是四大组件之一,他有两个作用:在无需UI的情况下执行后台工作,提供功能给其他应用使用。
本文主要讨论第一个作用,也就是后台工作。这里的后台工作是与Activity执行的前台工作相对应的,他的后台指的是不需要界面交互,而工作指的是一系列服务于用户的功能。
Service与Activity一样继承自Context,因此它可以使用一系列Android运行环境相关的功能,比如读取资源文件。我们可以视Service跟Activity为同一级别,当我们的应用需要提供一些功能,且无需UI交互时,可以选择使用Service。
我们常说Work Thread执行的也是后台工作,对比Service的后台工作与Thread的后台工作,前者可以理解为“界面之外”,后者则是“UI线程之外”,前者关注的是无需界面交互的功能,后者负责不阻塞UI线程的执行操作。Service在默认的情况下是运行在进程的主线程中的
,因此在Service中执行耗时操作也会阻塞UI线程,此时我们需要使用工作线程以避免ANR。Android系统为我们提供了IntentService类,他是一个自带了工作线程的Service。
启动模式
Service的启动模式分为Start型与Bound型。
Start型
Start型由startService()
方法启动,首次创建时Service的onCreate()
会被调用,随后onStartCommand()
被调用。当onStartCommand()
被调用后Service会无限期的执行下去,我们需要主动负责Service的结束,通过调用stopSelf()
或stopService
。
Start型的Service响应我们的指令并执行任务(onStartCommand()
),当任务完成时Service可以自行结束(stopSelf()
),当任务不再需要执行也可以由外界结束(stopService()
)。
Bound型
Bound型由bindService()
方法启动,首次创建Service时会调用onCreate()
,随后调用onBind()
并返回一个IBinder
,IBinder能够让我们与Service交互,我们可以向Service发出请求并获得结果,就好像Client-Server架构一样。因此我们把调用bindService()的组件称为Client,Bound型Service的结束不需要我们负责,当所有Client都通过unBindService()
解绑后Service就会销毁。
两种类型非常好区分,他们的调用方式不同,生命周期的回调也不同。而一个Service可以即是Start型又是Bound型,只要他通过两种方法都启动过就行了,这个时候Service的结束条件是调用了stop方法且所有Client的连接都断开了,否则就能持续存活并运转。这也好理解,用两种方法启动过就需要用两种方法结束。
两种模式的使用场景
作为开发者,我们更关注的是如何选择启动类型。那我认为我们首先要能理解设计者对这两种模式的定位与设计:
- Start型的Service关注的是任务,我们启动Service并让他按我们的指令去执行任务,当Service的任务完成或者我们不再需要它执行时,他的使命就结束了。
- Bind型的Service关注的是客户,客户与他连接并持续获得服务,当所有的客户都断开连接,他的使命结束。
所以我们根据自己的意图选择启动类型就可以了。如果我们的目的是启动一段与界面无关的工作,无论当前界面切换还是消失这段工作都能持续进行下去,那就选择Start型。如果我们希望当前组件能够与Service做些交互,把Service看成当前组件的一个功能模块,那就选择Bound型。如果两种意图兼而有之,那就通过两种启动类型启动。
后面会以Android UniversalMusicPlayer为例看下启动模式的选择与使用。
onBind()与IBinder
我曾见过身边同事对IBinder产生误解,看到IBinder就联想到IPC(跨进程通信),继而认为onBind()方法也是为跨进程场景服务的,对其敬而远之。实际上IBinder只是允许远程操作的对象的基本接口,如同Serializable是序列化操作的基本接口。我们实现IBinder以确保Service提供的操作对象能顺利传输给其他组件,开发中我们的重点应该放在其他组件如何通过IBinder与Service交互。
官方提供给了我们三种方案去获得IBinder对象:
- 继承Binder类
- 使用Messenger类
- 使用AIDL
继承自Binder类
当我们确保Service仅为本应用提供服务并且与Client总是运行在同一个进程
中时,最佳的方法是扩展Binder对象,并为Binder对象设置public方法以便组件调用。
使用Messenger类
当我们需要跨进程通信
时,最简单的方法是创建一个Messenger类,Messenger能与一个Handler建立关联并向他发送消息,Messenger类自带了一个方法getBinder()
。我们需要在Service中定义一个Handler,决定Service如何响应消息,然后将Messenger跟Handler关联,这样其他组件就能通过Messenger向Service发送消息了。
这是最简单的实现IPC的方案,所有对Messenger发送的消息都集中到了一个线程中,因此也不会有线程安全之类的问题需要考虑。
使用AIDL
AIDL是系统级别的实现IPC的方案,上面的Messenger也是基于AIDL实现的,对这一块我没有深入研究,上面两个方案基本能覆盖我们的需求。
onBind实例 - 自定义Binder类
onBind()的实现实际上非常容易,我们来看下官方的demo。
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
// 获得Service对象,继而可以调用Service中的所有公开方法
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
// 返回自定义Binder的实例
return mBinder;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
unbindService(mConnection);
mBound = false;
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute) */
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// occur in a separate thread to avoid slowing down the activity performance.
// 连接成功时,操作Service对象
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** Defines callbacks for service binding, passed to bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
// 获得连接的Service对象
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
逻辑非常简单
- 在Binder中定义组件可执行的方法,或者直接返回Service对象
- 组件成功连接Service时获得IBinder对象
- 组件确认Service保持连接,然后通过IBinder对象与Service交互,demo中自定义Binder直接返回了Service实例,那Activity种就可以直接操作这个Service实例。
onBind实例 - 使用Messenger
再看下使用Messenger的demo:
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
在设计上也很简单:
- 在Service中定义一个Handler,响应操作
- 获得一个与Handler相关联的Messenger,通过Messenger可以获得IBinder实例
- 组件拿到IBinder后,通过IBinder生成一个Messenger
- 组件确认Service还保持连接,使用自己生成的Messenger发送消息即可,Service会接收到他的消息
实例 - Android UniversalMusicPlayer
Android UniversalMusicPlayer是Google音乐播放器的Sample,我们思考下音乐播放器中后台任务的需求:
- 音乐播放器肯定是个后台任务,无论是否展示UI都应该保持工作,适合使用Service。
- 音乐播放器开始播放后应该保持工作,直到音乐播完或是我们结束播放,适合使用Start型启动
- 音乐播放过程中用户可以使用UI控制播放情况,因此Activity可以与Service交互,适合使用Bound型启动
- 当用户不播放音乐时,可能不需要Service,可以使用stopService()避免Service长期存活
因此Android UniversalMusicPlayer 适合使用Start型与Bound型两种启动方式配合。我们来看下他的源码的做法。
启动Service
我们在Activity中使用MediaBrowserCompat
去控制播放器,首先第一步就是启动播放器的Service,在源码中我们可以看到:
/// MediaBrowserCompat.java
public void connect() {
...
Intent intent = new Intent("android.media.browse.MediaBrowserService");
intent.setComponent(MediaBrowserImplBase.this.mServiceComponent);
MediaBrowserImplBase.this.mServiceConnection = MediaBrowserImplBase.this.new MediaServiceConnection();
...
/// 通过BindService()启动Service
/// 绑定结果在ServiceConnection的回调中
bound = MediaBrowserImplBase.this.mContext.bindService(intent, MediaBrowserImplBase.this.mServiceConnection, 1);
...
}
所以播放器的Service是通过bindService()启动的,由前文中我们对bound型Service的了解,我们接下来应该要做两步:一是去Service的onBind()看下Service会为Client提供哪些功能。二是看下ServiceConnection回调后Client会如何操作IBinder。
Service.onBind()
Android UniversalMusicPlayer中播放器的Service是MusicService.java
,他继承自MediaBrowserServiceCompat
,这是Android Media框架提供的基类。
/// MediaBrowserServiceImplBase.java
public IBinder onBind(Intent intent) {
/// action与MediaBrowserCompat中调用的一致
return "android.media.browse.MediaBrowserService".equals(intent.getAction())?this.mMessenger.getBinder():null;
}
他选择的IBinder生成方案是Messenger,前面提到这种方案的优点是支持IPC,既然是Messenger就要看下他对应的Handler。
/// MediaBrowserServiceImplBase.java
public void onCreate() {
this.mMessenger = new Messenger(MediaBrowserServiceCompat.this.mHandler);
}
final MediaBrowserServiceCompat.ServiceHandler mHandler = new MediaBrowserServiceCompat.ServiceHandler();
private final class ServiceHandler extends Handler {
private final MediaBrowserServiceCompat.ServiceBinderImpl mServiceBinderImpl = MediaBrowserServiceCompat.this.new ServiceBinderImpl();
ServiceHandler() {
}
public void handleMessage(Message msg) {
Bundle data = msg.getData();
switch(msg.what) {
case 1:
this.mServiceBinderImpl.connect(data.getString("data_package_name"), data.getInt("data_calling_uid"), data.getBundle("data_root_hints"), new MediaBrowserServiceCompat.ServiceCallbacksCompat(msg.replyTo));
break;
case 2:
this.mServiceBinderImpl.disconnect(new MediaBrowserServiceCompat.ServiceCallbacksCompat(msg.replyTo));
break;
...
default:
Log.w("MBServiceCompat", "Unhandled message: " + msg + "\n Service version: " + 1 + "\n Client version: " + msg.arg1);
}
}
}
Handler的细节我们先不细看,我们回去看下Activity中的ServiceConnection
ServiceConnection.onServiceConnected
/// MedaiBrowserCompat.java
private class MediaServiceConnection implements ServiceConnection {
MediaServiceConnection() {
}
public void onServiceConnected(final ComponentName name, final IBinder binder) {
this.postOrRun(new Runnable() {
public void run() {
if(MediaServiceConnection.this.isCurrent("onServiceConnected")) {
MediaBrowserImplBase.this.mServiceBinderWrapper = new MediaBrowserCompat.ServiceBinderWrapper(binder, MediaBrowserImplBase.this.mRootHints);
MediaBrowserImplBase.this.mServiceBinderWrapper.connect(MediaBrowserImplBase.this.mContext, MediaBrowserImplBase.this.mCallbacksMessenger);
}
}
});
}
public void onServiceDisconnected(final ComponentName name) {
this.postOrRun(new Runnable() {
public void run() {
if(MediaServiceConnection.this.isCurrent("onServiceDisconnected")) {
MediaBrowserImplBase.this.mServiceBinderWrapper = null;
}
}
});
}
}
具体方案也跟我们前面demo中的一致,收到ServiceConnection的回调后更新状态,并通过一个ServiceBinderWrapper封装IBinder,我们猜想这个Wrapper类封装了对Binder的操作,也就是当前组件与Service的交互行为,我们去验证一下。
向Service发起请求
private static class ServiceBinderWrapper {
private Messenger mMessenger;
public ServiceBinderWrapper(IBinder target, Bundle rootHints) {
this.mMessenger = new Messenger(target);
}
void connect(Context context, Messenger callbacksMessenger) throws RemoteException {
Bundle data = new Bundle();
data.putString("data_package_name", context.getPackageName());
this.sendRequest(1, data, callbacksMessenger);
}
...
private void sendRequest(int what, Bundle data, Messenger cbMessenger) throws RemoteException {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = 1;
msg.setData(data);
msg.replyTo = cbMessenger;
this.mMessenger.send(msg);
}
}
基本验证了我们的猜想,组件通过ServiceBinderWrapper向Service发起请求,内部实现其实就是用Service传来的IBinder去生成一个Messenger,并发送消息。以connect()为例,生成了一个msg.what为1的Message并通过Messenger发送,这个消息由Service中的ServiceHandler收到并执行。可以看到ServiceHandler中对what为1的消息的处理就是connect。所以,Client调用connect()方法,顺利触发了Service中的connect()方法。
至此,组件启动了Service并与之连接,组件就可以操作播放器了。这里整个流程其实与onBind()方法的实例代码是一致的,只是业务场景复杂了许多。
startService
通过bindService,我们启动了Service并允许组件与之交互。我们前面提到过Bound型Service的结束条件是所有连接的Client都断开,如果我们开始播放音乐了,却由于界面切换而导致音乐断开,这是不能接受的,所以,UniversalMusicPlayer中也使用了Start型启动。
/// MusicService.java
@Override
public void onPlaybackStart() {
...
// The service needs to continue running even after the bound client (usually a
// MediaController) disconnects, otherwise the music playback will stop.
// Calling startService(Intent) will keep the service running until it is explicitly killed.
startService(new Intent(getApplicationContext(), MusicService.class));
}
对于Start型的Service我们需要负责他的结束
@Override
public void onPlaybackStop() {
mSession.setActive(false);
// Reset the delayed stop handler, so after STOP_DELAY it will be executed again,
// potentially stopping the service.
mDelayedStopHandler.removeCallbacksAndMessages(null);
mDelayedStopHandler.sendEmptyMessageDelayed(0, STOP_DELAY);
stopForeground(true);
}
private static class DelayedStopHandler extends Handler {
private final WeakReference mWeakReference;
private DelayedStopHandler(MusicService service) {
mWeakReference = new WeakReference<>(service);
}
@Override
public void handleMessage(Message msg) {
MusicService service = mWeakReference.get();
if (service != null && service.mPlaybackManager.getPlayback() != null) {
if (service.mPlaybackManager.getPlayback().isPlaying()) {
LogHelper.d(TAG, "Ignoring delayed stop since the media player is in use.");
return;
}
LogHelper.d(TAG, "Stopping service with delay handler.");
service.stopSelf();
}
}
}
当音乐结束时,会发送一个延迟消息,若消息到位了还是没有播放音乐,通过stopSelf()
结束Service。这样做的作用是如果用户结束音乐后很快需要再次播放,不需要重启Service。且在Start型跟Bound型共同作用时,需要所有连接Service的组件都断开连接播放器才会销毁。