Android跨进程传图片或者大数据(解决TransactionTooLargeException)

跨进程传图片方案

  1. 直接intent传bitmap
  2. 使用文件读写
  3. intent传递自定义binder,binder中传递image
  4. 使用网络传输
一、直接intent传bitmap

优势

使用简单


劣势

相关代码可能有侵入性,必须在四大组件中接收。

  1. intent传递数据的总大小是1MB,其中还包括启动四大组件相关的信息。因此使用intent传递的图片不宜超过500KB,甚至应该更小,因为还可能会传递其他数据。
  2. 如果通过此方案传递大图片,必须先压缩后传输。开发者需要自己评估业务场景是否适用,毕竟很多场景不适合让图片质量下降。

如果intent传递的数据超过1MB时,就会报错TransactionTooLargeException。

二、使用文件读写

优势

  1. 使用相对简单
  2. 一定程度上可以避免逻辑耦合的问题,对于单独的模块来说只需要负责“读”或者“写”。

劣势

  1. 需要自己控制读写的时机。
  2. 读写操作相比直接传递效率更低,耗时更长。
三、intent传递自定义binder,binder中传递image

优势

  1. 效率相对最高
  2. 传递图片没有大小限制

劣势

  1. 使用相对麻烦,需要自定义aidl
  2. 相关代码可能有侵入性,必须在四大组件中接收。
四、使用网络传输

这个方案比较特殊,只有特殊场景才会使用。

一般存在两种情况:

  1. 两个进程都与服务端通信,一个进程传输,一个进程接收。如果是图片上传和下载的场景可以使用,但是效率肯定没有直接传输高。
  2. 两个进程一个作为服务端,一个作为客户端。 这个方案的关键在于这个“作为服务端的进程”,需要这个进程本身就是某种图片服务的提供者,且通过网络来对其他进程或模块提供服务度。


intent通过binder传递bitmap的Demo
有兴趣的读者可以自行看下Demo:

github地址
https://github.com/Double2hao/ProcessImageTest

intent通过binder传递bitmap的原理

bitmap在native层传递的时候会有两种方案:

1. 直接将图片写入进程的缓冲区。

缓冲区是进程在初始化的时候就已经申请了的,并且大小是一定的。因此如果写入的大小超过了缓冲区的大小,就会报错。

2. 使用共享内存,将共享内存的fd,也就是文件描述符写入缓冲区。

这样的好处就是传递图片的大小不会受限制。

intent直接传递bitmap对应方案1,intent通过binder传递bitmap对应方案2。

为什么intent传递bitmap不默认使用共享内存?

个人理解,缓冲区的大小是进程创建的时候就申请好的,如果能保证不超出缓冲区大小的情况下使用缓冲区,不需要再另外申请共享内存肯定是最好的。

如果默认就使用共享内存,而缓冲区资源又没人用的话,就造成了资源浪费。

因此如果开发者自己认为需要传递大文件的话,就使用共享内存,默认不使用。
 


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

aidl传输文件有大小1M限制,单次传输不适合传递大数据,可以使用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 112 rawBitmap:" + rawBitmap + " 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 class test {
    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);


跨进程传递大图,你能想到哪些方案呢?

跨进程传大图,有哪些方案?

通过IPC的方式转发图片数据。

  • Binder:性能很好,方便使用,但是有大小限制
  • Socket,管道:存在多次copy问题,性能差,也有大小限制
  • 共享内存:性能好

主要看两个指标

1. 性能,减少copy次数

2. 内存泄露,资源及时关闭

面试官:Intent 直接传 Bitmap 会有什么问题?

😨:Bitmap 太大会抛 TransactionTooLargeException 异常,原因是:底层判断只要 Binder Transaction 失败,且 Intent 的数据大于 200k 就会抛这个异常了。(见:android_util_Binder.cpp 文件 signalExceptionForError 方法)

面试官:为什么 Intent 传值会有大小限制。

😨:应用进程在启动 Binder 机制时会映射一块 1M 大小的内存,所有正在进行的 Binder 事务共享这 1M 的缓冲区 。当使用 Intent 进行 IPC 时申请的缓存超过 1M - 其他事务占用的内存时,就会申请失败抛 TransactionTooLargeException 异常了。 (哼,不会像上次一样答不出来了。见:“谈谈你对 binder 的理解?这样回答才过关”)

面试官:如何绕开这个限制呢?

😨:通过 AIDL 使用 Binder 进行 IPC 就不受这个限制,具体代码如下:

Bundle bundle = new Bundle();
bundle.putBinder("binder", new IRemoteGetBitmap.Stub() {
    @Override
    public Bitmap getBitMap() throws RemoteException {
        return mBitmap;
    }
});
intent.putExtras(bundle);

面试官:为什么通过 putBinder 的方式传 Bitmap 不会抛 TransactionTooLargeException 异常

🤔️:这个问题,我们先来看下,底层在 IPC 时是怎么把 Bitmap 写进 Parcel 的。

Android - 28 Bitmap.cpp
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject, ...) {
    // 拿到 Native 的 Bitmap                                
    auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
    // 拿到其对应的 SkBitmap, 用于获取 Bitmap 的像素信息
    bitmapWrapper->getSkBitmap(&bitmap);

    int fd = bitmapWrapper->bitmap().getAshmemFd();
    if (fd >= 0 && !isMutable && p->allowFds()) {
   	 		// Bitmap 带了 ashmemFd && Bitmap 不可修改 && Parcel 允许带 fd
    		// 就直接把 FD 写到 Parcel 里,结束。
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        return JNI_TRUE;
    }

    // 不满足上面的条件就要把 Bitmap 拷贝到一块新的缓冲区
    android::Parcel::WritableBlob blob;
  	// 通过 writeBlob 拿到一块缓冲区 blob
    status = p->writeBlob(size, mutableCopy, &blob);

    // 获取像素信息并写到缓冲区
    const void* pSrc =  bitmap.getPixels();
    if (pSrc == NULL) {
        memset(blob.data(), 0, size);
    } else {
        memcpy(blob.data(), pSrc, size);
    }
}

接下来我们看一下 writeBlob 是怎么获取缓冲区的(注意虽然方法名写着 write , 但是实际往缓冲区写数据是在这个方法执行之后)

Android - 28 Parcel.cpp
// Maximum size of a blob to transfer in-place.
static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
    if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
    // 如果不允许带 fd ,或者这个数据小于 16K
    // 就直接在 Parcel 的缓冲区里分配一块空间来保存这个数据
        status = writeInt32(BLOB_INPLACE);
        void* ptr = writeInplace(len);
        outBlob->init(-1, ptr, len, false);
        return NO_ERROR;
    }

		// 另外开辟一个 ashmem,映射出一块内存,后续数据将保存在 ashmem 的内存里
    int fd = ashmem_create_region("Parcel Blob", len);
    void* ptr = ::mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    ...
  	// parcel 里只写个 fd 就好了,这样就算数据量很大,parcel 自己的缓冲区也不用很大
    status = writeFileDescriptor(fd, true /*takeOwnership*/);
 		outBlob->init(fd, ptr, len, mutableCopy);
    return status;
}

通过上面的分析,我们可以看出,同一个 Bitmap 写入到 Parcel 所占的缓冲区大小和 Pacel 的 allowFds 有关。

直接通过 Intent 传 Bitmap 容易抛 TransactionTooLargeException 异常,就是因为 Parcel 的 allowFds = false,直接把 Bitmap 写入缓冲区占用了较大的内存。

接下来,我们来看一下,allowFds 是什么时候被设置成 false 的呢:

// 启动 Activity 执行到 Instrumentation.java 的这个方法
public ActivityResult execStartActivity(..., Intent intent, ...){
  ...
  intent.prepareToLeaveProcess(who);
	ActivityManager.getService().startActivity(...,intent,...)
}

// Intent.java
public void prepareToLeaveProcess(boolean leavingPackage) {
 // 这边一层层传递到最后设置 Parcel 的 allowfds
  setAllowFds(false);
  ....
}

 🤔️:总结一下:较大的 bitmap 直接通过 Intent 传递容易抛异常是因为 Intent 启动组件时,系统禁掉了文件描述符 fd 机制 , bitmap 无法利用共享内存,只能拷贝到 Binder 映射的缓冲区,导致缓冲区超限, 触发异常; 而通过 putBinder 的方式,避免了 Intent 禁用描述符的影响,bitmap 写 parcel 时的 allowFds 默认是 true , 可以利用共享内存,所以能高效传输图片。


Android跨进程传大图思考及实现——附上原理分析

1.还原现场

intent.putExtra("myBitmap",fhBitmap)

使用Intent去传递一个大的Bitmap给(Activity),如果你的图片够大,会出现类似下面这样的错误,请继续往下看:

Caused by: android.os.TransactionTooLargeException: data parcel size 8294952 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:535)
        at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
        at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)

所以TransactionTooLargeException这玩意爆出来的地方在哪呢?

2.问题定位分析

我们可以看到错误的日志信息里面看到调用了BinderProxy.transactNative,这个transactNative是一个native方法

//android.os.BinderProxy
/**
 * Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
            int flags) throws RemoteException;

Android Code Search,全局搜索一下:android_os_BinderProxy_transact

//frameworks/base/core/jni/android_util_Binder.cpp

static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
    ......
    status_t err = target->transact(code, *data, reply, flags);
    ......
    if (err == NO_ERROR) { 
        //如果匹配成功直接拦截不往下面执行了
        return JNI_TRUE;
    } else if (err == UNKNOWN_TRANSACTION) {
        return JNI_FALSE;
    }
    signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
    return JNI_FALSE;
}

我们打开signalExceptionForError方法看看里面的内容

//frameworks/base/core/jni/android_util_Binder.cpp
//处理异常的方法
void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
        bool canThrowRemoteException, int parcelSize)
{
    switch (err) {
        //其他异常,大家可以自行阅读了解;
        //如:没有权限异常,文件太大,错误的文件描述符,等等;
        ........
        case FAILED_TRANSACTION: {
            const char* exceptionToThrow;
            char msg[128];
            //官方在FIXME中写道:事务过大是FAILED_TRANSACTION最常见的原因
            //但它不是唯一的原因,Binder驱动可以返回 BR_FAILED_REPLY
            //也有其他原因可能是:事务格式不正确,文件描述符FD已经被关闭等等
            
            //parcelSize大于200K就会报错,canThrowRemoteException传递进来的是true
            if (canThrowRemoteException && parcelSize > 200*1024) {
                // bona fide large payload
                exceptionToThrow = "android/os/TransactionTooLargeException";
                snprintf(msg, sizeof(msg)-1, "data parcel size %d bytes", parcelSize);
            } else {
                ..........
            }
            //使用指定的类和消息内容抛出异常
            jniThrowException(env, exceptionToThrow, msg);
        } break;
        ........
    }
}

此时我们看到: parcelSize大于200K就会报错,难道一定是200K以内?先别着急着下结论,继续往下看👇👇

3.提出疑问

法海:我有个疑问,我看到文档写的1M大小啊;

许仙:别急,妹夫,来先看一下文档的解释,看一下使用说明:
官方TransactionTooLargeException的文档中描述到:Binder 事务缓冲区有一个有限的固定大小,目前为 1MB,由进程所有正在进行的事务共享
可以看到写的是:共享事务的缓冲区

如来佛祖:汝等别急,我们简单测试一下,Intent传递201*1024个字节数组,我们发现可以正常传递过去,Logcat仅仅输出了一个Error提示的日志信息,还是可以正常传递的

E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 205848, icicle size: 0

我们再测试一个值,intent传递800*1024个字节数组,我们发现会崩溃

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)
        at android.app.servertransaction.ClientTransaction.schedule(ClientTransaction.java:136)

不要着急,我们继续往下看分析

4.解答疑问

我们来看一下,下面两行代码

//frameworks/base/core/jni/android_util_Binder.cpp
//这个方法android_os_BinderProxy_transact里面的
IBinder* target = getBPNativeData(env, obj)->mObject.get();
status_t err = target->transact(code, *data, reply, flags);

从上面的分析和测试结果,我们从target->transact这里来找err返回值, 先根据头文件,搜索对应的cpp类,我们看一下这几个cpp类:BpBinder.cppIPCThreadState.cppProcessState.cpp

//frameworks/native/libs/binder/ProcessState.cpp

// (1 * 1024 * 1024) - (4096 *2)
#define BINDER_VM_SIZE ((1 * 1024 * 1024) - sysconf(_SC_PAGE_SIZE) * 2)
#define DEFAULT_MAX_BINDER_THREADS 15

//下面两个注释
//引用自官方文档:https://source.android.google.cn/devices/architecture/hidl/binder-ipc
#ifdef __ANDROID_VNDK__
//供应商/供应商进程之间的IPC,使用 AIDL 接口
const char* kDefaultDriver = "/dev/vndbinder";
#else
// "/dev/binder" 设备节点成为框架进程的专有节点
const char* kDefaultDriver = "/dev/binder";
#endif

//构造函数:初始化一些变量,Binder最大线程数等
ProcessState::ProcessState(const char* driver)
      : mDriverName(String8(driver)),
        mDriverFD(-1),
        mVMStart(MAP_FAILED),
        ......
        mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
        mStarvationStartTimeMs(0),
        mThreadPoolStarted(false),
        mThreadPoolSeq(1),
        mCallRestriction(CallRestriction::NONE) {
    ......
    //打开驱动
    base::Result<int> opened = open_driver(driver);
    if (opened.ok()) {
        //映射(1M-8k)的mmap空间
        mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
                        opened.value(), 0);
        ......
    }
    ......
}

点击查看sysconf.cpp
getauxval(AT_PAGESZ) = 4096,可以得出Binder内存限制BINDER_VM_SIZE = 1M-8kb

这里为什么不是1M,而是1M-8K?
最开始的时候,官方写的是1M,后来他们内部自己优化了;
来看这里👉👉官方提交的ProcessState.cpp提交的log日志:允许内核更有效地利用其虚拟地址空间

我们知道:微信的MMKV美团的Logan的日志组件,都是基于mmap来实现的;

binder驱动的注册逻辑在Binder.c中,我们看一下binder_mmap方法

//kernel/msm/drivers/android/binder.c
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
	int ret;
	struct binder_proc *proc = filp->private_data;
	const char *failure_string;
	if (proc->tsk != current->group_leader)
		return -EINVAL;
        //这里可以看到:映射空间最多4M
	if ((vma->vm_end - vma->vm_start) > SZ_4M)
		vma->vm_end = vma->vm_start + SZ_4M;
	......
        //初始化指定的空间vma用于分配绑定缓冲区
	ret = binder_alloc_mmap_handler(&proc->alloc, vma);
        ......
}

这里能看到映射空间最多4M,我们再来看一下binder_alloc_mmap_handler这个方法,点击查看binder_alloc.c

//kernel/msm/drivers/android/binder_alloc.c
//由binder_mmap()调用来初始化指定的空间vma用于分配绑定缓冲区
int binder_alloc_mmap_handler(struct binder_alloc *alloc,
			      struct vm_area_struct *vma)
{
     ......
     //buffer_size最大4M
     alloc->buffer_size = vma->vm_end - vma->vm_start;
     ......
     //异步事务的空闲缓冲区大小最大2M
     alloc->free_async_space = alloc->buffer_size / 2;
     ......
}

从上面的分析得出结论:
1.Binder驱动给每个进程最多分配4M的buffer空间大小;
2.异步事务的空闲缓冲区空间大小最多为2M
3.Binder内核内存上限为1M-8k;
4.异步事务缓冲区空间大小等于buffer_size/2,具体值取决于buffer_size;


同步、异步是定义在AIDL文件中的,我们看上面测试的两个例子,其中有一个传了800*1024个字节数组崩溃如下:

android.os.TransactionTooLargeException: data parcel size 821976 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)

点击查看IApplicationThread.aidl 查看AIDL里面的内容,我们看到scheduleTransaction是一个异步的方法;
因为oneway修饰在interface之前,会让interface内所有的方法都隐式地带上oneway;

由于oneway异步调用,我们这个时候修改一下,传递(1M-8k)/2大小之内的数据测试一下

// ((1024 * 1024 - 8 * 1024)/2)-1

 E/ActivityTaskManager: Transaction too large, intent: Intent { cmp=com.melody.test/.SecondActivity (has extras) }, extras size: 520236, icicle size: 0
 
Exception when starting activity com.melody.test/.SecondActivity
    android.os.TransactionTooLargeException: data parcel size 522968 bytes
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(BinderProxy.java:540)
        at android.app.IApplicationThread$Stub$Proxy.scheduleTransaction(IApplicationThread.java:2504)

可以看到还是会报错,说明异步事务的可用空间不够,仔细看一下为什么不够,细心的同学可能发现了:
警告的日志打印extras size: 520236
崩溃的日志打印data parcel size: 522968
大小相差2732 约等于 2.7k

如果这个时候我们用Intent传递一个ByteArray,比之前的大小减去3k
ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024)

startActivity(Intent(this,SecondActivity::class.java).apply {
            putExtra("KEY",ByteArray((1024*1024 - (8 * 1024))/2 - 3 * 1024))
})

这个时候发现(1M-8k)/2 -3k,可以成功传递数据,说明有其他数据占用了这部分空间。
我们上面写了,不要忘记:共享事务的缓冲区这里减去3k仅测试用的,我们继续往下分析;

找一下:异步事务的空闲缓冲区空间大小比较的地方,打开binder_alloc.c,找到binder_alloc_new_buf方法

//kernel/msm/drivers/android/binder_alloc.c
//分配一个新缓冲区
struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
					   size_t data_size,
					   size_t offsets_size,
					   size_t extra_buffers_size,
					   int is_async,
					   int pid)
{
	......
	buffer = binder_alloc_new_buf_locked(alloc, data_size, offsets_size,extra_buffers_size, is_async, pid);
        .......
}

我们来看一下binder_alloc_new_buf_locked方法

//kernel/msm/drivers/android/binder_alloc.c
static struct binder_buffer *binder_alloc_new_buf_locked(	
	struct binder_alloc *alloc,
	size_t data_size,
	size_t offsets_size,
	size_t extra_buffers_size,
	int is_async,
	int pid)
{
    ......
    //如果是异步事务,检查所需的大小是否在异步事务的空闲缓冲区区间内
    if (is_async &&
	alloc->free_async_space < size + sizeof(struct binder_buffer)) {
            return ERR_PTR(-ENOSPC);
	}
}

分析了这么多,不论是同步还是异步,都是共享事务的缓冲区,如果有大量数据需要通过Activity的Intent传递,数据大小最好维持在200k以内
上面测试的时候,超出200k数据传递的时候,LogCat已经给我们打印提示“Transaction too large”了,但是只要没有超出异步事务空闲的缓冲区大小,就不会崩溃
如果Intent传递大量的数据完全可以使用别的方式方法;

5.Intent设置Bitmap发生了什么?

5.1-Intent.writeToParcel

Intent数据写入到parcel中,在writeToParcel方法里面,Intent把Bundle写入到Parcel中

//android.content.Intent

public void writeToParcel(Parcel out, int flags) {
    ......
    //把Bundle写入到Parcel中
    out.writeBundle(mExtras);
}

打开out.writeBundle方法

//android.os.Parcel#writeBundle
public final void writeBundle(@Nullable Bundle val) {
     if (val == null) {
         writeInt(-1);
         return;
     }
     //执行Bundle自身的writeToParcel方法
     val.writeToParcel(this, 0);
}
5.2-Bundle.writeToParcel
//android.os.Bundle

public void writeToParcel(Parcel parcel, int flags) {
     final boolean oldAllowFds = parcel.pushAllowFds((mFlags & FLAG_ALLOW_FDS) != 0);
     try {
        //这里官方注释已经写的很详细了:
        //将Bundle内容写入Parcel,通常是为了让它通过IBinder连接传递
        super.writeToParcelInner(parcel, flags);
     } finally {
        //把mAllowFds值设置回来
        parcel.restoreAllowFds(oldAllowFds);
     }
}

点击查看Parcel.cpp,我们看一下里面的pushAllowFds方法

//frameworks/native/libs/binder/Parcel.cpp
bool Parcel::pushAllowFds(bool allowFds)
{
    const bool origValue = mAllowFds;
    if (!allowFds) {
        mAllowFds = false;
    }
    return origValue;
}

如果Bundle设置了不允许带描述符,当调用pushAllowFds之后Parcel中的内容也不带描述符;
在文章开头,我们举的例子中:通过Intent去传递一个Bitmap,在执行到Instrumentation#execStartActivity的时候,我们发现Intent有个prepareToLeaveProcess方法,在此方法里面调用了Bundle#setAllowFds(false)

//android.app.Instrumentation
public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        try {
            ......
            intent.prepareToLeaveProcess(who);
            ......
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }
5.3-Parcel.writeArrayMapInternal

刚刚上面Bundle.writeToParcel方法里面super.writeToParcelInner触发下面方法

//android.os.BaseBundle
void writeToParcelInner(Parcel parcel, int flags) {
       ......
       parcel.writeArrayMapInternal(map);
       ......
}

我们看一下writeArrayMapInternal方法

void writeArrayMapInternal(@Nullable ArrayMap<String, Object> val) {
        ......
        for (int i=0; i<N; i++) {
            writeString(val.keyAt(i));
            //根据不同数据类型调用不同的write方法
            writeValue(val.valueAt(i));
        }
    }
5.4-writeValue

文章一开头我们使用的是intent.putExtra("bmp",fhbitmap)

//android.os.Parcel
public final void writeValue(@Nullable Object v) {
    ......
    if (v instanceof Parcelable) {
        writeInt(VAL_PARCELABLE);
        writeParcelable((Parcelable) v, 0);
    } 
    ......
}
public final void writeParcelable(@Nullable Parcelable p, int parcelableFlags) {
    ......
    writeParcelableCreator(p);
    p.writeToParcel(this, parcelableFlags);
}

因为传入的是Bitmap,我们看Bitmap.writeToParcel

5.5-Bitmap.writeToParcel
//android.graphics.Bitmap
public void writeToParcel(Parcel p, int flags) {
    noteHardwareBitmapSlowCall();
    //打开Bitmap.cpp找对应的native方法
    if (!nativeWriteToParcel(mNativePtr, mDensity, p)) {
        throw new RuntimeException("native writeToParcel failed");
    }
}

点击打开Bitmap.cpp,查看Bitmap_writeToParcel方法

//frameworks/base/libs/hwui/jni/Bitmap.cpp

static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
                                     jlong bitmapHandle, jint density, jobject parcel) {
    ......
    //获得Native层的对象
    android::Parcel* p = parcelForJavaObject(env, parcel);
    SkBitmap bitmap;
    auto bitmapWrapper = reinterpret_cast<BitmapWrapper*>(bitmapHandle);
    //获取SkBitmap
    bitmapWrapper->getSkBitmap(&bitmap);
    //写入parcel
    p->writeInt32(!bitmap.isImmutable());
    ......
    p->writeInt32(bitmap.width());
    p->writeInt32(bitmap.height());
    p->writeInt32(bitmap.rowBytes());
    p->writeInt32(density);

    // Transfer the underlying ashmem region if we have one and it's immutable.
    android::status_t status;
    int fd = bitmapWrapper->bitmap().getAshmemFd();
    if (fd >= 0 && bitmap.isImmutable() && p->allowFds()) {
        //AshmemFd大于等于0 && bitmap不可变 && parcel允许带Fd
        //符合上述条件,将fd写入到parcel中
        status = p->writeDupImmutableBlobFileDescriptor(fd);
        if (status) {
            doThrowRE(env, "Could not write bitmap blob file descriptor.");
            return JNI_FALSE;
        }
        return JNI_TRUE;
    }
    
    //mutableCopy=true:表示bitmap是可变的
    const bool mutableCopy = !bitmap.isImmutable();
    //返回像素存储所需的最小内存
    size_t size = bitmap.computeByteSize();
    android::Parcel::WritableBlob blob;
    //获取到一块blob缓冲区,往下翻有代码分析
    status = p->writeBlob(size, mutableCopy, &blob);
    ......
}

我们来看看writeBlob里面做了什么事情

5.6-Parcel::writeBlob
//frameworks/native/libs/binder/Parcel.cpp

static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;  // 16k

status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
    status_t status;
    if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
        //如果不允许带FD 或者 数据小于等于16k,则直接将图片写入到parcel中
        status = writeInt32(BLOB_INPLACE);
        if (status) return status;
        void* ptr = writeInplace(len);
        if (!ptr) return NO_MEMORY;
        outBlob->init(-1, ptr, len, false);
        return NO_ERROR;
    }
    //不满足上面的条件,即(允许Fd && len > 16k):
    //创建一个新的ashmem区域并返回文件描述符FD
    //ashmem-dev.cpp里面有注释说明:
    //https://cs.android.com/android/platform/superproject/+/master:system/core/libcutils/ashmem-dev.cpp
    int fd = ashmem_create_region("Parcel Blob", len);
    if (fd < 0) return NO_MEMORY;
    //设置ashmem这块区域是“可读可写”
    int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
    if (result < 0) {
        status = result;
    } else {
         //根据fd,映射 “len大小” 的mmap的空间
         void* ptr = ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
         ......
         if (!status) {
           //将fd写入到parcel中
           status = writeFileDescriptor(fd, true /*takeOwnership*/);
            if (!status) {
                outBlob->init(fd, ptr, len, mutableCopy);
                return NO_ERROR;
             }
        }
        ......
    }
    ......
}

看到这里,大家应该知道我们为什么先分析Intent传递数据大小的上限了吧;
在 5.2-Bundle.writeToParcel已经说明清楚了,Intent启动Activity的时候,禁用掉了文件描述符;
所以: 在执行writeBlob方法只能执行到第一个分支,直接将图片写入到parcel中,我们在5.4中给出Intent传递数据大小限制的结论;

那么如何不受Intent禁用文件描述符和数据大小的限制?

6.跨进程传大图

在Parcel类中看到writeValue方法里面有个分支,判断当前value是不是IBinder,如果是IBinder类型的会调用writeStrongBinder把这个对象写入到Parcel中;

所以我们可以使用Bundle的putBinder来把IBinder对象写入到Parcel中,通过putBinder不会受Intent禁用文件描述符的影响,数据大小也没有限制,Bitmap写入到parcel中默认是true,可以使用匿名共享内存(Ashmem);

6.1-单进程下putBinder用法
//定义一个IntentBinder,此方法仅在『同一个进程』下有效哦,切记切记!!!!
class IntentBinder(val imageBmp:Bitmap? = null): Binder()

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
        putBinder("myBinder",IntentBinder(bitmap))
}))

//------------------------获取Bitmap并显示如下--------------------------//
//com.xxx.xxx.SecondActivity
val bundle: Bundle? = intent.extras
val imageBinder:IntentBinder? = bundle?.getBinder("myBinder") as IntentBinder?
//拿到Binder中的Bitmap
val bitmap = imageBinder?.imageBmp
//自行压缩后显示到ImageView上.....

注意: 这个用法不能跨进程,喜欢动手的同学,可以试一试,给SecondActivity配置一个android:process=":remote",你会发现会报一个强制转换的异常错误

//错误的用在多进程场景下,报错如下:
 
java.lang.ClassCastException: android.os.BinderProxy cannot be cast to com.xxx.xxx.IntentBinder

🤔为什么可以通过这种方式传递对象?
Binder会为我们的对象创建一个全局的JNI引用,点击查看android_util_Binder.cpp

//frameworks/base/core/jni/android_util_Binder.cpp
......
static struct bindernative_offsets_t
{
    // Class state.
    jclass mClass;
    jmethodID mExecTransact;
    jmethodID mGetInterfaceDescriptor;

    // Object state.
    jfieldID mObject;

} gBinderOffsets;
......
static const JNINativeMethod gBinderMethods[] = {
     /* name, signature, funcPtr */
    // @CriticalNative
    { "getCallingPid", "()I", (void*)android_os_Binder_getCallingPid },
    // @CriticalNative
    { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid },
    ......
    { "getExtension", "()Landroid/os/IBinder;", (void*)android_os_Binder_getExtension },
    { "setExtension", "(Landroid/os/IBinder;)V", (void*)android_os_Binder_setExtension },
};

const char* const kBinderPathName = "android/os/Binder";

//调用下面这个方法,完成Binder类的注册
static int int_register_android_os_Binder(JNIEnv* env)
{
    //获取Binder的class对象
    jclass clazz = FindClassOrDie(env, kBinderPathName);
    
    //内部创建全局引用,并将clazz保存到全局变量中
    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    
    //获取Java层的Binder的成员方法execTransact
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
    
    //获取Java层的Binder的成员方法getInterfaceDescriptor
    gBinderOffsets.mGetInterfaceDescriptor = GetMethodIDOrDie(env, clazz, "getInterfaceDescriptor",
        "()Ljava/lang/String;");
        
    //获取Java层的Binder的成员变量mObject
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
    
    //注册gBinderMethods中定义的函数
    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}
......

6.2-多进程下putBinder用法

//先定义一个IGetBitmapService.aidl
package com.xxx.aidl;
interface IGetBitmapService {
    Bitmap getIntentBitmap();
}

//------------------------使用如下--------------------------//
//com.xxx.xxx.MainActivity      👉进程A
val bitmap = BitmapFactory.decodeStream(...)
startActivity(Intent(this,SecondActivity::class.java).putExtras(Bundle().apply {
    putBinder("myBinder",object: IGetBitmapService.Stub() {
        override fun getIntentBitmap(): Bitmap {
            return bitmap
        }
    })
}))

//------------------------获取Bitmap并显示如下--------------------------//
//com.xxx.xxx.SecondActivity      👉进程B
val bundle: Bundle? = intent.extras
//返回IGetBitmapService类型
val getBitmapService = IGetBitmapService.Stub.asInterface(bundle?.getBinder("myBinder"))
val bitmap = getBitmapService.intentBitmap
//自行压缩后显示到ImageView上.....


Android 共享内存实现跨进程大文件传输(设计思路和Demo实现绕过Binder传输限制) 

项目链接  AndroidSharedMemoryDemo

下图是文件详情:13.7M

项目在客户端最终的显示效果:

本人建议可以下载下来直接查看就可以,对照着代码查看.

项目整体分为三个 部分

1.客户端clientapp:负责调用SDK测试

2.SDKjar包:mylibrary:扶着整体的共享内存的开辟以及读取操作.

3.服务端serverapp:当客户端请求数据时,往共享内存里面写数据.

本文不再对如何提供SDK给第三方项目使用的进行讲解,只针对部代码进行详解,如果想看项目的详解可以查看 Android 应用提供SDK Jar包给第三方使用 (设计思路 以及实现步骤) 和本项目的架构类似。

本项目的整体调用时序图如下:

本项目的类关系图:

MemoryFile简介:

MemoryFile是android在最开始就引入的一套框架,其内部实际上是封装了android特有的内存共享机制Ashmem匿名共享内存,简单来说,Ashmem在Android内核中是被注册成一个特殊的字符设备,Ashmem驱动通过在内核的一个自定义slab缓冲区中初始化一段内存区域,然后通过mmap把申请的内存映射到用户的进程空间中(通过tmpfs),这样子就可以在用户进程中使用这里申请的内存了,另外,Ashmem的一个特性就是可以在系统内存不足的时候,回收掉被标记为"unpin"的内存,这个后面会讲到,另外,MemoryFile也可以通过Binder跨进程调用来让两个进程共享一段内存区域。由于整个申请内存的过程并不再Java层上,可以很明显的看出使用MemoryFile申请的内存实际上是并不会占用Java堆内存的。

MemoryFile.java位置在如下,有兴趣的同学可以翻阅源码看一看

frameworks/base/core/java/android/os/MemoryFile.java

mylibrary简介:
本项目中 mylibrary负责整体的内存开辟以及读操作

MemoryFileHelper.java是开辟空间的具体操作类,具体拿到MemoryFIle用的是反射方法,核心方法如下:

    public static MemoryFile openMemoryFile(FileDescriptor fd, int length, int mode) {
        MemoryFile memoryFile = null;
        try {
            memoryFile = new MemoryFile("tem", 1);
            memoryFile.close();
            if (!Utils.isMoreThanAPI27()) {
                Class<?> c = MemoryFile.class;
                Method native_mmap = null;
                Method[] ms = c.getDeclaredMethods();
                for (int i = 0; ms != null && i < ms.length; i++) {
                    if (ms[i].getName().equals("native_mmap")) {
                        native_mmap = ms[i];
                    }
                }
                ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mFD", fd);
                ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mLength", length);
                if (Utils.isMoreThanAPI21()) {
                    long address = (long) ReflectUtils.invokeMethod(null, native_mmap, fd, length, mode);
                    ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mAddress", address);
                } else {
                    int address = (int) ReflectUtils.invokeMethod(null, native_mmap, fd, length, mode);
                    ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mAddress", address);
                }
            } else {
                SharedMemory sharedMemory = SharedMemory.create("tem", 1);
                sharedMemory.close();
                ReflectUtils.setField("android.os.SharedMemory", sharedMemory, "mFileDescriptor", fd);
                ReflectUtils.setField("android.os.SharedMemory", sharedMemory, "mSize", length);
                ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mSharedMemory", sharedMemory);
                ReflectUtils.setField("android.os.MemoryFile", memoryFile, "mMapping", sharedMemory.mapReadWrite());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return memoryFile;
    }

MyControllerImp.java负责开辟共享内存和负责通过Aidl和服务端交互的核心业务类.最核心的方法在链接建立之后,将自己创建的ParcelFileDescriptor对象传递给server这样保证了serverapp拿到的MemoryFile对象是同一个对象

@Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        Log.d("mysdk", " sdk  onServiceConnected  ");
        if (service == null) {
            if (mMyRemoteCtrl != null) {
                try {
                    mMyRemoteCtrl.unlinkToDeath(mFrameDataCallBack.asBinder());
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
            mMyRemoteCtrl = null;
        } else {
            mMyRemoteCtrl = IMyRemoteCtrl.Stub.asInterface(service);
            if (mMyRemoteCtrl != null) {
                try {
                    mMyRemoteCtrl.linkToDeath(mFrameDataCallBack.asBinder());
                    Log.d("mysdk", " sdk  onServiceConnected  setBackBufferCallBack ");
                    if (mCallBack != null) {
                        mMyRemoteCtrl.setParcelFileDescriptor(mMemoryFile.getParcelFileDescriptor());
                        mMyRemoteCtrl.registerFrameByteCallBack(mFrameDataCallBack);
                        mMemoryFile.setReadBufferCallBack(mCallBack);
                    } else {
                        mMyRemoteCtrl.unregisterFrameByteCallBack(mFrameDataCallBack);
                        mMemoryFile.release();
                    }
                    Log.d("mysdk", " sdk  onServiceConnected  setBackBufferCallBack  eld ");
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

而客户端注册的IReadBufferCallBack.java的对象也被MyControllerImp.java 设置到了MemoryFileImp.java中当,也就是说MemoryFileImp.java持有客户端注册的数据回调对象

mMemoryFile.setReadBufferCallBack(mCallBack);

serverapp简介:


服务端最核心的类ServerClientService.java中的内部类MyRemoteCtrlImpl.java负责和mylibrary 中的MyControllerImp.java通讯,用于接收传递过来的远端ParcelFileDescriptor对象和callBack.最核心的代码如下,因为没有持续的流可以写,就自己准备了一张在草原天路拍色的图片放在服务端的assets文件夹下 13M 绝对超出了Binder限制.

public class MyRemoteCtrlImpl extends IMyRemoteCtrl.Stub {
...........省略代码.......
        @Override
        public void readFile(String msg) throws RemoteException {
            Log.d("mysdk"," mParcelFileDescriptor  = null ? " + (mParcelFileDescriptor == null));
            if (mParcelFileDescriptor != null) {
                memoryFile = MemoryFileHelper.openMemoryFile(mParcelFileDescriptor, MEMORY_SIZE, 0x3);
            }
            Log.d("mysdk"," memoryFile  = null ? " + (memoryFile == null));
            try {
                InputStream open = getResources().getAssets().open("IMG.JPG");
                byte[] buffer = new byte[open.available()];
                Log.d("mysdk"," 服务端 buffer " + buffer.length );
                open.read(buffer);
                readImage(buffer);
                open.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
//写共享内存方法
    private void readImage(byte[] frame) {
        if (memoryFile != null) {
            try {
                memoryFile.readBytes(isCanRead, 0, 0, 1);
                if (isCanRead[0]== 0) {
                    memoryFile.writeBytes(frame, 0, 1, frame.length);
                    isCanRead[0] = 1;
                    memoryFile.writeBytes(isCanRead, 0, 0, 1);
                }
                Log.d("mysdk"," 服务端 canReadFrameData " );
                mIReadDataCallBack.canReadFileData();
            } catch (Exception e ) {
                Log.d("mysdk"," 服务端 Exception  "  + e.getMessage()  );
                e.printStackTrace();
            }
        }
    }

clientapp简介

集成mylibrary的jar包 不知道如何打jar包的可以看 Android 应用提供SDK Jar包给第三方使用 (设计思路 以及实现步骤) 

核心代码就是读取数据进行显示MainActivity.java中

public class MainActivity extends AppCompatActivity {
    ImageView iv;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         iv = findViewById(R.id.iv);
        SharedMemoryLibSDK.getInstance().init(this);
        SharedMemoryLibSDK.getInstance().setBackBufferCallBack(new IReadBufferCallBack() {
            @Override
            public void onReadBuffer(final byte[] bytes, int i) {
                Log.d("mysdk"," 客户端 读取到客户写到共享内存的大小为: " + bytes.length);
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Bitmap bitmap = byteToBitmap(bytes);
                        iv.setImageBitmap(bitmap);
                    }
                });
            }
        });
    }
    public static Bitmap byteToBitmap(byte[] imgByte) {  
         InputStream input = null;  
         Bitmap bitmap = null;  
         BitmapFactory.Options options = new BitmapFactory.Options();  
         options.inSampleSize = 8;  
         input = new ByteArrayInputStream(imgByte);
         SoftReference softRef = new SoftReference(BitmapFactory.decodeStream(  
                                  input, null, options));  
         bitmap = (Bitmap) softRef.get();  
         if (imgByte != null) {  
             imgByte = null;  
         }  
         try {
             if (input != null) {  
                  input.close();  
             }  
         } catch (IOException e) {
             // TODO Auto-generated catch block  
             e.printStackTrace();  
         }  
         return bitmap;  
    }
 
    public void  readFIle(View view) {
        Log.d("mysdk"," 客户端  调用服务端的 readFIle  " );
        SharedMemoryLibSDK.getInstance().readFile("我是客户端");
    }
}


点击按钮的最后效果:因为数据太大在用byte生成BitMap的时候容易内存溢出,在客户端读取完成数据之后对生成的BitMap使用了中压缩了处理.

  • 24
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值