问题背景
最近在开发中遇到一个问题——CameraX的imageCapture在拍照成功以后onCaptureSuccess
中得到的是一个ByteBuffer,我们通常处理图片需要用到的是ByteArray,第一反应是直接调用ByteBuffer.array()进行转换。这个时候报了UnsupportedOperationException
的异常。
DirectBuffer
看一下源码为什么曝出这个问题
public final byte[] array() {
if (hb == null)
throw new UnsupportedOperationException();
if (isReadOnly)
throw new ReadOnlyBufferException();
return hb;
}
可以看到当hb == null的时候会抛出这个异常,而hb其实就是HeapBuffer的缩写,也就是堆上的Buffer。
final byte[] hb; // Non-null only for heap buffers
而如果打印这个imageProxy.planes[0].buffer
的类型,会发现这是一个NIO.DirectBuffer,那么需要了解一下ByteBuffer的原理才能明白这到底是什么东西。 ByteBuffer是一块缓冲区,它有三个属性capacity,limit还有postion,其中position代表缓冲区里的指针移动位置,而limit默认就是容量大小,limit一般用于设置读操作的终点。在读取ByteBuffer的时候的代码通常为
byteBuffer.limit(bytebuffer.position);
byteBuffer.position(0);
// 可以缩写为
byteBuffer.flip();
而HeapBuffer和DirectBuffer的区别就在于HeapBuffer里多了一块backingArraybyte[]
也叫支持数组。熟悉JVM的同学可以从Heap这个词就可以看出,HeapBuffer是在堆内分配内存的,而DirectBuffer是在堆外,堆内堆外的区别在于GC,ByteBuffer如果不断被GC整理改变了内存地址,会让IO操作更笨重,所以为了使用的效率就有了DirectBuffer。在Java中创建对象即在堆内开辟了一块内存操作,所以直接声明byte[] hb
这样的方式当然是堆内创建,而DirectBuffer则是在堆外创建,这里会用到Native的方法,关于JVM的原理太多了就不深入探究了。 看一下创建DirectBuffer的方法
public static ByteBuffer allocateDirect(int capacity) {
// Android-changed: Android's DirectByteBuffers carry a MemoryRef.
// return new DirectByteBuffer(capacity);
DirectByteBuffer.MemoryRef memoryRef = new DirectByteBuffer.MemoryRef(capacity);
return new DirectByteBuffer(capacity, memoryRef);
}
关于MemoryRef
创建初始化的代码:
MemoryRef(int capacity) {
VMRuntime runtime = VMRuntime.getRuntime();
buffer = (byte[]) runtime.newNonMovableArray(byte.class, capacity + 7);
allocatedAddress = runtime.addressOf(buffer);
// Offset is set to handle the alignment: http://b/16449607
offset = (int) (((allocatedAddress + 7) & ~(long) 7) - allocatedAddress);
isAccessible = true;
isFreed = false;
originalBufferObject = null;
}
这个newNonMovableArray
和addressOf
在性能优化团队可能比较常见,可以简单理解为我们自己拿到了虚拟机来分配内存和设置指针。总而言之DirectBuffer由于堆外分配的原因是不存在backingArray的所以没有办法直接调用array方法来转换为ByteArray。
解决方案
先贴出最后的代码,这里参考的是Google的代码,解释一下就是首先 回到ByteBuffer的起点,然后新建一个等大的ByteArray,然后把ByteBuffer内容拷贝过去并返回这个ByteArray。
fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
比较迷惑的是这个get方法,是怎么做到内容的拷贝的。
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
public ByteBuffer get(byte[] dst, int offset, int length) {
checkBounds(offset, length, dst.length);
if (length > remaining())
throw new BufferUnderflowException();
int end = offset + length;
for (int i = offset; i < end; i++)
dst[i] = get();
return this;
}
其实也很简单,就是根据当前数组的大小,进行遍历并且拿buffer赋值。
private byte get(long a) {
return Memory.peekByte(a);
}
public static native byte peekByte(long address);
最后调用到的是Memory的读字节方法,这个方法是一个native方法,就是操作内存去了,就不再往下看了。
总结
一个非常简单的小问题,HeapBuffer有backingArray可以直接拿来用,而DirectBuffer则需要自己设置个Array然后遍历。但是这里可以初窥到自己使用虚拟机来操作内存的方法,对于性能优化,尤其是bitmap的性能优化有一些参考价值。
文末
要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
一、架构师筑基必备技能
1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……
二、Android百大框架源码解析
1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程
三、Android性能优化实战解析
- 腾讯Bugly:对字符串匹配算法的一点理解
- 爱奇艺:安卓APP崩溃捕获方案——xCrash
- 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
- 百度APP技术:Android H5首屏优化实践
- 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
- 携程:从智行 Android 项目看组件化架构实践
- 网易新闻构建优化:如何让你的构建速度“势如闪电”?
- …
四、高级kotlin强化实战
1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》
-
从一个膜拜大神的 Demo 开始
-
Kotlin 写 Gradle 脚本是一种什么体验?
-
Kotlin 编程的三重境界
-
Kotlin 高阶函数
-
Kotlin 泛型
-
Kotlin 扩展
-
Kotlin 委托
-
协程“不为人知”的调试技巧
-
图解协程:suspend
五、Android高级UI开源框架进阶解密
1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
六、NDK模块开发
1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习
七、Flutter技术进阶
1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)
…
八、微信小程序开发
1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓