Android NDK——实战演练之从零到零点八Android中借助mmap实现I/O(二)

引言

前一篇文章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");
    }
}

完。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值