关于java:BAT大厂技术大佬-3分钟带你看懂android的Binder机制必看

4次阅读

共计 8317 个字符,预计需要花费 21 分钟才能阅读完成。

一. 引言
最近一段时间因为工作,接触到 framework 局部比拟多一点,也不免要和 Binder 打一些交道,也整顿了一些相干常识,但筹备写这篇文章时,还是有些慌。而且对于整个 Binder 机制的复杂程度不是喋喋不休能描叙分明的,也胆怯本人的了解有些偏差,误导一些敌人(ps: 反正也没人看 …. 扎心)所以也参考了很多材料。

本文次要站在 Android 开发的角度来大抵解析下 Binder 在 java 层的一些常识原理,给大家脑子造成一个残缺的概念,比方 AIDL 的实现原理,Binder 是怎么通信的等等,文章文字较多,请急躁观看

相熟的敌人能够看看下篇,将介绍 Activity 的启动流程以及 Android 中的 Hook 技术:

震惊!Activity 不必注册?手把手教你 Hook

二.Binder 概述
2.1 Android 为什么抉择 Binder
Android 是基于 Linux 内核的,所以 Android 要实现过程间的通信,其实大可应用 linux 原有的一些伎俩,比方管道,共享内存,socket 等形式,然而 Android 还是采纳了 Binder 作为次要机制,阐明 Binder 具备无可比拟的劣势。

其实过程通信大略就两个方面因素,一者性能方面,传输效率问题,传统的管道队列模式采纳内存缓冲区的形式,数据先从发送方缓存区拷贝到内核开拓的缓存区中,而后再从内核缓存区拷贝到接管方缓存区,至多有两次拷贝过程,而 socket 都晓得传输效率低,开销大,用于跨网络过程交互比拟多,共享内存尽管无需拷贝。

二者这是平安问题,Android 作为一个开放式,领有泛滥开发者的的平台,应用程序的起源宽泛,确保终端平安是十分重要的,传统的 IPC 通信形式没有任何措施,根本依附下层协定,其一无奈确认对方牢靠的身份,Android 为每个装置好的应用程序调配了本人的 UID,故过程的 UID 是甄别过程身份的重要标记,传统的 IPC 要发送相似的 UID 也只能放在数据包里,但也容易被拦挡,歹意防御,socket 则须要裸露本人的 ip 和端口,晓得这些恶意程序则能够进行任意接入。

综上所述,Android 须要一种高效率,安全性高的过程通信形式,也就是 Binder,Binder 只须要一次拷贝,性能仅次于共享内存,而且采纳的传统的 C / S 构造,稳定性也是没得说,发送增加 UID/PID,安全性高。

2.2 Binder 实现机制
2.2.1 过程隔离
咱们晓得过程之间是无奈间接进行交互的,每个过程独享本人的数据,而且操作系统为了保障本身的平安稳定性,将零碎内核空间和用户空间拆散开来,保障用户程序过程解体时不会影响到整个零碎,简略的说就是,内核空间(Kernel)是零碎内核运行的空间,用户空间(UserSpace)是用户程序运行的空间。为了保障安全性,它们之间是隔离的,所以用户空间的过程要进行交互须要通过内核空间来驱动整个过程。

2.2.2 C/ S 构造
Binder 是基于 C / S 机制的,要实现这样的机制,server 必须须要有特定的节点来承受到 client 的申请,也就是入口地址,像输出一个网址,通过 DNS 解析出对应的 ip,而后进行拜访,这个就是 server 提供进去的节点地址,而 Binder 而言的话,与传统的 C / S 不太一样,Binder 自身来作为 Server 中提供的节点,client 拿到 Binder 实体对象对应的地址去拜访 Server,对于 client 而言,怎么拿到这个地址并建设起整个通道是整个交互的关键所在,而且 Binder 作为一个 Server 中的实体,对象提供一系列的办法来实现服务端和客户端之间的申请,只有 client 拿到这个援用就能够或者一个有着该办法代理对象的援用,就能够进行通信了。

援用 Android Bander 设计与实现 – 设计篇一段话来总结:

面向对象思维的引入将过程间通信转化为通过对某个 Binder 对象的援用调用该对象的办法,而其独特之处在于 Binder 对象是一个能够跨过程援用的对象,它的实体位于一个过程中,而它的援用却遍布于零碎的各个过程之中。最迷人的是,这个援用和 java 里援用一样既能够是强类型,也能够是弱类型,而且能够从一个过程传给其它过程,让大家都能拜访同一 Server,就象将一个对象或援用赋值给另一个援用一样。Binder 含糊了过程边界,淡化了过程间通信过程,整个零碎好像运行于同一个面向对象的程序之中。不拘一格的 Binder 对象以及星罗棋布的援用好像粘接各个应用程序的胶水,这也是 Binder 在英文里的原意。

2.2.3 Binder 通信模型
Binder 基于 C / S 的构造下,定义了 4 个角色:Server、Client、ServerManager、Binder 驱动,其中前三者是在用户空间的,也就是彼此之间无奈间接进行交互,Binder 驱动是属于内核空间的,属于整个通信的外围,尽管叫驱动,然而实际上和硬件没有太大关系,只是实现的形式和驱动差不多,驱动负责过程之间 Binder 通信的建设,Binder 在过程之间的传递,Binder 援用计数治理,数据包在过程之间的传递和交互等一系列底层反对。

ServerManager 的作用?

咱们晓得 ServerManager 也是属于用户空间的一个过程,次要作用就是作为 Server 和 client 的桥梁,client 能够 ServerManager 拿到 Server 中 Binder 实体的援用,这么说可能有点含糊,举个简略的例子,咱们拜访,www.baidu.com,百度首页页面就显示进去了,首先咱们晓得,这个页面必定是公布在百度某个服务器上的,DNS 通过你这个地址,解析出对应的 ip 地址,再去拜访对应的页面,而后再把数据返回给客户端,实现交互。这个和 Binder 的 C / S 十分相似,这里的 DNS 就是对应的 ServerManager,首先,Server 中的 Binder 实体对象,将本人的援用(也就是 ip 地址)注册到 ServerManager,client 通过特定的 key(也就是百度这个网址)和这个援用进行绑定,ServerManager 外部本人保护一个相似 MAP 的表来一一对应,通过这个 key 就能够向 ServerManager 拿到 Server 中 Binder 的援用,对应到 Android 开发中,咱们晓得很多零碎服务都是通过 Binder 去和 AMS 进行交互的,比方获取音量服务:

AudioManager am = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE);

仔细的敌人应该发现 ServerManager 和 Server 也是两个不同的过程呀,Server 要向 ServerManager 去注册不是也要波及到过程间的通信吗,以后实现过程间通信又要用到过程间的通信,你这不是扯犊子吗 …. 莫急莫急,Binder 的奇妙之处在于,当 ServerManager 作为 Serve 端的时候,它提供的 Binder 比拟非凡,它没有名字也不须要注册,当一个过程应用 BINDER_SET_CONTEXT_MGR 命令将本人注册成 SMgr 时 Binder 驱动会主动为它创立 Binder 实体,这个 Binder 的援用在所有 Client 中都固定为 0 而无须通过其它伎俩取得。也就是说,一个 Server 若要向 ServerManager 注册本人 Binder 就必须通过 0 这个援用号和 ServerManager 的 Binder 通信, 有敌人又要问了,server 和 client 属于两个不同的过程,client 怎么能拿到 server 中对象,无妨先看看上面的交互图

从上图很清晰的能够看进去整个的交互过程,本来从 SM 中拿到 binder 的援用,通过 Binder 驱动层的解决之后,返回给了 client 一个代理对象,实际上如果 client 和 server 处于同一个过程,返回的就是以后 binder 对象,如果 client 和 server 不处于同一个过程,返回给 client 的就是一个代理对象,这一点,有趣味能够看下源码。

2.2.4 Binder 角色的定位
Binder 实质上只是提供了一种通信的形式,和咱们具体要实现的内容没有关系,为了实现这个服务,咱们须要定义一些接口,让 client 可能近程调用服务,因为是跨过程,这时候就要设计到代理模式,以接口函数位基准,client 和 server 去实现接口函数,Server 是服务真正的实现,client 作为一个近程的调用。

从 Server 过程来看,Binder 是存在的实体对象,client 通过 transact()函数,通过 Binder 驱动,最终回调到 Binder 实体的 onTransact()函数中。
从 Client 过程的角度看,Binder 指的是对 Binder 代理对象,是 Binder 实体对象的一个近程代理,通过 Binder 驱动进行交互
3. 手写过程通信
下面说了那么多,大家伙也看累了,上面通过代码的模式让大家对 Binder 加深点了解,日常开发中,波及到过程间通信的话,咱们首先想到的可能就是 AIDL,但不晓得有没有和我感觉一样的敌人。。第一次写 AIDL 是蒙蔽的,通过.aidl 文件,编译器主动生成代码,生成一个 java 文件,外面又有 Stub 类,外面还有 Proxy 类,齐全不了解外面的机制,的确不便于咱们了解学习,为了加深了解,咱们摈弃 aidl,手写一个通信代码。

首先咱们要定义一个接口服务,也就是上述的服务端要具备的能力来提供给客户端, 定义一个接口继承 IInterface,代表了服务端的能力

public interface PersonManger extends IInterface {

void addPerson(Person mPerson);
List<Person> getPersonList();

}
复制代码
接下来咱们就要定义一个 Server 中的 Binder 实体对象了,首先必定要继承 Binder,其次须要实现下面定义好的服务接口

public abstract class BinderObj extends Binder implements PersonManger {

public static final String DESCRIPTOR = "com.example.taolin.hellobinder";
public static final int TRANSAVTION_getPerson = IBinder.FIRST_CALL_TRANSACTION;
public static final int TRANSAVTION_addPerson = IBinder.FIRST_CALL_TRANSACTION + 1;
public static PersonManger asInterface(IBinder mIBinder){IInterface iInterface = mIBinder.queryLocalInterface(DESCRIPTOR);
    if (null!=iInterface&&iInterface instanceof PersonManger){return (PersonManger)iInterface;
    }
    return new Proxy(mIBinder);
}
@Override
protected boolean onTransact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags) throws RemoteException {switch (code){
        case INTERFACE_TRANSACTION:
            reply.writeString(DESCRIPTOR);
            return true;

        case TRANSAVTION_getPerson:
            data.enforceInterface(DESCRIPTOR);
            List<Person> result = this.getPersonList();
            reply.writeNoException();
            reply.writeTypedList(result);
            return true;

        case TRANSAVTION_addPerson:
            data.enforceInterface(DESCRIPTOR);
            Person arg0 = null;
            if (data.readInt() != 0) {arg0 = Person.CREATOR.createFromParcel(data);
            }
            this.addPerson(arg0);
            reply.writeNoException();
            return true;
    }
    return super.onTransact(code, data, reply, flags);

}

@Override
public IBinder asBinder() {return this;}

}
复制代码
首先咱们看 asInterface 办法,Binder 驱动传来的 IBinder 对象,通过 queryLocalInterface 办法,查找本地 Binder 对象,如果返回的就是 PersonManger,阐明 client 和 server 处于同一个过程,间接返回,如果不是,返回给一个代理对象。

当然作为代理对象,也是须要实现服务接口

public class Proxy implements PersonManger {

private IBinder mIBinder;
public Proxy(IBinder mIBinder) {this.mIBinder =mIBinder;}

@Override
public void addPerson(Person mPerson) {Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();

    try {data.writeInterfaceToken(DESCRIPTOR);
        if (mPerson != null) {data.writeInt(1);
            mPerson.writeToParcel(data, 0);
        } else {data.writeInt(0);
        }
        mIBinder.transact(BinderObj.TRANSAVTION_addPerson, data, replay, 0);
        replay.readException();} catch (RemoteException e){e.printStackTrace();
    } finally {replay.recycle();
        data.recycle();}
}

@Override
public List<Person> getPersonList() {Parcel data = Parcel.obtain();
    Parcel replay = Parcel.obtain();
    List<Person> result = null;
    try {data.writeInterfaceToken(DESCRIPTOR);
        mIBinder.transact(BinderObj.TRANSAVTION_getPerson, data, replay, 0);
        replay.readException();
        result = replay.createTypedArrayList(Person.CREATOR);
    }catch (RemoteException e){e.printStackTrace();
    } finally{replay.recycle();
        data.recycle();}
    return result;
}

@Override
public IBinder asBinder() {return null;}

}
复制代码
这里的代理对象本质就是 client 最终拿到的代理服务,通过这个就能够和 Server 进行通信了,首先通过 Parcel 将数据序列化,而后调用 remote.transact()将办法 code,和 data 传输过来,对应的会回调在在 Server 中的 onTransact()中

而后是咱们的 Server 过程,onBind 办法返回 mStub 对象,也就是 Server 中的 Binder 实体对象

public class ServerSevice extends Service {

private static final String TAG = "ServerSevice";
private List<Person> mPeople = new ArrayList<>();

@Override
public void onCreate() {mPeople.add(new Person());
    super.onCreate();}

@Nullable
@Override
public IBinder onBind(Intent intent) {return mStub;}
private BinderObj mStub = new BinderObj() {
    @Override
    public void addPerson(Person mPerson) {if (mPerson==null){mPerson = new Person();
            Log.e(TAG,"null obj");
        }
        mPeople.add(mPerson);
        Log.e(TAG,mPeople.size()+"");
    }

    @Override
    public List<Person> getPersonList() {return mPeople;}
};

}
复制代码
最终咱们在客户端过程,bindService 传入一个 ServiceConnection 对象,在与服务端建设连贯时,通过咱们定义好的 BinderObj 的 asInterface 办法返回一个代理对象,再调用办法进行交互

public class MainActivity extends AppCompatActivity {

private boolean isConnect = false;
private static final String TAG = "MainActivity";
private PersonManger personManger;
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    start();
    findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {if (personManger==null){Log.e(TAG,"connect error");
                return;
            }
            personManger.addPerson(new Person());
            Log.e(TAG,personManger.getPersonList().size()+"");
        }
    });
}

private void start() {Intent intent = new Intent(this, ServerSevice.class);
    bindService(intent,mServiceConnection,Context.BIND_AUTO_CREATE);
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {Log.e(TAG,"connect success");
        isConnect = true;
        personManger = BinderObj.asInterface(service);
        List<Person> personList = personManger.getPersonList();
        Log.e(TAG,personList.size()+"");
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {Log.e(TAG,"connect failed");
        isConnect = false;
    }
};

}
复制代码
这样的话,一次实现的过程间的交互就实现了~ 是不是感觉没有设想中那么难,最初倡议大家在不借助 AIDL 的状况下手写实现 Client 和 Server 过程的通信,加深对 Binder 通信过程的了解。

正文完
 0