Binder机制总结(上篇)--java层与框架层分析

彻底搞懂Binder,看这一篇就够了


前言

作为安卓进程间通信最主要的方式,Binder机制可以说是每个安卓开发者必须要掌握的知识,不过由于它涉及到linux以及c/c++,所以理解起来有点难。但本文会带领大家从0到1一步一步去深挖Binder机制,务求让大家对Binder不再疑惑。


提示:以下是本篇文章正文内容

一、Linux已有的进程通信方式

要讲Binder机制之前,先要了解Linux已有的进程通信方式,因为既然Linux已经有好几种进程通信方式,为什么Google还要搞一套Binder机制。接下来看看Linux的几种进程通信方式:
在这里插入图片描述

1)管道通信:以字符流形式将数据送入管道里,接收方与发送方通过这个管道去通信。它属于一对一的通信。优点是稳定安全,没有任何限制,任何类之间都能通信,不过缺点就是发生两次数据拷贝,所以性能消耗上不是很好,而且是一对一,不能一对多通信。
在这里插入图片描述

2)Socket:是网络通信方式,c/s模式,传输效率相对来说也较低,每一次建立与关闭连接都需要很大的开销,很损耗性能,但由于是c/s模式,所以它支持一对多通信。

3)共享内存:进程与进程之间通过一个共享的内存区域去进行通信,不用拷贝数据,效率最高,但安全性差,也比较容易引发并发问题

在这里插入图片描述
4)文件,IO操作,性能很慢。

综上所述,作为安卓端,我们应该需要一对多的通信的,因为系统进程只有一个,而它要跟这么多个app应用进行通信,肯定是需要支持一对多通信,首先管道通信就不符合了,然后Socket虽然符合一对多,但是由于它的性能与效率都不是很好,因此也不适合,而文件通信的性能更慢,就更不用考虑,而共享内存的方式虽然高效,但安全性不能保障,因为不能让app应用进程随时就能访问到系统进程。因此为了既要保持较高传输效率,又要支持一对多,以及考虑安全性问题Google就创建了一种新的进程间通信方式,就是Binder机制。共享内存的方式不发生数据拷贝,而Socket以及管道的数据拷贝次数要2次,而Binder的内存拷贝次数是只有1次,因此性能上来说,它是有优势的。

二、Binder机制

我们使用的Aidl就是通过Binder去实现进程间通信的,所以这也是Binder的特性之一,就是按照它的规定的模板去实现:
在这里插入图片描述
要使用Binder,就要创建一个Service类,然后设置和重写一些方法,比如上图中,如果进程1要使用进程2的方法或者参数,就必须事先定义一个aidl文件去配置要访问的类、方法、数据以及返回的参数是什么,这些都在aidl文件里配置。

1.实现

现在先写一个基本的用aidl实现的进程间通信例子,首先建立进程client:
在这里插入图片描述

然后在app进程里创建Service类:
在这里插入图片描述
要实现app进程与client进程间通信,在onBind()方法里就要返回aidl实例了,所以接下来要在app进程里创建aidl文件:

// IAppAidlInterface.aidl
package com.pingred.mytest41;

// Declare any non-default types here with import statements

interface IAppAidlInterface {
    String requestApp(String params);
}

这个IAppAidlInterface.aidl文件可以理解为是一个实现binder机制要遵循而写的配置文件,在里面定义好要调用的方法与要访问的数据,然后点击build编译的时候,虚拟机会使用aidl.exe工具去加载这个文件,然后替你去生成一些代码,构建与实现Binder机制:
在这里插入图片描述
此时IAppAidlInterface看起来是个接口,但它只是一个配置文件,需要build,然后aidl.exe去扫描这个文件,然后编译成真正的类以及相关代码:

package com.pingred.mytest41;
// Declare any non-default types here with import statements

public interface IAppAidlInterface extends android.os.IInterface
{
  /** Default implementation for IAppAidlInterface. */
  public static class Default implements com.pingred.mytest41.IAppAidlInterface
  {
    @Override public java.lang.String requestApp(java.lang.String params) 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.pingred.mytest41.IAppAidlInterface
  {
    private static final java.lang.String DESCRIPTOR = "com.pingred.mytest41.IAppAidlInterface";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    ...
    

然后我们在app进程里的MainService里的onBind()方法里返回这个aidl接口:

public class MainService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return new IAppAidlInterface.Stub() {
            @Override
            public String requestApp(String params) throws RemoteException {
                return "成功调用";
            }
        };
    }
}

最后,在AndroidManifest.xml文件里声明一下这个MainService:

...
<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/Theme.MyTest41"
        android:hardwareAccelerated="true"
        android:usesCleartextTraffic="true">
        <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=".MainService" android:exported="true"/>
    </application>
    ...

接下来,就可以让我们的client进程去调用app进程的这个requestApp方法,首先在client进程里MainActivity里启动app进程的MainService:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = findViewById(R.id.click_btn);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                requestApp();
            }
        });
    }

    private void requestApp() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.pingred.mytest41", "com.pingred.mytest41.MainService"));
        bindService(intent, new MainServiceConnection(), Context.BIND_AUTO_CREATE);
    }

    class MainServiceConnection implements ServiceConnection{

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    }
}

当启动成功也就是连接成功后,onServiceConnected()方法触发,我们就可以通过参数IBinder对象去构造出我们的app进程对象了,不过先要在client进程里创建一个包名跟app进程里的aidl对象一样的aidl对象,用来接收并构造出app进程的引用:

在这里插入图片描述

同样定义了requestApp()方法,然后build一下,编译一个真正的接口类对象,接下来就可以继续在client进程里的MainActivity里拿到app进程服务对象:

class MainServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            IAppAidlInterface service = IAppAidlInterface.Stub.asInterface(iBinder);
            try {
                service.requestApp("调用App方法");
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    }

代码不是很难,当client进程启动app进程的MainService成功后,触发onServiceConnected()方法,通过该方法里的参数IBinder对象传进IClientAidlInterface.Stub.asInterface()方法里,就可以构造出client的aidl对象(相当于app进程服务端的一个虚引用),然后通过它来接收app进程的映射对象,然后client进程可以通过调用IClientAidlInterface对象的方法requestApp()方法,然后就可以触发到app进程里MainService的onBind方法,从而实现了进程间通信。

三、应用层看Binder

看完实现之后,可以从应用层的角度去看这个Aidl接口类的源码:
在这里插入图片描述
看看它的结构里,有两个对象值得我们关注,就是Stub和Proxy。现在进程client要和进程app通信,app进程使用Proxy对象把数据发送到client进程,然后client进程的Stub对象接收这些数据,然后同样client进程也使用Proxy去发送数据到app进程的Stub对象接收。

Proxy是Stub的内部类,它们的关系:
在这里插入图片描述
Stub继承Binder,Binder继承IBinder,Proxy继承我们定义的IAppAidlInterface。client客户端进程在拿到系统返回给的IBnder对象后,会去调用asInterface方法:

/**
     * Cast an IBinder object into an com.pingred.mytest41.IAppAidlInterface interface,
     * generating a proxy if needed.
     */
    public static com.pingred.mytest41.IAppAidlInterface asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.pingred.mytest41.IAppAidlInterface))) {
        return ((com.pingred.mytest41.IAppAidlInterface)iin);
      }
      return new com.pingred.mytest41.IAppAidlInterface.Stub.Proxy(obj);
    }

可以看到这里调用了IBinder的queryLocalInterface()方法去构造IInterface对象,但由于上面的关系图可以知道我们的IAppAidlInterface就是继承IInterface对象,因此这里是有父类引用指向子类对象来构造我们的IAppAidlInterface对象,所以queryLocalInterface()方法其实是当我们使用binder仅仅是同一个进程内通信的话,则通过queryLocalInterface()方法去查找:
在这里插入图片描述
很明显就是通过DESCRIPTOR全类名去查找这个服务类IAppAidlInterface对象,再结合IAppAidlInterface类里的源码里有这样一个方法:

public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }

没错,就是Stub的构造方法,它里面调用了attachInterface方法,全类名就是这时候绑定的,因此可以理解为当是同一个进程内进行通信时,直接可以通过全类名的查找方法queryLocalInterface()去获取到服务类对象。

如果不是同一个进程,那就不能从本地这里获取到这个服务,因为DESCRIPTOR的值肯定是不一样的,因此就继续走源码,也就是会new一个Proxy对象。所以此时我们看回这段代码:

class MainServiceConnection implements ServiceConnection {

        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            IAppAidlInterface service = IAppAidlInterface.Stub.asInterface(iBinder);
            try {
                Toast.makeText(MainActivity.this, "" + service.requestApp("调用App方法"), Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            Log.d("TAG", "onServiceDisconnected: ");
        }
    }

此时client客户端的IAppAidlInterface 对象实际上就是Proxy对象,那么就来看看它的requestApp()方法源码:

@Override public java.lang.String requestApp(java.lang.String params) 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);
          _data.writeString(params);
          boolean _status = mRemote.transact(Stub.TRANSACTION_requestApp, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().requestApp(params);
          }
          _reply.readException();
          _result = _reply.readString();
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }

这里构造了Parcel序列化类型的_data和_reply,然后调用writeInterfaceToken()方法把全类名DESCRIPTOR传进去,这里就相当于声明了要进行通信的类是哪个(此时是app服务端进程),然后就是调用writeString()把要传过去的数据写过去。

此时此刻还没真的进行进程间通信,直到调用了mRemote.transact()方法,把Stub.TRANSACTION_requestApp、_data, _reply等传进去,而Stub.TRANSACTION_requestApp的值此时为1,表示了该通信过程中client进程要调用的是app进程里的requestApp方法:
在这里插入图片描述
用int值去区分IAppAidlInterface里每个方法,每个方法的int值都依次加1,也就是下一个方法比上一个方法值加1,依次类推,当然服务端和客户端也要保持一致。

而这里的mRemote是BinderProxy对象,它是这样来的:
在这里插入图片描述
一开始传到asInterface方法里去,然后又传到Proxy的构造方法里:
在这里插入图片描述
最后就把它赋值给mRemote:
在这里插入图片描述
现在再来看这幅关系图,应该就能明白各个类的关系了:
在这里插入图片描述
所以最终还是将采集到数据以及要调用的方法都传到了Binder底层里,来看看BinderProxy的transact方法:

public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");

        if (mWarnOnBlocking && ((flags & FLAG_ONEWAY) == 0)) {
            // For now, avoid spamming the log by disabling after we've logged
            // about this interface at least once
            mWarnOnBlocking = false;
            Log.w(Binder.TAG, "Outgoing transactions from this process must be FLAG_ONEWAY",
                    new Throwable());
        }

        final boolean tracingEnabled = Binder.isTracingEnabled();
        if (tracingEnabled) {
            final Throwable tr = new Throwable();
            Binder.getTransactionTracker().addTrace(tr);
            StackTraceElement stackTraceElement = tr.getStackTrace()[1];
            Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
                    stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
        }
        try {
            return transactNative(code, data, reply, flags);
        } finally {
            if (tracingEnabled) {
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
        }
    }

它最后是调用了transactNative()方法:

public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;

transactNative()方法是native本地方法,它底层就是调用Binder去发送数据,根据刚刚传的参数(要调用的类名、数据、以及方法等)去发送给另一个进程。至于底层里做了什么,这就涉及到native层和Binder驱动层,等下会详细讲,现在还是先从应用层这个角度去分析整个过程:
在这里插入图片描述

客户端调用请求方法,把进程类名,请求方法,数据都写入到里面去,然后mRemote就会去transact,开始调用Binder底层驱动方法,进行进程间通信(binder底层会通知调用服务端的Stab里的onTransact方法):

@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_requestApp:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          java.lang.String _result = this.requestApp(_arg0);
          reply.writeNoException();
          reply.writeString(_result);
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }

根据code去判断要调用的类和方法,然后把data也就是客户端传过来的数据解析出来,然后传到requestApp()方法,requestApp()方法的具体实现也就是我此时app进程里MainService的requestApp()方法:

public class MainService extends Service {
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {

        return new IAppAidlInterface.Stub() {
            @Override
            public String requestApp(String params) throws RemoteException {
                return "成功调用";
            }
        };
    }
}

这里requestApp返回的数据就再次写到reply里去:

 java.lang.String _result = this.requestApp(_arg0);
 reply.writeNoException();
 reply.writeString(_result);

这个reply是当初客户端传的,相当于一个容器,所以也就是说服务端把requestApp返回的数据又写到了客户端reply中去,完成了跟客户端的通信。当通信成功后,客户端返回的_status成功,因此客户端会从_reply读取数据,提取响应数据:
在这里插入图片描述
整个应用层过程就是这样,说到这里应该要知道实际上这个IAppAidlInterface服务类对象我们自己也可以去写,不一定非要用aidl文件去让系统自动生成,我们只要按照Binder的规定要配置哪些数据以及实现方法来写也可以实现进程间通信。aidl文件只是谷歌给我们提供的一种简便的配置方式用来实现Binder机制。

注:aidl支持传

四、native层看Binder

Binder其实是一个驱动文件来的:
在这里插入图片描述
它的源码是在binder.c文件里,当初我在 应用的开端–PackageManagerService(PMS) 讲解PMS的时候说过,当安卓手机开机时,init进程首先启动,调用init.rc脚本文件,然后进而启动ServiceManager进程(ServiceManager进程是个管理核心服务进程),然后执行service_manager.c脚本的main方法:

int main(int argc, char** argv)
{
    struct binder_state *bs;
    union selinux_callback cb;
    char *driver;

    if (argc > 1) {
        driver = argv[1];
    } else {
        driver = "/dev/binder";
    }

    bs = binder_open(driver, 128*1024);   
...

}

可以看到它调用了binder_open()方法,传的参数是/dev/binder驱动文件路径,来看看binder_open()方法是什么逻辑,它在binder.c文件里:

struct binder_state *binder_open(const char* driver, size_t mapsize)
{
    struct binder_state *bs;
    struct binder_version vers;

    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return NULL;
    }

    bs->fd = open(driver, O_RDWR | O_CLOEXEC);
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open %s (%s)\n",
                driver, strerror(errno));
        goto fail_open;
    }

    if ((ioctl(bs->fd, BINDER_VERSION, &vers) == -1) ||
        (vers.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION)) {
        fprintf(stderr,
                "binder: kernel driver version (%d) differs from user space version (%d)\n",
                vers.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION);
        goto fail_open;
    }

    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }

    return bs;

fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return NULL;
}

代码有点长,但我们关注一些关键的地方便可,首先看到open()方法,它打开binder驱动文件路径,然后返回文件的指针回来,也就是bs->fd,之后要操作binder驱动文件就可以用该指针操作了。

接下来我们再看它调用了一个非常重要的方法,就是mmap()方法,它能将文件(硬盘)映射到内存(物理内存)。什么意思呢,在讲mmap()方法前,先要知道进程间的通信的本质其实是将进程1的数据拷贝到进程2里:
在这里插入图片描述
而进程的本质就是内存里占用的某块空间。平时我们使用OutPutStream写文件的时候,它的write方法源码是:

public void write(byte b[], int off, int len) throws IOException {
        // Android-added: close() check before I/O.
        if (closed && len > 0) {
            throw new IOException("Stream Closed");
        }

        // Android-added: Tracking of unbuffered I/O.
        tracker.trackIo(len);

        // Android-changed: Use IoBridge instead of calling native method.
        IoBridge.write(fd, b, off, len);
    }

最终调用的是一个IoBridge去写文件的:
在这里插入图片描述
所以其实我们应用层是不能直接去读和写文件的,中间会有linux替我们去干,这样才能保证文件系统的安全,而mmap函数打破了这一规则,让文件直接映射到内存上,这样应用层直接操作这个文件时,也就等于直接在内存中操作这个文件:
在这里插入图片描述
以上就是两个进程和一个文件的同一个区域的共享映射,这个过程就不会发生内存拷贝了,因为两个进程直接就在这块映射区域读写数据。

Binder机制虽然是调用了mmap函数,但它是只有服务端才会产生映射,客户端仍然需要copy数据到内核空间(Binder驱动)里,此时内核空间和服务空间同时映射了一块共享内存区域,而这块区域使用了mmap函数,因为这块区域跟物理内存产生了映射,所以内核空间和服务空间各自在这个区域做操作,双方都会感知到,这样服务端也就不用产生拷贝操作了。
在这里插入图片描述
当app进程里创建Service,然后设置到清单文件中,表示app进程就是作为服务端,然后ServiceManager就会去打开Binder驱动文件,初始化后调用mmap函数把服务端(app进程)和binder内核进行映射,binder_proc记录了该服务端跟binder驱动内核的映射关系(可以理解为key-value这样一种形式)。当其他进程也要作为服务端加入进来之后,就又跟Binder驱动内核发生新的映射关系对应,如此类推,所以ServiceManager会在循环里不断去解析每对服务进程跟binder内核发生的映射,最后去取相应的这组binder里数据给对应的客户端进程:
在这里插入图片描述

app进程就被加进到ServiceManager里的队列里,然后跟其他服务端进程在一起,等待着被ServiceManager去匹配之后跟对应的客户端进程进行通信。

原先两个进程间通信,肯定是会产生内存拷贝的,现如今使用了mmap映射之后,两者操作那块物理内存映射出来的共享空间就能免去拷贝的操作了。客户端进程没有使用mmap映射的操作,还是要拷贝存取。

既然说到这里,相信大家已经对mmap函数有了一定的了解,其实我们应用层也可以调用它,使用jni开发,在cpp文件里参照源码那样,调用open函数打开一个文件:

#include <jni.h>
#include <string>
#import <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_pingred_mytest_Binder_binder_open(JNIEnv *env, jobject thiz) {
	//之前源码这里open的是/dev/binder
    std::string file = "/storage/emulated/0/test.txt";
	//返回文件的引用
    int file_fd=open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);

}

因为要给文件映射到物理内存上,那肯定是需要定义映射文件所需的内存大小,又因为在Linux里读写硬盘是一页一页读写的,而一页的内存大小为4k,所以这里映射文件的内存大小是4k的倍数,所以我们先调用getpagesize()函数来得到一页大小,然后调用ftruncate函数设置文件大小为一页大小4k:

#include <jni.h>
#include <string>
#import <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_pingred_mytest_Binder_binder_open(JNIEnv *env, jobject thiz) {
	//之前源码这里open的是/dev/binder
    std::string file = "/storage/emulated/0/test.txt";
	//返回文件的引用
    int file_fd=open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
	//一页为4k
    int32_t file_size = getpagesize();
	//文件设置为4K
    ftruncate(file_fd, file_size);
}

然后就调用mmap函数:

#include <jni.h>
#include <string>
#import <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_pingred_mytest_Binder_binder_open(JNIEnv *env, jobject thiz) {
	//之前源码这里open的是/dev/binder
    std::string file = "/storage/emulated/0/test.txt";
	//返回文件的引用
    int file_fd=open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
	//一页为4k
    int32_t file_size = getpagesize();
	//文件设置为4K
    ftruncate(file_fd, file_size);
    
    int8_t* file_ptr = static_cast<int8_t *>(mmap(0, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd,0));
}

这里说一下mmap函数里要传的六个参数:

	void *addr; //映射区首地址,传NULL

    size_t length; //映射区的大小
    //会自动调为4k的整数倍
    //不能为0
    //一般文件多大,length就指定多大

    int prot; //映射区权限
    //PROT_READ 映射区比必须要有读权限
    //PROT_WRITE 映射区比必须要有写权限
    //PROT_READ | PROT_WRITE 要有读写权限

    int flags; //标志位参数
    //MAP_SHARED 修改了内存数据会同步到磁盘
    //MAP_PRIVATE 修改了内存数据不会同步到磁盘

    int fd; //要映射的文件对应的引用fd

    off_t offset; //映射文件的偏移量,从文件的哪里开始操作
    //映射的时候文件指针的偏移量
    //必须是4k的整数倍
    //一般设置为0

每个参数的含义以及要设置什么值都已经在注释中说得很清楚了。

mmap函数最后会返回一个空指针类型的数据,这个值其实就是这块内存区域的内存地址,知道内存地址就可以用指针操作这块区域了。因此现在每个进程都可以通过这个区域(物理内存)去读写数据,并且都会反映到磁盘里。所以接下来我们直接对这块内存进行操作:

#include <jni.h>
#include <string>
#import <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
extern "C"
JNIEXPORT void JNICALL
Java_com_pingred_mytest_Binder_binder_open(JNIEnv *env, jobject thiz) {
	//之前源码这里open的是/dev/binder
    std::string file = "/storage/emulated/0/test.txt";
	//返回文件的引用
    int file_fd=open(file.c_str(), O_RDWR | O_CREAT, S_IRWXU);
	//一页为4k
    int32_t file_size = getpagesize();
	//文件设置为4K
    ftruncate(file_fd, file_size);
    
    //返回的file_ptr指针就是test.txt文件的内存地址
    int8_t* file_ptr = static_cast<int8_t *>(mmap(0, file_size, PROT_READ | PROT_WRITE, MAP_SHARED, file_fd,0));

	std::string data("写入数据");
	//将data内存区域的数据写到test.txt文件的内存区域里
    memcpy(file_ptr, data.data(), data.size());
    //关闭文件
    close(file_fd);
}

如果想获取完整的代码可以关注我公号Pingred

有过c/c++开发的同学应该都知道memcpy函数是什么作用,它就是直接把一块内存区域的数据按照设置的大小去拷贝到另一个内存区域里,这里就是把data这个区域的全部数据复制到刚刚mmap映射出txt文件里,而且因为mmap的特性,现在这个tex文件的区域是等于操作的是它在物理内存里的真实区域。所以如果我在其他进程里调用cpp文件(它属于app进程)的这个jni方法就等于可以直接操作到txt文件了,实现了跨进程通信的操作了。

public class MainActivity extends AppCompatActivity {

    Binder binder = new Binder();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		//申请权限等操作
		write();
    }

    public void write(View view) {
        binder.binde_open();
    }
}

现在在app进程直接调用也可以实现写入数据到test.txt文件里,这样就在不用FileOutputStream文件流的情况下,直接使用mmap函数来达到应用层直接读写硬盘里的文件了。

看到这里相信大家对Binder已经有了很大的理解,本篇作为上篇先从java应用层和native框架层去分析Binder,但Binder最核心的部分也就是驱动层我们还没讲,这部分将会在下篇继续为大家讲解。

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值