全面理解Binder

2018-10-08  本文已影响0人  左大人

image.png

序言

好早就想写一篇关于Binder的学习笔记,但一直对Binder没有一个全面的认识,不知从何下笔。最近看了一篇关于Binder,讲解很全面的文章:
Android跨进程通信:图文详解 Binder机制 原理
确实写的挺好,对于理解Binder很有帮助。
现在,按照我自己的思路,记录一下对Binder的理解。将从下面几个主题阐述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. 用户空间&内核空间交互
    在进程内,用户空间和内核空间交互需要通过系统调用,主要是两个方法:
  1. 内存映射
    通过mmap方法建立用户空间和内存空间的映射关系,从而减少数据传递需要拷贝的次数。
    传统的跨进程通信: image.png
Binder(内存映射)跨进程通信: image.png

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

跨进程通信机制

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

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

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

  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. 额外说明

至此,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这个类的结构。

@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

一张图介绍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源码也是十分有帮助的。

上一篇下一篇

猜你喜欢

热点阅读