Android进程间通信之共享内存的使用

Posted Jason_Lee155

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进程间通信之共享内存的使用相关的知识,希望对你有一定的参考价值。

前言

android 提供了几种进程间通信的方式,除了Socket,基本都是基于binder实现的。为什么要用共享内存来实现呢?因为binder传输数据被限制在1M-8k,在较大的数据交换一般会使用文件,但效率非常的低,因此使用共享内存是很好的方式。在内存中开辟一块空间,通过binder或者其他方式将fd(文件描述符)传递到客户端或服务端进程,从而实现大文件传输。

原理

Android 的 匿名共享内存(Ashmem) 基于 Linux 的共享内存,都是在临时文件系统(tmpfs)上创建虚拟文件,再映射到不同的进程。它可以让多个进程操作同一块内存区域,并且除了物理内存限制,没有其他大小限制。相对于 Linux 的共享内存,Ashmem 对内存的管理更加精细化,并且添加了互斥锁。Java 层在使用时需要用到 MemoryFile,它封装了 native 代码。

Java 层使用匿名共享内存的4个点:

  • 通过 MemoryFile 开辟内存空间,获得 FileDescriptor;
  • 将 FileDescriptor 传递给其他进程;
  • 往共享内存写入数据;
  • 从共享内存读取数据。

关于 匿名共享内存 和 mmap 的关系

MemoryFile 在底层是调用了 mmap 将内存映射在了一个虚拟文件上。 两个进程通过 MemoryFile 访问这个虚拟文件对应的内存。

举例实现

一,服务端

  1. 首先在APP内部存储中存放一张图片:路径 data/data/com.example.ashmem_service/files/image.jpg
  2. 将文件写入到共享内存,并返回ParcelFileDescriptor
  3. 通过binder将返回的pfd传递给客户端

aidl文件

// IMyAidlInterface.aidl
package com.example.ashmem;

// Declare any non-default types here with import statements

interface IMyAidlInterface 
    ParcelFileDescriptor getPfd();

public class MyService extends Service 
    @Override
    public void onCreate() 
        super.onCreate();
    

    IMyAidlInterface.Stub myAidlInterface=new IMyAidlInterface.Stub() 
        @Override
        public ParcelFileDescriptor getPfd() throws RemoteException 
            return createMemory();
        
    ;

    @Nullable
    @Override
    public IBinder onBind(Intent intent) 
        return myAidlInterface;
    


    //创建共享内存区域
    private ParcelFileDescriptor createMemory() 
        MemoryFile memoryFile=null;
        byte[] bytes =readFile(new File(getFilesDir().getPath(),"image.jpg"));
        try 
       		//创建内存对象 参数一:name 参数二:开辟的内存大小
            memoryFile=new MemoryFile("test_memory",1024*4);
            //将数据写入到内存当中
            memoryFile.getOutputStream().write(bytes);
            //通过反射获取到getFileDescriptor方法,注意
            Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
            //得到文件描述符
            FileDescriptor fd = (FileDescriptor) method.invoke(memoryFile);
            //将文件描述符返回
            return ParcelFileDescriptor.dup(fd);
         catch (IOException e) 
            e.printStackTrace();
         catch (NoSuchMethodException e) 
            e.printStackTrace();
         catch (IllegalAccessException e) 
            e.printStackTrace();
         catch (InvocationTargetException e) 
            e.printStackTrace();
        
        return null;
    


    //file文件读取成byte[]
    public static byte[] readFile(File file) 
        RandomAccessFile rf = null;
        byte[] data = null;
        try 
            rf = new RandomAccessFile(file, "r");
            data = new byte[(int) rf.length()];
            rf.readFully(data);
         catch (Exception exception) 
            exception.printStackTrace();
         finally 
            closeQuietly(rf);
        
        return data;
    
    //关闭读取file
    public static void closeQuietly(Closeable closeable) 
        try 
            if (closeable != null) 
                closeable.close();
            
         catch (Exception exception) 
            exception.printStackTrace();
        
    

二,客户端

拿到fd写入到文件中即可:

public class MainActivity extends AppCompatActivity implements View.OnClickListener 

    private static final String TAG = "MainActivity";
    IMyAidlInterface aidl;
    ParcelFileDescriptor pfd = null;
    private ServiceConnection connection;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String filePath = this.getFilesDir().getPath();
        File file = new File(filePath + "/image.jpg");
        connection = new ServiceConnection() 
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) 
                aidl = IMyAidlInterface.Stub.asInterface(service);
                try 
                    pfd = aidl.getPfd();
                    FileDescriptor fileDescriptor = pfd.getFileDescriptor();
                    FileInputStream inputStream=new FileInputStream(fileDescriptor);
                    file.createNewFile();
                    FileOutputStream fo = new FileOutputStream(file);
                    int read = inputStream.read();
                    while (read != -1) 
                        fo.write(read);
                        read = inputStream.read();
                    
                    //关闭流
                    fo.flush();
                    fo.close();
                    inputStream.close();

//                    Log.i(TAG,"接收到的数据:"+new String(content,"UTF-8"));
                 catch (RemoteException | IOException e) 
                    e.printStackTrace();
                
            

            @Override
            public void onServiceDisconnected(ComponentName name) 

            
        ;
        findViewById(R.id.bindService).setOnClickListener(this);


    

    private void bindService() 
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.example.ashmem", "com.example.ashmem.MyService"));
        boolean isbind = bindService(intent, connection, Context.BIND_AUTO_CREATE);
        Log.i(TAG, "bindService: "+isbind);
    

    @Override
    public void onClick(View v) 
        switch (v.getId()) 
            case R.id.bindService:
                bindService();
                break;
            default:
                break;
        
    

以上是关于Android进程间通信之共享内存的使用的主要内容,如果未能解决你的问题,请参考以下文章

进程间通信之共享内存

进程间通信IPC之--共享内存

Android跨进程通信-共享内存

UnixIPC之共享内存

Android探索之AIDL实现进程间通信

进程间通信(共享内存)