美文网首页
Android官方文档笔记:Bound Services

Android官方文档笔记:Bound Services

作者: 御天证道 | 来源:发表于2019-03-28 17:01 被阅读0次

    bound service是CS接口中的服务端。一个bound service允许组件(例如activity)与它绑定,发送请求,接受回应甚至执行IPC。一般情况下,一个bound service只有在服务其余应用组件的时候才存活而不会单独的在后台运行。

    基础

    bound service实现了service的接口,允许其他组件与它绑定并与它交互。你必须实现onBind回调方法来给service提供绑定。此方法返回一个IBinder对象,该对象定义了客户端可用于与service交互的编程接口。

    一个客户端可以调用bindService来与service绑定。这时客户端必须提供一个ServiceConnection的实现来监控与service的连接。bindService方法会直接返回,不带任何值。但是如果Android系统创建了客户端与service之间的连接,它会调用ServiceConnectiononServiceConnected方法来传递一个IBinder对象给客户端,这个IBinder可以用来与service通讯。

    多个客户端可以立刻连接到一个service。然而,系统只会在第一个客户端与service绑定的时候调用onBind方法来检索IBinder。然后系统会返回相同的IBinder给其余的绑定客户端,而不是再次调用onBind。

    当最后一个客户端与service解绑的时候,系统会销毁该service。(除非该service已经被startServie方法启动过)

    如果你允许你的service可以被start也可以被绑定,那么当它被start后,所有客户端与它解绑的时候,系统是不会销毁它的。你还需要调用stopSelf或者stopService来停止它。

    当你实现你的bound service的时候,最重要的部分就是定义onBind回调方法返回的接口。有几种不同的方式来定义你的IBinder接口,下面将讨论每一种技术。

    创建一个bound service

    当创建一个可以绑定的service的时候,你必须提供一个IBinder,该IBinder提供了客户端可以与service交互的编程接口。这里有三种方式可以定义这个接口:

    继承Binder类

    如果你的service是你应用私有的,和客户端运行在一个进程的,你应该继承实现Binder类来创建你的接口,并且在onBind方法中返回该接口的实例。客户端接收到Binder后可以用它来直接访问Binder实现中的甚至service中的公开方法。

    当你的service仅仅是你应用程序的后台工作人员的时候,这是首选的技术。你不使用这种方式创建你的service的唯一的原因是你的service可以被外部应用使用或者可以跨进程。

    使用Messenger

    如果你需要你的接口在不同的线程中工作,你可以用Messenger来给service创建一个接口。以这种方式,service定义了一个handler来回应不同类型的Message对象,这个Handler是Messenger的基础,可以和客户端分享一个IBinder,允许客户端用Message向service发送指令。客户端可以定义自己的Messenger,这样service就可以发送Message回来。

    这是最简单的方式来进程间通讯,因为Messenger将所有的请求在一个线程中排出队列所以你不需要考虑你的service要是线程安全的。

    使用AIDL

    AIDL(Android接口定义语言)执行所有的工作,将对象分解成操作系统可以理解的原语,并在进程间编组它们以执行IPC。前面的技术使用了Messenger,这也是基于AIDL作为它的底层结构。像上面提到的,Messenger在单个线程中给所有的客户端请求创建了一个队列,所以service同一时间只能接收到一个请求。如果你想要你的service可以同时处理多个请求,你可以直接使用AIDL。这时候,你的service必须能支持多线程且是线程安全的。

    要直接使用AIDL,您必须创建一个定义编程接口的.aidl文件。Android SDK工具使用此文件生成一个抽象类,该类实现接口并处理IPC,然后可以在你的service中继承它。

    注意:大多数的应用不需要通过AIDL来创建一个bound service。因为这需要支持多线程且导致更加复杂的实现。因此,AIDL不适合大多数的应用,这篇文章也不会介绍如何给service使用它,如果你确定要直接使用AIDL,请查看专门的AIDL文章。

    继承Binder类

    如果你的service只在本应用中使用,不需要跨进程工作,那么你可以实现你自己的IBinder来让你的客户端直接访问service中的公开方法。

    注意:这只有在service和client在同一个进程中才有效。

    以下是设置方法:

    1. 在你的service中,用以下方法中的一个来创建IBinder实例:

      • 包含client可以调用的公开方法
      • 返回当前的service实例,该service包含client可以调用的公开方法
      • 返回一个service持有的对象,client可以访问该对象的公开方法
    2. onBind回调方法中返回该IBinder的实例。

    3. 在client中,接受从onServiceConnected回调方法中传来的IBinder,然后可以通过提供的方法调用service。

    说明:service和client必须在同一个线程中的原因是client可以将返回的对象转型并正确的调用它的API。服务和客户端也必须处于同一个进程中,因为此技术不会跨进程执行任何编组。

    下面有个例子,这个service通过实现Binder来让client可以访问service的方法:

    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方法给client来获取当前LocalService的实例。这将允许client调用service的公开方法。例如client可以调用service的getRandomNumber方法。

    下面是一个activity与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();
    
            // Unbind from the service
    
            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) {
    
                // 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;
    
            }
    
        };
    
    }
    

    使用Messenger

    如果你的service需要和远程进程通信,你可以使用一个Messenger来给你的service提供接口。这项技术可以让你不使用AIDL就能进行IPC。

    以下是如何使用Messenger的摘要:

    • 该service实现了一个handler来接受来自client的每个调用的回调。
    • Messenger创建了一个IBinder,service将该IBinder通过onBinder方法返回给client。
    • 客户端使用IBinder实例化Messenger(引用服务的Handler),客户端用它将Message对象发送到service。
    • service在它的Handler中接收到每个Message,明确的说,在handleMessage方法中。

    以这种方式,client在service上没有方法可以调用,取而代之的是,client可以发送信息,service可以在Handler中接受信息。

    以下是一个service使用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();
    
        }
    
    }
    

    client需要做的就是使用service返回的IBinder创建一个Messenger并且使用它的send方法发送信息。下面是一个activity绑定到上面service并发送信息给该service的例子:

    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如何对client做出回应。如果你想要你的service做出回应,那么你也需要在client中创建一个Messenger。然后当client接收到onServiceConnected回调的时候,client发送了一条Message给service,这条Message在它的reply参数中包含了client的Messenger

    绑定一个service

    应用组件(client)可以 通过调用bindService来和一个service进行绑定。Android系统会调用service的OnBind方法,该方法返回一个可以与service交互的IBinder

    绑定是异步的,bindSercice会立刻返回但是不会返回IBinder给client,client必须创建一个ServiceConnection实例并将它传递给bindServiceServiceConnection包含了一个方法,系统会调用这个方法传递IBinder

    说明:只有activity,service和content provider可以和service绑定-你不能将broadcast receiver和service绑定。

    所以,为了能后绑定service,你必须要做到:

    1. 实现ServiceConnection

    你的实现必须重写两个方法:

    • onServiceConnected()
      系统调用这个方法来传送从onBind方法返回的IBinder给client
    • onServiceDisconnected()
      Android系统会在client与service的连接异常断开的时候(例如service崩溃或者被杀死)调用该方法。client解绑的时候不会调用该方法。
    1. 调用bindService,传递ServiceConnection的实现。

    2. 当系统调用onServiceConnected回调方法时,你可以使用接口定义的方法开始调用service。

    3. 调用unBindService来解绑。

    当client销毁的时候,会与service解绑。但是在你与service交互完成之后或者在activity pause的时候,你应该解绑。以便service可以在不使用的时候关闭。

    下面的例子连接了client和一个通过继承Binder对象来创建的service。所有要做的就是将返回的IBinder转型为LocalBinder且获取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;
    
        }
    
    };
    

    client将该ServiceConnection传递给bindService来使用它。例如:

    Intent intent = new Intent(this, LocalService.class);
    bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    

    bindService()的第一个参数是一个Intent,它明确地命名要绑定的服务(该intent可能是隐式的)。
    第二个参数是ServiceConnetction对象
    第三个参数是一个指示绑定选项的标志。它通常应该是BIND_AUTO_CREATE以便于在service没有存活的时候创建它。其余可能的值为BIND_DEBUG_UNBINDBIND_NOT_FOREGROUND或者0表示没有。

    附加说明:

    以下有一些关于绑定service的重要说明:
    你应该总是捕获DeadObjectException,这会在连接被打断的时候抛出。这是远程方法唯一抛出的一个异常。
    对象是跨进程引用计数的

    你通常应该将绑定和解绑与对应的client的生命周期中的创建和销毁对应起来。例如:

    • 如果你想你的activity在可见的时候与service交互,你应该在onSatrt期间绑定在onStop期间解绑。
    • 如果你想你的activity在stop的时候仍然可也接受到回应,那么你可以在onCreate中绑定,在onDestroy中解绑。注意,这意味着你的activity需要在整个生命周期中使用service(即使是在后台),如果该service是在另一个进程。你增加了该进程的负担,系统更有可能杀死它。

    在activity的onResumeonPause期间,通常不应该绑定和解除绑定,因为这些回调会在每个生命周期转换时发生,并且应该将在这些转换时发生的处理保持在最低限度。 另外,如果应用程序中的多个activity绑定到相同的service,并且这两个活动之间存在转换,则可能会因为当前activity在下一个activity绑定(在resume期间)之前解除绑定(在pause期间)而被破坏并重新创建。

    管理bound service的生命周期

    如果一个service被所有的组件解绑,那么Android系统就会销毁它(除非它也被onStartCommand启动了)。因此,如果你的service是一个单纯的bound service,那么你不用管理它的生命周期-Android系统会根据是否有client与之绑定来为你管理。

    然而,如果你选择了实现onSatrtCommand回调方法,那么你必须明确的停止service,因为现在该service被认为是started。这种情况下,该service会一直运行到自己调用stopSelf或者其余组件调用stopService来停止它,而不管它是否与所有的client解绑。

    除此之外,如果你的service是started且接受绑定,那么当系统调用onUnbind方法的时候,你可选择返回true,如果你想在下次一个client与这个service绑定的时候接受到一个onRebind的回调。onRebind返回void,但是client还是会在onServiceConnected回调中接收到IBinder。下面的图说明了这种生命周期的逻辑:

    image.png

    相关文章

      网友评论

          本文标题:Android官方文档笔记:Bound Services

          本文链接:https://www.haomeiwen.com/subject/lgtcbqtx.html