Android开发记事本--把DirectBuffer转成ByteArray

问题背景

最近在开发中遇到一个问题——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;
} 

这个newNonMovableArrayaddressOf在性能优化团队可能比较常见,可以简单理解为我们自己拿到了虚拟机来分配内存和设置指针。总而言之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%免费】↓↓↓
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值