zoukankan      html  css  js  c++  java
  • Android 内核--Binder架构分析

    一、Binder架构

      在Android中,Binder用于完成进程间通信(IPC),即把多个进程关联在一起。比如,普通应用程序可以调用音乐播放服务提供的播放、暂停、停止等功能。
    Binder工作在Linux层面,属于一个驱动,只是这个驱动不需要硬件,或者说其操作的硬件是基于一小段内存。从线程的角度来讲,Binder驱动代码运行在内核态,客户端程序调用Binder是通过系统调用完成的。

      Binder是一种架构,这种架构提供了服务端接口、Binder驱动、客户端接口三个模块。
      服务端:一个Binder服务端实际上就是一个Binder类的对象,该对象一旦创建,内部就启动一个隐藏线程。该线程接下来会接收Binder驱动发送的消息,收到消息后,会执行到Binder对象中的onTransact()函数,并按照该函数的参数执行不同的服务代码。因此,要实现一个Binder服务,就必须重载onTransact()方法。重载onTransact()函数的主要内容是把onTransact()函数的参数转换为服务函数的参数,而onTransact()函数的参数来源是客户端调用transact()函数时输入的,因此,如果transact()有固定格式的输入,那么onTransact()就会有固定格式的输出。
      Binder驱动:任意一个服务端Binder对象被创建时,同时会在Binder驱动中创建一个mRemote对象,该对象的类型也是Binder类。客户端要访问远程服务时,都是通过mRemote对象。
      客户端:客户端要想访问远程服务,必须获取远程服务在Binder对象中对应的mRemote引用,至于如何获取,下面将会介绍。获得该mRemote对象后,就可以调用其transact()方法,而在Binder驱动中,mRemote对象也重载了transact()方法,重载的内容主要包括以下几项。

    •  以线程间消息通信的模式,向服务端发送客户端传递过来的参数。
    •  挂起当前线程,当前线程正是客户端线程,并等待服务端线程执行完指定服务函数后通知(notify)。
    •  接收到服务端线程的通知,然后继续执行客户端线程,并返回到客户端代码区。


    从这里可以看出,对应用程序开发员来讲,客户端似乎是直接调用远程服务对应的Binder,而实际上是通过Binder驱动进行了中转。即存在两个Binder对象,一个是服务端的Binder对象,另一个则是Binder驱动中的Binder对象,不同的是Binder驱动中的对象不会再额外产生一个线程。

    二、Service端

    设计Service端很简单,从代码的角度来讲,只要基于Binder类新建一个Servier类即可。以下以设计一个Service类为例。

        class MyService extends Binder {
            @Override
            protected boolean onTransact(int code, Parcel data, Parcel reply,
                    int flags) throws RemoteException { // 接收客户端传过来的消息参数
                return super.onTransact(code, data, reply, flags);
            }
            public void start(String name) {
            }
            public void stop() {
            }
        }

    当要启动该服务时,只需要初始化一个MyService对象即可。之后可以在DDMS中看到多了一个线程

      定义了服务类后,接下来需要重载onTrasact()方法,并从data变量中读出客户端传递的参数,比如start()方法所需要的name变量。然而,服务端如何知道这个参数在data变量中的位置?因此,这就需要调用者和服务者双方有个约定。假定客户端在传入的包裹data中放入的第一个数据就是filePath变量,则onTransact()的代码可以如下所示:

                switch (code) {
                case 0x100:
                    data.enforceInterface("MyService");
                    String name = data.readString();
                    start(name);
                    break;
                }

    code变量用于标识客户端期望调用服务端的哪个函数,因此,双方需要约定一组int值,不同的值代表不同的服务端函数,该值和客户端的transact()函数中第一个参数code的值是一致的。这里假定0x100是双方约定要调用start()函数的值。
    enforceInterface()是为了某种校验,它与客户端的writeInterfaceToken()对应,具体见下一小节。
    readString()用于从包裹中取出一个字符串。取出filePath变量后,就可以调用服务端的start()函数了。如果该IPC调用的客户端期望返回一些结果,则可以在返回包裹reply中调用Parcel提供的相关函数写入相应的结果。Parcel.writeXXX();

    三、Binder客户端设计

      要想使用服务端,首先要获取服务端在Binder驱动中对应的mRemote变量的引用,获取的方法后面将介绍。获得该变量的引用后,就可以调用该变量的transact()方法。该方法的函数原型:
      public final boolean transact(int code, Parcel data, Parcel reply,int flags)
      其中data表示的是要传递给远程Binder服务的包裹(Parcel),远程服务函数所需要的参数必须放入这个包裹中。包裹中只能放入特定类型的变量,这些类型包括常用的原子类型,比如String、int、long等,要查看包裹可以放入的全部数据类型,可以参照Parcel类。除了一般的原子变量外,Parcel还提供了一个writeParcel()方法,可以在包裹中包含一个小包裹。因此,要进行Binder远程服务调用时,服务函数的参数要么是一个原子类,要么必须继承于Parcel类,否则,是不能传递的。

            IBinder mRemote = null;
            String name = "Livingstone";
            int code = 0x100;
            Parcel data = Parcel.obtain();
            Parcel reply = Parcel.obtain();
            data.writeInterfaceToken("MyService");
            data.writeString(name);
            mRemote.transact(code, data, reply, 0);
            IBinder binder = reply.readStrongBinder();
            reply.recycle();
            data.recycle();

      现在来分析以上代码。首先,包裹不是客户端自己创建的,而是调用Parcel.obtain()申请的,这正如生活中的邮局一样,用户一般只能用邮局提供的信封(尤其是EMS)。其中data和reply变量都由客户端提供,reply变量用户服务端把返回的结果放入其中。
      writeInterfaceToken()方法标注远程服务名称,理论上讲,这个名称不是必需的,因为客户端既然已经获取指定远程服务的Binder引用,那么就不会调用到其他远程服务。该名称将作为Binder驱动确保客户端的确想调用指定的服务端。
      writeString()方法用于向包裹中添加一个String变量。注意,包裹中添加的内容是有序的,这个顺序必须是客户端和服务端事先约定好的,在服务端的onTransact()方法中会按照约定的顺序取出变量。

      接着调用transact()方法。调用该方法后,客户端线程进入Binder驱动,Binder驱动就会挂起当前线程,并向远程服务发送一个消息,消息中包含了客户端传进来的包裹。服务端拿到包裹后,会对包裹进行拆解,然后执行指定的服务函数,执行完毕后,再把执行结果放入客户端提供的reply包裹中。然后服务端向Binder驱动发送一个notify的消息,从而使得客户端线程从Binder驱动代码区返回到客户端代码区。
      transact()的最后一个参数的含义是执行IPC调用的模式,分为两种:一种是双向,用常量0表示,其含义是服务端执行完指定服务后会返回一定的数据;另一种是单向,用常量1表示,其含义是不返回任何数据。
      最后,客户端就可以从reply中解析返回的数据了,同样,返回包裹中包含的数据也必须是有序的,而且这个顺序也必须是服务端和客户端事先约定好的。

    四、使用Service类

    以上手工编写Binder服务端和客户端的过程存在两个重要问题。
      第一,客户端如何获得服务端的Binder对象引用。
      第二,客户端和服务端必须事先约定好两件事情。
       服务端函数的参数在包裹中的顺序。
       服务端不同函数的int型标识。
    关于第一个问题,为什么要用Binder。答案很简单,即为了提供一个全局服务,所谓的“全局”,是指系统中的任何应用程序都可以访问。很明显,这是一个操作系统应该提供的最基本的功能之一,Android的工程师自然也是这么认为的,因此,他们提供了一个更傻瓜的解决方法,那就是Service。它是Android应用程序四个基本程序片段(Component)之一,四个基本片段包括Activity、Service、Content Provier、Receiver。
    无论是否使用Service类,都必须要解决以上两个重要问题。

    1、获取Binder对象

    事实上,对于有创造力的程序员来讲,可以完全不使用Service类,而仅仅基于Binder类编写服务程序,但只是一部分。具体来讲,可以仅使用Binder类扩展系统服务,而对于客户端服务则必须基于Service类来编写。所谓的系统服务是指可以使用getSystemService()方法获取的服务,所谓的客户端服务是指应用程序提供的自定义服务。
    那么,Service类是如何解决本节开头所提出的两个重要问题的呢?
    首先,AmS提供了startService()函数用于启动客户服务,而对于客户端来讲,可以使用以下两个函数来和一个服务建立连接,其原型在android.app. ContextImpl类中。

    public ComponentName startService(Intent intent);
    该函数用于启动intent指定的服务,而启动后,客户端暂时还没有服务端的Binder引用,因此,暂时还不能调用任何服务功能。

        @Override
        public boolean bindService(Intent service, ServiceConnection conn, int flags) {
            IServiceConnection sd;
            if (mPackageInfo != null) {
                sd = mPackageInfo.getServiceDispatcher(conn, getOuterContext(),
                        mMainThread.getHandler(), flags);
            } 
            // ......int res = ActivityManagerNative.getDefault().bindService(
                    mMainThread.getApplicationThread(), getActivityToken(),
                    service, service.resolveTypeIfNeeded(getContentResolver()),
                    sd, flags);
        }

    该函数用于绑定一个服务,这就是第一个重要问题的关键所在。其中第二个参数是一个interface类,该interface的定义如以下代码所示:

    public interface ServiceConnection {
      public void onServiceConnected(ComponentName name, IBinder service);
      public void onServiceDisconnected(ComponentName name);
    }

      此interface中的onServiceConnected()方法的第二个变量Service。当客户端请求AmS启动某个Service后, 该Service如果正常启动,那么AmS就会远程调用ActivityThread类中的ApplicationThread对象,调用的参数中会包含Service的Binder引用,然后在ApplicationThread中会回调bindService中的conn接口。因此,在客户端中,可以在onServiceConnected()方法中将其参数Service保存为一个全局变量,从而在客户端的任何地方都可以随时调用该远程服务。这就解决了第一个重要问题,即客户端如何获取远程服务的Binder引用。


    下面查看源码

            public final void scheduleBindService(IBinder token, Intent intent, boolean rebind) {
                BindServiceData s = new BindServiceData();
                s.token = token;
                s.intent = intent;
                s.rebind = rebind;
                queueOrSendMessage(H.BIND_SERVICE, s);
            }
        // if the thread hasn't started yet, we don't have the handler, so just
        // save the messages until we're ready.
        private final void queueOrSendMessage(int what, Object obj) {
            queueOrSendMessage(what, obj, 0, 0);
        }
        private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
            synchronized (this) {
                // ...
                Message msg = Message.obtain();
                msg.what = what;
                msg.obj = obj;
                msg.arg1 = arg1;
                msg.arg2 = arg2;
                mH.sendMessage(msg); 
            }
        }

    2、AIDL工具对象使用

    关于第二个问题,Android的SDK中提供了一个aidl工具,该工具可以把一个aidl文件转换为一个Java类文件,在该Java类文件中,同时重载了transact和onTransact()方法,统一了存入包裹和读取包裹参数,从而使设计者可以把注意力放到服务代码本身上。aidl工具不是必需的,对于有经验的程序员来讲,手工编写一个参数统一的包裹存入和包裹读出代码并不是一件复杂的事情。
    下面是一个服务对应的AIDL文件,服务中包含两个服务函数,分别是start()和stop()。那么,可以首先编写一个IMyService.aidl文件。如以下代码所示:

    package com.androidstudy;
    interface IMyService{
    boolean start(String path);
    void stop();
    }

    该文件的名称必须遵循一定的规范,第一个字母“I”不是必需的,但是,为了程序风格的统一,“I”的含义是IInterface类,即这是一个可以提供访问远程服务的类。服务的类名可以是任意的,但是,aidl工具会以该名称命名输出Java类。这些规则都只是Eclipse下ADT插件的默认规则,aidl本身只是一个命令行程序,借助命令行的话,则可以灵活指定输出文件的名称及位置。

    aidl文件的语法基本类似于Java,package指定输出后的Java文件对应的包名。如果该文件需要引用其他Java类,则可以使用import关键字,但需要注意的是,包裹内只能写入以下三个类型的内容。

    •  Java原子类型,如int、long、String等变量。
    •  Binder引用。
    •  实现了Parcelable的对象。

    因此,基本上来讲,import所引用的Java类也只能是以上三个类型。
    interface为关键字,有时会在interface前面加一个oneway,代表该service提供的方法都是没有返回值的,即都是void类型。

        public interface IMyService extends android.os.IInterface {
            /** Local-side IPC implementation stub class. */
            public static abstract class Stub extends android.os.Binder implements
                    com.androidstudy.IMyService {
                private static final java.lang.String DESCRIPTOR = "com.androidstudy.IMyService";
    
                /** Construct the stub at attach it to the interface. */
                public Stub() {
                    this.attachInterface(this, DESCRIPTOR);
                }
    
                /**
                 * Cast an IBinder object into an com.androidstudy.IMyService
                 * interface, generating a proxy if needed.
                 */
                public static com.androidstudy.IMyService asInterface(
                        android.os.IBinder obj) {
                    if ((obj == null)) {
                        return null;
                    }
                    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                    if (((iin != null) && (iin instanceof com.androidstudy.IMyService))) {
                        return ((com.androidstudy.IMyService) iin);
                    }
                    return new com.androidstudy.IMyService.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_start: {
                        data.enforceInterface(DESCRIPTOR);
                        java.lang.String _arg0;
                        _arg0 = data.readString();
                        boolean _result = this.start(_arg0);
                        reply.writeNoException();
                        reply.writeInt(((_result) ? (1) : (0)));
                        return true;
                    }
                    case TRANSACTION_stop: {
                        data.enforceInterface(DESCRIPTOR);
                        this.stop();
                        reply.writeNoException();
                        return true;
                    }
                    }
                    return super.onTransact(code, data, reply, flags);
                }
    
                private static class Proxy implements com.androidstudy.IMyService {
                    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 boolean start(java.lang.String path)
                            throws android.os.RemoteException {
                        android.os.Parcel _data = android.os.Parcel.obtain();
                        android.os.Parcel _reply = android.os.Parcel.obtain();
                        boolean _result;
                        try {
                            _data.writeInterfaceToken(DESCRIPTOR);
                            _data.writeString(path);
                            mRemote.transact(Stub.TRANSACTION_start, _data, _reply,
                                    0);
                            _reply.readException();
                            _result = (0 != _reply.readInt());
                        } finally {
                            _reply.recycle();
                            _data.recycle();
                        }
                        return _result;
                    }
    
                    @Override
                    public void stop() 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_stop, _data, _reply,
                                    0);
                            _reply.readException();
                        } finally {
                            _reply.recycle();
                            _data.recycle();
                        }
                    }
                }
    
                static final int TRANSACTION_start = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
                static final int TRANSACTION_stop = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
            }
    
            public boolean start(java.lang.String path)
                    throws android.os.RemoteException;
    
            public void stop() throws android.os.RemoteException;
        }

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

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

      在Stub类中还定义了一些int常量,比如TRANSACTION_start,这些常量与服务函数对应, transact()和onTransact()方法的第一个参数code的值即来源于此。
      在Stub类中,除了以上所述的任务外,Stub还提供了一个asInterface()函数。提供这个函数的作用是这样的:首先需要明确的是,aidl所产生的代码完全可以由应用程序员手工编写,IMyService中的函数只是一种编码习惯而已,asInterface即如此,提供这个函数的原因是服务端提供的服务除了其他进程可以使用外,在服务进程内部的其他类也可以使用该服务,对于后者,显然是不需要经过IPC调用,而可以直接在进程内部调用的,而Binder内部有一个queryLocalInterface(String description)函数,该函数是根据输入的字符串判断该Binder对象是一个本地的Binder引用。在第一张图中曾经指出,当创建一个Binder对象时,服务端进程内部创建一个Binder对象,Binder驱动中也会创建一个Binder对象。如果从远程获取服务端的Binder,则只会返回Binder驱动中的Binder对象,而如果从服务端进程内部获取Binder对象,则会获取服务端本身的Binder对象。

    因此,asInterface()函数正是利用了queryLocalInterface()方法,提供了一个统一的接口。无论是远程客户端还是本地端,当获取Binder对象后,可以把获取的Binder对象作为asInterface()的参数,从而返回一个IMyService 接口,该接口要么使用Proxy类,要么直接使用Stub所实现的相应服务函数。

    五、系统服务中的Binder对象

      在应用程序中,经常使用getSystemService(String serviceName)方法获取一个系统服务,那么,这些系统服务的Binder引用是如何传递给客户端的呢?须知系统服务并不是通过startService()启动的。getSystemService()函数的实现是在ContextImpl类中,该函数所返回的Service比较多,具体可参照源码。这些Service一般都由ServiceManager管理。
    1、ServiceManger管理的服务
    ServiceManager是一个独立进程,其作用如名称所示,管理各种系统服务,管理的逻辑如下

      ServiceManager本身也是一个Service,Framework提供了一个系统函数,可以获取该Service对应的Binder引用,那就是BinderInternal.getContextObject()。该静态函数返回ServiceManager后,就可以通过ServiceManager提供的方法获取其他系统Service的Binder引用。这种设计模式在日常生活中到处可见,ServiceManager就像是一个公司的总机,这个总机号码是公开的,系统中任何进程都可以使用BinderInternal.getContextObject()获取该总机的Binder对象,而当用户想联系公司中的其他人(服务)时,则要经过总机再获得分机号码。这种设计的好处是系统中仅暴露一个全局Binder引用,那就是ServiceManager,而其他系统服务则可以隐藏起来,从而有助于系统服务的扩展,以及调用系统服务的安全检查。其他系统服务在启动时,首先把自己的Binder对象传递给ServiceManager,即所谓的注册(addService)。
      下面查看获取一个Service{IMPUT_METHOD_SERVICE}

            if (INPUT_METHOD_SERVICE.equals(name)) {
                return InputMethodManager.getInstance(this);
        static public InputMethodManager getInstance(Looper mainLooper) {
            synchronized (mInstanceSync) {
                if (mInstance != null) {
                    return mInstance;
                }
                IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                mInstance = new InputMethodManager(service, mainLooper);
            }
            return mInstance;
        }

      即通过ServiceManager获取InputMethod Service对应的Binder对象b,然后再将该Binder对象作为IInputMethodManager.Stub.asInterface()的参数,返回一个IInputMethodManager的统一接口。
      ServiceManager.getService()的代码如下

        public static IBinder getService(String name) {
            try {
                IBinder service = sCache.get(name);
                if (service != null) {
                    return service;
                } else {
                    return getIServiceManager().getService(name);
                }
            } catch (RemoteException e) {
                Log.e(TAG, "error in getService", e);
            }
            return null;
        }

    即首先从sCache 缓存中查看是否有对应的Binder 对象,有则返回,没有则调用getIServiceManager().getService(name),函数getIServiceManager()即用于返回系统中唯一的ServiceManager对应的Binder,其代码如下:

        private static IServiceManager getIServiceManager() {
            if (sServiceManager != null) {
                return sServiceManager;
            }
            // Find the service manager
            sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
            return sServiceManager;
        }

      BinderInternal.getContextObject()静态函数即用于返回ServiceManager对应的全局Binder对象,该函数不需要任何参数,因为它的作用是固定的。其他所有通过ServiceManager获取的系统服务的过程与以上基本类似,所不同的就是传递给ServiceManager的服务名称不同,因为ServiceManager正是按照服务的名称(String类型)来保存不同的Binder对象的。
      使用addService()向ServiceManager中添加一个服务一般是在SystemService进程启动时完成的。

    2、理解Manger

      ServiceManager所管理的所有Service都是以相应的Manager返回给客户端,因此,这里简述一下Framework中关于Manager的语义。在我们中国的企业里,Manager一般指经理,比如项目经理、人事经理、部门经理。经理本身的含义比较模糊,其角色有些是给我们分配任务,比如项目经理;有些是给我们提供某种服务,比如人事经理;有些则是监督我们的工作等。而在Android中,Manager的含义更应该翻译为经纪人,Manager所manage的对象是服务本身,因为每个具体的服务一般都会提供多个API接口 ,而Manager所manage的正是这些API。客户端一般不能直接通过Binder引用去访问具体的服务,而是要经过一个Manager,相应 的Manager类对客户端是可见的,而远程的服务类对客户端则是隐藏的。而这些Manager的类内部都会有一个远程服务Binder的变量,而且在一般情况下,这些Manager的构造函数参数中会包含这个Binder对象。简单地讲,即先通过ServiceManager获取远程服务的Binder引用,然后使用这个Binder引用构造一个客户端本地可以访问的经纪人,然后客户端就可以通过该经纪人访问远程的服务。这种设计的作用是屏蔽直接访问远程服务,从而可以给应用程序提供灵活的、可控的API接口,比如AmS。系统不希望用户直接去访问AmS,而是经过ActivityManager类去访问,而ActivityManager内部提供了一些更具可操作性的数据结构,比如RecentTaskInfo数据类封装了最近访问过的Task列表,MemoryInfo数据类封装了和内存相关的信息。

    通过本地Manger访问远程服务的模型图如下




  • 相关阅读:
    【Python学习笔记之三】lambda表达式用法小结
    Cisco Packet Tracer 6.0 实验笔记
    Kali Linux 下安装中文版输入法
    kali 2018.1安装教程
    Kali Linux菜单中各工具功能大全
    互联网电商购物车架构演变案例
    互联网业务场景下消息队列架构
    物流系统高可用架构案例
    高并发下海量容器案例一
    客服系统微服务架构的演化
  • 原文地址:https://www.cnblogs.com/a284628487/p/3187320.html
Copyright © 2011-2022 走看看