Binder
讨论到Binder相关知识应该三天三夜也讨论不完,的 隔壁老李头 大佬,花了15篇博客,重头到位系统的介绍了IPC的过程,涉及Linux基础,JNI等先关知识也罗列一通,但本文只面向初学者,所以文章得从基本用法开始.
简介
Binder是安卓的一个类,翻译成中文被称作“粘合剂”,他实现了IBinder的接口
- 从IPC的角度来看Binder是安卓的一个跨进程的方式 ,深层次的讲你可以把他理解为一个虚拟的物理设备(下一章 的Binder驱动),改设备的驱动是/dev/binder,
- 对于Framwork层,Binder是ServceMananger连接各各Mananger的ManangerService的桥梁。
- 在应用层来说Binder是客户端与服务端的进行通信的媒介,当服务端实现bindService方法的时候,要求返回一个Binder对象,而客户端可以通过该对象来获服务 提供的数据和服务(包括普通的服务或者基于AIDL的服务)
每层比较完后 ,我们再来讨论一下Binder到底是什么?
Binder 采用的是面向对象的思想,而且是很典型的面向对象的思想,在Binder模型(下一章会将)的四个角色里,他们都代表BBBBBBINDERRR,对于使用者而言,eg:Serve端 和 Client 端口 都持有Binder对象你不会发现他们有什么不同,一个Binder就代表了所有,我们根本不用关心他的具体的实现过程或者细节。可能现在感受不到,接着往下看吧:
创建aidl文件,然后reBuild可以生成以下java文件,你可能会问aidl是什么?
aidl:安卓接口定义语言,它是实现IPC一个很重要的方式,并且底层基于binder。
然后写入跨进程的接口:
// IBookMananger.aidl
package com.example.lixiongjie.ipc;
import com.example.lixiongjie.ipc.Book;//手动导入的
// Declare any non-default types here with import statements
interface IBookMananger {
List getBookList();
void addBook(in Book book);
}
这里值得注意的是,实现接口需要导入其他的类的时候(除了基本变量以外的成员属性的注意以下问题:
- 变量必须进行序列化,而且Binder支持的是Parcelable序列化。
- 创建该成员的AIDL文件声明
- 手动导入该类(例如文中的Book类)
接着就像上文我们所说的reBuild生成:
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/lixiongjie/Desktop/Ipc/app/src/main/aidl/com/example/lixiongjie/ipc/IBookMananger.aidl
*/
package com.example.lixiongjie.ipc;
// Declare any non-default types here with import statements
public interface IBookMananger extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
{
//Binder的唯一标识,一般用类名表示。
private static final java.lang.String DESCRIPTOR = "com.example.lixiongjie.ipc.IBookMananger";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.lixiongjie.ipc.IBookMananger interface,
* generating a proxy if needed.
*/
//用于将服务端的Binder对象转换为客户端所需的AIDL接口类型的对象,这个转换是有区别的,如果客户端与服务端统一进程返回的就是Stub对象本身,否则就返回封装后的Stub.proxy.
public static com.example.lixiongjie.ipc.IBookMananger asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);//查询是不是本地的,如果是就直接返回Stub对象本身。
if (((iin!=null)&&(iin instanceof com.example.lixiongjie.ipc.IBookMananger))) {
return ((com.example.lixiongjie.ipc.IBookMananger)iin);
}
return new com.example.lixiongjie.ipc.IBookMananger.Stub.Proxy(obj);//否则证明不是本地的,传入远程代理过来。
}
//返回当前Binder对象
@Override
public android.os.IBinder asBinder()
{
return this;
}
/**
*这个方法比较复杂,一般运行在远程端中的Binder线程池中,当客户端发送跨进程请求后远程,然后远程端会通过系统底层的分装来把远程请求交给此方法来处理,然后来看看方法内部的实现吧:
* 首先根据code确定目标方法,然后通过data取出目标方法参数,执行完后向reply写入返回值
*@param code 可以确定客户端的请求的目标方法是啥。
* @param data 取出目标方法的参数
* @return 如果为true代表远程请求成功,否则就为失败,可以利用这个特性来做权限验证。
*/
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.lixiongjie.ipc.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.lixiongjie.ipc.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.lixiongjie.ipc.IBookMananger
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
/**
* 1.创建两个Parcel对象1.data传入的对象2.reply输出的对象 还要返回对象reslut:List;
* 2.调用transact方法发起RPC(远程过程调用)请求,同时线程挂起
* 3.然后服务端的onTranscat会调用,直到RPC返回值,线程才恢复
* 4.最后从reply中取出PRC的结果,返回得到的值
* @return
* @throws android.os.RemoteException
*/
@Override
public java.util.List getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.lixiongjie.ipc.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
/**
* 和上面一样,但是值得注意的是addBook没有返回值,所以他不需要reply,但是在传参进去会调用writeToParcel()传入序列化的对象Parcel中
* @param book
* @throws android.os.RemoteException
*/
@Override
public void addBook(com.example.lixiongjie.ipc.Book book) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List getBookList() throws android.os.RemoteException;
public void addBook(com.example.lixiongjie.ipc.Book book) throws android.os.RemoteException;
}
由于客户端进行PRC的时候,客户端的线程会挂起等待服务端的进程返回数值,但是如果这个远程方法很耗时,那就不能再UI线程发起次远程请求了,其次服务端的Binder运行在Binder的线程池中,所以不管耗不耗时间都应该采用同步方式去处理,因为它们都在一个线程中。
手动写Binder在服务端需要创建一个BookManangerImpl并在Service得到onBind方法返回即可。
上述代码中我们查看结构,第一个该接口实现是 android.os.IInterface接口,这里的IInterface代表的就是远程server对象具有什么能力。
抽象类Stub extends android.os.Binder implements com.example.lixiongjie.ipc.IBookMananger
这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力,至于为什么是抽象类,是因为设定的方法要实现的子类是服务端,及在接口中远程端实现的方法。
DESCRIPTOR:是该Binder的唯一标识,会发现我们在 onTransact 函数中的返回数据体reply,和add方法里写入数据的时候都会加上这个标识,验证就在onTransact的case中验证。
in、out、inout
在官方文档中支出,所有的非原语参数需要指示数据的方向标记,可以是in、out、inout。默认的原语是in,不能是其他流向。
这里指定的非原语是指:除了Java的基本类型外的其他参数,也就是对象。我们在AIDL使用的时候需要知道这个参数的流向。
那什么是数据的方向标记呢?
首先,数据的方向标记是针对客户端中的那个传入的方法参数而言。数据流向的标识符不能使用在返回参数上,只能使用在方法参数上面。
in:他表示的是这个参数只能从客户端流向服务端,比如客户端传递了一个User对象给服务端,服务端会收到一个完整的User对象,然后假如在服务端对这个对象进行操作,那么这个改变是不会反映到客户端的,这个流向也就是只能从客户端到服务端。
out:他表示,当客户端传递参数到服务端的时候,服务端将会收到一个空的对象,假如服务端对该对象进行操作,将会反映到客户端。比如,客户端传递一个User对象到服务端,服务端接收到的是一个空的User对象(不是null,只是有点像new一个User对象)。当服务端对这个User对象进行改变的时候,他的值变化将会反映到客户端。
inout,它具有这二者的功能,也就是客户端传递对象到服务端,可以接收到完整的对象,同时服务端改变对象,也会反映到客户端。
总结来说,in类似于传值,out类似于传引用,只是out的引用 到了服务端为空,inout则具有二者的功能,默认的是in。
linkToDeath和unlinkToDeath
如果远程端因为某些原因终止了连接,这样会导致我们远程调用失败,但是关键的地方是我们并不知道Binder的连接断开,这样会导致客户端受影响。
然后英雄linkToDeath和unlinkToDeath登场,通过linkToDeath去设置死亡代理,当监听到死亡的时候,我们会受到通知,然后进行下一步处理工作。
首先声明DeathRecipient对象,该接口只有一个方法binderDied,当Binder死亡的时候会回到该方法,然后我们可以移出之前重写绑定远程服务。
Bundle
四大组件传输的三大组件(除了ContentPrider),都是都支持在Intent的中传输Bundle数据,由于Bundle实现了Parcelable接口,所以在组件间支持跨进程传输也理所应当,当然传输的数据也应该被序列化,eg:基本类型,实现parcelable接口对象,实现了SerialVersion接口的对象,和Android特殊的对象。
除了直接传输的典型场景
见《艺术探索》62※
使用文件共享
共享文件是一个不错的进程通信方式,在Window上,一个文件加上排斥锁来解决线程读写的问题,但是Android是基于Linux ,使得读写的并发可以没有限制的进行,所以两个线程对同一个文件进行操作被允许了,但是有可能会出现问题,当然我们还是可以, 通过序列化一个对象在系统文件中然后另外个进程在中恢复这个对象,
//在第一进程的Activity储存
private void persistToFile(){
new Thread(new Runnable() {
@Override
public void run() {
Book book = new Book(1,"童话故事");
File file = new File();
if (!file.exists()){
file.mkdirs();
}
File cache = new File();
ObjectOutputStream out = null;
try {
out = new ObjectOutputStream(new FileOutputStream(cache));
out.writeObject(book);
} catch (IOException e) {
e.printStackTrace();
}
finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
//在第二进程的Activity读取
private void recoverFromFile(){
new Thread(new Runnable() {
@Override
public void run() {
Book book = null;
File cache = new File();
if (cache.exists()){
ObjectInputStream inputStream =null;
try {
inputStream = new ObjectInputStream(new FileInputStream(""));
book = (Book) inputStream.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
可以看到这个其实就是之前的内容,但是我得注意的是储存的格式可以是文本文件,也可以是XML文件,但是我们得让读取的双方约定格式,第二份存在的问题就是并发读写的问题,如果是并发读有可能拿不到最新的,但是并发写问题就更为严重,所以要么避免要么得同步来限制多线程的写。
SharedPrefercens
SharedPrefercens是轻量级储存方案,它通过键值对来储存数据,底层实现XML文件来存储键值对,目录位于/data/data/packagename(包名)/shared_prefs下,本质上讲SP是文件一种,但是系统对它的读写有缓存策略,导致内存有一份SP文件备份缓存,因此在多线程的模式下,读写会不可靠