Android 四大组件之 Service (二)

来源:互联网 发布:php 打印数组格式 编辑:程序博客网 时间:2024/04/29 09:24

started 模式的 Service 明显缺乏交互性,而 bound service 就是来干这事的

创建 Bound Service 的 3 种方式

创建 Bound Service 的时候你必须提供一个编程接口使客户端可以使用它与 Service 进行交互:

  1. 扩展 Binder 类

    当你的 Service 只是自己应用私有的,且在同一个进程中,那么这是推荐的方式。唯一一个你不建立自己接口的原因是你的服务被其他应用程序所用或使用了多个分开的进程。

  2. 使用 Messenger

    如果你想让你的接口在多个不同的进程间工作,你可以为服务创建一个带有Messenger的接口。在这种情况下,服务将定义一个Handler来响应不同类型的Message对象。这个Handler是Messenger的基础,它可以与客户共享一个IBinder,允许客户使用Message对象向服务发送指令。以此,客户可以定义一个属于自己的Messenger,这样,服务就可以把消息传递回来。

    这是最简单的执行进种间通信(IPC)的方法,因为Messenger队列所有的请求到一个单独的线程当中,所以你不需要设计你的服务为线程安全(thread-safe)

  3. AIDL

    大多数应用程序不应该使用AIDL来创建一个绑定服务,因为它可能要求能够支持多线程,其结果将会是很复杂的实现。AIDL不适用于大多数应用程序,实际我看没看完 AIDL

扩展 Binder 类

  1. 创建一个Binder类的实例,实现以下功能之一:
    • 包含客户可以调用的公共方法
    • 返回当前Service的实例,其包含了用户可以访问的公共方法
    • 或返回这个服务包含的另一个类,并含有客户可以访问的公共方法
  2. 从onBind()回调函数返回这个Binder的实例。
  3. 在客户端,从onServiceConnected()回调方法接收这个Binder,并用提供的方法来调用绑定服务。
public class LocalService extends Service {
// 给客户端的 Binder
private final IBinder mBinder = new LocalBinder();
// 栗子就是产生随机数的…
private final Random mGenerator = new Random();
 
/**
* 我们知道service和客户端运行在同一进程,所以不考虑 IPC
*/
public class LocalBinder extends Binder {
LocalService getService() {
// 返回 LocalService 实例,客户端可以调用 public 方法
return LocalService.this;
}
}
 
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
 
/** 准备让客户端调用的方法 */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}

下面是客户端绑定 Service 并调用方法的代码

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();
// 绑定 LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
 
@Override
protected void onStop() {
super.onStop();
// 解绑
if (mBound) {
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) {
// 调用Service方法,
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
 
/** 绑定服务的回调, 传递给 bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
 
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
// 成功绑定后…
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
 
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}

使用一个消息传递器

如果你需要你的服务能够与远程进程通信,那么你可以使用一个Messenger为你的服务提供接口。这个方法允许你执行进程间通信(IPC)而不需要使用AIDL。

  • 服务实现了一个Handler,用来接收每一次调用从客户端返回的回调方法。
  • Handler被用来创建一个Messenger对象(其为Handler的一个引用)。
  • Messenger创建一个IBinder,服务从onBind()方法将其返回给客户端。
  • 客户端使用这个IBinder来实例化这个Messenger(其引用到服务的Handler),客户端可以用来向服务发送Message对象。
  • 服务通过它的Handler接收每一个Message——更确切的说,是在handleMessage()方法中接收。

通过这种方法,在服务端没有客户端能调用的“方法”。而是,客户传递“消息”(Message对象),同时服务在其Handler中接收。

public class MessengerService extends Service {
 
static final int MSG_SAY_HELLO = 1;
 
/**
* 处理来自客户端的消息
*/
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);
}
}
}
 
final Messenger mMessenger = new Messenger(new IncomingHandler());
 
/**
* 当绑定 Service 的时候,我们返回Messenger的接口来让客户端发消息
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}

注意Handler中的handleMessage()方法,服务在其中接收进入的Message,基于what成员,决定下一步的做法。

客户端所需做的只是基于服务返回的IBinder创建一个Messenger并使用send()方法发送一条消息。

public class ActivityMessenger extends Activity {
 
Messenger mService = null;
 
/** 是否已经绑定服务 */
boolean mBound;
 
/**
* Class for interacting with the main interface of the service.
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
mBound = true;
}
 
public void onServiceDisconnected(ComponentName className) {
mService = null;
mBound = false;
}
};
 
public void sayHello(View v) {
if (!mBound) return;
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();
bindService(new Intent(this, MessengerService.class), mConnection, Context.BIND_AUTO_CREATE);
}
 
@Override
protected void onStop() {
super.onStop();
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}

注意这个范例中并没有展现服务将怎样响应客户端。如果你希望服务做出反应,那么你需要在客户端创建一个Messenger当客户端接收到onServiceConnected()回调方法,它发送一条Message到服务,其中包括了客户端的Messenger,其存放在在send()中replyTo参数之中。

绑定服务的一些额外内容

bindService() 并不是同步的,因此需要ServiceConnection

Only activities, services, and content providers can bind to a service—you cannot bind to a service from a broadcast receiver.

  • 你应该始终捕获DeadObjectException异常,这个异常在连接断开时被抛出。在这远程方法唯一会抛出的异常。
  • 对象为进程引用数。
  • 在客户端生命周期的创建和摧毁时刻,你应该成对使用绑定和解绑。例如:

    • 当你的活动可见时,如果你需要做的只是与服务交互,你应该在onStart()中绑定,在onStop()中解绑。
    • 如果你希望即使活动在后台停止时也接收响应,那么你可以在onCreate()方法中绑定,并在onDestroy()方法中解绑。需要注意的是,这个实现使得你的活动需要在所有服务运行的时候占用该服务(即使在后台运行时)。所以,如果服务存在于另一个进程中,那么你增加了进程的比重,因此,系统也将更有可能性将其杀死。

      通常情况下,你不应该在活动的onResume()和onPause()方法中绑定与解绑,因为这些回调方法发生在生命周期转换的时候,你应该把此期间的发生的进程减小到最少。同样,如果有多个活动绑定到同一个服务,两个活动间必然存在着转换,当当前的活动在下一个活动绑定(在resume期间)之前,进行解绑(在pause期间),服务可能会被摧毁或重新创建。(这个活动转换中,关于活动如何协调其生命的情况,在Activities)文档中做了详细的表述。

Managing the Lifecycle of a Bound Service


当一个服务从所有客户端解绑,Android系统将将其摧毁(除非这个活动被onStartCommand()启动)。这样,如果你的服务只是纯粹一个绑定服务,那么你不需要自己管理其生命周期——Android系统将根据其是否被绑定到客户端,来管理其生命周期。

然而,如果你选择实现onStartCommand()回调方法,那么你必须明确的停止这个服务,因为服务现在被认为已被启动。在这种情况下,服务将一些运行,直到它被自身的stopSelf()方法停止,或其它活动组件调用stopService()方法,而不考虑其是否绑定到客户端。

此外,如果你的服务被启动,并接收绑定,那么当系统调用了onUnbind()方法,你可以有选择性的返回true,如果你希望下一次一个客户端绑定到服务时接收一个onRebind()方法调用(而不是接收一个onBind()调用)。onRebind()返回一个空值,但客户端依然可以接收到onServiceConnected()回调方法中的IBinder。

0 0
原创粉丝点击