码迷,mamicode.com
首页 > 其他好文 > 详细

零基础理解Binder

时间:2015-02-15 16:36:43      阅读:167      评论:0      收藏:0      [点我收藏+]

标签:binder   android   跨进程   

写在前面的

当一个Android App存在某个不需要UI的后台运行需求时,或者是因为内存占用需要采用多进程方案时,我们免不了与多进程打交道。必不可少的,需要考虑Binder在其中如何实现。

最常见的Binder实现当然是AIDL,然而Binder的实现绝不仅仅只有AIDL一种方式,如果止步于写.aidl,那么对于Binder,对于Android整体的跨进程传输过程的理解都只能流于形式。

但是想理解Binder不是一件容易的事情,Binder的概念涉及太多知识点,遍观各大论坛上关于Binder的博客或是文章,作者多半已经默认阅读者具备了必要的学习基础。这对于零基础的同学肿么办,我罗列出了几个自己学习中觉得十分重要的知识点,如果希望能够彻底的理解Binder JAVA层概念,那么务必要明了这些知识点。希望对那些初学者朋友有一定帮助。

接口语言

AIDL是Android为了在不同进程中实现Binder接口的一套专用接口语言。

维基百科上对于接口语言的定义是这样的

接口描述语言(Interface description language,缩写IDL),是用来描述软件组件接口的一种计算机语言。IDL通过一种中立的方式来描述接口,使得在不同平台上运行的对象和用不同语言编写的程序可以相互通信交流;比如,一个组件用C++写成,另一个组件用Java写成。

接口语言通常用来描述两种不同对象之间共同一套通信协议,放在Android概念里,一般指的是两个不同进程之间需要通信时必须遵守的一套标准。

IPC

Binder的实质仍然是IPC。

通常我们所说的IPC,指的是以某种标准所做的进程间通信过程(inter-process communication)。我们知道,Android的每一个应用可以视作是跑在Davlk虚拟机上的独立进程。而Linux内核出于对进程安全的考虑,要求不同UID/GID的进程不能访问互相访问彼此的资源。因此一般来讲,每个Android应用,或者说每个独立进程的组件都不能像本地方法一样调用其他进程方法。

基于这个原因,Linux内核支持若干种进程间通信机制,为的是在维护进程安全性的同时还能提供针对不同场景的通信手段。而在Android Binder用到的是叫做匿名共享内存(Anonymous Shared Memory)的方式。Binder的实质就是通过这种IPC方式来完成不同进程之间的通信的。

C/S结构

Binder的实现是一个典型的C/S结构。

C/S可以简单的理解为系统为了负载均衡,将一个大任务拆分为多个子任务,并分发给不同的终端结构来处理,以达到减少负荷提高响应速度的目的。在Android的环境中,在分布式系统中,终端结构可以是计算机与服务器,而在Android系统中,则指的是不同的进程实体。理解了C/S结构的一些特征,对Binder的消息握手,传输模式等等行为会有更深刻的理解。

Binder的概念

终于说到了Binder。

对于Binder的解释,最官方的莫过于developerAndroid对于Service使用这一章的说明。如果详细的读过这篇文档,我想大部分人能够对Binder的使用,特别是如何通过AIDL来实现Binder接口的操作,有比较清楚的认识。

但是Binder究竟是什么?它是如何实现的?

这里十分推荐老罗关于Binder学习计划这个系列的文档,对于Binder的整体运行机制有了十分清晰的描述,如果你需要整体全面的理解Binder,那么阅读源代码的同时,这套文档一定可以帮助你很多。


弄清楚以上几个概念后,我们可以开始讨论Binder的实现了。

Binder的实现有很多种,最常见的方式是通过AIDL来实现,相信做过跨进程访问Service的同学也肯定写过.aidl。除此之外,Android还支持通过Message方式和直接继承Binder类的方式来完成Binder实现。

而我要给大家介绍的,就是最后一种方式。

继承Binder类的IBinder接口实现

我以一个最简单的功能为例——控制并打印不同进程的pid,来介绍一个典型的Binder实现过程。

创建项目,并设置多进程运行环境

为了在同一个项目中模拟多进程情形,我将Service组件设置为其他的进程

        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service
            android:name=".BinderService"
            android:process=":other"
            android:enabled="true"
            android:exported="true" >
        </service>

Activity以及Service表现

在我们的模拟代码中,Activity与Service绑定之后可以在onServiceConnection回调中获取一个Binder实例,通过对这个对象向下转型得到指定Interface的接口对象,从而调用Service中实现的具体的LogService方法。
真实的情况虽然没有所描述的这么简单,但也与之相差不远,流程图如下:

Created with Rapha?l 2.1.2ActivityActivityProxyProxyASMASMStubStubServiceService客户端进程获取Binder代理服务端进程实现具体请求方法返回值将返回值返回至客户端进程

Activity和Servie的代码实现如下:
Activity端

void onServiceConnected(ComponentName name, IBinder service) {
            mService = StubService.asInterface(service);
            Log.d(TAG, "pid=" + android.os.Process.myPid());
            mService.LogService();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mService = null;

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        bindService(new Intent(this, BinderService.class), serviceConnection, BIND_AUTO_CREATE);
    }
    ...
}

Service端实现

public class BinderService extends Service {
    private static final String TAG = "BinderService";


    //implement the interface
    private StubService mBinder = new StubService() {
        @Override
        public void LogService() {
            Log.d(TAG, "pid=" + android.os.Process.myPid());
        }
    };


    //expose the interface to client
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

简单的描述就是。
- Actiivty尝试去bind一个Service。
- bind成功之后,Activity可以得到一个Binder对象。
- 通过这个Binder对象,Activity能够调用被bind的那个Service里实现的具体方法,也就是实现了远程调用Service的目的。

而这里有几个问题:
1. Activity获取的Binder对象本质是什么?
2. 为什么可以通过这个Binder对象远程调用Service方法?
3. 我们如何区分不同进程中的实例对象?

这几个问题可以从具体的实现代码中得到答案。

Binder代理端的实现

在我们的demo中,Activity代表的是C/S模型中的Client端进程,Binder结构实现的过程之一,就是会交给这个Client进程一个它的Proxy实现,也就是我们在onServiceConnection回调中所得到的Binder实例。

具体怎么生成Proxy实例的呢?从代码中可以看到:

public interface IMyTransInterface extends IInterface {

    public void LogService();
}

最开始,我们必须创建一个继承子IInterface的接口,并在这个接口中声明我们在Client进程中要调用的,Service进程中会实现的方法。

之后我们在Proxy类中就可以实现这个接口了

public class ProxyClient implements IMyTransInterface {
    public static final int TRANSACTION_LogService = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    private IBinder mRemote;

    ProxyClient(IBinder mRemote) {
        this.mRemote = mRemote;
    }

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

    @Override
    public void LogService() {
        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        try {
            _data.writeInterfaceToken(StubService.DESCRIPTOR);
            mRemote.transact(TRANSACTION_LogService, _data, _reply, 0);
            _reply.readException();
        } catch (RemoteException e) {
            e.printStackTrace();
        }

    }

}

这个Proxy的实现,就是Activity所得到的向下转型Binder对象。它之所以可以向下转型从代码中看到,是因为它实现了IMyTaransInterface接口,也就是支持我们自定义的一套接口定义。在我们用AIDL实现的场合中,这套细节是隐藏的,但是我们仍然知道我们所以能够自定义的接口,是因为在.aidl文件中声明的缘故。

Proxy部分的代码中,完成了两个非常重要的功能。
- 通过asBinder方法返回一个IBinder接口实现。这个返回的mRemote对象可以看到是在构造函数中传入的,至于什么时候会构造Proxy对象稍后会介绍。但是需要知道通过asBinder能够得到一个远端对象。我们在想的远一点,从字面意义上来理解,这个远端指的应该是什么呢?
- 实现了IMyTransInterface声明的接口方法LogService。这是Binder结构中至关重要的一个环节。
LogService在Proxy中的实现并没有真正的逻辑结构,只是机械的将对应参数序列化后,并调用远端对象的transact方法写入到某个缓存区中,之后读取可能存在的返回值
而我们在Activity中,也就是Client进程中实际调用的也就是这个方法,所谓的远程调用Service进程的方法不过只是从宏观得到的结论而已,此时真正完成的,只是将数据写入缓存区并得到回传的结果而已。

这一套实现机制,从模型上看,就是典型的C/S结构,Client端发送请求并等待计算结果,并不关心实现的过程。而真正实现的细节完全交给Server来完成。而真正的实现细节,也即是我们写入的缓存后的处理,最终也会由匿名共享内存机制来完成。

Binder服务器端的实现

如果你对Binder代理的设置已经理解的话,那么对服务器端的实现很快就能明白——因为这两者基本是对称的。

还是先把代码贴出来:

public abstract class StubService extends Binder implements IMyTransInterface {
    public static final java.lang.String DESCRIPTOR = "com.guiyutest.bindertest.IMyTrasInterfacke";


    public abstract void LogService();

    public StubService() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IMyTransInterface asInterface(IBinder obj) {
        if (obj == null) {
            return null;
        }
        IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if ((iin != null) && (iin instanceof IMyTransInterface)) {
            return (IMyTransInterface) iin;
        }
        return new ProxyClient(obj);
    }


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

    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case INTERFACE_TRANSACTION:
                reply.writeString(DESCRIPTOR);
                return true;
            case ProxyClient.TRANSACTION_LogService:
                data.enforceInterface(DESCRIPTOR);
                this.LogService();
                reply.writeNoException();
                return true;
        }
        return super.onTransact(code, data, reply, flags);
    }
}

从代码结构上看,Service端的实现包括三个主要部分,构造函数,asInterface方法以及onTrasnact回调方法。

值得一提的是后两者:

  • asInterface方法。这个方法返回的是我们定义的IMyTransInterface接口对象,也就是一个可以实现我们自定义接口的对象,而返回的究竟是什么,取决于实例化这个StubService的Service进程,与获取Proxy实例的Client进程,是否是同一个进程。如果是同一个进程,但么返回的就是这个StubService对象本身,如果不是同一个进程,则应该返回Proxy的一个实例——在这里,我们的Binder代理端对象才会被实例化。
  • onTransact回调。正如字面意义的描述,当Proxy进程调用到对应的LogService方法时,会进而调用mRemote.transact方法将数据发送到匿名共享内存维护的缓存区中,当传输完成时所引发的一个系统回调。在这个回调中,Service进程应该可以得到来自Proxy发送来的序列化数据,方法标示以及序列化返回对象等,Service根据这些条件调用真正的服务端实现逻辑,也就是LogService方法。

Service端的代码,故名思议,应该是可以实现C/S模型中Server端具体业务逻辑的代码。通过匿名共享内存机制,收到Proxy进程打包发来的数据并且加以处理,然后返回结果。期间无需关注数据的来源以及调用者的信息。


说了这么多,细心的你肯定已经发现,我所罗列出的这一套代码逻辑,正是AIDL在gen中生成的.java文件机制。唯一的不同是,懒惰的Google只用了一个文件,而我将它们拆分成为了本该属于不同进程的三个文件。

在具体的实现细节中,我们也可以看到Google的用苦良心,Binder的Service端实现其实完全可以写在Stub中,只不过为了为了重用性考虑以及更好的便于开发者使用,Google把这个功能加以抽象包装,把底层的功能再次封装,仅仅只暴露出用以实现具体逻辑的抽象类给了开发者,然我们可以心无旁骛的专注于业务逻辑,而对这整整一套传输过程都无需关注,实在是居功甚伟。


Demo代码下载地址:
BinderTest

—–最后的分割线——
这是我用Markdown写的第一篇文档,不得不赞一句,真是好东西。

零基础理解Binder

标签:binder   android   跨进程   

原文地址:http://blog.csdn.net/guiyu_1985/article/details/43835735

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!