目录
原理讲解
在Linux中一般来说我们写数据到文件是通过调用系统的函数将我们用户进程中的数据先拷贝给Linux内核然后由Linux内核再将数据写到文件中,中间经历了两个过程,如下图所示
而我们使用mmap文件映射的话就可以将数据直接写到文件中,如下图所示
这样的话中间就可以省略一个步骤,因此效率也会大大提升,这时我们再将这块映射的文件区域进行共享让其他进程可以访问,如下图所示,这样我们就实现了一个简单的跨进程通信了
代码实现
这里建立了两个项目,Mmap(发送信息的项目)和Mmapobserve(接收信息的项目),接下来我们先从Mmap(发送信息的项目)说起
●Mmap(发送信息的项目)
这里我创建了三个native方法
/**
* 开启共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 关闭共享映射
*/
public native void mmapClose();
/**
* 写入数据
* @param content
*/
public native void mmapWrite(String content);
#include <jni.h>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
extern "C"{
char * ptr = NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_mmap_MainActivity_mmapOpen(
JNIEnv* env,
jobject /* this */,
jstring path) {
const char *file_path = env->GetStringUTFChars(path,0);
int fd = open(file_path, O_RDWR|O_CREAT|O_TRUNC,0644); //打开本地磁盘中的文件(如果没有就创建一个), 获取fd,0644是可读写的意思
if(fd == -1) {
perror("open error");
}
//改变文件的大小(否则大小对应不起来就报错)
ftruncate(fd,100);
ptr = (char*)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error");
}
//关闭文件句柄
close(fd);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmap_MainActivity_mmapClose(JNIEnv *env, jobject thiz) {
if(ptr != NULL){
// 释放内存映射区
int ret = munmap(ptr, 100);
if(ret == -1) {
perror("munmap error");
}
}
}extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmap_MainActivity_mmapWrite(JNIEnv *env, jobject thiz, jstring content) {
if(ptr != NULL){
const char *c_content = env->GetStringUTFChars(content,0);
// 修改映射区数据
strcpy(ptr, c_content);
}
}
这里各个函数的大致流程如下图所示
Activity中调用的逻辑如下
public class MainActivity extends AppCompatActivity {
private Button btOpen;
private EditText etContent;
private Button btWrite;
private Button btClose;
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btOpen = (Button) findViewById(R.id.bt_open);
etContent = (EditText) findViewById(R.id.et_content);
btWrite = (Button) findViewById(R.id.bt_write);
btClose = (Button) findViewById(R.id.bt_close);
btOpen.setOnClickListener(v->{
mmapOpen(Environment.getExternalStorageDirectory().getAbsolutePath()+"/mmaptest.txt");
});
btClose.setOnClickListener(v->{
mmapClose();
});
btWrite.setOnClickListener(v->{
String content = etContent.getText().toString();
mmapWrite(content);
etContent.setText("");
});
}
/**
* 开启共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 关闭共享映射
*/
public native void mmapClose();
/**
* 写入数据
* @param content
*/
public native void mmapWrite(String content);
}
布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_write"
android:text="写入"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_open"
android:text="打开"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_close"
android:text="关闭"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
接下来我们运行APP写入一段文字
我们打开SD卡中对应的文件
发现信息已经写入
接下来我们再来看接收信息的项目
●Mmapobserve(接收信息的项目)
这里我创建了四个native方法
/**
* 开启共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 关闭共享映射
*/
public native void mmapClose();
/**
* 将映射区置空
*/
public native void mmapSetEmpty();
/**
* 监听映射区的内容
* @param defaultVal 传入的默认值
* @return
*/
public native String observe(String defaultVal);
#include <jni.h>
#include <string>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>
extern "C"{
char * ptr = NULL;
}
extern "C" JNIEXPORT void JNICALL
Java_com_itfitness_mmapobserve_MainActivity_mmapOpen(
JNIEnv* env,
jobject /* this */,
jstring path) {
const char *file_path = env->GetStringUTFChars(path,0);
int fd = open(file_path, O_RDWR|O_CREAT|O_TRUNC,0644); //打开本地磁盘中的文件(如果没有就创建一个), 获取fd,0644是可读写的意思
if(fd == -1) {
perror("open error");
}
//改变文件的大小(否则大小对应不起来就报错)
ftruncate(fd,100);
ptr = (char*)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error");
}
//关闭文件句柄
close(fd);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmapobserve_MainActivity_mmapClose(JNIEnv *env, jobject thiz) {
if(ptr != NULL){
// 释放内存映射区
int ret = munmap(ptr, 100);
if(ret == -1) {
perror("munmap error");
}
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmapobserve_MainActivity_mmapSetEmpty(JNIEnv *env, jobject thiz) {
if(ptr != NULL){
// 将共享映射区置空
memset(ptr, 0, 100);
}
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_itfitness_mmapobserve_MainActivity_observe(
JNIEnv* env,
jobject /* this */,
jstring defaultVal) {
if(ptr != NULL){
return env->NewStringUTF(ptr);
}
return defaultVal;
}
由于上面已经展示了mmapOpen和mmapClose函数的流程了,因此这里我就只展示下mmapSetEmpty和observe函数的流程
Activity中的逻辑如下
public class MainActivity extends AppCompatActivity {
private Button btObserve;
private Button btOpen;
private Button btClose;
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
private boolean isObserve = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btObserve = (Button) findViewById(R.id.bt_observe);
btOpen = (Button) findViewById(R.id.bt_open);
btClose = (Button) findViewById(R.id.bt_close);
btOpen.setOnClickListener(v->{
mmapOpen(Environment.getExternalStorageDirectory().getAbsolutePath()+"/mmaptest.txt");
});
btClose.setOnClickListener(v->{
isObserve = false;
mmapClose();
Toast.makeText(this, "关闭并停止监听", Toast.LENGTH_SHORT).show();
});
btObserve.setOnClickListener(v->{
if(isObserve)
return;
isObserve = true;
//开线程每隔500毫秒获取一下共享映射区的内容
new Thread(()->{
while (isObserve){
try {
String observe = observe("");
//当我们监听到共享区的内容不为空的时候就将内容以Toast的方式显示出来
if(!TextUtils.isEmpty(observe)){
runOnUiThread(()->{
Toast.makeText(this, observe, Toast.LENGTH_SHORT).show();
});
//获取完之后将共享区内容置空
mmapSetEmpty();
}
Thread.sleep(500);
}catch (Exception e){}
}
}).start();
Toast.makeText(this, "开始监听", Toast.LENGTH_SHORT).show();
});
}
/**
* 开启共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 关闭共享映射
*/
public native void mmapClose();
/**
* 将映射区置空
*/
public native void mmapSetEmpty();
/**
* 监听映射区的内容
* @param defaultVal 传入的默认值
* @return
*/
public native String observe(String defaultVal);
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/bt_observe"
android:text="监听"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_open"
android:text="打开"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/bt_close"
android:text="关闭"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
这里我们主要的逻辑就是通过开启子线程去循环读取共享映射区的内容,如果内容不为空我们就将读取的内容显示出来,然后我们将共享区的内容置空
效果展示
接下来我们将两个项目运行起来,看一看效果
案例源码
https://gitee.com/itfitness/mmap
https://gitee.com/itfitness/mmap-observe