Binder框架的一些简单总结(关于自定义服务中的Binder)
一、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框架在自定义服务里面的一些简单了解,如有不对之处还请各位朋友多多批评指正,谢谢!
更多相关文章
- 如何保证手机端的app访问web服务器的安全
- 第九章:Android中的数据存取
- 关于android各种双卡手机获取imei,imsi的处置(mtk,展讯,高通等)
- android动态增加控件时控制样式的方法
- Android中UI线程与后台线程交互设计的5种方法
- Android(安卓)滑动绘制流程探究 系统是如何提高滑动性能?
- Android(安卓)大图压缩处理,避免OOM
- 进程(一) 1.1 Android中异步处理大杀器——AsyncTask
- 像写Flutter一样开发Android原生应用