ParcelFileDescriptor:文件描述符,是一种程序读写已打开文件、socket的对象。
FileDescriptor对象,它代表了原始的Linux文件描述符ParcelFileDescriptor对象,是原始文件描述符的一个复制,对象跟fd不同,但都是操作同一个底层文件流以及文件位置指针
需求一个通信通道,实现跨进程的的Socket网络通信。 具体的通信通道的图如下。
需求分析 我们需要一个进程一直做通信通道的事情,业务进程把数据通过进程间通信交给通信进程。通信进程通过Socket通道将数据发给网络另外一端的通信进程。接收端的通信进程把数据再交给业务进程。 android进程间通信基本方式android进程间通信是使用Binder来传数据,而Binder传输的数据,有一个最为基本的要求,就是要实现Parcelable接口。
ParcelFileDescriptorParcelFileDescriptor是android提供的一个数据结构。
public class ParcelFileDescriptor extends Object implements Parcelable, CloseableParcelFileDescriptor是可以用于进程间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可加载bytesAndroid跨进程传图片或者大数据(解决TransactionTooLargeException)_binder传递bitmap-CSDN博客
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 mCallbacks = new HashMap();/** * 针对某个指令的处理 * @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)}时,将会回调本方法。 */@Overrideprotected 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");}@Overridepublic IBinder asBinder() {return this;}@Overridepublic String getInterfaceDescriptor() {return DESCRIPTOR;}@Overridepublic 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();@Overridepublic IBinder onBind(Intent intent) {mCmdDispatcher.addCallback(REQUEST, this);return mCmdDispatcher;}@Overridepublic void onCreate() {super.onCreate();} @Overridepublic int onStartCommand(Intent intent, int flags, int startId) {Log.i(LOG_TAG, "onStartCommand enter");return super.onStartCommand(intent, flags, startId);}@Overridepublic 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() {@Overridepublic void onServiceDisconnected(ComponentName name) {}@Overridepublic 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();} @Overridepublic 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