【后台任务】绑定的服务概述(13)

概要


绑定服务是客户端 - 服务器接口中的服务器。它允许组件(如活动)绑定到服务,发送请求,接收响应以及执行进程间通信(IPC)。绑定服务通常只在服务于另一个应用程序组件时才存在,并且不会无限期地在后台运行。

本文档介绍了如何创建绑定服务,包括如何绑定来自其他应用程序组件的服务。有关一般服务的其他信息,例如如何从服务传递通知并将服务设置为在前台运行,请参阅 服务文档。

基础


绑定服务是Service该类的一个实现,允许其他应用程序绑定并与之交互。要为服务提供绑定,您必须实现onBind()回调方法。此方法返回一个IBinder对象,该对象定义客户端可用于与服务交互的编程接口。

绑定到已启动的服务

正如服务 文档中所讨论的,您可以创建一个既启动又受约束的服务。也就是说,您可以通过调用来启动服务startService(),这可以使服务无限期运行,并且还可以允许客户端通过调用来绑定到服务bindService()。

如果你让你的服务要启动和约束,那么当该服务已启动,系统并不会在所有客户端解除绑定摧毁服务。相反,您必须通过调用stopSelf()或显式地停止服务stopService()。

虽然你通常实现onBind() 或者 onStartCommand(),但有时需要实现两者。例如,音乐播放器可能会发现允许其服务无限期运行并提供绑定是有用的。这样,一项活动即可开始播放某些音乐的服务,即使用户离开应用程序,音乐也会继续播放。然后,当用户返回到应用程序时,活动可以绑定到服务以重新获得播放控制权。

有关向已启动服务添加绑定时的服务生命周期的更多信息,请参阅管理绑定服务的生命周期。

客户端通过调用绑定到服务 bindService()。当它这样做时,它必须提供一个实现ServiceConnection,它监视与服务的连接。返回值 bindService()指示请求的服务是否存在以及客户端是否被允许访问它。当Android系统会在客户端和服务之间的连接,它调用onServiceConnected()的ServiceConnection。该 onServiceConnected()方法包含一个IBinder 参数,然后客户端使用该参数与绑定服务进行通信。

您可以同时将多个客户端连接到服务。但是,系统缓存IBinder服务通信信道。换句话说,系统调用服务的onBind()方法,以便IBinder在第一个客户端绑定时才生成。然后,系统将该内容传递IBinder给绑定到相同服务的所有其他客户端,而无需onBind()再次调用 。

当最后一个客户端从服务中解除绑定时,系统将销毁该服务,除非该服务也是由该服务启动的startService()。

绑定服务实现中最重要的部分是定义onBind()回调方法返回的接口。以下部分讨论了可以定义服务IBinder界面的几种不同方式 。

创建一个绑定的服务


创建提供绑定的服务时,必须提供一个IBinder 提供客户端可用于与服务交互的编程接口。有三种方法可以定义接口:

扩展Binder类
如果您的服务对您自己的应用程序是私有的,并且与客户端运行在相同的进程中(这很常见),您应该通过扩展Binder 该类并从中返回实例来 创建接口onBind()。客户端收到Binder并可以使用它来直接访问Binder 实现中或公共方法中可用的公共方法Service。
当您的服务仅仅是您自己的应用程序的后台工作人员时,这是首选技术。您不会以这种方式创建界面的唯一原因是因为您的服务被其他应用程序或单独的进程使用。

使用Messenger
如果你需要你的界面在不同的进程中工作,你可以使用a创建一个服务界面Messenger。以这种方式,服务定义了一个Handler响应不同类型的Message对象的响应。这Handler 是一个Messenger可以IBinder 与客户端共享的基础,允许客户端使用Message对象向服务发送命令。另外,客户端可以Messenger自己定义一个,所以服务可以发回消息。
这是执行进程间通信(IPC)的最简单方法,因为Messenger队列将所有请求放入单个线程中,因此您不必将您的服务设计为线程安全。

使用AIDL
Android接口定义语言(AIDL)将对象分解成操作系统可以理解的原语,并在进程间编组它们以执行IPC。以前的技术,使用a Messenger,实际上是基于AIDL作为其基础结构。如上所述,Messenger在单个线程中创建所有客户端请求的队列,以便服务一次接收一个请求。但是,如果您希望您的服务同时处理多个请求,则可以直接使用AIDL。在这种情况下,您的服务必须是线程安全的并且能够进行多线程。
要直接使用AIDL,您必须创建一个.aidl定义编程接口的文件。Android SDK工具使用此文件生成一个抽象类,该类实现接口并处理IPC,然后可以在您的服务中进行扩展。

注意:大多数应用程序不应该使用AIDL来创建绑定服务,因为它可能需要多线程功能并可能导致更复杂的实现。因此,AIDL不适用于大多数应用程序,本文档不讨论如何将其用于您的服务。如果您确定需要直接使用AIDL,请参阅AIDL 文档。

扩展Binder类

如果您的服务仅由本地应用程序使用,并且不需要跨进程工作,那么您可以实现自己的Binder类,以便客户端直接访问服务中的公共方法。

注意:仅当客户端和服务处于相同的应用程序和进程中时才有效,这是最常见的。例如,对于需要将活动绑定到在后台播放音乐的自己的服务的音乐应用程序,这可以很好地工作。

以下是设置方法:

  1. 在您的服务中,创建一个实例Binder,执行以下操作之一:
    • 包含客户端可以调用的公共方法。
    • 返回当前Service实例,该实例具有客户端可以调用的公共方法。
    • 通过客户端可以调用的公共方法返回服务托管的另一个类的实例。
  2. Binder从onBind()回调方法返回这个实例。
  3. 在客户端,接收Binder来自onServiceConnected()回调方法并使用提供的方法调用绑定服务。

注意:服务和客户端必须位于同一个应用程序中,以便客户端可以投射返回的对象并正确调用其API。服务和客户端也必须处于相同的过程中,因为此技术不会跨进程执行任何编组。

例如,以下是一个服务,通过Binder实现为客户提供对服务中方法的访问:

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 {
        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调用的活动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();
        // 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.
            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;
            mService = binder.getService();
            mBound = true;
        }

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

上面的示例显示了客户端如何使用实现ServiceConnection和onServiceConnected()回调绑定到服务 。下一节提供了关于绑定到服务的这个过程的更多信息。

注意:在上面的示例中,该 onStop()方法将客户端从服务中解除绑定。客户应该在适当的时候解除服务绑定,如 附加说明中所述。

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

使用Messenger

与AIDL相比
当你需要执行IPC时,Messenger为你的接口使用a 比使用AIDL更简单,因为Messenger队列调用了所有的服务。纯AIDL接口向服务发送同时请求,然后服务必须处理多线程。

对于大多数应用程序,服务不需要执行多线程,因此使用a Messenger允许服务一次处理一个呼叫。如果您的服务多线程化很重要,请使用AIDL来定义您的界面。

如果您需要您的服务与远程进程进行通信,则可以使用a Messenger为您的服务提供接口。这种技术允许您执行进程间通信(IPC)而无需使用AIDL。

以下是如何使用a的总结Messenger:

  1. 该服务实现了一个Handler接收来自客户端的每个呼叫的回调。
  2. 该服务使用Handler创建一个Messenger 对象(这是对该对象的引用Handler)。
  3. 在Messenger创建IBinder该服务从返回给客户端onBind()。
  4. 客户端使用IBinder实例化Messenger (引用服务的Handler)(客户端用来将Message对象发送 到服务)。
  5. 服务接收的每个Message在其Handler-特别,在handleMessage()方法。

通过这种方式,有没有方法为客户端上的服务电话。相反,客户端提供服务在其中接收的消息(Message对象)Handler。

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

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

请注意,该handleMessage()方法 Handler是服务收到传入的地址,Message 并根据该what成员决定要执行的操作。

所有客户端需要做的是Messenger根据IBinder服务返回的信息创建一个消息并使用消息发送消息send()。例如,以下是绑定到服务并将MSG_SAY_HELLO消息传递给服务的简单活动:

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

注意这个例子并没有显示服务如何响应客户端。如果你想要服务响应,你还需要Messenger在客户端创建一个。当客户端收到onServiceConnected()回调时,它会在方法的参数中发送一个Message包含客户端的服务。MessengerreplyTosend()

您可以看到如何在(服务)和(客户端)示例中提供双向消息处理的示例。 MessengerService.java MessengerServiceActivities.java

绑定到服务


应用程序组件(客户端)可以通过调用绑定到服务 bindService()。然后Android系统调用服务的onBind()方法,该方法返回一个IBinder与服务交互的方法。

该绑定是异步的,并bindService()立即返回而不返回IBinder给客户端。为了接收IBinder,客户端必须创建一个实例ServiceConnection并传递给它bindService()。在ServiceConnection包括系统调用提供的回调方法IBinder。

注意:只有活动,服务和内容提供商可以绑定到服务 - 您不能绑定到广播接收方的服务。

要绑定到客户端的服务,请按照下列步骤操作:
执行ServiceConnection。
你的实现必须覆盖两个回调方法:

onServiceConnected()
系统调用它来传递IBinder服务onBind()方法返回的结果。
onServiceDisconnected()
Android系统在与服务的连接意外丢失时调用此服务,例如服务崩溃或已被终止。这是不是 在客户端解除绑定调用。
调用bindService(),通过ServiceConnection实现。
注意:如果该方法返回false,则您的客户端没有与该服务的有效连接。但是,您的客户仍然应该打电话unbindService(); 否则,您的客户端会在空闲时关闭服务。

当系统调用onServiceConnected()回调方法时,可以使用接口定义的方法开始调用服务。
要断开与服务的连接,请致电unbindService()。
如果您的客户端在您的应用程序销毁客户端时仍然与服务绑定,则销毁会导致客户端解除绑定。一旦完成与服务的交互,最好解除客户端的绑定。这样做可以使空闲服务关闭。有关绑定和解除绑定的适当时间的更多信息,请参阅其他注释。

下面的示例通过扩展Binder类将客户端连接到上面创建的服务 ,所以它必须将返回 IBinder的LocalService类转换为类并请求该LocalService实例:

LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Because we have bound to an explicit
        // service that is running in our own process, we can
        // cast its IBinder to a concrete class and directly access it.
        LocalBinder binder = (LocalBinder) service;
        mService = binder.getService();
        mBound = true;
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

通过这种方式ServiceConnection,客户端可以通过传递给服务来绑定到服务bindService(),如以下示例所示:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  • 第一个参数bindService()是 Intent明确命名要绑定的服务。

警告:如果您使用意图绑定到a Service,请使用明确的 意图确保您的应用程序是安全的。使用隐式意图启动服务会带来安全隐患,因为您无法确定哪些服务会响应该意图,并且用户无法看到启动哪项服务。从Android 5.0(API级别21)开始,如果您bindService() 使用隐式意图调用,则系统会引发异常。

  • 第二个参数是ServiceConnection对象。
  • 第三个参数是一个指示绑定选项的标志。它通常应该BIND_AUTO_CREATE是为了创建服务,如果它还没有活着。其他可能的值是BIND_DEBUG_UNBIND 和BIND_NOT_FOREGROUND,或0为无。

补充笔记

以下是关于绑定到服务的一些重要提示

  • 您应该始终捕获DeadObjectException连接断开时引发的异常。这是远程方法抛出的唯一异常。
  • 对象是跨进程的引用计数。
  • 您通常在匹配客户生命周期的启动和拆卸时间期间对绑定和解除绑定进行配对,如以下示例中所述:

  • 如果您只有在您的活动可见时才需要与服务进行交互,则您应该在期间onStart()绑定和解除绑定onStop()。
  • 如果您希望活动即使在后台停止时也能收到回复,那么您可以在期间onCreate()绑定和解除绑定onDestroy()。当心,这意味着你的活动需要使用该服务,它的运行(甚至在背景中)的整段时间,因此,如果该服务是在另一个进程中,那么你就增加了过程的重量,它变得更容易,该系统将杀了它。

注意:您不一般绑定,并在您的活动的解除绑定onResume()和onPause(),因为这些回调发生在每一个生命周期的过渡和你应该保持发生在这些过渡到最低限度的处理。另外,如果应用程序中的多个活动绑定到相同的服务,并且这两个活动之间存在转换,则可能会因为当前活动在下一个绑定(在恢复期间)之前解除绑定(在暂停期间)而被破坏并重新创建。活动 文档中描述了活动如何协调其生命周期的活动转换。

有关更多示例代码,展示如何绑定到服务,请参阅ApiDemos中的类。 RemoteService.java

管理绑定服务的生命周期


当一个服务从所有客户端解除绑定时,Android系统将销毁它(除非它也是以它开始的onStartCommand())。因此,如果服务是纯粹的绑定服务,则不必管理服务的生命周期 - Android系统会根据它是否绑定到任何客户端来管理它。

但是,如果选择实现onStartCommand()回调方法,则必须明确停止服务,因为现在认为该服务已启动。在这种情况下,服务会一直运行,直到服务停止stopSelf()或使用其他组件调用stopService(),而不管它是否绑定到任何客户端。

此外,如果您的服务已启动并接受绑定,那么当系统调用您的onUnbind()方法时,true如果您希望onRebind()下次客户端绑定到服务时收到呼叫,则可以选择返回 。onRebind()返回void,但客户仍然收到IBinder它的 onServiceConnected()回调。下图说明了这种生命周期的逻辑。

【后台任务】绑定的服务概述(13)
图1.启动并允许绑定的服务的生命周期。

有关已启动服务的生命周期的更多信息,请参阅服务文档。

Lastest Update:2018.05.08

联系我

QQ:94297366
微信打赏:https://pan.baidu.com/s/1dSBXk3eFZu3mAMkw3xu9KQ

公众号推荐:

【后台任务】绑定的服务概述(13)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章