平常都在一个进程中使用Bitmap,最近用了用跨进程使用Bitmap。Bitmap本来就是内存大户,再叠加跨进程传送,难免会想到会不会导致使用的内存剧增呢?
最简单的方式
跨进程传送Bitmap肯定不能用Intent+Bundle的方式传送,都是知道会抛出TransactionTooLargeException。那就走AIDL跨进程通信呗,如果把Bitmap当做一个普通的类来对待,写一段跨进程传送Bitmap的AIDL代码,就是这普通的写法:
1 | // IO.aidl |
形参前in是我加上的,AIDL文件默认的就是in,它表示调用方传过来的这个Bitmap的修改不会同步给调用方。其实它们就是两个不同的Bitmap了,对这个Bitmap的修改丝毫不会影响到调用方的Bitmap,这不是我们想要的,我们需要的是对这个Bitmap的修改能够返回给调用方,很自然的就会想到使用inout关键字:
1 | void changeColor(inout Bitmap io); |
inout表示对该对象的修改,会在执行完这个方法后再序列化该对象传送到调用方,调用方会阻塞等待完成。但这段代码编译不过,提示没有Bitmap实现readFromParcel方法,为什么居然没实现呢?想想也是累,调用方进程一个Bitmap,跨进程到这里是另外一个Bitmap,返回给调用方又是一个新的Bitmap,一来回三Bitmap,显然太不合理。我们需要最理想情况是:两个进程持有、修改的Bitmap始终是同一个Bitmap。
使用共享内存
Bitmap的内存在Android 7之后就保存在了native层,我们实际上可以使用mmap的方式把它这块内存共享出去,这样其他进程对它的修改就可以实时的同步到自己进程。Android中提供了SharedMemory用来共享内存,我们可以用它来共享Bitmap的native内存,定义的数据结构如下:
1 | class IOBitmap( |
AIDL文件修改为:
1 | void changeColor(inout IOBitmap io); |
下面这段代码就是将Bitmap封装进SharedMemory传送:
1 | lifecycleScope.launch(Dispatchers.IO) { |
changeColor(ioBitmap)是IPC方法,会堵塞该线程,等待服务端进程修改完成后继续执行,服务端进程的代码:
1 | private inner class MyBinder : IO.Stub() { |
收到SharedMemory后拿出它的ByteBuffer修改,这里的ByteBuffer是DirectByteBuffer,没有意外,它也是堆外内存,所以对它的操作放在了native层,DirectBufferInterface的changeColor方法会调用native方法直接修改DirectByteBuffer里面的数组。
完成之后,再次序列化IOBitmap对象,并通知到正在堵塞的对方线程,对方线程的收到后执行changeColor方法后面的代码,显示修改完成的Bitmap。
验证
调用方进程新建一个10000x10000像素、ARGB8888的Bitmap,大小为10000x10000x4/1024/1024=381.5MB,使用共享内存的方式发送给服务方进程,服务方进程修改像素的色值后,再同步给调用方进程。以下是使用Profiler查看两个进程内存变化的情况:
客户端进程新建Bitmap
新建这个Bitmap并显示出来,可以看见Native和Graphics都增加了相同大小的区域
服务端进程修改Bitmap
客户端进程发起跨进程到服务端进程修改这个Bitmap的像素,服务端进程的内存变化情况:
服务端进程的Others内存增加后迅速恢复到以前的水平
客户端进程更新Bitmap
服务端进程修改完这个Bitmap后客户端继续执行,显示修改后的Bitmap。内存变化参考上面客户端进程内存变化图的最后部分,可以确定Native内存并没有波澜,只是显示出来时Graphics内存增加了。
使用HardwareBuffer传送Bitmap
Android 12后Bitmap多了一个方法:
1 | Bitmap: |
HardwareBuffer类:
HardwareBuffer wraps a native AHardwareBuffer object, which is a low-level object representing a memory buffer >accessible by various hardware units. HardwareBuffer allows sharing buffers across different application >processes. In particular, HardwareBuffers may be mappable to memory accessibly to various hardware systems, >such as the GPU, a sensor or context hub, or other auxiliary processing units. For more information, see the >NDK documentation for AHardwareBuffer.
1 | public final class HardwareBuffer implements Parcelable, AutoCloseable |
所以我们还可以新建一个Hardware的Bitmap,再通过共享HardwareBuffer的方式把这Bitmap跨进程传送。
服务端提供Hardware Bitmap:
1 |
|
客户端跨进程加载并显示:
1 |
|
服务端进程内存变化:
客户端进程内存变化:
可以发现最后只剩下Graphics内存了。但这样做有一个缺点,这块Bitmap的像素不可以操作,只能显示。
代码
以上演示的Demo代码在AndroidBitmapSharingTest