5.4 Java访问接口层详解

分析完匿名共享内存的C++访问接口层后,在本节开始分析其Java访问接口层的实现过程。在Android应用程序框架层中,通过使用接口MemoryFile来封装匿名共享内存文件的创建和使用。接口MemoryFile在文件“frameworks/base/core/java/android/os/MemoryFile.java”中定义,具体实现代码如下所示:

        public class MemoryFile
        {
            private static String TAG = "MemoryFile";
           //mmap(2) protection flags from <sys/mman.h>
            private static final int PROT_READ = 0x1;
            private static final int PROT_WRITE = 0x2;
            private static native FileDescriptor native_open(String name, int length) throws IOException;
           //returns memory address for ashmem region
            private static native int native_mmap(FileDescriptor fd, int length, int mode)
                  throws IOException;
            private static native void native_munmap(int addr, int length) throws IOException;
            private static native void native_close(FileDescriptor fd);
            private static native int native_read(FileDescriptor fd, int address, byte[] buffer,
                  int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
            private static native void native_write(FileDescriptor fd, int address, byte[] buffer,
                  int srcOffset, int destOffset, int count, boolean isUnpinned) throws IOException;
            private static native void native_pin(FileDescriptor fd, boolean pin) throws IOException;
            private static native int native_get_size(FileDescriptor fd) throws IOException;
            private FileDescriptor mFD;        //ashmem file descriptor
            private int mAddress;   //address of ashmem memory
            private int mLength;    //total length of our ashmem region
            private boolean mAllowPurging = false; //true if our ashmem region is unpinned
            /**
             * Allocates a new ashmem region. The region is initially not purgable.
             *
             * @param name optional name for the file (can be null).
             * @param length of the memory file in bytes.
             * @throws IOException if the memory file could not be created.
             */
            public MemoryFile(String name, int length) throws IOException {
                mLength = length;
                mFD = native_open(name, length);
                if (length > 0) {
                  mAddress = native_mmap(mFD, length, PROT_READ | PROT_WRITE);
                } else {
                  mAddress = 0;
                }
            }

在上述代码中,构造方法MemoryFile以指定的字符串调用了JNI方法native_open,目的是建立一个匿名共享内存文件,这样可以得到一个文件描述符。然后使用这个文件描述符为参数调用JNI方法natvie_mmap,并把匿名共享内存文件映射到进程空间中,这样就可以通过映射得到地址空间的方式直接访问内存数据。

再看JNI函数android_os_MemoryFile_get_size,此函数在文件“frameworks\base\core\jni\android_os_MemoryFile.cpp”中定义,具体实现代码如下所示:

        static jint android_os_MemoryFile_get_size(JNIEnv* env, jobject clazz,
              jobject fileDescriptor) {
            int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
           //Use ASHMEM_GET_SIZE to find out if the fd refers to an ashmem region.
           //ASHMEM_GET_SIZE should succeed for all ashmem regions, and the kernel
           //should return ENOTTY for all other valid file descriptors
            int result = ashmem_get_size_region(fd);
            if (result < 0) {
              if (errno == ENOTTY) {
                 //ENOTTY means that the ioctl does not apply to this object,
                 //i.e., it is not an ashmem region.
                  return (jint) -1;
              }
             //Some other error, throw exception
              jniThrowIOException(env, errno);
              return (jint) -1;
            }
            return (jint) result;
        }

再看JNI函数android_os_MemoryFile_open,此函数在文件“frameworks\base\core\jni\android_os_MemoryFile.cpp”中定义,具体实现代码如下所示:

        static jobject android_os_MemoryFile_open(JNIEnv* env, jobject clazz, jstring name, jint length)
        {
            const char* namestr = (name ? env->GetStringUTFChars(name, NULL) : NULL);
            int result = ashmem_create_region(namestr, length);
            if (name)
              env->ReleaseStringUTFChars(name, namestr);
            if (result < 0) {
              jniThrowException(env, "java/io/IOException", "ashmem_create_region failed");
              return NULL;
            }
            return jniCreateFileDescriptor(env, result);
        }

再看JNI函数android_os_MemoryFile_mmap,此函数在文件“frameworks\base\core\jni\android_os_MemoryFile.cpp”中定义,具体实现代码如下所示:

        static jint android_os_MemoryFile_mmap(JNIEnv* env, jobject clazz, jobject fileDescriptor,
              jint length, jint prot)
        {
            int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
            jint result = (jint)mmap(NULL, length, prot, MAP_SHARED, fd, 0);
            if (! result)
              jniThrowException(env, "java/io/IOException", "mmap failed");
            return result;
        }

在文件“frameworks/base/core/java/android/os/MemoryFile.java”中,再看类MemoryFile的成员函数readBytes,功能是读取某一块匿名共享内存的内容。具体实现代码如下所示:

            public int readBytes(byte[] buffer, int srcOffset, int destOffset, int count)
                  throws IOException {
                if (isDeactivated()) {
                  throw new IOException("Can't read from deactivated memory file.");
                }
                if (destOffset < 0 || destOffset > buffer.length || count < 0
                      || count > buffer.length - destOffset
                      || srcOffset < 0 || srcOffset > mLength
                      || count > mLength - srcOffset) {
                  throw new IndexOutOfBoundsException();
                }
                return native_read(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
            }

在文件“frameworks/base/core/java/android/os/MemoryFile.java”中,再看类MemoryFile的成员函数writeBytes,功能是写入某一块匿名共享内存的内容。具体实现代码如下所示:

        public void writeBytes(byte[] buffer, int srcOffset, int destOffset, int count)
                  throws IOException {
              if (isDeactivated()) {
                  throw new IOException("Can't write to deactivated memory file.");
              }
              if (srcOffset < 0 || srcOffset > buffer.length || count < 0
                    || count > buffer.length - srcOffset
                    || destOffset < 0 || destOffset > mLength
                    || count > mLength - destOffset) {
                  throw new IndexOutOfBoundsException();
              }
              native_write(mFD, mAddress, buffer, srcOffset, destOffset, count, mAllowPurging);
        }

在文件“frameworks/base/core/java/android/os/MemoryFile.java”中,再看类MemoryFile的成员函数isDeactivated,功能是保证匿名共享内存已经被映射到进程的地址空间中。具体实现代码如下所示:

        void deactivate() {
              if (! isDeactivated()) {
                  try {
                    native_munmap(mAddress, mLength);
                    mAddress = 0;
                  } catch (IOException ex) {
                    Log.e(TAG, ex.toString());
                  }
        }
        }
        private boolean isDeactivated() {
              return mAddress == 0;
        }

JNI函数native_read和native_write分别由位于C++层的函数android_os_MemoryFile_read和android_os_MemoryFile_write实现,这两个C++的函数在文件frameworks\base\core\jni\android_os_MemoryFile.cpp中定义,具体实现代码如下所示:

        static jint android_os_MemoryFile_read(JNIEnv* env, jobject clazz,
              jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
              jint count, jboolean unpinned)
        {
            int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
            if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
              ashmem_unpin_region(fd, 0, 0);
              jniThrowException(env, "java/io/IOException", "ashmem region was purged");
              return -1;
            }
            env->SetByteArrayRegion(buffer, destOffset, count, (const jbyte *)address + srcOffset);
            if (unpinned) {
              ashmem_unpin_region(fd, 0, 0);
            }
            return count;
        }
        static jint android_os_MemoryFile_write(JNIEnv* env, jobject clazz,
              jobject fileDescriptor, jint address, jbyteArray buffer, jint srcOffset, jint destOffset,
              jint count, jboolean unpinned)
        {
            int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
            if (unpinned && ashmem_pin_region(fd, 0, 0) == ASHMEM_WAS_PURGED) {
              ashmem_unpin_region(fd, 0, 0);
              jniThrowException(env, "java/io/IOException", "ashmem region was purged");
              return -1;
            }
            env->GetByteArrayRegion(buffer, srcOffset, count, (jbyte *)address + destOffset);
            if (unpinned) {
              ashmem_unpin_region(fd, 0, 0);
            }
            return count;
        }