序言
好早就想写一篇关于Binder的学习笔记,但一直对Binder没有一个全面的认识,不知从何下笔。最近看了一篇关于Binder,讲解很全面的文章:
Android跨进程通信:图文详解 Binder机制 原理
确实写的挺好,对于理解Binder很有帮助。
现在,按照我自己的思路,记录一下对Binder的理解。将从下面几个主题阐述Binder:
- Binder是什么
- Binder的作用以及原理
- 跨进程通信机制
- 怎么使用Binder
- 分析WindowManagerService的原理(通过Binder实现)
- 总结
Binder是什么
Binder(粘合剂)一个复杂的概念,可以理解为进程之间的粘合剂,可以实现进程间通信。
下面将从几个角度给出Binder的定义:
- 功能定义:Binder是一种IPC(进程间通信)的方式
- 存在形式:Binder是一个类,实现了IBinder接口
- IPC机制:Binder是一种虚拟的物理设备驱动,用来连接Client进程、Service进程、ServiceManager进程
前两个角度很好理解,但是,最后的虚拟的物理设备驱动
是什么鬼?不着急,后面会对这个做出解释。
Binder的作用及原理
上面也提到过,Binder的主要作用是实现进程间通信。
Android是基于Linux的发行版,Linux上的进程通信方式有管道、消息队列、共享内存、信号量、Socket。那么,为什么还要特意提供一个Binder实现进程通信呢?这要从Linux的进程空间来说明。
- 进程空间
Linux上,进程空间分为用户空间和内核空间。
- 用户空间:数据不可在进程间共享
- 内核空间:数据可在进程间共享
- 用户空间&内核空间交互
在进程内,用户空间和内核空间交互需要通过系统调用,主要是两个方法:
-
copy_from_user
:把用户空间的数据拷贝到内核空间 -
copy_to_user
:把内核空间的数据拷贝到用户空间
- 内存映射
通过mmap
方法建立用户空间和内存空间的映射关系,从而减少数据传递需要拷贝的次数。
传统的跨进程通信:image.png
这两张图片很好的表明了Binder和其他进程间通信的区别,总结一下,Binder具有以下优点:
- 高效:Binder拷贝数据只需要1次,管道、Socket、消息队列都需要2次
- 使用简单:采用C/S架构,实现面向对象调用方式,使用Binder跟调用本地对象一样操作简单
- 安全性高:Binder给每个进程分配UID/PID作为身份标识,通信时会根据UID/PID校验身份,其他通信方式没有严格的校验过程
跨进程通信机制
下图是跨进程通信机制模型图,基于C/S架构:首先,介绍一下模型中的几个角色:
- Client:客户端,需要使用服务的进程
- Server:服务端,提供服务的进程
- ServiceManager:服务管理器,管理服务的注册与查询。服务端在ServiceManager中注册成功之后,ServiceManager保存一个键值对,服务名称->服务端Binder对象。此时,客户端传入一个服务名称,该对象可以根据名称查询具体的Binder,并且把Binder的引用返回给客户端。
- Binder驱动:连接ServiceManager、Client、Server的桥梁,主要作用是传递数据(内存映射),实现线程控制,采用Binder驱动自己管理的线程池。
接下来,介绍一下跨进程通信的流程,图中也已经标注出来了:
注册服务
Server进程向Binder驱动发起注册请求,Binder驱动把请求转给ServiceManager进程,ServiceManager添加Server进程信息,主要是保存一个键值对服务名称 -> Binder实例
获取服务
Client进程向Binder驱动发起获取服务请求,传递一个服务名称,Binder驱动把请求转发给ServiceManager进程,ServiceManager根据服务名称查找到服务对象,通过Binder驱动把服务对象的引用返回给Client进程-
调用服务
首先,Binder驱动为跨进程通信做准备,即调用mmap
实现内存映射。Client进程发送参数到Server进程,Server进程解析参数并调用目标方法,Server进程将目标方法结果返回给Client进程。
下图比较清晰的描述了这个过程:image.png
- 额外说明
-
模型内存空间图: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的使用。
下面,我们想要实现一个这样的场景:服务端进程提供查询图书列表和添加图书的功能,客户端进程可调用服务接口查询图书和添加图书。
- 首先我们先来实现服务端,定义图书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中有两个方法getBookList
和addBook
,就是我们在IBookManager.aidl中声明的。
c. 声明了一个内部类StubIBookManager.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结果
- 其次,服务端定义一个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"/>
- 客户端实现
首先客户端要绑定远程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个步骤:
- 创建ViewRootImpl对象
- 将布局参数设置给view
- 存储ViewRootImpl、view、LayoutParam到列表中
- 通过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源码也是十分有帮助的。