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和上层的应用程序抽像的隔离开。这里有两个隔离,一个进程间是相互隔离的,二是进程内用户和内核的隔离。刚才提到了进程空间都是虚拟空间,当然在使用的时候还需要映射的实际的物理内存上去,这个就由系统的内存管理功能实现了。具体的模型如下图所示:

process-memory.png

图1  进程内存空间

linux-virtual-memory-space.png

图2  Linux进程内存空间

memory-map.png

图3  虚拟内存和物理内存

1.2 Binder由来

这种隔离机制就带来一个问题,如果进程间要相互通信如何处理?为了处理进程间通信的问题,很多IPC机制应运而生,在传统的Linux上,我们还是有很多选择可以用来实现进程间通信,如管道、命名管道、Socket、内存映射、共享内存等机制。在Android中,出于性能和安全方面的原因,新提供了一种叫Binder的进程间通信机制。Binder实现进程间通信机制的核心是使用了内存映射,整个参与者包括Client(客户端)、Server(服务端)、ServiceManager(守护进程)、以及Binder驱动。来看一张常见的Binder机制原理图,简单的描述了Binder的机制:

binder-原理.png 另外还有一张图,详细的描述的Binder的原理,其中包含了更细致的元素和对应关系,其中还谈及到进程管理和线程管理等更多复杂的知识,我们后续在解析Binder源码的时候再来回顾这个图:

binder-深入原理.png

图4  Binder详细原理

2 通过AIDL初次使用Binder

由于Binder本身比较复杂,我们的Android很贴心的为开发者提供了更为简洁AIDL语法,根据AIDL文件生成模板代码,通过AIDL,可以在一个进程中获取另一个进程的数据和调用其暴露出来的方法,从而满足进程间通信的需求,完全让开发者感受不到Binder的存在,却又实实在在的使用了Binder来实现进程间通信。我们通过一个简单的DEMO来实现跨进程通信,开始之前我们先熟悉一下AIDL语法。

2.1 AIDL语法

AIDL的语法十分简单,与Java语言基本保持一致,需要记住的规则有以下几点:

  1. AIDL文件以 .aidl 为后缀名
  2. AIDL支持的数据类型分为如下几种:
    • 八种基本数据类型:byte、char、short、int、long、float、double、boolean、String,CharSequence
    • 实现了Parcelable接口的数据类型
    • List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
    • Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
  3. AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。还有一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值
  4. 定向Tag。定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通

2.2 创建AIDL文件

在Android Studio中直接创建AIDL文件即可,我们创建两个文件,在MyMessage.aidl中声明我们的数据类型,在EncryptManager中声明接口,另外还要新增一个MyMessage类实现parcelable接口。

add-aidl.png

图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 测试

binder-client-log.png

图6  客户端日志

binder-service-log.png

图7  服务端日志