Android内核之Binder

一,Binder框架讲解
Binder是一种框架,这种架构提供了服务端接口,Binder驱动,客户端接口三个模块
服务端 一个Binder服务端实际上就是一个Binder类对象,该对象那个一旦创建,内部就会创建一个隐藏的线程,该线程就会接收Binder驱动发送的消息,收到消息后,会执行Binder中的onTransact()函数,并按照该函数的参数执行不同的服务代码,因此 ,要是先一个onTransact()方法
Binder驱动
Binder驱动,任意一个服务端Binder对象被创建时,同时会在Binder驱动创建一个mRemote对象,该对象的类型也是Binder类.客户端要访问远程服务时,都是通过mRemote对象.
程序客户端
客户端想要访问远程服务,必须要获取服务在Binder对象中对应的mRemote引用,至于如何获取,获得该mRemote对象后,就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载transact()方法
重载的内容主要包括:
- 以线程间消息通信的模式,向服务端发送客户端传递过来的参数
- 当挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定函数通知(notify).
- 接收到通知线程的通知,然后继续发送客户端线程,并返回到客户端代码区

注意:客户端并不是直接调用了远程服务对应的BInder,而事实上则是通过Binder驱动进行中转.即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,所不同的是Binder驱动中的对象那个不会额外产生一个线程

二,程序设计的Service端
设计Service端一般都Android已经为你实现了,但是有实力的Android程序员可以按照自己实现一个Service,只要基于Binder类新建一个Service类即可,以下的BaseService代码就是继承了Binder类,并通过start(String filepath)和stop()方法来实现Service类


public class BaseService extends Binder{
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        // TODO Auto-generated method stub
        return super.onTransact(code, data, reply, flags);
    }
    public void start(String filepath) {

    }
    public void stop() {

    }
}

通过创建BaseService的实例来启动

可以看到DDMS的Threads中多了一个Binder_3线程,定义了服务类之后,就需要重写onTransact()方法,并在data变量中读出客户端传递的参数,start()方法需要变量filepath变量,客户端需要与服务者双方有一个约定.
客户端在传入的包裹data中放入第一个数据就是filepath变量,那么,onTransact()的代码就需要这么设计:

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags)
            throws RemoteException {
        switch (code) {
        case 1000:
            data.enforceInterface("BaseService");
            String filepath=data.readString();
            start(filepath);
            break;

        default:
            break;
        }
        return super.onTransact(code, data, reply, flags);
    }

AIDL中onTransact()方法实现

@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_getMyService:
{
data.enforceInterface(DESCRIPTOR);
this.getMyService();
reply.writeNoException();
return true;
}
}
code变量是用于标识客户端期望的调用的哪个函数,因此,双方需要有一个约定的int值用来识别服务器,不同的服务器有不同的code值,该值和客户端的transact()函数中的第一个code值是一致的,
enforceInterface()是为了校验,它与客户端的writeInterfaceToken()对应
readString()用于从包裹去除一个字符串,去除filePath变量后,就可以调用服务端的start()函数了
如果IPC调用的客户端期望返回一些结果,则可以在返回包裹reply中调用parcel提供的相关函数写入相应结果.

三,Binder客户端的设计
客户端要想使用服务端提供的服务,就必须要后去服务端在Binder驱动中对应的mRemote变量的引用,获得变量的饮用后滴啊用变量的transact()方法.
在IBinder类中的transact()方法体如下

      public boolean transact(int code, Parcel data, Parcel reply, int flags)
        throws RemoteException;

其中的data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函所需要的参数必须要放入这个包裹中,包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,如String,int,long等,要查看包裹可以放入的全部类型,可以参照Parcel类,除了一般的原子变量外,Parcel还提供了一个writeParcel()方法,可以在包裹包含一个小包裹,因此,要调用Binder远程服务时,服务函数的参数要么是一个原子类,要么必须继承与Parcel类,否则不能传递.
Service的客户端需要实现IBinder接口,而Android已经通过Binder类帮我们实现了IBinder的抽象方法,而其中客户端需要调用transact()方法.

 protected boolean onTransact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (code == INTERFACE_TRANSACTION) {
            reply.writeString(getInterfaceDescriptor());
            return true;
        } else if (code == DUMP_TRANSACTION) {
            ParcelFileDescriptor fd = data.readFileDescriptor();
            String[] args = data.readStringArray();
            if (fd != null) {
                try {
                    dump(fd.getFileDescriptor(), args);
                } finally {
                    try {
                        fd.close();
                    } catch (IOException e) {
                        // swallowed, not propagated back to the caller
                    }
                }
            }
            // Write the StrictMode header.
            if (reply != null) {
                reply.writeNoException();
            } else {
                StrictMode.clearGatheredViolations();
            }
            return true;
        }
        return false;
    }

 public final boolean transact(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);
        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }
以上代码分析,首先,包裹不是客户端自己创建的,而是调用了Parcel.obtain()申请的,就像邮局一样,用户一般只能用邮局提供的信封,其中data和reply变量由客户端提供,reply变量用户服务端把返回的结果放入其中.
public class MyParcel implements Parcelable{
@Override
public int describeContents() {
    //
    return 0;
}
/*
 * 用于标注远程服务名称,理论上这个是不需要的 ,因为客户端已经
 * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
 */
@Override
public void writeToParcel(Parcel dest, int flags) {

}
}

writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获得指定远程服务的Binder引用,那么就不会调用其他远程服务。该名称作为Binder驱动确保客户端的缺项调用指定服务端
writeString()方法用于向包裹中添加一个String变量.注意,包裹中添加的内容是有序的 ,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact()方法中会按照约定的顺序取出变量.
调用transact()方法,调用该方法后,客户端线程进入Binder驱动,Binder驱动就会启动隐藏线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹,服务端拿到包裹后,会对包裹进行拆分,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中,然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区
transact()的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向的,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据,一种是单向,用常量1表示,其含义是不返回任何数据.
最后客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的.

四,使用Service类
三种设计到两个问题:

  1. 客户端如何获得Binder的引用
  2. 客户端和服务端必须事先约定好两件事情
    服务端函数的参数在包裹中的顺序
    服务端不同函数的int型标识
    获取Binder的对象
    Android系统提供了startService()方法用于启动客户端,对于客户端而言,可以使用以下两个方法来链接服务,其原型在android.app.ContextImpl类中,
//这个方法是用于启动客户端服务
    @Override
        public ComponentName startService(Intent service) {
            return super.startService(service);
        }

    ServiceConnection connection=new ServiceConnection() {

        @Override
        public void onServiceDisconnected(ComponentName name) {
            // TODO Auto-generated method stub

        }
        //该方法是用于绑定服务
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // TODO Auto-generated method stub

        }
    };

使用保证包裹内参数顺序的aidl工具
Android提供的aidl工具,可以把一个aidl文件转换为一个java文件,在该java类文件中,同时重载transact()和onTransact(),统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放在服务代码本身上.
aidl工具不是必须的,对于大牛来说,可以手工编写一个参数统一的包裹存入和包裹读出代码是一个小case(ps:博主水平也就勉强能写23333)

//aidl代码
package com.example.androidneihe;
interface MyAidl{
    void getMyService();
}
aidl文件语法基于java,package指定的是输出后的java文件对应的包名,如果该文件需要引用其他类对应服务的类名,则需要时使用import关键字,但需要注意的是包裹内只能写入以下三个类型的内容:
  • java原子类型,如int,long,String等变量;
  • Binder引用.
  • 实现了Parcelale的对象
  • 因此,基本来讲.import所引用的类型也只能是以上三种类型
    interface 关键字,有时候会在interface前边加一个oneway,代表service提供的方法都是没有返回值,即都是void类型.
package com.example.androidneihe;
public interface MyAidl extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.androidneihe.MyAidl
{
private static final java.lang.String DESCRIPTOR = "com.example.androidneihe.MyAidl";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
 * Cast an IBinder object into an com.example.androidneihe.MyAidl interface,
 * generating a proxy if needed.
 */
public static com.example.androidneihe.MyAidl asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.androidneihe.MyAidl))) {
return ((com.example.androidneihe.MyAidl)iin);
}
return new com.example.androidneihe.MyAidl.Stub.Proxy(obj);
}
@Override public android.os.IBinder asBinder()
{
return this;
}
@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_getMyService:
{
data.enforceInterface(DESCRIPTOR);
this.getMyService();
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.androidneihe.MyAidl
{
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;
}
@Override public void getMyService() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getMyService, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getMyService = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public void getMyService() throws android.os.RemoteException;
}

这些代码主要完成以下三个任务.

  • 一个java interface,内部包含aidl文件所声明的服务函数,类的名称为MyAidl,并且该类基于IInterface接口,即需要提供一个asBinder()函数.
  • 定义了一个Proxy类,该类将作为客户端程序访问服务端的代理.所谓的代理主要就是为了前面所提到的第二个问题—–统一包裹内的写入参数顺序.
  • 定义了一个Stub类,这是一个abstract类,基于Binder类,并且实现了MyAidl接口,主要由服务端来使用.该类之所以要定义类abstract的原因是:具体服务函数必须有程序员实现,因此,MyAidl接口中定义的Stub类中可以没有具体实现.同时,在Stub类中重载onTransact()方法中,aidl工具自然之道应该按照何种顺序从包裹中取出相应参数.

在Stub类中定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应,transact()和onTransact()方法的第一个参数code的值
在Stub类中,除了以上所述的任务外,Stub还提供了一个asInterface()函数.提供这个函数的作用是:
首先要明确的是爱捣乱所产生的代码完全可以由应用程序员手工编写,MyAidl中的函数只有一中编码习惯而已,asInterface即如此,提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,显然是不需要经过IPC调用的,而可以直接在进程内部调用的,而Binder对象是一个本地Binder引用.
当一个Binder对象创建时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象,如果从远程获取服务端的Binder,则返回Binder驱动中的Binder对象,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象,具体流程图可见下图

asInterface()函数利用queryLocalInterface()方法,提供了一个统一的接口,无论客户端还是本地端,当获取Binder对象后,可以把获取到的对象作为asInterface()的参数返回一个MyAIdl接口,该接口要么使用Proxy类,要么使用Stub所实现的响应函数.

时间: 2024-10-10 14:29:43

Android内核之Binder的相关文章

从源码角度分析Android中的Binder机制的前因后果

前面我也讲述过一篇文章<带你从零学习linux下的socket编程>,主要是从进程通信的角度开篇然后延伸到linux中的socket的开发.本篇文章依然是从进程通信的角度去分析下Android中的进程通信机制. 为什么在Android中使用binder通信机制? 众所周知linux中的进程通信有很多种方式,比如说管道.消息队列.socket机制等.socket我们再熟悉不过了,然而其作为一款通用的接口,通信开销大,数据传输效率低,主要用在跨网络间的进程间通信以及在本地的低速通信.消息队列和管道

Android Native层Binder.transact()函数调用 Binder.onTransact() 函数失败分析

Q:Android Native层Binder.transact()函数调用 Binder.onTransact() 函数失败? 在Android Native层调用Camera.h中的api实现一个截屏功能的应用时,发现通过gCamera->setListener(new ScreenCaptureListener())设置到Camera的mListener的用于接收Camera预览数据的回调函数没有被调用,导致截屏失败? 注: Camera类文件汇总: libcamera_client.so

Android内核开发:源码的版本与分支详解

我想很多初学者或许跟我一样,看完Android源码下载相关的文章以后,就开始兴致勃勃地去下载Android源码了,但是下载完了源码后,有没有像我一样产生如下几个困惑呢? (1) Android版本有哪些分支可用?每个分支的TAG是什么? (2) Android源码下载完了怎么没有看到Linux内核代码?Android源码对应Linux内核是否可以从kernel.org官网去下载?Android对标准的Linux内核做了哪些修改? (3) Android源码分支与Linux版本分支的对应关系是什么

Android深入浅出之Binder机制(转)

Android深入浅出之Binder机制 一 说明 Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的.所以搞明白Binder的话,在很大程度上就能理解程序运行的流程. 我们这里将以MediaService的例子来分析Binder的使用: l         ServiceManager,这是Android OS的整个服务的管理程序 l         MediaService,这个程序里边注册了提供媒体播放的服

《深入理解Android内核设计思想》

<深入理解Android内核设计思想> 基本信息 作者: 林学森 出版社:人民邮电出版社 ISBN:9787115348418 上架时间:2014-4-25 出版日期:2014 年5月 开本:16开 页码:687 版次:1-1 所属分类:计算机 > 软件与程序设计 > 移动开发 > Android 更多关于>>><深入理解Android内核设计思想> 编辑推荐 基于Android SDK最新版本 全面细致地剖析了进程/线程模型.内存管理.Bind

《深入理解Android内核设计思想》书本目录,及部分章节内容分享

第1篇 android编译篇 第1章 android系统简介 2  1.1 android系统发展历程 2  1.2 android系统特点 4  1.3 android系统框架 8 第2章 android源码下载及编译 10  2.1 android源码下载指南 10  2.1.1 基于repo和git的版本管理 10  2.1.2 android源码下载流程 11  2.2 原生态系统编译指南 12    2.2.1 建立编译环境 13    2.2.2 编译流程 15  2.3 定制产品的

Android深入浅出之Binder机制【转】

Android深入浅出之Binder机制 一 说明 Android系统最常见也是初学者最难搞明白的就是Binder了,很多很多的Service就是通过Binder机制来和客户端通讯交互的.所以搞明白Binder的话,在很大程度上就能理解程序运行的流程. 我们这里将以MediaService的例子来分析Binder的使用: <!--[if !supportLists]-->l         <!--[endif]-->ServiceManager,这是Android OS的整个服务

Android内核开发:理解和掌握repo工具

由于Android源码是用repo工具来管理的,因此,搞Android内核开发,首先要搞清楚repo是什么东西,它该怎么使用?作为<Android内核开发>系列文章的第二篇,我们首先谈谈对repo工具的理解和使用. 1. repo是什么? repo是一种代码版本管理工具,它是由一系列的Python脚本组成,封装了一系列的Git命令,用来统一管理多个Git仓库. 2. 为什么要用repo? 因为Android源码引用了很多开源项目,每一个子项目都是一个Git仓库,每个Git仓库都有很多分支版本,

Android内核开发:系统启动速度优化

在学习新知识的过程中,我一直很推荐结合实战任务去学习,只有经历实战,才能加深对理论知识的理解.<Android内核开发>系列已经写了八篇了,本文就结合前面的内容,给大家布置一个实战任务: 优化Android系统的启动速度. 这里我简单介绍一下优化的基本思路和涉及的文件,具体细节由大家自己在实践去摸索,提高自己Google能力和解决问题的能力. Android系统的启动优化主要分为三大部分: (1) Bootloader优化 (2) Linux Kernel的剪裁与优化 (3) Android