Android ParcelFileDescriptor实现进程间通信

简介: Android ParcelFileDescriptor实现进程间通信

ParcelFileDescriptor:文件描述符,是一种程序读写已打开文件、socket的对象。

FileDescriptor对象,它代表了原始的Linux文件描述符

ParcelFileDescriptor对象,是原始文件描述符的一个复制,对象跟fd不同,但都是操作同一个底层文件流以及文件位置指针

需求

一个通信通道,实现跨进程的的Socket网络通信。

具体的通信通道的图如下。

需求分析
  1. 我们需要一个进程一直做通信通道的事情,业务进程把数据通过进程间通信交给通信进程。
  2. 通信进程通过Socket通道将数据发给网络另外一端的通信进程。
  3. 接收端的通信进程把数据再交给业务进程。
android进程间通信基本方式

android进程间通信是使用Binder来传数据,而Binder传输的数据,有一个最为基本的要求,就是要实现Parcelable接口。

ParcelFileDescriptor

ParcelFileDescriptor是android提供的一个数据结构。

public class ParcelFileDescriptor 
extends Object implements Parcelable, Closeable

ParcelFileDescriptor是可以用于进程间Binder通信的FileDescriptor。支持stream 写入和stream 读出

public static class ParcelFileDescriptor.AutoCloseInputStream 
extends FileInputStream 
 
public static class ParcelFileDescriptor.AutoCloseOutputStream 
extends FileOutputStream 
 

我们可以使用

ParcelFileDescriptor open (File file, int mode)

来将PacecelFileDescriptor 与File对应起来,以实现进程间的文件共享。

我们也可以使用

ParcelFileDescriptor[] createPipe ()

来建立一个pipe通信通道,ParcelFileDescriptor数组第一个元素是read端,第二个元素是write端,通过write端的AutoCloseOutputStream和read端的AutoCloseInputStream,我们就可以实现进程见的数据流传输了。

完整的跨进程数据传输方案如下:
  1. 文件传输
    两端业务层都把Uri对应的ParcelFileDescriptor发送给通信层,发送端通信层从AutoCloseInputStream中取数据发送,接收端通信层获取到数据后,写入到AutoCloseOutputStream中。
  2. 流传输

发送端:

1. 业务层调用getOutputStream向通信层发起请求

2. 通信层通过creatPipe 建立一个ParcelFileDescriptor数组,并将write端的pipe[1]返回给业务层

3. 业务层得到pipe[1](ParcelFileDescriptor)后,可以通过AutoCloseOutputStream写入数据

4. 从通信层的pipe[0]的AutoCloseInputStream中读出数据通过socket发送出去

接收端:

1. 业务层调用getInputStream向通信层发起请求

2. 通信层通过creatPipe 建立一个ParcelFileDescriptor数组,并将read端的pipe[0]返回给业务层

3. 业务层得到pipe0后,可以通过AutoCloseInputStream读取数据。(如没有数据,则阻塞,一直等到有数据为止)

4. socket中读取数据,写入到通信层的pipe[1]的AutoCloseOutputStream。(pipe[1]一旦写入,第三步中pipe[2]就可以读取出数据)

简单的ParcelFileDescriptor使用——pipe

public class DemoParcefliledescriptor extends AppCompatActivity {
 
    private static final String TAG = "DemoPFD";
 
    private static final String[] PERMISSIONS = {
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
    };
    private static final int PERMISSIONS_CODE = 3006;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_demo_null);
 
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(PERMISSIONS, PERMISSIONS_CODE);
        }
 
        FileOutputStream outputStream = new FileOutputStream(getStreamFd());
        try {
            outputStream.write(99);
            outputStream.write(98);
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
 
 
    private FileDescriptor getStreamFd() {
        ParcelFileDescriptor[] pipes = null;
 
        try {
            pipes = ParcelFileDescriptor.createPipe();
            new TransferThread(new ParcelFileDescriptor.AutoCloseInputStream(pipes[0])).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        return pipes[1].getFileDescriptor();
    }
 
    static class TransferThread extends Thread {
        InputStream in;
        FileOutputStream out;
 
        TransferThread(InputStream in, FileOutputStream out) {
            this.in = in;
            this.out = out;
        }
 
        TransferThread(InputStream in) {
            this.in = in;
 
            File outFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/zlq_pdf");
            Log.i(TAG, "File: " + outFile.getAbsolutePath());
 
            try {
                out = new FileOutputStream(outFile);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
 
        @Override
        public void run() {
            byte[] buf = new byte[1024*2];
 
            int len;
            try {
                while((len=in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                    Log.i(TAG, "out:" + len);
 
                }
 
                in.close();
                out.flush();
                out.getFD().sync();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

借助ParcelFileDescriptor将Uri转为bitmap

    private Bitmap uriToBitmap(Uri selectedFileUri) {
        Bitmap bitmap = null;
        try {
            ParcelFileDescriptor parcelFileDescriptor =
                    getContentResolver().openFileDescriptor(selectedFileUri, "r");
            FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
            bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
            parcelFileDescriptor.close();
 
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bitmap;
    }
 
    /*
     * bitmap转base64
     * */
    private String bitmapToBase64(Bitmap bitmap) {
        String result = null;
        ByteArrayOutputStream baos = null;
        try {
            if (bitmap != null) {
                baos = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
 
                baos.flush();
                baos.close();
 
                byte[] bitmapBytes = baos.toByteArray();
                result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (baos != null) {
                    baos.flush();
                    baos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    /*end*/
 
    /**
     * base64转为bitmap
     *
     * @param base64Data
     * @return
     */
    private Bitmap base64ToBitmap(String base64Data) {
        byte[] bytes = Base64.decode(base64Data, Base64.DEFAULT);
        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    }
 
 
调用:
 
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 String base64 = bitmapToBase64(uriToBitmap(imageUri));
                            
                            
 base64ToBitmap(base64).compress(Bitmap.CompressFormat.PNG, 100, baos);
 byte[] bytes = baos.toByteArray();
 
 
glide可加载bytes



android中非aidl实现进程间通信(编写顺序的parcel写入与读出)

在android源码中基于Binder对象的通信随处可见,几乎可以认定为以 I 打头的class,都具有进程间通信能力,如:IServiceManager,IContentProvider等。

在源码中实现的方式也可概括为两种:

1. 通过aidl来生成对Binder的操作。

2.手动调用IBinder.transact编写按照顺序写入与读出的parcel代码实现。

第一种方法网上案例较多,不多说。第二种方法实现源码参考:ActivityManagerNative,ActivityManagerProxy

关于第二种方法的实现本人做了一个demo,请看以下代码。

package dw.test;
 
import java.util.HashMap;
 
import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
/**
 * 负责接收指令({@link CmdCode}),并将指令派发到相应的处理器({@link CmdDispatcher.Callback})
 */
public final class CmdDispatcher extends Binder implements IInterface{
 
  private static final String LOG_TAG = CmdDispatcher.class.getSimpleName();
  
  public static final String DESCRIPTOR = CmdDispatcher.class.getName();
  /**
   * 存储所有指令处理器
   * map.key = {@link CmdCode}
   */
  private HashMap<Integer,Callback> mCallbacks = new HashMap<Integer, Callback>();
  /**
   * 针对某个指令的处理
   * @see #addCallback
   * @see #removeCallback
   */
  public interface Callback {
    /**
     * @param code 请求指令集{@link CmdCode.Request},响应 指令集{@link CmdCode.Response}
     * @param data 数据 {@link Parcel}
     * @param reply 处理data的结果 {@link Parcel}
     * @return
     */
    public boolean onTransact(int code, Parcel data, Parcel reply);
  }
  /**
   * 当client端调用 {@link IBinder#transact(int, Parcel, Parcel, int)}时,将会回调本方法。
   */
  @Override
  protected boolean onTransact(int code, Parcel data, Parcel reply,
      int flags) throws RemoteException {
    
    dispatch(code,data,reply);
 
    return true;
  }
  /**
   * 得到某个指令处理器并调用
   */
  private void dispatch(int code, Parcel data, Parcel reply) {
    Log.i(LOG_TAG, "dispatch reply enter");
    
    Callback callback = mCallbacks.get(code);
    
    if(callback!=null){
      callback.onTransact(code, data, reply);
    }
    
    Log.i(LOG_TAG, "dispatch reply exit");
  }
  
  @Override
  public IBinder asBinder() {
    return this;
  }
  
  @Override
  public String getInterfaceDescriptor() {
    return DESCRIPTOR;
  }
  
  @Override
  public IInterface queryLocalInterface(String descriptor) {
    return this;
  }
  /**
   * 针对某一个指令,如:请求指令集{@link CmdCode.Request},响应 指令集{@link CmdCode.Response}
   * 添加回调处理
   * @param code 指令编码
   * @param callback 针对某一个指定的处理 {@link Callback}
   */
  public void addCallback(int code,Callback callback) {
    mCallbacks.put(code, callback);
  }
 
  public void removeCallback(int code) {
    mCallbacks.remove(code);
  }
  
}
package dw.test;
/**
 * 定义指令集
 */
public interface CmdCode {
  
  public interface BaseCode {
    /**
     * 每个parcel的头
     */
    public static final int PARCEL_HEAD = 0xffff;
    public static final int RESULT_SUCCESS = 0x0001;
    public static final int RESULT_ERROR = 0x0002;
  }
  /**
   * 请求指令集
   */
  public interface Request extends BaseCode{
    public static final int REQUEST = 0x0001;
  }
  /**
   * 响应指令集
   */
  public interface Response extends BaseCode {
    public static final int RESPONSE = 0x0001;
  }
  
}
package dw.test;
 
import dw.test.CmdDispatcher.Callback;
 
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.Parcel;
import android.util.Log;
 
/**
 * RemoteService作为一个独立进程存在.
 */
public class RemoteCmdService extends Service implements Callback,CmdCode.Request{
  
  private static final String LOG_TAG = RemoteCmdService.class.getSimpleName();
  
  private final CmdDispatcher mCmdDispatcher = new CmdDispatcher();
  
  @Override
  public IBinder onBind(Intent intent) {
    mCmdDispatcher.addCallback(REQUEST, this);
    return mCmdDispatcher;
  }
  
  @Override
  public void onCreate() {
    super.onCreate();
  }
 
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    
    Log.i(LOG_TAG, "onStartCommand enter");
    
    return super.onStartCommand(intent, flags, startId);
  }
  
  @Override
  public boolean onTransact(int code, Parcel data, Parcel reply) {
    Log.i(LOG_TAG, "remove service handle Reply enter");
    data.enforceInterface(CmdDispatcher.DESCRIPTOR);
    //读取包头
    int head = data.readInt();
    if(head==PARCEL_HEAD) {
      String handeResult = data.readString();
      reply.writeInt(RESULT_SUCCESS);
      Log.i(LOG_TAG, handeResult);
    } else {
      reply.writeInt(RESULT_ERROR); 
    }
    Log.i(LOG_TAG, "remove service handle Reply exit");
    return true;
  }
 
}
package dw.test.activity;
 
import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import dw.test.CmdCode;
import dw.test.CmdDispatcher;
import dw.test.R;
import dw.test.RemoteCmdService;
 
public class MainActivity extends Activity implements OnClickListener , CmdCode.Request,CmdCode.Response{
 
  private static final String LOG_TAG = MainActivity.class.getSimpleName();
  
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        this.findViewById(R.id.test_remote_binder_btn).setOnClickListener(this);
    }
    
  /**
     * 连接并调用远程服务
     */
    private void testRemote(){
      Intent intent = new Intent(MainActivity.this,RemoteCmdService.class);
      //绑定远程服务
      bindService(intent, new ServiceConnection() {
      
      @Override
      public void onServiceDisconnected(ComponentName name) {
        
      }
      
      @Override
      public void onServiceConnected(ComponentName name, IBinder service) {
        
        replyTo(service);
        
      }
    }, BIND_AUTO_CREATE);
    }
    
  private void replyTo(IBinder service) {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    
    data.writeInterfaceToken(CmdDispatcher.DESCRIPTOR);
    //写入包头
    data.writeInt(PARCEL_HEAD);
    //写入要发送的字符数据
    data.writeString("serviceConnected");
    
    //当然你也可以传递一个binder对象过去作为callback,这样两个进程间就可以交互了。
//    data.writeStrongBinder(IBinder binder); 
    
    try {
      //调用远程MESSAGE_REQUEST服务
      service.transact(REQUEST, data, reply,0);
    } catch (RemoteException e) {
      //ignore
    }
    
    //MESSAGE_REQUEST服务所返回的结果 
    int result = reply.readInt();
    
    if(RESULT_SUCCESS==result) {
      Log.i(LOG_TAG, "ok");
    }
    
    data.recycle();
    reply.recycle();
  }
 
  @Override
  public void onClick(View v) {
    int id = v.getId();
    if(R.id.test_remote_binder_btn==id){
      testRemote();
    }
  }
 
    
}

基于Android 匿名共享内存实现跨进程实时传输大量图片或数据

aidl传输文件有大小1M限制,单次传输不适合传递大数据,可以通过Android匿名共享内存的方法来解决这个问题, 使用aidl传递共享内存引用ParcelFileDescriptor方式传递图片信息。具体实现如下

一、service端

1.1.aidl文件IIpcService.aidl 定义,这里主要用到pfd参数
interface IIpcService {
  /**
  * Demonstrates some basic types that you can use as parameters
  * and return values in AIDL.
  */
  // void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
  // double aDouble, String aString);
  void register2Server(String packageName,IIpcServiceListener ipcServiceListener);
  void unregister2Server(String packageName);
  String processClientRequest(String packageName,String clientRequest,inout ParcelFileDescriptor pfd);
}
1.2 service端 处理客户端传递的图片流

引用ParcelFileDescriptor ,将获取的ParcelFileDescriptor转换成Bitmap 并回调给ui层显示

public String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) {
 
    Log.i(TAG, "processClientRequest 11 packageName:" + packageName
    + " clientRequest:" + clientRequest + " pfd:" + pfd);
    String ret = clientRequest;
    FileDescriptor fileDescriptor = pfd.getFileDescriptor();
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(fileDescriptor);
        Bitmap rawBitmap = BitmapFactory.decodeStream(fis);
        ret += " process success!";
 
        Log.i(TAG, "processClientRequest 222 rawBitmap ByteCount:" + rawBitmap.getByteCount() + " mUiShow:" + mUiShow);
 
        if (null != mUiShow) {
            mUiShow.showBitmap(rawBitmap);
        }
    } catch (Exception e) {
        Log.i(TAG, "processClientRequest 22 error:" + e);
        e.printStackTrace();
    } finally {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (IOException e) {
                Log.i(TAG, "processClientRequest 33 error:" + e);
        }
    }
 
    Log.i(TAG, "processClientRequest 22 end ret:" + ret);
    return ret;
}
1.3 也可以处理客户端传递的字节数组 数据引用,处理代码如下
public String processClientRequest(String packageName, String clientRequest, ParcelFileDescriptor pfd) {
 
    Log.i(TAG, "processClientRequest 11 packageName:" + packageName
    + " clientRequest:" + clientRequest + " pfd:" + pfd);
    String ret = clientRequest;
    FileDescriptor fileDescriptor = pfd.getFileDescriptor();
    FileInputStream fis = null;
    try {
        fis = new FileInputStream(fileDescriptor);
 
        byte [] content = new byte[5];
        fis.read(content);
        Log.i(TAG, "processClientRequest 111 content:" + content);
        for(int i=0;i<content.length;i++){
            Log.i(TAG, "processClientRequest 113 content["+i+"]=" + content[i]);
        }
    } catch (Exception e) {
        Log.i(TAG, "processClientRequest 33 error:" + e);
        e.printStackTrace();
    } finally {
        try {
            if (fis != null) {
                fis.close();
            }
        } catch (IOException e) {
            Log.i(TAG, "processClientRequest 44 error:" + e);
        }
    }
 
    Log.i(TAG, "processClientRequest 55 end ret:" + ret);
    return ret;
}

二客户端

2.1 客户端连接到service后,调用接口 传递图片文件引用 ParcelFileDescriptor
String path = "/sdcard/lilei/20230207161749238.jpg";
public ParcelFileDescriptor getPfd() {
    ParcelFileDescriptor pfd = null;
    try {
        pfd = ParcelFileDescriptor.open(new File(path), MODE_READ_WRITE);
    } catch (FileNotFoundException e) {
        throw new RuntimeException(e);
    }
    Log.i(TAG, "getPfd() pfd:" + pfd);
    return pfd;
}
 
public String sendFile(String requestJson) {
    Log.i(TAG, "sendFile() requestJson:" + requestJson);
    if (null != mFtIpcManager) {
        return mFtIpcManager.processClientRequest(requestJson, getPfd());
    }
    return "error";
}
2.2 客户端也可以传递 byte数组
public ParcelFileDescriptor getTextPfd() {
    ParcelFileDescriptor pfd = null;
    try {
        MemoryFile memoryFile = new MemoryFile("test", 1024);
        Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
        FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
        pfd = ParcelFileDescriptor.dup(des);
        //向内存中写入字节数组
        memoryFile.getOutputStream().write(new byte[]{1,2,5,4,3});
        //关闭流
        memoryFile.getOutputStream().close();
        memoryFile.close();
 
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);a
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    Log.i(TAG, "getTextPfd() pfd:" + pfd);
    return pfd;
}
public String sendFile(String requestJson) {
    Log.i(TAG, "sendFile() requestJson:" + requestJson);
    if (null != mFtIpcManager) {
        return mFtIpcManager.processClientRequest(requestJson, getTextPfd());
    }
    return "error";
}
2.3 客户端也可以传递Bitmap数据,需要先将Bitmap转换成 byte数组,service端接收同1.2
public ParcelFileDescriptor getBitmapPfd() {
    ParcelFileDescriptor pfd = null;
 
    Bitmap bitmap= BitmapFactory.decodeResource(FtClientApp.getAppContext().getResources(), R.drawable.btn_send);
    //将Bitmap转成字节数组
    ByteArrayOutputStream stream = new ByteArrayOutputStream();
    bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
    byte[] byteArray = stream.toByteArray();
 
    try {
        MemoryFile memoryFile = new MemoryFile("test", bitmap.getByteCount());
        Method method = MemoryFile.class.getDeclaredMethod("getFileDescriptor");
        FileDescriptor des = (FileDescriptor) method.invoke(memoryFile);
        pfd = ParcelFileDescriptor.dup(des);
        //向内存中写入字节数组
        memoryFile.getOutputStream().write(byteArray);
        //关闭流
        memoryFile.getOutputStream().close();
        memoryFile.close();
 
    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
    Log.i(TAG, "getPfd() pfd:" + pfd);
    return pfd;
}
public String sendFile(String requestJson) {
    Log.i(TAG, "sendFile() requestJson:" + requestJson);
    if (null != mFtIpcManager) {
        return mFtIpcManager.processClientRequest(requestJson, getBitmapPfd());
    }
    return "error";
}

PS:这里也可以共享内存传递大字符串,只是需要将字符串和字节数组转换一下再传递,转换实现如下。

1.string 字符串转换成 byte[] 数组

String str = "reagan";

byte[] srtbyte = str.getBytes();

2.byte[] 数组转换成 string字符串

String res = new String(srtbyte);

或者

String res = new String(srtbyte,"UTF-8");

System.out.println(res);

 

如下服务端1.2 log 所示,服务端可以接受客户端2.3发送 的 31M左右的图片数据

2024-01-09 01:14:45.797 11247-11267 SimpleTestActivity com.android.demo.lileidemo I showBitmap() bitmap:android.graphics.Bitmap@3fe9243 getByteCount:33177600

2024-01-09 01:14:45.798 11247-11267 lileiDemo_...sitoryImpl com.android.demo.lileidemo I processClientRequest 55 end ret:image process success!

相关文章
|
2月前
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
311 4
|
3月前
|
API Android开发
Android P 性能优化:创建APP进程白名单,杀死白名单之外的进程
本文介绍了在Android P系统中通过创建应用进程白名单并杀死白名单之外的进程来优化性能的方法,包括设置权限、获取运行中的APP列表、配置白名单以及在应用启动时杀死非白名单进程的代码实现。
62 1
|
3月前
|
Android开发 开发者 Kotlin
Android 多进程情况下判断应用是否处于前台或者后台
本文介绍在多进程环境下判断Android应用前后台状态的方法。通过`ActivityManager`和服务信息`RunningAppProcessInfo`可有效检测应用状态,优化资源使用。提供Kotlin代码示例,帮助开发者轻松集成。
266 8
|
6月前
|
XML 前端开发 Android开发
Android架构设计——MVC(1),Android多进程从头讲到尾
Android架构设计——MVC(1),Android多进程从头讲到尾
|
Java Linux Android开发
理解Android进程创建流程
理解Android进程创建流程
109 0
|
6月前
|
安全 Linux API
Android进程与线程
Android进程与线程
50 0
|
监控 测试技术 Shell
性能测试 基于Python结合InfluxDB及Grafana图表实时监控Android系统和应用进程
性能测试 基于Python结合InfluxDB及Grafana图表实时监控Android系统和应用进程
312 0
性能测试 基于Python结合InfluxDB及Grafana图表实时监控Android系统和应用进程
|
Java Android开发
|
缓存 监控 网络协议
|
Unix Linux Android开发
Android C++系列:Linux进程间通信(二)
mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存 地址,对文件的读写可以直接用指针来做而不需要read/write函数。
108 0

相关实验场景

更多