美文网首页
全面理解Binder

全面理解Binder

作者: 左大人 | 来源:发表于2018-10-08 19:28 被阅读0次

    image.png

    序言

    好早就想写一篇关于Binder的学习笔记,但一直对Binder没有一个全面的认识,不知从何下笔。最近看了一篇关于Binder,讲解很全面的文章:
    Android跨进程通信:图文详解 Binder机制 原理
    确实写的挺好,对于理解Binder很有帮助。
    现在,按照我自己的思路,记录一下对Binder的理解。将从下面几个主题阐述Binder:

    • Binder是什么
    • Binder的作用以及原理
    • 跨进程通信机制
    • 怎么使用Binder
    • 分析WindowManagerService的原理(通过Binder实现)
    • 总结

    Binder是什么

    Binder(粘合剂)一个复杂的概念,可以理解为进程之间的粘合剂,可以实现进程间通信。
    下面将从几个角度给出Binder的定义:

    1. 功能定义:Binder是一种IPC(进程间通信)的方式
    2. 存在形式:Binder是一个类,实现了IBinder接口
    3. IPC机制:Binder是一种虚拟的物理设备驱动,用来连接Client进程、Service进程、ServiceManager进程

    前两个角度很好理解,但是,最后的虚拟的物理设备驱动是什么鬼?不着急,后面会对这个做出解释。

    Binder的作用及原理

    上面也提到过,Binder的主要作用是实现进程间通信

    Android是基于Linux的发行版,Linux上的进程通信方式有管道、消息队列、共享内存、信号量、Socket。那么,为什么还要特意提供一个Binder实现进程通信呢?这要从Linux的进程空间来说明。

    1. 进程空间
      Linux上,进程空间分为用户空间内核空间
    • 用户空间:数据不可在进程间共享
    • 内核空间:数据可在进程间共享
    1. 用户空间&内核空间交互
      在进程内,用户空间和内核空间交互需要通过系统调用,主要是两个方法:
    • copy_from_user:把用户空间的数据拷贝到内核空间
    • copy_to_user:把内核空间的数据拷贝到用户空间
    1. 内存映射
      通过mmap方法建立用户空间和内存空间的映射关系,从而减少数据传递需要拷贝的次数。
      传统的跨进程通信: image.png
    Binder(内存映射)跨进程通信: image.png

    这两张图片很好的表明了Binder和其他进程间通信的区别,总结一下,Binder具有以下优点:

    • 高效:Binder拷贝数据只需要1次,管道、Socket、消息队列都需要2次
    • 使用简单:采用C/S架构,实现面向对象调用方式,使用Binder跟调用本地对象一样操作简单
    • 安全性高:Binder给每个进程分配UID/PID作为身份标识,通信时会根据UID/PID校验身份,其他通信方式没有严格的校验过程

    跨进程通信机制

    下图是跨进程通信机制模型图,基于C/S架构: image.png

    首先,介绍一下模型中的几个角色:

    • Client:客户端,需要使用服务的进程
    • Server:服务端,提供服务的进程
    • ServiceManager:服务管理器,管理服务的注册与查询。服务端在ServiceManager中注册成功之后,ServiceManager保存一个键值对,服务名称->服务端Binder对象。此时,客户端传入一个服务名称,该对象可以根据名称查询具体的Binder,并且把Binder的引用返回给客户端。
    • Binder驱动:连接ServiceManager、Client、Server的桥梁,主要作用是传递数据(内存映射),实现线程控制,采用Binder驱动自己管理的线程池。

    接下来,介绍一下跨进程通信的流程,图中也已经标注出来了:

    1. 注册服务
      Server进程向Binder驱动发起注册请求,Binder驱动把请求转给ServiceManager进程,ServiceManager添加Server进程信息,主要是保存一个键值对服务名称 -> Binder实例

    2. 获取服务
      Client进程向Binder驱动发起获取服务请求,传递一个服务名称,Binder驱动把请求转发给ServiceManager进程,ServiceManager根据服务名称查找到服务对象,通过Binder驱动把服务对象的引用返回给Client进程

    3. 调用服务
      首先,Binder驱动为跨进程通信做准备,即调用mmap实现内存映射。Client进程发送参数到Server进程,Server进程解析参数并调用目标方法,Server进程将目标方法结果返回给Client进程。
      下图比较清晰的描述了这个过程:

      image.png
    1. 额外说明
    • 模型内存空间图: image.png
    • 模型分层示意图: image.png
    • 线程管理:Server进程会创建很多线程处理Binder请求,而这些线程由Binder驱动进行管理,Binder默认最大线程数是16,超过的会阻塞等待空闲线程。

    • 解释Binder驱动: image.png

    至此,Binder跨进程通信机制已经介绍完毕。接下来,看一下Binder在实际开发过程怎么使用。

    怎么使用Binder

    Android开发中,Binder主要用在Service中,包括AIDL和Messenger,其中普通的Service不涉及进程间通信,无法触及Binder的核心,而Messenger的底层是AIDL,这里选择用AIDL来演示Binder的使用。
    下面,我们想要实现一个这样的场景:服务端进程提供查询图书列表和添加图书的功能,客户端进程可调用服务接口查询图书和添加图书。

    1. 首先我们先来实现服务端,定义图书Bean,定义接口提供图书相关操作。新建三个文件Book.java、Book.aidl、IBookManager.aidl,代码如下:
    //Book.java
    //实现Parcelable,可序列化,在进程间传递
    public class Book implements Parcelable {
    
        private int id;
        private String name;
        private String desc;
    
        public Book(int id, String name, String desc) {
            this.id = id;
            this.name = name;
            this.desc = desc;
        }
    
        private Book(Parcel parcel) {
            this.id = parcel.readInt();
            this.name = parcel.readString();
            this.desc = parcel.readString();
        }
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeInt(id);
            dest.writeString(name);
            dest.writeString(desc);
        }
    
        public static final Parcelable.Creator<Book> CREATOR = new Parcelable.Creator<Book>() {
            @Override
            public Book createFromParcel(Parcel source) {
                return new Book(source);
            }
    
            @Override
            public Book[] newArray(int size) {
                return new Book[size];
            }
        };
    
        @Override
        public String toString() {
            return "{id:" + id + ", "
                    + "name:" + name + ", "
                    + "desc:" + desc + "}";
        }
    }
    
    
    //Book.aidl
    package study.self.zf.service.bean;
    
    parcelable Book;
    
    // IBookManager.aidl
    package study.self.zf.service.bean;
    
    import study.self.zf.service.bean.Book;
    import study.self.zf.service.bean.INewBookListener;
    
    interface IBookManager {
        List<Book> getBookList();
        void addBook(in Book book);listener);
    }
    

    编译一下,系统会根据AIDL文件为我们生成一个Binder类,如下就是系统为IBookManager生成的Binder类:

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: /Users/user_zf/AndroidStudioProjects/ScrollConflict/selfview/service/src/main/aidl/study/self/zf/service/bean/IBookManager.aidl
     */
    package study.self.zf.service.bean;
    
    public interface IBookManager extends android.os.IInterface {
        /**
         * Local-side IPC implementation stub class.
         */
        public static abstract class Stub extends android.os.Binder implements study.self.zf.service.bean.IBookManager {
            private static final java.lang.String DESCRIPTOR = "study.self.zf.service.bean.IBookManager";
    
            /**
             * Construct the stub at attach it to the interface.
             */
            public Stub() {
                this.attachInterface(this, DESCRIPTOR);
            }
    
            /**
             * Cast an IBinder object into an study.self.zf.service.bean.IBookManager interface,
             * generating a proxy if needed.
             */
            public static study.self.zf.service.bean.IBookManager asInterface(android.os.IBinder obj) {
                if ((obj == null)) {
                    return null;
                }
                android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
                if (((iin != null) && (iin instanceof study.self.zf.service.bean.IBookManager))) {
                    return ((study.self.zf.service.bean.IBookManager) iin);
                }
                return new study.self.zf.service.bean.IBookManager.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_getBookList: {
                        data.enforceInterface(DESCRIPTOR);
                        java.util.List<study.self.zf.service.bean.Book> _result = this.getBookList();
                        reply.writeNoException();
                        reply.writeTypedList(_result);
                        return true;
                    }
                    case TRANSACTION_addBook: {
                        data.enforceInterface(DESCRIPTOR);
                        study.self.zf.service.bean.Book _arg0;
                        if ((0 != data.readInt())) {
                            _arg0 = study.self.zf.service.bean.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 study.self.zf.service.bean.IBookManager {
                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 java.util.List<study.self.zf.service.bean.Book> getBookList() throws android.os.RemoteException {
                    android.os.Parcel _data = android.os.Parcel.obtain();
                    android.os.Parcel _reply = android.os.Parcel.obtain();
                    java.util.List<study.self.zf.service.bean.Book> _result;
                    try {
                        _data.writeInterfaceToken(DESCRIPTOR);
                        mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
                        _reply.readException();
                        _result = _reply.createTypedArrayList(study.self.zf.service.bean.Book.CREATOR);
                    } finally {
                        _reply.recycle();
                        _data.recycle();
                    }
                    return _result;
                }
    
                @Override
                public void addBook(study.self.zf.service.bean.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<study.self.zf.service.bean.Book> getBookList() throws android.os.RemoteException;
    
        public void addBook(study.self.zf.service.bean.Book book) throws android.os.RemoteException;
    }
    

    这里,我们来分析一下IBookManager这个类的结构。

    • IBookManger类
      a. IBookManager继承了IInterface接口,同时它自己也是个接口,所有需要在Binder中传输的接口都需要继承IInterface接口。
      b. IBookManager中有两个方法getBookListaddBook,就是我们在IBookManager.aidl中声明的。
      c. 声明了一个内部类Stub

    • IBookManager.Stub类
      a. Stub是一个Binder类,继承Binder,实现IBookManager
      b. DESCRIPTOR是Binder的唯一标志,一般用当前Binder的类名(带包名)表示
      c. TRANSACTION_getBookList和TRANSACTION_addBook,这两个整形id分别用来标识两个方法,主要用在transact过程来标识具体请求哪个方法
      d. asInterface(android.os.IBinder obj),用来把服务端的Binder转换为客户端需要的AIDL接口类型的对象。转换过程区分进程,如果server和client处于同一进程,则返回服务端Stub对象本身,否则返回Stub.Proxy对象
      e. asBinder,用于返回当前Binder对象
      f. onTransact,该方法运行在服务端的Binder线程池中,客户端发起跨进程请求时,请求通过系统底层封装后交给该方法来处理,该方法原型如下:

    @Override
    public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
        //处理请求
        ......
    }
    

    服务端通过code确定请求的目标方法,接着从data中解析出目标方法所需参数,执行目标方法,执行完毕之后,想reply中写入返回值。如果该方法返回false,表示请求失败

    g. 声明了一个内部类Proxy

    • IBookManager.Stub.Proxy
      a. Proxy实现IBookManager接口
      b. getBookList和addBook,这两个方法运行在客户端,内部实现是这样的:首先创建方法需要的输入类型Parcel对象_data、输出类型Parcel对象_reply和返回值对象List,把该方法的参数写入_data对象,接着调用transact进行RPC(远程过程调用),客户端线程挂起,服务端执行onTransact方法,知道RPC过程返回后,客户端线程继续执行,并从_reply中取出RPC结果
    一张图介绍Binder的工作机制: image.png
    1. 其次,服务端定义一个BookService提供服务:
    public class BookService extends Service {
        private static final String TAG = "BookManager";
        private List<Book> mBookList = new ArrayList();
        private Binder = new IBookManager.Stub() {
            @Override
            public List<Book> getBookList() throws RemoteException {
                return mBookList;
            }
    
            @Override
            public void addBook(Book book) throws RemoteException {
                mBookList.add(book);
            }
        }
    
        @Override
        public void onCreate() {
            super.onCreate();
            mBookList.add(new Book(1, "Android"));
            mBookList.add(new Book(2, "IOS"));
            mBookList.add(new Book(3, "Java"));
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return mBinder; 
        }
    }
    

    接着在Manifest中注册Service,指定为remote进程

    <service
        android:name=".aidl.BookService"
        android:process=":remote"/>
    
    1. 客户端实现
      首先客户端要绑定远程Service,绑定成功之后把返回的Binder转换成AIDL接口,然后调用远程方法,代码如下:
    public class BookActivity extends Activity {
        private static final String TAG = "BookService";
        pirvate ServiceConnection mConnection = new ServiceConnection() {
            public void onServiceConnected(ComponentName className, IBinder service) {
                if (service != null) {
                    IBookManager bookManager = IBookManager.Stub.asInterface(service);
                    try {
                        List<Book> list = bookManager.getBookList();
                    } catch (RemoteException) {
                        e.printStackTrace();
                    }
                }
            }
    
            public void onServiceDisconected(ComponentName className) {
                //no-op
            }
        }
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_book);
            Intent intent = new Intent(this, BookService.class);
            bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
        }
    
        @Override
        protected void onDestroy() {
            unbindService(mConnection);
            super.onDestroy();
        }
    }
    

    Binder跨进程调用的基本用法介绍完毕,其中还有一些难点,这里不做深入探讨,大家在用的过程中再去慢慢体会。

    分析WindowManagerService的原理

    此处,我们通过Dialog是如何显示在屏幕上来分析WMS的原理。
    首先,我们看一下Dialog的构造方法:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
            if (themeResId == ResourceId.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }
    
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    
        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setOnWindowSwipeDismissedCallback(() -> {
            if (mCancelable) {
                cancel();
            }
        });
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
    
        mListenersHandler = new ListenersHandler(this);
    }
    

    需要着重看几句代码,首先,创建了一个Window对象并且保存在mWindow中,然后获取系统服务WindowManager,之后把Window
    和WindowManger关联起来w.setWindowManager(mWindowManager, null, null);

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
        setWindowManager(wm, appToken, appName, false);
    }
    
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated
                || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    

    最后一句代码调用了createLocalWindowManager方法

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow);
    }
    

    这个方法很简单,单纯创建一个WindowManagerImpl对象,但与ContextImpl注册的WindowManager相比多了一个参数parentWindow,表明此时构建的WindowManager对象是与具体Window关联的。接着我们分析一下WindowManagerImpl:

    public final class WindowManagerImpl implements WindowManager {
        private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
        private final Context mContext;
        private final Window mParentWindow;
        
        public WindowManagerImpl(Context context) {
            this(context, null);
        }
    
        private WindowManagerImpl(Context context, Window parentWindow) {
            mContext = context;
            mParentWindow = parentWindow;
        }
    
        @Override
        public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
        }
    
        @Override
        public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
            applyDefaultToken(params);
            mGlobal.updateViewLayout(view, params);
        }
        
        @Override
        public void removeView(View view) {
            mGlobal.removeView(view, false);
        }
    
        //省略其他代码
    }
    

    可以看到,实际上真正干活的并不是WindowManagerImpl,而是WindowManagerGlobal类。Dialog显示在屏幕上实际上是调用addView
    方法,我们跟进WindowManagerGlobal的addView方法:

    public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
        //省略代码
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            //省略代码
            root = new ViewRootImpl(view.getContext(), display);
    
            view.setLayoutParams(wparams);
    
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
    
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
    

    上面代码主要分了一下4个步骤:

    1. 创建ViewRootImpl对象
    2. 将布局参数设置给view
    3. 存储ViewRootImpl、view、LayoutParam到列表中
    4. 通过ViewRootImpl.setView把View显示到窗口上

    看过Android Framework的同学对ViewRootImpl不会太陌生,ViewRootImpl是Native与Java层通信的桥梁,比如我们熟悉的performTraversals方法就是在收到系统绘制消息后,调用视图树上各个节点来绘制整个视图树的。
    接下来,我们看一下ViewRootImpl的构造方法:

    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        //省略代码
        //保存当前线程,更新UI的线程只能是创建ViewRootImpl的线程,在开发应用的过程中,子线程中更新UI会抛异常,但并不是只有UI线程能更新UI,而是ViewRootImpl在UI线程中创建
        mThread = Thread.currentThread();
    }
    

    着重看WindowManagerGlobal.getWindowSession()这句代码:

    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
    
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }
    

    Framework首先通过getWindowManagerService获取IWindowManager对象,该函数中通过ServiceManager.getService("window")获取WMS,实际上ServiceManager.getService返回一个IBinder对象(系统服务的Binder对象提前已经注册到ServiceManager中),然后通过IWindowManager.Stub.asInterface把Binder转换为IWindowManager类型。可见Framework于WMS间通过Binder通信。最后通过openSession与WMS建立通信会话,之后都是通过Session来交换信息。

    与WMS建立Session之后,调用ViewRootImpl的setView,该方法会向WMS发送显示Dialog或Activity中DecorView的请求:

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            requestLayout();
            try{
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(),
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mInputChannel);
            }
        }
    }
    

    requestLayout方法请求布局,会调用performTraversals方法,performTraversals方法发送DO_TRAVERSAL消息,这个消息会触发整个视图树重绘。

    到此,分析完了Dialog是如何显示在界面上了。

    总结

    本文主要讲解了Binder的工作机制,以及Binder在Framework中的应用,还有实际开发过程中怎样使用Binder。
    系统服务基本都是使用Binder进行通信,所以理解Binder对于看Android源码也是十分有帮助的。

    相关文章

      网友评论

          本文标题:全面理解Binder

          本文链接:https://www.haomeiwen.com/subject/ekgjaftx.html