一、Android中的信息通信

Android中的信息通信分为两种情况
1、 当信息交互双方处于同一个进程的时候,那么信息交换就会很容易,在其中一方中获取另外一方的引用,然后就可以调用另外一方的方法,就完成了信息的交互。
2、 当信息交互双方处于不同的进程的时候,由于不同的进程的内存单元不同,它们互相独立,所以直接获取另外一方的引用基本不可能,另外因为Android中的内存可以分为两部分,一个是用户应用内存空间,另外一个就是内核空间,两个处于不同进程的应用虽然不能直接通信,但是它们都和内核空间关联,可以通过内核进行中转,Binder驱动就运行在内核空间内,所以官方提供了另外一个方法—Binder,用来实现IPC(进程间通信)。

二、Binder的基础知识

Binder服务是一种架构,提供了服务端接口,binder驱动,客户端接口三个模块,先看服务端。

Binder服务端

一个binder服务端实际上就是一个Binder类的对象,该对象会启动一个线程,等待接收binder驱动发来的消息,并且根据参数来执行内部onTransact方法内的不同模块代码,因此,binder服务端必须要重载onTransact方法。

Binder驱动

任意一个binder服务端被创建的时候,在binder驱动中就会相应的创建一个对应的mRemote对象,这个对象也是binder类,客户端访问远程服务的时候,都是通过mRemote对象来完成的。

客户端

客户端要访问远程服务,必须先获取到远程服务在binder驱动中对应的mRemote对象(mRemote对象其实也是一个binder类型的对象),然后调用mRemote对象中的transact方法,transact方法会执行以下几个内容:1、向服务端发送客户端传来的参数数据2、挂起客户端线程,等待服务端的结果3、接受服务返回的消息之后唤醒并继续执行客户端。(这里会产生一个问题,客户端如何获得远程服务在binder驱动中对应的mRemote对象,这个下面会说)

使用service类

服务分为两种,一个是系统服务,通过getSystemService调用,另一种是自定义服务Service,这里先以用户自定义服务为例,当客户端activity想要访问远程service的时候,有两种方法可以访问(源码在android.app.ContextImpl中)
1、 startService(Intent intent);
此方法用于启动指定的服务,但是并没有获得服务的binder引用,因此不能调用服务端的方法功能
2、 bindService(Intent service,ServiceConnection conn,int flags);
此方法用于绑定一个服务,方法的第二个参数是一个interface,如下:

public interface ServiceConnection {  //服务连接成功的时候回调此方法    public void onServiceConnected(ComponentName name, IBinder service);  //服务断开的时候回调此方法    public void onServiceDisconnected(ComponentName name);}

方法onServiceConnected的第二个参数就是一个IBinder对象,当客户端调用bindService方法请求访问服务,如果访问服务成功,那么就会启动回调onServiceConnected方法,返回目标服务的binder引用(这个引用就是服务在binder驱动中对应的mRemote对象),客户端可以将这个引用保存为全局变量,如此即可在客户端的任何位置都可以随时调用服务。
PS:获取到的binder对象是服务在驱动中对应的mRemote对象,而并不是服务本身的binder对象,注意这点区别
下面使用自定义服务来演示以下如何使用Binder来进行跨进程通信

三、示例

先建立服务端,新建一个Service,如下:

public class PersonalService extends Service{    public static final String DESCRIPTOR = "PersonalService";    @Override    public IBinder onBind(Intent intent) {        // TODO Auto-generated method stub        Log.v(DESCRIPTOR, "onBind");        return null;    }}

这只是一个单纯的service服务,前面说过,真正的服务端口是一个重载了onTransact方法的binder类,所以重点是下一步:创建真正的binder服务端口,代码如下:

public class PersonalService extends Service{    public static final String DESCRIPTOR = "PersonalService";    public PersonalBinder mBinder = new PersonalBinder();    @Override    public IBinder onBind(Intent intent) {        // TODO Auto-generated method stub        Log.v(DESCRIPTOR, "onBind");        return mBinder;    }    /**     * 此类是真正为远程客户端提供的服务接口,实际上是一个binder类的对象,     * 接收到binder驱动发来的消息后会根据 code执行不同的服务端方法     * */    private class PersonalBinder extends Binder{        @Override        protected boolean onTransact(int code, Parcel data, Parcel reply,                int flags) throws RemoteException {            // TODO Auto-generated method stub            //code标识着客户端期望调用服务端的哪个方法,需要双方约定好一组int值,不同的值代表不同的模块            switch(code){            case 1:            {                data.enforceInterface(DESCRIPTOR);//标注服务名称                //从data中取出参数的时候,也是需要双方有个约定                int a;                a = data.readInt();                int b;                b = data.readInt();                int result = a+b;                reply.writeNoException();                reply.writeInt(result);//注意回复是使用客户端传来的reply进行回复的                return true;            }            case 2:            {                data.enforceInterface(DESCRIPTOR);                String s = DESCRIPTOR;                reply.writeNoException();                reply.writeString(s);                return true;            }            }            return super.onTransact(code, data, reply, flags);        }    }}

上面的代码很简单,提供了两个方法,第一个方法是执行一个算术加法运算,将传进来的int类型的参数加起来得到结果并返回;第二个方法就是将当前的DESCRIPTOR字符串内容返回,别忘了在配置文件中为service添加action:

<service             android:name="com.example.server.PersonalService">            <intent-filter>                <action android:name="com.zm.server.personal">action>                <category android:name="android.intent.category.DEFAULT">category>            intent-filter>        service>

接下来创建客户端,根据前面说过的,客户端要先获取到远程服务在binder驱动中对应的mRemote对象,然后执行transact方法来和远程服务通信,代码如下:

public class BinderActivity extends Activity implements OnClickListener{    //三个按钮,绑定服务、执行算术运算、获取服务名称    private Button btn_bind,btn_add,btn_get;    //目标服务的binder引用    private IBinder personalBinder;    private ServiceConnection connection = new ServiceConnection() {        @Override        public void onServiceDisconnected(ComponentName name) {            // TODO Auto-generated method stub            personalBinder = null;        }        @Override        public void onServiceConnected(ComponentName name, IBinder service) {            // TODO Auto-generated method stub            Toast.makeText(BinderActivity.this, "onServiceConnected Success", Toast.LENGTH_SHORT).show();            //将目标服务的binder引用保存为全局变量,以便于随时随地调用            personalBinder = service;        }    };    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_second);        initView();    }    private void initView(){        btn_bind = (Button)findViewById(R.id.btn_bind);             btn_add = (Button)findViewById(R.id.btn_add);        btn_get = (Button)findViewById(R.id.btn_get);        btn_bind.setOnClickListener(this);              btn_add.setOnClickListener(this);        btn_get.setOnClickListener(this);    }    @Override    public void onClick(View v) {        // TODO Auto-generated method stub        switch(v.getId()){        case R.id.btn_bind:            Intent service = new Intent("com.zm.server.personal");            bindService(service, connection, Context.BIND_AUTO_CREATE);            break;        case R.id.btn_add://通过远程服务执行算术运算            if(personalBinder==null){                Toast.makeText(BinderActivity.this, "未获取到服务的binder引用", Toast.LENGTH_SHORT).show();                return;            }            //Parcel包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的            //正如生活中的邮局一样,用户一般使用的都是邮局的信封            android.os.Parcel data = android.os.Parcel.obtain();            android.os.Parcel reply = android.os.Parcel.obtain();            //返回结果            int result;                     try {                               data.writeInterfaceToken("PersonalService");//标注远程服务名称                //向包裹中添加参数变量,注意参数变量的个数和顺序需要约定好                data.writeInt(3);                data.writeInt(5);                //调用transact方法后,客户端线程进入binder驱动,客户端线程挂起,驱动向远程服务                //发送消息,包含了客户端传来的包裹,服务端收到包裹后取出参数,执行相应的代码,将结果                //放入reply包裹中返回,客户端notify唤醒                //第四个参数为0时,表示双向传输,代表有返回值,为1时表示单向,代表无返回                personalBinder.transact(1, data, reply, 0);                reply.readException();                //获取结果                result = reply.readInt();                Toast.makeText(BinderActivity.this, "结果是:"+result, Toast.LENGTH_SHORT).show();            } catch (RemoteException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }finally{                //记得将用完的包裹回收                data.recycle();                reply.recycle();            }                       break;        case R.id.btn_get:            if(personalBinder==null){                Toast.makeText(BinderActivity.this, "未获取到服务的binder引用", Toast.LENGTH_SHORT).show();                return;            }            //使用系统提供的Parcel类            android.os.Parcel data1 = android.os.Parcel.obtain();            android.os.Parcel reply1 = android.os.Parcel.obtain();            //返回结果            String result1;                 try {                data1.writeInterfaceToken("PersonalService");                personalBinder.transact(2, data1, reply1, 0);                reply1.readException();                result1 = reply1.readString();                Toast.makeText(BinderActivity.this, "结果是:"+result1, Toast.LENGTH_SHORT).show();            } catch (RemoteException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }finally{                data1.recycle();                reply1.recycle();            }            break;        }    }}

绑定服务之后,执行加法运算或者获取字符串内容,可以得到正确的结果,说明使用Binder框架进行IPC通信成功。

四、关于AIDL

其实aidl并不是IPC通信中必须的,但是它是非常实用的,它可以帮助程序员省去一些繁琐的操作,比如统一信息包裹中参数的顺序等。aidl工具可以把一个aidl文件转换成java文件,自动重载了onTransact和transact方法,并且在其中使用了stub-proxy的存根-代理模式。

关于binder框架,提供一张图,此图来源于android内核剖析,同时推荐此书

Binder框架远不止这些,以上只是我对binder框架在自定义服务里面的一些简单了解,如有不对之处还请各位朋友多多批评指正,谢谢!

更多相关文章

  1. 如何保证手机端的app访问web服务器的安全
  2. 第九章:Android中的数据存取
  3. 关于android各种双卡手机获取imei,imsi的处置(mtk,展讯,高通等)
  4. android动态增加控件时控制样式的方法
  5. Android中UI线程与后台线程交互设计的5种方法
  6. Android(安卓)滑动绘制流程探究 系统是如何提高滑动性能?
  7. Android(安卓)大图压缩处理,避免OOM
  8. 进程(一) 1.1 Android中异步处理大杀器——AsyncTask
  9. 像写Flutter一样开发Android原生应用

随机推荐

  1. android studio 使用NDK和swig编译c++示
  2. Android上的Back键事件捕获
  3. Android如何获取APP启动时间
  4. Android: Android学习的几点建议
  5. Android的数据处理:使用annotation实现JSO
  6. [置顶] Android防止内存溢出浅析
  7. Android小项目——简单计算器的实现
  8. Android(安卓)Retrofit与Spring后台配合,
  9. Android面试看重你什么?(推荐!!!)
  10. Android(安卓): 输入设备键值从底层到应