Binder机制学习(一):如何在App开发中使用Binder
Binder在Android系统中扮演了很重要的角色,很多地方都用到了Binder机制,比如最常见的系统服务ActivityManagerService、MediaPlayerService等。常规的App中也会用到Binder机制,AIDL就是借助Binder实现的。就连你编写的Activity中都使用过Binder机制(如startActivity的执行过程),但是你却说不出什么是Binder,甚至很多人都不知道Binder的存在。
1 Binder介绍
1.1 Linux进程基础
为了保护进程空间不被别的进程破坏或者干扰,Linux的进程是相互独立的(进程隔离),Linux为每一个进程都提供了单独的内存空间(虚拟内存空间),而且一个进程的内存空间还分为用户空间和内核(Kernel)空间,内核空间所有进程共有,相当于把Kernel和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内用户和内核的隔离。刚才提到了进程空间都是虚拟空间,当然在使用的时候还需要映射的实际的物理内存上去,这个就由系统的内存管理功能实现了。具体的模型如下图所示:
图1 进程内存空间
图2 Linux进程内存空间
图3 虚拟内存和物理内存
1.2 Binder由来
这种隔离机制就带来一个问题,如果进程间要相互通信如何处理?为了处理进程间通信的问题,很多IPC机制应运而生,在传统的Linux上,我们还是有很多选择可以用来实现进程间通信,如管道、命名管道、Socket、内存映射、共享内存等机制。在Android中,出于性能和安全方面的原因,新提供了一种叫Binder的进程间通信机制。Binder实现进程间通信机制的核心是使用了内存映射,整个参与者包括Client(客户端)、Server(服务端)、ServiceManager(守护进程)、以及Binder驱动。来看一张常见的Binder机制原理图,简单的描述了Binder的机制:
另外还有一张图,详细的描述的Binder的原理,其中包含了更细致的元素和对应关系,其中还谈及到进程管理和线程管理等更多复杂的知识,我们后续在解析Binder源码的时候再来回顾这个图:
图4 Binder详细原理
2 通过AIDL初次使用Binder
由于Binder本身比较复杂,我们的Android很贴心的为开发者提供了更为简洁AIDL语法,根据AIDL文件生成模板代码,通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求,完全让开发者感受不到Binder的存在,却又实实在在的使用了Binder来实现进程间通信。我们通过一个简单的DEMO来实现跨进程通信,开始之前我们先熟悉一下AIDL语法。
2.1 AIDL语法
AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:
- AIDL文件以 .aidl 为后缀名
- AIDL支持的数据类型分为如下几种:
- 八种基本数据类型:byte、char、short、int、long、float、double、boolean、String,CharSequence
- 实现了Parcelable接口的数据类型
- List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
- Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
- AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值
- 定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通
2.2 创建AIDL文件
在Android Studio中直接创建AIDL文件即可,我们创建两个文件,在MyMessage.aidl中声明我们的数据类型,在EncryptManager中声明接口,另外还要新增一个MyMessage类实现parcelable接口。
图5 创建AIDL文件
// MyMessage.aidl package com.flysands.binderexample; // Declare any non-default types here with import statements parcelable MyMessage;
// EncryptManager.aidl package com.flysands.binderexample; import com.flysands.binderexample.MyMessage; // Declare any non-default types here with import statements interface EncryptManager { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ String revert(in MyMessage source); String append(in MyMessage source); }
// This file is intentionally left blank as placeholder for parcel declaration. package com.flysands.binderexample; import android.os.Parcel; import android.os.Parcelable; public class MyMessage implements Parcelable { public MyMessage() { } public String text; protected MyMessage(Parcel in) { text = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int i) { parcel.writeString(text); } public static final Creator<MyMessage> CREATOR = new Creator<MyMessage>() { @Override public MyMessage createFromParcel(Parcel in) { return new MyMessage(in); } @Override public MyMessage[] newArray(int size) { return new MyMessage[size]; } }; }
定义之后,直接 Build -> Make Project,Android会自动帮我们生成部分代码。最重要的就是EncryptManager.java,正是这段生成的代码帮我们屏蔽了Binder的细节,让我们不必驻足泥潭之中。
/* * This file is auto-generated. DO NOT MODIFY. */ package com.flysands.binderexample; // Declare any non-default types here with import statements public interface EncryptManager extends android.os.IInterface { /** Default implementation for EncryptManager. */ public static class Default implements com.flysands.binderexample.EncryptManager { /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ @Override public java.lang.String revert(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException { return null; } @Override public java.lang.String append(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException { return null; } @Override public android.os.IBinder asBinder() { return null; } } /** Local-side IPC implementation stub class. */ public static abstract class Stub extends android.os.Binder implements com.flysands.binderexample.EncryptManager { private static final java.lang.String DESCRIPTOR = "com.flysands.binderexample.EncryptManager"; /** Construct the stub at attach it to the interface. */ public Stub() { this.attachInterface(this, DESCRIPTOR); } /** * Cast an IBinder object into an com.flysands.binderexample.EncryptManager interface, * generating a proxy if needed. */ public static com.flysands.binderexample.EncryptManager asInterface(android.os.IBinder obj) { if ((obj==null)) { return null; } android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); if (((iin!=null)&&(iin instanceof com.flysands.binderexample.EncryptManager))) { return ((com.flysands.binderexample.EncryptManager)iin); } return new com.flysands.binderexample.EncryptManager.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 { java.lang.String descriptor = DESCRIPTOR; switch (code) { case INTERFACE_TRANSACTION: { reply.writeString(descriptor); return true; } case TRANSACTION_revert: { data.enforceInterface(descriptor); com.flysands.binderexample.MyMessage _arg0; if ((0!=data.readInt())) { _arg0 = com.flysands.binderexample.MyMessage.CREATOR.createFromParcel(data); } else { _arg0 = null; } java.lang.String _result = this.revert(_arg0); reply.writeNoException(); reply.writeString(_result); return true; } case TRANSACTION_append: { data.enforceInterface(descriptor); com.flysands.binderexample.MyMessage _arg0; if ((0!=data.readInt())) { _arg0 = com.flysands.binderexample.MyMessage.CREATOR.createFromParcel(data); } else { _arg0 = null; } java.lang.String _result = this.append(_arg0); reply.writeNoException(); reply.writeString(_result); return true; } default: { return super.onTransact(code, data, reply, flags); } } } private static class Proxy implements com.flysands.binderexample.EncryptManager { 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; } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ @Override public java.lang.String revert(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); if ((source!=null)) { _data.writeInt(1); source.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(Stub.TRANSACTION_revert, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().revert(source); } _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } @Override public java.lang.String append(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException { android.os.Parcel _data = android.os.Parcel.obtain(); android.os.Parcel _reply = android.os.Parcel.obtain(); java.lang.String _result; try { _data.writeInterfaceToken(DESCRIPTOR); if ((source!=null)) { _data.writeInt(1); source.writeToParcel(_data, 0); } else { _data.writeInt(0); } boolean _status = mRemote.transact(Stub.TRANSACTION_append, _data, _reply, 0); if (!_status && getDefaultImpl() != null) { return getDefaultImpl().append(source); } _reply.readException(); _result = _reply.readString(); } finally { _reply.recycle(); _data.recycle(); } return _result; } public static com.flysands.binderexample.EncryptManager sDefaultImpl; } static final int TRANSACTION_revert = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); static final int TRANSACTION_append = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); public static boolean setDefaultImpl(com.flysands.binderexample.EncryptManager impl) { if (Stub.Proxy.sDefaultImpl == null && impl != null) { Stub.Proxy.sDefaultImpl = impl; return true; } return false; } public static com.flysands.binderexample.EncryptManager getDefaultImpl() { return Stub.Proxy.sDefaultImpl; } } /** * Demonstrates some basic types that you can use as parameters * and return values in AIDL. */ public java.lang.String revert(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException; public java.lang.String append(com.flysands.binderexample.MyMessage source) throws android.os.RemoteException; }
2.3 实现服务端代码-继承Stub类
刚才那一段自动生成的代码中,有个抽象类Stub需要我们关注。这个Stub类是面向Binder服务端的,需要继承它并重写revert和append方法。
package com.flysands.binderexample; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import androidx.annotation.Nullable; import static android.os.Process.myPid; public class RemoteService extends Service { private EncryptManagerNative mEncryptManagerNative = new EncryptManagerNative(); class EncryptManagerNative extends EncryptManager.Stub { @Override public String revert(MyMessage source) throws RemoteException { Log.d(Const.TAG, "pid " + myPid() + " tid " + Thread.currentThread().getName() + " receive revert call"); return new StringBuffer(source.text).reverse().toString(); } @Override public String append(MyMessage source) throws RemoteException { Log.d(Const.TAG, "pid " + myPid() + " tid " + Thread.currentThread().getName() + " receive append call"); return source.text + " add append string"; } } @Nullable @Override public IBinder onBind(Intent intent) { Log.d(Const.TAG, "remote service bind"); return mEncryptManagerNative; } }
2.4 实现客户端代码
客户端代码比较简单,通过系统API bindService进行服务绑定,传递一个ServiceConnetion对象即可,在onServiceConnected获取EncryptManager对象。然后在按钮响应函数中直接调用即可。
package com.flysands.binderexample; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import androidx.appcompat.app.AppCompatActivity; import static android.os.Process.myPid; public class MainActivity extends AppCompatActivity { Button append; Button revert; EditText input; EncryptManager encryptManager; private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder iBinder) { encryptManager = EncryptManager.Stub.asInterface(iBinder); } @Override public void onServiceDisconnected(ComponentName componentName) { encryptManager = null; } }; private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() { @Override public void binderDied() { if (encryptManager != null) { encryptManager.asBinder().unlinkToDeath(mDeathRecipient, 0); encryptManager = null; bindService(); } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); append = findViewById(R.id.append); input = findViewById(R.id.input); revert = findViewById(R.id.revert); bindService(); revert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d(Const.TAG, "pid " + myPid() + " tid " + Thread.currentThread().getName() + " call revert"); MyMessage message = new MyMessage(); message.text = input.getText().toString(); try { input.setText(encryptManager.revert(message)); } catch (RemoteException e) { e.printStackTrace(); } } }); append.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Log.d(Const.TAG, "pid " + myPid() + " tid " + Thread.currentThread().getName() + " call append"); MyMessage message = new MyMessage(); message.text = input.getText().toString(); try { input.setText(encryptManager.append(message)); } catch (RemoteException e) { e.printStackTrace(); } } }); } private void bindService() { Intent intent = new Intent(); intent.setAction("com.flysands.binder"); intent.setPackage("com.flysands.binderexample"); Log.d(Const.TAG, "client request bind"); bindService(intent, connection, Context.BIND_AUTO_CREATE); } }
2.5 指定服务端代码运行进程
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.flysands.binderexample"> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".RemoteService" android:process=":remote"> <intent-filter> <action android:name="com.flysands.binder" /> </intent-filter> </service> </application> </manifest>
3 测试
图6 客户端日志
图7 服务端日志