Android調用C++實現共享內存(Native層)

Android下匿名共享內存java層接口利用MemoryFile實現進程間內存共享;利用MemoryFile可以參考這篇文章:https://blog.csdn.net/qq_24451593/article/details/80514566

MemoryFile是java層封裝的接口,它實現共享內存主要調用瞭如下函數:

int fd = open("/dev/ashmem",O_RDWR);
ioctl(fd, ASHMEM_SET_NAME,name);
ioctl(fd,ASHMEM_SET_SIZE, size);
//addr爲共享內存地址
addr = (char*)mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

創建出來的fd轉換成ParcelFileDescriptor通過AIDL傳遞給另一個進程;另一個進程將ParcelFileDescriptor轉換成該進程對應fd,調用mmap來使用該共享內存。
注:進程A的fd與進程B的fd是不一樣的,但是都對應同一文件
在這裏插入圖片描述

既然知道了MemoryFile實際調用函數,那麼我們就可以在native層用C++代碼實現共享內存。

(1)C++代碼實現

android_shm.cpp

#include "android_shm.h"

#define ASHMEM_DEVICE  "/dev/ashmem"

//ret= 0 創建成功;ret=-1,失敗;
//注:ret =1,共享內存已經存在,但是目前這個沒用,暫時放這
int create_shared_memory(const char* name, U64 size, int node, char*& addr, U64& shm_id){
    U64 fd = open(ASHMEM_DEVICE, O_RDWR);
    if(fd < 0){
        return -1;
    }
    U64 len = ioctl(fd, ASHMEM_GET_SIZE, NULL);
    if(len > 0){             
        addr = (char*)mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
   shm_id = fd;
   return 1;
    }else{
        int ret = ioctl(fd, ASHMEM_SET_NAME,name);
        if(ret < 0){
       close(fd);
            return -1;
        }
        ret = ioctl(fd,ASHMEM_SET_SIZE, size);
        if(ret < 0){
       close(fd);
            return -1;
        }
        addr = (char*)mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        shm_id = fd;
    }
    return 0;
}

int open_shared_memory(const char* name, int node, char*& addr, U64& shm_id){
    U64 size = ioctl(shm_id, ASHMEM_GET_SIZE,NULL);
    if(size > 0){ 
        addr = (char*)mmap(NULL, size , PROT_READ | PROT_WRITE, MAP_SHARED, shm_id, 0);
    }else{
        return -1;     
    }
    return 0;
}

int close_shared_memory(U64& shm_id, char*& addr){
    U64 size = ioctl(shm_id, ASHMEM_GET_SIZE, NULL);
    if(size <0){
        return -1;
    }  
    int ret = munmap((void*)addr, size);
    if(ret == -1){
        return -1;
    }
    ret = close(shm_id);
    if(ret == -1){
        return -1;
    }
    return 0;
}

android_shm.h

#ifndef _SYS_SHM_H
#define _SYS_SHM_H 1
#include <fcntl.h>
#define SHM_HUGETLB    04000

#include <stdio.h>
#include <linux/ashmem.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <stddef.h>

/* Get common definition of System V style IPC.  */
#include <linux/ipc.h>

/* Get system dependent definition of `struct shmid_ds' and more.  */
#include <linux/shm.h>

typedef unsigned long long U64;
__BEGIN_DECLS


extern int create_shared_memory(const char* name, U64 size, int node, char*& addr, U64& shm_id);

extern int open_shared_memory(const char* name, int node, char*& addr, U64& shm_id);

extern int close_shared_memory(U64& shm_id, char*& addr);


__END_DECLS

#endif /* sys/shm.h */

Android.mk

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := libandroid_shm
LOCAL_SRC_FILES := android_shm.cpp

include $(BUILD_SHARED_LIBRARY)

(2)ndk-build編譯C++

參考我另一篇博客:https://blog.csdn.net/liny000/article/details/83020530

(3)將進程A的fd傳給進程B

利用binder跨進程通信
1)服務類實現binder onTransact函數(兩個進程傳遞ParcelFileDescriptor)
MainService.java

public class MainService extends Service {

    ParcelFileDescriptor pfd;

    @Override
    public IBinder onBind(Intent arg0) {
        return new MyBinder();
    }


    class MyBinder extends Binder {
        @Override
        protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case 0:
                    //pfd = data.readParcelable(null);
                    // 或者
                    pfd = data.readFileDescriptor();
                    break;
                case 1:
                    //reply.writeParcelable(pfd,0);
                    // 或者
                    reply.writeFileDescriptor(pfd.getFileDescriptor());
                    break;

                default:
                    break;
            }
            return true;
        }
    }

}

2)MainActivity 調用C++代碼創建共享內存並寫入內容,得到fd,轉換爲ParcelFileDescriptor 傳遞給MainActivity2
MainActivity.java

package com.example.linyuan.shared_memory;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.MemoryFile;
import android.os.ParcelFileDescriptor;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.view.Menu;
import android.view.MenuItem;

import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    Binder mBinder;
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            mBinder = (Binder) service;
        }

        public void onServiceDisconnected(ComponentName className) {
            mBinder = null;
        }
    };


    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        mConnection = null;
        super.onDestroy();
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Intent it = new Intent(MainActivity.this, MainService.class);
        startService(it);
        bindService(it, mConnection, Context.BIND_AUTO_CREATE);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        Button button1 = (Button)findViewById(R.id.button1);
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,MainActivity2.class));
            }
        });
    }

    android.os.Parcel data = android.os.Parcel.obtain();
    android.os.Parcel reply = android.os.Parcel.obtain();
    public void write(View v){
        try {
            if(mBinder!=null){
                //  data.writeParcelable(pfd, 0);
                // 或者
                int fd = fdFromJNI();
                TextView tv = (TextView) findViewById(R.id.sample_text);
                tv.setText("fd:"+ new String(String.valueOf(fd)));
                ParcelFileDescriptor cfd = ParcelFileDescriptor.fromFd(fd);
                FileDescriptor fileDescriptor2 = cfd.getFileDescriptor();
                data.writeFileDescriptor(fileDescriptor2);
                mBinder.transact(0, data, reply, 0);
            }
        } catch (Exception e) {
            e.printStackTrace();
//            Toast.makeText(this, "寫失敗", 0).show();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native int fdFromJNI();
}


3)獲取ParcelFileDescriptor,轉換爲fd,讀取共享內存上內容
MainActivity2.java

public class MainActivity2 extends AppCompatActivity {
    static {
        System.loadLibrary("native-lib");
    }

    private byte[] buffer = new byte[20];

    IBinder mBinder;
    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            // 非服務創建進程,獲取的Binder只是一個代理Binder對象,不能直接轉換
            // mBinder = (Binder) service;
            mBinder = service;
        }

        public void onServiceDisconnected(ComponentName className) {
            mBinder = null;
        }
    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_2);

        Intent it = new Intent(MainActivity2.this,MainService.class);
        startService(it);
        bindService(it, mConnection, Context.BIND_AUTO_CREATE);
    }

    public void read(View v) {
        TextView tv = (TextView) findViewById(R.id.tv);
        try {
            if (mBinder != null) {
                android.os.Parcel data = android.os.Parcel.obtain();
                android.os.Parcel reply = android.os.Parcel.obtain();
                mBinder.transact(1, data, reply, 0);
                //ParcelFileDescriptor pfd = reply.readParcelable(null);
                // 或者
                ParcelFileDescriptor pfd = reply.readFileDescriptor();
                if(pfd==null){
                    buffer = "ParcelFileDescriptor 空指針".getBytes();
                    tv.setText(new String(buffer));
                    return;
                }
                int fd = pfd.getFd();
                String buf;
                buf = read(fd);
                tv.setText(new String(buf));
            }

        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        unbindService(mConnection);
        mConnection = null;
        super.onDestroy();
    }

    public static   native String read(int fd);
}

native-lib.cpp 調用C++代碼

#include <jni.h>
#include <string>
#include "android_shm.h"

extern "C" JNIEXPORT jint JNICALL
Java_com_example_linyuan_shared_1memory_MainActivity_fdFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    char* buf;
    U64 ufd = 0;
    int ret = create_shared_memory("test1",1024,-1,buf,ufd);
    strcpy(buf,"shared_test你好");
    return ufd;
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_linyuan_shared_1memory_MainActivity2_read(
        JNIEnv *env,
        jobject /* this */,jint fd) {
    std::string hello = "進程2 ";
    U64  ufd = (U64)fd;
    char* buf;
    open_shared_memory("test1",-1,buf,ufd);
    char c[20];
    sprintf(c,"%D",ufd);
    hello = hello +buf +"    fd:" +c;
    close_shared_memory(ufd,buf);
    return  env->NewStringUTF(hello.c_str());
}

需要在AndroidManifest.xml裏添加如下,android:process=":activity_2"是確保另一個進程讀取共享內存

<activity
    android:name=".MainActivity2"
    android:label="activity_2"
    android:theme="@style/AppTheme.NoActionBar"
    android:process=":activity_2">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<service android:name="com.example.linyuan.shared_memory.MainService">
    <intent-filter>
        <action android:name="com.example.dtmlluadminuser.test2.MainService" />
    </intent-filter>
</service>

整個工程文件(Android studio)下載:https://download.csdn.net/download/liny000/10763992

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章