android绑定Service(含IPC)

来源:互联网 发布:首席体验官 知乎 编辑:程序博客网 时间:2024/06/06 13:14

前言:四大组件中的service是其中除了activity之外用得最多的可能就是它了,当然,其他两个组件有它们自己的应用场合,这个在每个应用中使用情况可能不同,需要根据应用的需要选择使用相应的组件来完成任务。这篇文章将介绍如何绑定一个服务Service,使得客户端和Service进行通讯。

一、绑定Service

 绑定一个服务,首先需要定义一个类继承系统的Service基类,然后必须重写onBind()方法并返回一个IBinder对象给客户端使用,这个返回的IBinder对象作为这个Service的代理对象,通过它可以使得客户端和这个服务进行通讯。通常,多个不同的客户端可以同时绑定这服务,但是需要注意的是onBind()方法并不会在每次客户端绑定服务的时候都会调用,它只会在第一个客户端绑定这个服务的时候调用,所以,如果是多个客户端绑定相同的服务,那么只会有一个相同的IBinder对象。因此,绑定服务关键在于定义一个自己的IBinder对象,然后在onBind()方法中返回给客户端使用。下面我们就做关键的一步,创建IBinder对象。

有以下三种方式可以得到IBinder对象:

  1. 继承Binder类:这种方式适用于你的服务只有你自己的应用或者和你的这app在相同的进程里面的客户端使用,如果要让不同进程的应用使用你的服务,那么这种方式就行不通了。
  2. 使用messenger:这种方式适用于你的服务需要在不同的进程间通讯(IPC),但这种方式有一个缺点就是所有处理请求(消息)都是在单一的线程中(没必要再将你的service设置成线程安全的了),也就是说不能处理多线程并发的情况。
  3. 使用AIDL:使用AIDL实现绑定服务就是为了解决上面两种情况的弊端,一个是service不能在进程间通讯,另一个就是service不能处理多线程的情况,但是,你的应用除非必须要使用AIDL,否则上面两种可以满足需求的情况下就没必要使用AIDL(消耗系统资源)。

1. 继承Binder类

 要绑定一个服务(和服务通讯),那么客户端需要得到service的一个实例(引用)或者一个接口,我们知道在客户端使用了bindService()绑定了一个服务后系统会调用该服务的onBind()方法(第一次绑定该服务)返回一个IBinder对象给客户端,那么我们可以通过这个IBinder对象(一种通讯接口)和service进行交互(通讯)。通常我们在自定义的Binder类中定义一些public方法,这样,客户端可以通过这个方法获取service的实例或者做其他的一些事情,下面,我们通过一个例子来理解这种方法绑定服务。

LocalService.java

package com.example.lt.boundservice;import android.app.Service;import android.content.Intent;import android.os.Binder;import android.os.IBinder;import android.support.annotation.Nullable;public class LocalService extends Service{    private String[] names = {"吕布","赵子龙","关羽"};    private IBinder myBinder = new MyBinder();    @Nullable    @Override    public IBinder onBind(Intent intent) {        return myBinder;    }    public class MyBinder extends Binder{        public LocalService getService(){            return LocalService.this;        }    }    public String getName(int postion){        return names[postion];    }}

客户端端绑定服务:

package com.example.lt.boundservice;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.support.design.widget.FloatingActionButton;import android.support.design.widget.Snackbar;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import android.view.Menu;import android.view.MenuItem;import android.widget.TextView;public class MainActivity extends AppCompatActivity {    private LocalService localService;    private TextView textView;    private boolean mBound;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        textView = (TextView) findViewById(R.id.textView);    }    public void getName(View view){        if(mBound) {            textView.setText(localService.getName(1));        }    }    @Override    protected void onStart() {        super.onStart();        // 绑定服务        Intent intent = new Intent(MainActivity.this,LocalService.class);        mBound = bindService(intent, conn, Context.BIND_AUTO_CREATE);    }    ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            // 这里的service就是onBind返回的那个IBinder对象            System.out.println("ComponentName:"+name);            LocalService.MyBinder myBinder = (LocalService.MyBinder) service;            localService = myBinder.getService();        }        @Override        public void onServiceDisconnected(ComponentName name) {            System.out.println("onServiceDisconnected");            System.out.println("ComponentName:"+name);        }    };    @Override    protected void onStop() {        super.onStop();        unbindService(conn);    }}

这里需要注意的是绑定服务可能失败,所以要在确认绑定服务成功的情况下使用service,这种通过绑定启动一个服务的方式会将service和客户端(这里是activty)绑定在一起,所以需要在合适的时间绑定和取消绑定service(管理service的生命周期)。

2. 使用Messenger

 如果你的service需要在不同的进程间通讯,那么你可以使用Messenger去提供一个接口给你的service,使得你的service可以在不同的进程间通讯(IPC)。

通常,可以分为如下几个步骤来使用Messenger绑定服务:

  1. 在你的service里面提供一个Handler,这个Handler用来处理客户端发送过来的消息(响应);
  2. 创建这个Handler的对象,并用这个Handler对象创建一个Messenger对象(这个Messenger对象持有handler的引用);
  3. 在onBind()方法中返回这个Messenger对象创建的IBinder对象;
  4. 客户端绑定服务得到返回的IBinder对象,并用这个对象在客户端初始化Messenger对象,这个对象持有service的Handler对象的引用,这样客户端通过这个Messenger对象发送消息给service。

按照上面的步骤,咋们来写个测试代码来实践一下。由于是测试在进程间进行通讯,所以,我们需要创建两个项目(应用),一个包含那个远程服务,一个用来访问那个远程服务。

创建两个项目(应用),一个叫RemoteService,一个叫RemoteClient

(1)在RemoteService项目中创建远程服务RemoteService

package com.example.lt.messengerdemo;import android.app.Service;import android.content.Intent;import android.os.Handler;import android.os.IBinder;import android.os.Message;import android.os.Messenger;import android.support.annotation.Nullable;import android.widget.Toast;public class RemoteService extends Service{    private static final  int SAY_HELLO = 0;    @Nullable    @Override    public IBinder onBind(Intent intent) {        // 3. 通过Messenger对象返回一个IBinder对象        return mMessenger.getBinder();    }    /**     * 1. 创建一个Handler用来接收,处理客户端的消息     */    final Handler mHandler = new Handler(){        @Override        public void handleMessage(Message msg) {            switch (msg.what){                case SAY_HELLO:                    Toast.makeText(RemoteService.this,"hello,I am RemoteService",Toast.LENGTH_SHORT).show();                    break;                default:                    super.handleMessage(msg);            }        }    };    /**     * 2. 通过这个Handler创建一个Messenger对象     */    final Messenger mMessenger = new Messenger(mHandler);}

在清单文件中注册这个服务,并给一个action,方便客户端通过这个action来绑定这个服务:

<service android:name="com.example.lt.messengerdemo.RemoteService">    <intent-filter>        <action android:name="com.example.lt.messengerdemo.RemoteService"> </action>    </intent-filter></service>

可以看到,在这一步中完成了三面的三个步骤,接下来,我们需要在客户端来绑定这个远程服务。

(2)远程客户端绑定服务

package com.example.lt.remoteclient;import android.content.ComponentName;import android.content.Context;import android.content.Intent;import android.content.ServiceConnection;import android.os.Bundle;import android.os.IBinder;import android.os.Message;import android.os.Messenger;import android.os.RemoteException;import android.support.design.widget.FloatingActionButton;import android.support.design.widget.Snackbar;import android.support.v7.app.AppCompatActivity;import android.support.v7.widget.Toolbar;import android.view.View;import android.view.Menu;import android.view.MenuItem;import android.widget.Toast;public class MainActivity extends AppCompatActivity {    private Messenger mMessenger;    private boolean mBound;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    @Override    protected void onStart() {        super.onStart();        Intent intent = new Intent();        intent.setAction("com.example.lt.messengerdemo.RemoteService");        intent.setPackage("com.example.lt.messengerdemo");        mBound = bindService(intent, conn, Context.BIND_AUTO_CREATE);    }    ServiceConnection conn = new ServiceConnection() {        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            mMessenger = new Messenger(service);        }        @Override        public void onServiceDisconnected(ComponentName name) {        }    };    @Override    protected void onStop() {        super.onStop();        unbindService(conn);    }    public void hello(View view) throws RemoteException {        if(mBound) {            Message message = Message.obtain();            message.what = 0;            mMessenger.send(message);        }else{            Toast.makeText(MainActivity.this,"还没接通呢?",Toast.LENGTH_SHORT).show();        }    }}

这里先在onStart()方法中绑定远程服务,然后在绑定服务成功后的那个回调方法onServiceConnected中通过得到的IBinder对象创建Messenger对象,然后用户点击hello按钮后向远程服务发送一个消息(调用hello()方法),最后在onStop()方法中取消绑定服务。

注意:android5.0后对service的隐式启动做了一些限制,隐式启动需要设置action和package(service所在的那个应用的包名)。

客户端布局文件:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:orientation="horizontal"    android:gravity="center"    android:layout_height="match_parent"    >    <Button        android:onClick="hello"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="hello" /></LinearLayout>

这里就不贴出测试结果的动态图了,直接说个测试结果吧,测试结果是我们点击hello按钮后,系统弹出一个“hello,I am RemoteService”吐司,看到这个结果,我们就知道远程服务响应了客户端的请求(处理了接收到的消息)。

二、管理Service的生命周期

 我们知道,客户端可以通过bindService()绑定Service和startService()启动服务一个非常明显的区别就是前者会将Service的生命周期和客户端生命周期绑定在一起(通常是activity),后者Service的生命周期由这个服务自己管理,所以当我们绑定Service来启动服务的时候,我们需要在合适的时候(不需要和Service通讯)绑定和解除绑定服务,比如:当我们Activity在不可见的时候不需要服务(通讯),可见的时候需要服务(通讯),那么,我们就可以在Activity的onStart()中绑定服务,在onStop()中解除绑定;当我们需要Service一直运行在后台,直到Activity被销毁,那么,我们就可以在onCreate()onDestory()中分别绑定和解除绑定服务,但通常不会再onResume()onPause()中绑定和解除绑定服务,下面我们看一张图来理解Service生命周期:

这里写图片描述

这是android官网的一一张图片,这里这个说明,当客户端通过startService()方式启动服务的时候,系统会调用服务的onStartCommand()方法(我们一般在这个方法里面做任务)而不会调用onBind()方法;当客户端通过bindService()方法启动服务的时候,系统会调用onBind()方法将Service和客户端绑定在一起,而不会调用onStartCommand方法。

到这里,绑定一个服务的前2种方式基本介绍完了,关于AIDL绑定服务,大家可以参考:android使用AIDL实现跨进程通讯(IPC)。

总结:

 绑定服务可以有很多种方式,具体通过什么方式,这里有一个原则,如果service只服务于自己的这个应用或者在同一进程(通常不同应用在不同的进程中)中共享,那么通过第一种方式就行了;如果你应用的service需要向远程客户端提供服务,但不需要在这个service中处理多线程并发,那么可以选择messenger方式绑定服务;如果你的service既要在不同的应用(进程)中通讯(IPC),又要在service中处理多线程,那么可以考虑使用AIDL绑定服务。不过,到这里,我好奇的是Messenger绑定远程服务实现IPC是否能够像AIDL一样传递数据(包括自定义类型)呢?还是只能跨进程传递消息?求解。

0 0