android基础部分再学习---再谈Service进程服务通信

Posted wust小吴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android基础部分再学习---再谈Service进程服务通信相关的知识,希望对你有一定的参考价值。

Bound Services

快速查看
bound服务允许被其它控件绑定,以便与之交互并进行进程间通信
一旦所有的客户端都解除了绑定,bound服务将被销毁。除非该服务同时又是started类型的。
在本文中(参见目录)
关键类
Service
ServiceConnection
IBinder
范例
RemoteService
LocalService

bound服务是客户端-服务器模式的服务。bound服务允许组件(比如activity)对其进行绑定、发送请求、接收响应、甚至进行进程间通信(IPC)。 bound服务一般只在为其它应用程序组件服务期间才是存活的,而不会一直在后台保持运行。

本文展示了如何创建一个bound服务,包括如何从其它应用程序组件绑定到该服务。不过,通常你还应该参考服务文档以获取关于服务的更多信息,比如如何从服务中发送通知、如何将服务设置为前台运行等等。

目录


简介


bound服务是 Service 类的一种实现,它允许其它应用程序与其绑定并交互。为了让服务支持绑定,你必须实现onBind() 回调方法。这个方法返回一个 IBinder 对象,此对象定义了客户端与服务进行交互时所需的编程接口。

绑定到一个started服务

正如服务一文中所述,你可以创建一个同时支持started和bound的服务。也就是说,服务可以通过调用 startService() 来启动,这使它一直保持运行,同时它也允许客户端通过调用 bindService()来与之绑定。

如果你的服务确实可以是started和bound的,那么服务启动后,系统将不会在所有客户端解除绑定时销毁它。取而代之的是,你必须通过调用stopSelf() 或 stopService() 显式终止此服务。

虽然你通常应该要实现 onBind() onStartCommand() 中的一个,但有时需要同时实现两者。比如,音乐播放器的服务也许就需要同时实现后台运行和支持绑定。这样,activity就可以启动服务来播放音乐,并且音乐会一直播放下去,即使用户离开该应用程序也没关系,这个activity可以绑定播放服务来重新获得播放控制权。

请确保已经阅读了#管理Bound服务的生命周期章节,以获取更多向started服务添加绑定时的服务生命周期的有关信息。

客户端可以通过调用 bindService() 方法来绑定服务。在调用时,必须提供一个 ServiceConnection 的实现代码,用于监控与服务的联接。 bindService() 将会立即返回,没有返回值。但是android系统在创建客户端与服务之间的联接时,会调用 ServiceConnection 中的onServiceConnected() 方法,传递一个 IBinder ,客户端将用它与服务进行通信。

多个客户端可以同时联接到一个服务上。不过,只有在第一个客户端绑定时,系统才会调用服务的 onBind()方法来获取 IBinder 。然后,系统会向后续请求绑定的客户端传送这同一个 IBinder ,而不再调用 onBind() 。

当最后一个客户端解除绑定后,系统会销毁服务(除非服务同时是通过 startService() 启动的)。

当你实现自己的bound服务时,最重要的工作就是定义onBind() 回调方法所返回的接口。定义服务 IBinder 接口的方式有好几种,后续章节将会对每种技术进行论述。

创建一个Bound服务


创建一个支持绑定的服务时,你必须提供一个 IBinder,用作客户端和服务间进行通信的编程接口。定义这类接口的方式有三种:

扩展Binder类
如果服务是你的应用程序所私有的,并且与客户端运行于同一个进程中(通常都是如此),你应该通过扩展Binder类来创建你的接口,并从onBind()返回一个它的实例。客户端接收该Binder对象并用它来直接访问Binder甚至Service中可用的公共(public)方法。
如果你的服务只是为你自己的应用程序执行一些后台工作,那这就是首选的技术方案。不用这种方式来创建接口的理由只有一个,就是服务要被其它应用程序使用或者要跨多个进程使用。
使用Messenger
如果你需要接口跨越多个进程进行工作,可以通过Messenger来为服务创建接口。在这种方式下,服务定义一个响应各类消息对象MessageHandler。此HandlerMessenger与客户端共享同一个IBinder的基础,它使得客户端可以用消息对象Message向服务发送指令。此外,客户端还可以定义自己的Message,以便服务能够往回发送消息。
这是执行进程间通信(IPC)最为简便的方式,因为Messenger会把所有的请求放入一个独立进程中的队列,这样你就不一定非要把服务设计为线程安全的模式了。
使用AIDL
Android接口定义语言AIDL(Android Interface Definition Language)完成以下的所有工作:将对象解析为操作系统可识别的原始形态,并将它们跨进程序列化(marshal)以完成IPC。前一个使用Messenger的方式,实际上也是基于AIDL的,它用AIDL作为底层结构。如上所述,Messenger将在一个单独的进程中创建一个包含了所有客户端请求的队列,这样服务每次就只会收到一个请求。可是,如果想让你的服务能同时处理多个请求,那你就可以直接使用AIDL。这种情况下,你的服务必须拥有多线程处理能力,并且是以线程安全的方式编写的。
要直接使用AIDL,你必须创建一个.aidl文件,其中定义了编程的接口。Android SDK 工具使用此文件来生成一个抽象类(abstract class),其中实现了接口及对IPC的处理,然后你就可以在自己的服务中扩展该类。

注意: 绝大多数应用程序都不应该用AIDL来创建bound服务,因为这可能需要多线程处理能力并且会让代码变得更为复杂。 因此,AIDL对绝大多数应用程序都不适用,并且本文也不会讨论如何在服务中使用它的内容。如果你确信需要直接使用AIDL,那请参阅 AIDL 文档。

扩展Binder类

如果你的服务只用于本地应用程序并且不需要跨进程工作,那你只要实现自己的 Binder 类即可,这样你的客户端就能直接访问服务中的公共方法了。

注意:仅当客户端和服务位于同一个应用程序和进程中,这也是最常见的情况,这种方式才会有用。比如,一个音乐应用需要把一个activity绑定到它自己的后台音乐播放服务上,采用这种方式就会很不错。

以下是设置步骤:

  1. 在你的服务中,创建一个Binder的实例,其中实现以下三者之一:
    • 包含了可供客户端调用的公共方法
    • 返回当前Service实例,其中包含了可供客户端调用的公共方法。
    • 或者,返回内含服务类的其它类的一个实例,服务中包含了可供客户端调用的公共方法。
  2. 从回调方法onBind()中返回Binder的该实例。
  3. 在客户端中,在回调方法onServiceConnected()中接收Binder并用所提供的方法对绑定的服务进行调用。
注意:

服务和客户端之所以必须位于同一个应用程序中,是为了让客户端能够正确转换(cast)返回的对象并调用对象的API。 服务和客户端也必须位于同一个进程中,因为这种方式不能执行任何跨进程的序列化(marshalling)操作。

比如,以下是一个服务的示例,它通过实现一个Binder来为客户端访问它内部的方法提供支持:

public class LocalService extends Service {
    
// 给客户端的Binder
    
private final IBinder mBinder = new LocalBinder();
    
// 生成随机数
    
private final Random mGenerator = new Random();

    
/**
     * 用于客户端Binder的类。
     * 因为知道本服务总是运行于与客户端相同的进程中,我们就不需要用IPC进行处理。
     */

    
public class LocalBinder extends Binder {
        
LocalService getService() {
            
// Return this instance of LocalService so clients can call public methods
            
return LocalService.this;
        
}
    
}

    
@Override
    
public IBinder onBind(Intent intent) {
        
return mBinder;
    
}

    
/** method for clients */
    
public int getRandomNumber() {
      
return mGenerator.nextInt(100);
    
}
}

LocalBinder为客户端提供了getService()方法,用于返回当前LocalService的实例。 这就让客户端可以调用服务中的公共方法。比如,客户端可以调用服务中的getRandomNumber()

以下是一个绑定到LocalService的activity,当点击按钮时,它会调用getRandomNumber()

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;
        
}
    
}

    
/** 当按下按钮时调用(该按钮在layout文件中利用android:onClick属性与本方法关联 */
    
public void onButtonClick(View v) {
        
if (mBound) {
            
// 调用LocalService中的方法。
            
// 不过,如果该调用会导致某些操作的挂起,那么调用应该放入单独的线程中进行,
            
// 以免降低activity的性能。
            
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) {
            
// 我们已经绑定到LocalService了,对IBinder进行类型转换(cast)并获得LocalService对象的实例
            
LocalBinder binder = (LocalBinder) service;
            mService 
= binder.getService();
            mBound 
= true;
        
}

        
@Override
        
public void onServiceDisconnected(ComponentName arg0) {
            mBound 
= false;
        
}
    
};
}

上述例子展示了客户端如何利用 ServiceConnection 和 onServiceConnected() 回调方法绑定到服务。下一节将给出更多有关服务绑定过程的信息。

注意:

上述例子并没有明确地解除绑定,但所有的客户端都应该适时地解除绑定(比如activity暂停pause时)。

更多示例代码,请参阅ApiDemos 中的LocalService.java类和 LocalServiceActivities.java 类。

使用Messenger

与AIDL相比

当你需要进行IPC时,使用 Messenger 要比用AIDL实现接口要容易些,因为 Messenger 会把所有调用服务的请求放入一个队列。而纯粹的AIDL接口会把这些请求同时发送给服务,这样服务就必须要能够多线程运行。

对于绝大多数应用程序而言,服务没有必要多线程运行,因此利用 Messenger 可以让服务一次只处理一个调用。如果 你的服务非要多线程运行,那你就应该用 AIDL 来定义接口。

如果你的服务需要与远程进程进行通信,那你可以使用一个 Messenger 来提供服务的接口。这种技术能让你无需使用AIDL就能进行进程间通信(IPC)。

以下概括了Messenger的使用方法:

通过这种方式,客户端不需要调用服务中的“方法”。取而代之的是,客户端发送“消息”( Message对象),服务则接收位于 Handler中的这个消息。

以下是服务使用一个Messenger做为接口的简单例子:

public class MessengerService extends Service {
    
/** 发送给服务的用于显示信息的指令*/
    
static final int MSG_SAY_HELLO = 1;

    
/**
     * 从客户端接收消息的Handler
     */

    
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);
            
}
        
}
    
}

    
/**
     * 向客户端公布的用于向IncomingHandler发送信息的Messager
     */

    
final Messenger mMessenger = new Messenger(new IncomingHandler());

    
/**
     * 当绑定到服务时,我们向Messager返回接口,
     * 用于向服务发送消息
     */

    
@Override
    
public IBinder onBind(Intent intent) {
        
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        
return mMessenger.以上是关于android基础部分再学习---再谈Service进程服务通信的主要内容,如果未能解决你的问题,请参考以下文章

Android基础部分再学习---activity的状态保存

再谈Android消息分发

再谈Android消息分发

再谈Android消息分发

再谈Android消息分发

沉淀再出发:再谈java的多线程机制