ParcelFileDescriptor:文件描述符,是一种程序读写已打开文件、socket的对象。
FileDescriptor对象,它代表了原始的Linux文件描述符
ParcelFileDescriptor对象,是原始文件描述符的一个复制,对象跟fd不同,但都是操作同一个底层文件流以及文件位置指针
需求
一个通信通道,实现跨进程的的Socket网络通信。
具体的通信通道的图如下。
需求分析
- 我们需要一个进程一直做通信通道的事情,业务进程把数据通过进程间通信交给通信进程。
- 通信进程通过Socket通道将数据发给网络另外一端的通信进程。
- 接收端的通信进程把数据再交给业务进程。
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,我们就可以实现进程见的数据流传输了。
完整的跨进程数据传输方案如下:
- 文件传输
两端业务层都把Uri对应的ParcelFileDescriptor发送给通信层,发送端通信层从AutoCloseInputStream中取数据发送,接收端通信层获取到数据后,写入到AutoCloseOutputStream中。 - 流传输
发送端:
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!