文章大纲
引言
前一篇文章Android 进阶——从零到零点八真正详解存储映射mmap(一)总结了mmap相关基本原理用法,mmap在内核中的应用十分广泛,我们Android 中的Binder 本地代码也是使用了mmap的,这里只介绍通过JNI在Android中使用mmap实现I/O,希望不要造成mmap只能做I/O的错觉。
一、传统I/O概述
在Linux系统中把内存分为内核空间和用户空间,其中内核空间为所有进程共享,而用户空间则是进程私有的(比如说在32位系统时,总内存大小为4G,那么内核空间的大小为1G,3G为用户空间,理论上意味着进程可以使用的内存上限为3G,如果真的有一个进程已经占用了3G,那么就无法再启动其他进程了,因为内存不足了),而我们的程序是首先在用户空间上运行,直到系统调用才进到内核空间,进程之间不能直接通信,它们想要通信必须得经过内核空间,
所以传统(直接)I/O通过read和write实现的具体流程是:首先是从当前进程的用户空间把I/O流复制到内核空间中的高速缓存区,再由内核去把内核空间中的流复制到磁盘,无论是同步还是异步还是直接I/O方式都需要经过“2次”复制。
二、mmap概述
三、Android中通过JNI借助mmap实现I/O
1、利用c/c++ 调用内核函数实现mmap
mmap_util.h
//
// Created by Crazy.Mo on 2019/2/19.
//
#ifndef MMAP_MMAP_UTIL_H
#define MMAP_MMAP_UTIL_H
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <strings.h>
#include <android/log.h>
#include <string>
#define LOG(...) __android_log_print(ANDROID_LOG_ERROR,"CrazyMoMMAP",__VA_ARGS__);
//写在头文件里的变量如果不加上static 的话会造成重复引用 最好的办法就是不要写在头文件
static int8_t *_ptr=0;
static size_t _size;
static int32_t _fd;
void mmap_write(const char *src_ptr,const int32_t len, const char *path_ptr, const char *name_ptr);
void mmap_write(const std::string src_ptr, const std::string path, const std::string name);
void mmap_write(const void *src_ptr, const int32_t len, const char *path_ptr, const char *name_ptr);
#endif //MMAP_MMAP_UTIL_H
mmap_util.cpp
//
// Created by Crazy.Mo on 2019/2/19.
//
#include "mmap_util.h"
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string>
/**
* 保存字符串
* @param src_ptr
* @param len
* @param path_ptr 父目录
* @param name_ptr 文件名 文件的绝对路径=父目录+"/"+文件名;
*/
void mmap_write(const char *src_ptr,const int32_t len, const char *path_ptr, const char *name_ptr)
{
if(!src_ptr || !path_ptr || !name_ptr || len<=0){
LOG("非法参数");
return;
}
char name[256] = {0};
strcat(name, path_ptr);
strcat(name + strlen(path_ptr), "/");
strcat(name + strlen(path_ptr) + 1, name_ptr);
_fd=open(name,O_RDWR | O_CREAT, 0644);//打开或创建指定绝对文件名对应的文件并赋予当前用户可读可写权限
if(_fd==-1)
{
LOG("打开文件失败");
return;
}
#if 1
_size= (size_t) getpagesize();//要写入到磁盘文件的字节长度,不一定是页的大小,可以是任意值
ftruncate(_fd,len);
_ptr= (int8_t *) mmap(0, 4097, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if(_ptr==MAP_FAILED)
{
LOG("映射失败");
return;
}
memcpy(_ptr,src_ptr,len);
msync(_ptr,len,MS_SYNC);
munmap(_ptr,12);
#endif
LOG("写入数据成功");
// close(_fd);
}
void mmap_write(const std::string src, const std::string path, const std::string name)
{
if(src.empty() || path.empty() || name.empty())
{
return;
}
size_t len=src.length();
std::string file=path+name;
_fd=open(file.data(),O_RDWR | O_CREAT, 0644);//打开或创建指定绝对文件名对应的文件并赋予当前用户可读可写权限
if(_fd==-1)
{
LOG("打开文件失败");
return;
}
ftruncate(_fd,len);
_ptr= (int8_t *) mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if(_ptr==MAP_FAILED)
{
LOG("映射失败");
return;
}
memcpy(_ptr,src.data(),len);
msync(_ptr,len,MS_SYNC);
munmap(_ptr,len);
}
/**
* 保存二进制位图等信息
* @param src_ptr
* @param len
* @param path_ptr
* @param name_ptr
*/
void mmap_write(const void *src_ptr, const int32_t len, const char *path_ptr,const char *name_ptr)
{
if(!src_ptr || !path_ptr || !name_ptr || len<=0 ){
LOG("非法参数");
return;
}
char name[256] = {0};
strcat(name, path_ptr);
strcat(name + strlen(path_ptr), "/");
strcat(name + strlen(path_ptr) + 1, name_ptr);
_fd=open(name,O_RDWR | O_CREAT, 0644);//打开或创建指定绝对文件名对应的文件并赋予当前用户可读可写权限
if(_fd==-1)
{
LOG("打开位图文件失败");
return;
}
#if 1
_size= (size_t) getpagesize();//要写入到磁盘文件的字节长度,不一定是页的大小,可以是任意值
ftruncate(_fd,len);
_ptr= (int8_t *) mmap(0, len, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if(_ptr==MAP_FAILED)
{
LOG("映射位图失败");
return;
}
memcpy(_ptr,src_ptr,len);
msync(_ptr,len,MS_SYNC);
munmap(_ptr,len);
#endif
LOG("写入位图数据成功");
}
2、定义本地Java 接口类和方法
package com.crazymo.mmap;
import android.graphics.Bitmap;
import java.io.ByteArrayOutputStream;
/**
* Auther: Crazy.Mo on 2019/2/20 13:34
* Summary:
*/
public class MMAPHelper {
static {
System.loadLibrary("native-lib");
}
native public static void mmapWrite(String data,String path,String fileName);
native public static void mmapWriteByte(byte[] data,String path,String fileName);
/**
*Bitmap转换成byte[]
*/
public static byte[] bitmap2Bytes(Bitmap bitmap) {
ByteArrayOutputStream byteArrOutStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrOutStream);
return byteArrOutStream.toByteArray();
}
}
3、实现JNI层逻辑调用
#include <jni.h>
#include <string>
#include "mmap_util.h"
extern "C"
JNIEXPORT void JNICALL
Java_com_crazymo_mmap_MMAPHelper_mmapWrite(JNIEnv *env, jclass type, jstring data_, jstring path_,
jstring fileName_) {
const char *data = env->GetStringUTFChars(data_, 0);
const char *path = env->GetStringUTFChars(path_, 0);
const char *fileName = env->GetStringUTFChars(fileName_, 0);
mmap_write(data,env->GetStringUTFLength(data_),path,fileName);//这里有个陷进,如果使用GetStringLength的话可能会因为编码问题导致数据丢失
env->ReleaseStringUTFChars(data_, data);
env->ReleaseStringUTFChars(path_, path);
env->ReleaseStringUTFChars(fileName_, fileName);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_crazymo_mmap_MMAPHelper_mmapWriteByte(JNIEnv *env, jclass type, jbyteArray data_,
jstring path_, jstring fileName_) {
jbyte *data = env->GetByteArrayElements(data_, NULL);
const char *path = env->GetStringUTFChars(path_, 0);
const char *fileName = env->GetStringUTFChars(fileName_, 0);
mmap_write(data,env->GetArrayLength(data_),path,fileName);
env->ReleaseByteArrayElements(data_, data, 0);
env->ReleaseStringUTFChars(path_, path);
env->ReleaseStringUTFChars(fileName_, fileName);
}
4、编写配置CmakeList
cmake_minimum_required(VERSION 3.4.1)
add_library( # Sets the name of the library.
native-lib
# Sets the library as a shared library.
SHARED
# Provides a relative path to your source file(s).
src/main/cpp/mmap_util.cpp
src/main/cpp/native-lib.cpp )
find_library( # Sets the name of the path variable.
log-lib
# Specifies the name of the NDK library that
# you want CMake to locate.
log )
target_link_libraries( # Specifies the target library.
native-lib
# Links the target library to the log library
# included in the NDK.
${log-lib} )
5 、编写配置Gradle脚本
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.crazymo.mmap"
minSdkVersion 21
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-fexceptions"
}
}
ndk{
abiFilters "armeabi-v7a"
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
configurations.all {
resolutionStrategy {
force 'com.android.support:support-annotations:27.1.1'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
6、配置读写权限Activity 层调用JNI方法
package com.crazymo.mmap;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void save(View view) {
MMAPHelper.mmapWrite("11113311111111sss我的是是@!#", Environment.getExternalStorageDirectory().getPath() ,"crazymo4.txt");
}
public void saveBitmap(View view) {
Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.mipmap.ic_launcher_round);
MMAPHelper.mmapWriteByte(MMAPHelper.bitmap2Bytes(bitmap), Environment.getExternalStorageDirectory().getPath() ,"cmo.png");
}
}
完。