目錄
原理講解
在Linux中一般來說我們寫數(shù)據(jù)到文件是通過調(diào)用系統(tǒng)的函數(shù)將我們用戶進(jìn)程中的數(shù)據(jù)先拷貝給Linux內(nèi)核然后由Linux內(nèi)核再將數(shù)據(jù)寫到文件中霎褐,中間經(jīng)歷了兩個(gè)過程碉纺,如下圖所示
而我們使用mmap文件映射的話就可以將數(shù)據(jù)直接寫到文件中,如下圖所示
這樣的話中間就可以省略一個(gè)步驟卫旱,因此效率也會大大提升,這時(shí)我們再將這塊映射的文件區(qū)域進(jìn)行共享讓其他進(jìn)程可以訪問芥丧,如下圖所示磷脯,這樣我們就實(shí)現(xiàn)了一個(gè)簡單的跨進(jìn)程通信了
代碼實(shí)現(xiàn)
這里建立了兩個(gè)項(xiàng)目,Mmap(發(fā)送信息的項(xiàng)目)和Mmapobserve(接收信息的項(xiàng)目)霞幅,接下來我們先從Mmap(發(fā)送信息的項(xiàng)目)說起
●Mmap(發(fā)送信息的項(xiàng)目)
這里我創(chuàng)建了三個(gè)native方法
/**
* 開啟共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 關(guān)閉共享映射
*/
public native void mmapClose();
/**
* 寫入數(shù)據(jù)
* @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); //打開本地磁盤中的文件(如果沒有就創(chuàng)建一個(gè)), 獲取fd,0644是可讀寫的意思
if(fd == -1) {
perror("open error");
}
//改變文件的大心恰(否則大小對應(yīng)不起來就報(bào)錯(cuò))
ftruncate(fd,100);
ptr = (char*)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error");
}
//關(guān)閉文件句柄
close(fd);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmap_MainActivity_mmapClose(JNIEnv *env, jobject thiz) {
if(ptr != NULL){
// 釋放內(nèi)存映射區(qū)
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);
// 修改映射區(qū)數(shù)據(jù)
strcpy(ptr, c_content);
}
}
這里各個(gè)函數(shù)的大致流程如下圖所示
Activity中調(diào)用的邏輯如下
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);
/**
* 關(guān)閉共享映射
*/
public native void mmapClose();
/**
* 寫入數(shù)據(jù)
* @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="關(guān)閉"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
接下來我們運(yùn)行APP寫入一段文字
我們打開SD卡中對應(yīng)的文件
發(fā)現(xiàn)信息已經(jīng)寫入
接下來我們再來看接收信息的項(xiàng)目
●Mmapobserve(接收信息的項(xiàng)目)
這里我創(chuàng)建了四個(gè)native方法
/**
* 開啟共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 關(guān)閉共享映射
*/
public native void mmapClose();
/**
* 將映射區(qū)置空
*/
public native void mmapSetEmpty();
/**
* 監(jiān)聽映射區(qū)的內(nèi)容
* @param defaultVal 傳入的默認(rèn)值
* @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); //打開本地磁盤中的文件(如果沒有就創(chuàng)建一個(gè)), 獲取fd,0644是可讀寫的意思
if(fd == -1) {
perror("open error");
}
//改變文件的大小(否則大小對應(yīng)不起來就報(bào)錯(cuò))
ftruncate(fd,100);
ptr = (char*)mmap(NULL, 100, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error");
}
//關(guān)閉文件句柄
close(fd);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_itfitness_mmapobserve_MainActivity_mmapClose(JNIEnv *env, jobject thiz) {
if(ptr != NULL){
// 釋放內(nèi)存映射區(qū)
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){
// 將共享映射區(qū)置空
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;
}
由于上面已經(jīng)展示了mmapOpen和mmapClose函數(shù)的流程了司恳,因此這里我就只展示下mmapSetEmpty和observe函數(shù)的流程
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, "關(guān)閉并停止監(jiān)聽", Toast.LENGTH_SHORT).show();
});
btObserve.setOnClickListener(v->{
if(isObserve)
return;
isObserve = true;
//開線程每隔500毫秒獲取一下共享映射區(qū)的內(nèi)容
new Thread(()->{
while (isObserve){
try {
String observe = observe("");
//當(dāng)我們監(jiān)聽到共享區(qū)的內(nèi)容不為空的時(shí)候就將內(nèi)容以Toast的方式顯示出來
if(!TextUtils.isEmpty(observe)){
runOnUiThread(()->{
Toast.makeText(this, observe, Toast.LENGTH_SHORT).show();
});
//獲取完之后將共享區(qū)內(nèi)容置空
mmapSetEmpty();
}
Thread.sleep(500);
}catch (Exception e){}
}
}).start();
Toast.makeText(this, "開始監(jiān)聽", Toast.LENGTH_SHORT).show();
});
}
/**
* 開啟共享映射
* @param absolutePath
*/
public native void mmapOpen(String absolutePath);
/**
* 關(guān)閉共享映射
*/
public native void mmapClose();
/**
* 將映射區(qū)置空
*/
public native void mmapSetEmpty();
/**
* 監(jiān)聽映射區(qū)的內(nèi)容
* @param defaultVal 傳入的默認(rèn)值
* @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="監(jiān)聽"
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="關(guān)閉"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
這里我們主要的邏輯就是通過開啟子線程去循環(huán)讀取共享映射區(qū)的內(nèi)容途乃,如果內(nèi)容不為空我們就將讀取的內(nèi)容顯示出來,然后我們將共享區(qū)的內(nèi)容置空
效果展示
接下來我們將兩個(gè)項(xiàng)目運(yùn)行起來扔傅,看一看效果
案例源碼
https://gitee.com/itfitness/mmap
https://gitee.com/itfitness/mmap-observe