Android利用mmap共享映射實(shí)現(xiàn)進(jìn)程間通信

目錄

原理講解

在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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耍共,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子猎塞,更是在濱河造成了極大的恐慌试读,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邢享,死亡現(xiàn)場離奇詭異鹏往,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)骇塘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評論 2 384
  • 文/潘曉璐 我一進(jìn)店門伊履,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人款违,你說我怎么就攤上這事唐瀑。” “怎么了插爹?”我有些...
    開封第一講書人閱讀 156,723評論 0 345
  • 文/不壞的土叔 我叫張陵哄辣,是天一觀的道長请梢。 經(jīng)常有香客問我,道長力穗,這世上最難降的妖魔是什么毅弧? 我笑而不...
    開封第一講書人閱讀 56,357評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮当窗,結(jié)果婚禮上够坐,老公的妹妹穿的比我還像新娘。我一直安慰自己崖面,他們只是感情好元咙,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著巫员,像睡著了一般庶香。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上简识,一...
    開封第一講書人閱讀 49,760評論 1 289
  • 那天赶掖,我揣著相機(jī)與錄音,去河邊找鬼财异。 笑死倘零,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的戳寸。 我是一名探鬼主播,決...
    沈念sama閱讀 38,904評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼拷泽,長吁一口氣:“原來是場噩夢啊……” “哼疫鹊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起司致,我...
    開封第一講書人閱讀 37,672評論 0 266
  • 序言:老撾萬榮一對情侶失蹤拆吆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后脂矫,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體枣耀,經(jīng)...
    沈念sama閱讀 44,118評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評論 2 325
  • 正文 我和宋清朗相戀三年庭再,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捞奕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,599評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拄轻,死狀恐怖颅围,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情恨搓,我是刑警寧澤院促,帶...
    沈念sama閱讀 34,264評論 4 328
  • 正文 年R本政府宣布筏养,位于F島的核電站,受9級特大地震影響常拓,放射性物質(zhì)發(fā)生泄漏渐溶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評論 3 312
  • 文/蒙蒙 一弄抬、第九天 我趴在偏房一處隱蔽的房頂上張望掌猛。 院中可真熱鬧,春花似錦眉睹、人聲如沸荔茬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慕蔚。三九已至,卻和暖如春斋配,著一層夾襖步出監(jiān)牢的瞬間孔飒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評論 1 264
  • 我被黑心中介騙來泰國打工艰争, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坏瞄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評論 2 360
  • 正文 我出身青樓甩卓,卻偏偏與公主長得像鸠匀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子逾柿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評論 2 348

推薦閱讀更多精彩內(nèi)容