本文为您介绍 Glide 接入 veImageX 提供的 HEIF 解码库的接入流程,实现 HEIF 加载。
当前仅支持解码 HEIF 静图,HEIF 动图暂不支持。
建议使用 4.0.0 及以上版本的 Glide。
通过 Glide 使用解码库前,请确保已注册解码器插件。
说明
解码器插件分为HeifByteBufferBitmapDecoder
和StreamHeifDecoder
两种,分别可解码网络图和本地图。您在通过 Glide 使用 veImageX 解码库前,请确保已完成相应图片对应类型解码器的配置和注册操作。
您已完成独立 HEIF 解码库的集成准备。
在 project 根目录下的build.gradle
下配置服务,代码示例如下所示:
maven { url 'https://artifact.bytedance.com/repository/Volcengine/' // 最新版本号获取地址:https://artifact.bytedance.com/repository/Volcengine/ }
说明
Glide 为开源图片库,建议您使用 4.0.0 及以上版本,具体版本号请见 Glide 版本历史。
在 module 目录下的build.gradle
文件中的dependencies
中添加 SDK 依赖,代码示例如下所示。
dependencies { implementation("com.github.bumptech.glide:glide:xxx") // Glide 库(推荐 4.0.0 及以上版本) annotationProcessor("com.github.bumptech.glide:compiler:xxx") //Glide 库解码能力(推荐 4.0.0 及以上版本) }
如果您使用 Kotlin 语言来编写 AppGlideModule
,您需要使用 kapt 或者 ksp 插件实现编译处理,推荐您使用 ksp 插件。
在dependencies
中添加 SDK 依赖。
dependencies { implementation("com.github.bumptech.glide:glide:xxx") // Glide 库(推荐 4.0.0 及以上版本) ksp("com.github.bumptech.glide:ksp:xxx") // 使用 KSP 插件替代传统 Kotlin 注解处理器(推荐 4.0.0 及以上版本) }
在自定义的Application
的onCreate
方法中执行以下代码:
List<String> encodedAuthCode = new ArrayList<>(); encodedAuthCode.add("xxxxxxxxxxxxxxxxxxxxxxxxx"); InitConfig initConfig = new InitConfig( this, // Application Context "000000", // App id, 如实填写,您可在 独立 HEIF 解码库集成准备-获取 AppID 获取 "sample", // App Name,暂未用到,传入app名称 "debug", // channel,暂未用到,传入例如OPPO "0.0.1", // App的versionName, 如实填写 "1", // App的versionCode, 如实填写 "48144589260", // device id,暂未用到 InitConfig.CHINA, // App 上线的区域,如实填写 "M2ZmYzkzZjUtN2", // Token,如实填写,您可在 独立 HEIF 解码库集成准备-获取 Token 获取 encodedAuthCode // 授权码List<String>,如实填写,您可在 独立 HEIF 解码库集成准备-购买授权 获取 ); CloudControl.init(initConfig);
基于 HEIF 解码库配置 Glide 解码器插件,在 decode
方法中接入 veImageX HEIF 解码库,具体参考 Glide 官方文档中的自定义组件模块,代码示例如下所示。
说明
若 HEIF 图不包含 Alpha 通道,但仍选择使用Heif.toRgb565()
解码时,将可能导致最终图片解析异常。
BufferDecoder 为网络图片解码插件,您可以使用该插件对公网可访问的 HEIF 图片进行解码。
class HeifByteBufferBitmapDecoder(bitmapPool: BitmapPool) : ResourceDecoder<ByteBuffer, Bitmap> { private val bitmapPool: BitmapPool init { this.bitmapPool = Preconditions.checkNotNull(bitmapPool) } override fun handles(source: ByteBuffer, options: Options): Boolean { val buffer = ByteBufferUtil.toBytes(source) return Heif.isHeif(buffer, buffer.size) } override fun decode( source: ByteBuffer, width: Int, height: Int, options: Options ): Resource<Bitmap>? { // 将 ByteBuffer 转为 buffer 数组 val buffer = ByteBufferUtil.toBytes(source) var hasAlpha = true // 解析图片的基本信息 val meta = Heif.parseSimpleMeta(buffer, buffer.size) meta ?: return null if (meta.size > 8) { // 是否有 alpha 通道 hasAlpha = meta[8] != 0 } // 根据是否有 alpha 通道,选择解码成不同格式 val heifData = if (hasAlpha) { // 包含,使用 heif toRgba 解码 Heif.toRgba(buffer, buffer.size, false, 1, 0, 0, meta[1], meta[0]) } else { // 不包含,使用 heif toRgb565 解码 // 注意:如果有 alpha 通道的图片强行使用 toRgb565 方式解码,可能会造成图片解析异常。 Heif.toRgb565(buffer, buffer.size, false, 1, 0, 0, meta[1], meta[0]) } heifData ?: return null val config: Bitmap.Config = if (hasAlpha) { Bitmap.Config.ARGB_8888 } else { Bitmap.Config.RGB_565 } val bitmap = heifData.newBitmap(config) // 将 HEIF 图片解码为 Bitmap return BitmapResource.obtain(bitmap, bitmapPool) } }
StreamDecoder 为本地图片解码插件,您可以使用该插件对存储在手机本地的 HEIF 图片进行解码。
import android.graphics.Bitmap import com.bumptech.glide.load.Options import com.bumptech.glide.load.ResourceDecoder import com.bumptech.glide.load.engine.Resource import com.bumptech.glide.load.engine.bitmap_recycle.ArrayPool import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bytedance.fresco.nativeheif.Heif import com.facebook.common.logging.FLog import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream class StreamHeifDecoder(private val bitmapPool: BitmapPool, private val byteArrayPool: ArrayPool) : ResourceDecoder<InputStream, Bitmap> { private fun isHeif(source: InputStream, byteArrayPool: ArrayPool): Boolean { val arr = byteArrayPool.get( MAX_META, ByteArray::class.java ) try { if (source.read(arr) == -1) { return false } } catch (e: IOException) { e.printStackTrace() return false } byteArrayPool.put(arr) if (arr == null) { return false } return Heif.isHeif(arr, arr.size) } override fun handles(source: InputStream, options: Options): Boolean { if (source.available() == 0) { return false } return isHeif(source, byteArrayPool) } override fun decode( source: InputStream, width: Int, height: Int, options: Options ): Resource<Bitmap>? { // 将 InputStream 转为 buffer 数组 val buffer = inputStreamToBytes(source) var hasAlpha = true // 解析图片的基本信息 val meta = Heif.parseSimpleMeta(buffer, buffer.size) FLog.d(TAG, "heifData meta = $meta") meta ?: return null if (meta.size > 8) { // 是否有 alpha 通道 hasAlpha = meta[8] != 0 } // 根据是否有 alpha 通道,选择解码成不同格式 val heifData = if (hasAlpha) { // 包含,使用 heif toRgba 解码 Heif.toRgba(buffer, buffer.size, false, 1, 0, 0, meta[1], meta[0]) } else { // 不包含,使用 heif toRgb565 解码 // 注意:如果有 alpha 通道的图片强行使用 toRgb565 方式解码,可能会造成图片解析异常。 Heif.toRgb565(buffer, buffer.size, false, 1, 0, 0, meta[1], meta[0]) } FLog.d(TAG, "heifData hasAlpha = $hasAlpha") heifData ?: return null val config: Bitmap.Config = if (hasAlpha) { Bitmap.Config.ARGB_8888 } else { Bitmap.Config.RGB_565 } val bitmap = heifData.newBitmap(config) return HeifBitmapResource(bitmapPool, bitmap) } private fun inputStreamToBytes(inputStream: InputStream): ByteArray { val buffer = ByteArrayOutputStream() val data = ByteArray(READ_SIZE) var nRead: Int while ((inputStream.read(data, 0, data.size).also { nRead = it }) != -1) { buffer.write(data, 0, nRead) } return buffer.toByteArray() } companion object { const val TAG: String = "StreamHeifDecoder" const val MAX_META = 5000 private const val READ_SIZE = 10 * 1024 } }
StreamHeifDecoder 中的 HeifBitmapResource
类实现如下所示:
// # HeifBitmapResource import android.graphics.Bitmap; import androidx.annotation.NonNull; import com.bumptech.glide.load.engine.Resource; import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; public class HeifBitmapResource implements Resource<Bitmap> { private final BitmapPool bitmapPool; private final Bitmap mBitmap; public HeifBitmapResource(BitmapPool bitmapPool, Bitmap mBitmap) { this.bitmapPool = bitmapPool; this.mBitmap = mBitmap; } @NonNull @Override public Class<Bitmap> getResourceClass() { return Bitmap.class; } @NonNull @Override public Bitmap get() { return mBitmap; } @Override public int getSize() { return mBitmap.getByteCount(); } @Override public void recycle() { bitmapPool.put(mBitmap); } }
使用 prepend
方法注册 HeifByteBufferBitmapDecoder
解码器和 StreamHeifDecoder
解码器,并将其指定为解码 HEIF 图像时的最高优先级解码器。以便在使用 Glide 解码 HEIF 图像时,默认会优先使用该自定义解码器。
说明
对于加载某些在线 HEIF 图像出现的异常,建议参考使用 Glide 自定义解码器插件时处理 HEIF 图时加载异常的排查建议处理。
@GlideModule open class HeifGlideModule : AppGlideModule() { override fun registerComponents( context: Context, glide: Glide, registry: Registry ) { registerStaticImageDecoder(glide, registry) } /** * 注册静态图片解码器 */ private fun registerStaticImageDecoder(glide: Glide, registry: Registry) { val byteBufferBitmapDecoder = HeifByteBufferBitmapDecoder(glide.bitmapPool) // 优先使用 prepend 注册的解码器 // 静态图片解码器:buffer decoder,处理网络图片加载 registry.prepend( Registry.BUCKET_BITMAP, ByteBuffer::class.java, Bitmap::class.java, byteBufferBitmapDecoder ) // 静态图片解码器:stream decoder,处理本地图片加载 registry.prepend( Registry.BUCKET_BITMAP, InputStream::class.java, Bitmap::class.java, StreamHeifDecoder(glide.bitmapPool, glide.arrayPool) ) }
如果您是在单独的模块中封装 Glide 框架,而不是在主应用程序模块中使用 Glide,请您将 AppGlideModule
更改为 LibraryGlideModule
。
参考 Glide 官方文档加载 HEIF 图片,具体如下所示。
Glide.with(context) .load("http://test.com/demo.heic")// 网络 heic 图片的加载地址,加载前确保已配置和注册 BufferDecoder 解码器 //.load("file:///storage/emulated/0/demo.heic") // 本地 heic 图的加载地址,加载前确保已配置和注册 StreamDecoder 解码器 .into(imageView)
检查依赖配置:确保HeifGlideModule
类在编译构建后成功生成有效的注册代码。如果HeifGlideModule
没有被其他类引用到,通常是因为 Gradle 文件配置的问题,请检查是否正确配置了 Glide 依赖,确保配置正确并成功生成目标文件。
检查注册方法:确保使用registry.prepend()
接口进行解码器注册。如果您使用的是 append
方法注册,那么当 Glide 自带的解码器能够处理 HEIF 图时,将会优先使用自带解码器拦截,最终导致自定义的解码器无法得到执行。
检查缓存策略:在 Glide 中,当您需要使用自定义解码器插件处理 HEIF 时,如果设置了跳过磁盘缓存策略,Glide无法优先使用自定义解码器对图片进行解码,而会默认使用自带的解码器,这可能导致 HEIF 图像解码异常。此时,建议您采取以下操作进行排查和处理:
检查缓存策略:请确保没有设置跳过磁盘缓存的相关策略,如.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
或.diskCacheStrategy(DiskCacheStrategy.NONE)
。这样 Glide 才能将图像保存到磁盘缓存,并使用自定义解码器进行解码。
清理缓存:如果之前已经设置了跳过磁盘缓存策略,或者存在旧的缓存数据,建议清理缓存。通过清理缓存,可以删除旧的缓存文件,以防止其对解码效果产生影响。您可以使用 Glide 提供的缓存清理方法或手动删除相关缓存文件。