Android跨进程传递大数据

最近要从Service端给Client端传递图片数据,之前的数据都是通过aidl传递:
创建 Parcelable文件
ImageData.java

public class ImageData implements Parcelable {
    private byte[] data;
    public byte[] getData() {
        return data;
    }

    public ImageData(byte[] dataIn) {
        this.data = dataIn;
    }

    public ImageData(Parcel in) {
        int arrayLength = in.readInt();
        if (arrayLength > 0) {
            data = new byte[arrayLength];
            in.readByteArray(data);
        }
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        if (data != null && data.length > 0) {
            dest.writeInt(data.length);
            dest.writeByteArray(data);
        } else {
            dest.writeInt(0);
        }
    }
    ...
}

test.aidl

interface test {
    void sendMessage(ImageData data);
}

运行报错:

    android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:514)
        ...

原因

这里导致DeadObjectException的原因主要是binder创建的buffer被占满了:

kernel/msm-4.4/drivers/android/binder_alloc.c
 315     if (best_fit == NULL) {
...
341         pr_err("%d: binder_alloc_buf size %zd failed, no address space\n",
342                   alloc->pid, size);
343         pr_err("allocated: %zd (num: %zd largest: %zd), free: %zd (num: %zd largest: %zd)\n",
344                       total_alloc_size, allocated_buffers, largest_alloc_size,
345                  total_free_size, free_buffers, largest_free_size);
346            eret = ERR_PTR(-ENOSPC);
347              goto error_unlock;
348    }

传输中如果数据大于free_buffers,则会抛出DeadObjectException

解决

1.socket

socke传输不受大小限制,但实现比较复杂

2.文件

通过文件传输比较简单,但效率差,而且高版本会受到Android系统权限限制

3.数据切割

将较大数据切割成较小的数据传输,此方法是兼顾效率,复杂度较好的方案

定义数据体:

public class SliceData implements Parcelable {
    private byte[] data;
    private int length;
    ...
}

切割数据方法:

    public static byte[][] divideArray(byte[] source, int chunkSize) {
        int totalLength = source.length;
        int arraySize = (int) Math.ceil(totalLength / (double) chunkSize);
        byte[][] ret = new byte[arraySize][chunkSize];
        int start = 0;
        int parts = 0;
        for (int i = 0; i < arraySize; i++) {
            if (start + chunkSize > totalLength) {
                System.arraycopy(source, start, ret[i], 0, source.length - start);
            } else {
                System.arraycopy(source, start, ret[i], 0, chunkSize);
            }
            start += chunkSize;
            parts++;
        }
        return ret;
    }

将ImageData按顺序构建发送:

byte[][] divideData = divideArray(testBytes, 64 * 1024);//64k
for (byte[] item : divideData) {
    mEmitter.onNext(new SliceData(length, item));
}

client接收:

int chunkSize = bytes.length;
if(buffer == null) {
    buffer = new byte[length];
    index = 0;
}
if (index + chunkSize > bodyLength) {//最后一个数据块
    System.arraycopy(bytes, 0, buffer, index, bodyLength - index);
    visualResultData.bitmap = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
    buffer = null;
    index = 0;
} else {
    System.arraycopy(bytes, 0, buffer, index, chunkSize);
    index += chunkSize;
}

4.第三方

binder本身也是利用mmap,可以利用实现mmap的框架,比如 MMKV

5.Bitmap

如果传输的数据是Bitmap,还可以用BundleputBinder方案
定义binder

class ImageBinder extends IRemoteGetBitmap.Stub {
    @Override
    public Bitmap getBitMap() throws RemoteException {
        return mBitmap;
    }
}

发送

Bundle bundle = new Bundle();
bundle.putBinder("bitmap", new ImageBinder());
intent.putExtras(bundle);

接收:

ImageBinder imageBinder = (ImageBinder) bundle.getBinder("bitmap");
Bitmap bitmap = imageBinder.getBitmap();

推荐阅读更多精彩内容