You need to enable JavaScript to run this app.
导航
Glide 接入 HEIF 解码库操作说明
最近更新时间:2024.10.17 11:43:27首次发布时间:2024.05.07 15:24:06

本文为您介绍 Glide 接入 veImageX 提供的 HEIF 解码库的接入流程,实现 HEIF 加载。

注意事项

  • 当前仅支持解码 HEIF 静图,HEIF 动图暂不支持。

  • 建议使用 4.0.0 及以上版本的 Glide。

  • 通过 Glide 使用解码库前,请确保已注册解码器插件

    说明

    解码器插件分为HeifByteBufferBitmapDecoderStreamHeifDecoder两种,分别可解码网络图和本地图。您在通过 Glide 使用 veImageX 解码库前,请确保已完成相应图片对应类型解码器的配置和注册操作。

前提条件

您已完成独立 HEIF 解码库的集成准备

添加 Maven 仓库

在 project 根目录下的build.gradle下配置服务,代码示例如下所示:

maven {
    url 'https://artifact.bytedance.com/repository/Volcengine/'
    // 最新版本号获取地址:https://artifact.bytedance.com/repository/Volcengine/
}

添加 SDK 依赖

说明

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 插件。

  1. 在 gradle 项目文件中添加 ksp 插件

  2. 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 及以上版本)
}
   

配置授权

在自定义的ApplicationonCreate方法中执行以下代码:

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)

常见问题

使用 Glide 自定义解码器插件时处理 HEIF 图时加载异常的排查建议。

  1. 检查依赖配置:确保HeifGlideModule类在编译构建后成功生成有效的注册代码。如果HeifGlideModule没有被其他类引用到,通常是因为 Gradle 文件配置的问题,请检查是否正确配置了 Glide 依赖,确保配置正确并成功生成目标文件。

  2. 检查注册方法:确保使用registry.prepend()接口进行解码器注册。如果您使用的是 append 方法注册,那么当 Glide 自带的解码器能够处理 HEIF 图时,将会优先使用自带解码器拦截,最终导致自定义的解码器无法得到执行。

  3. 检查缓存策略:在 Glide 中,当您需要使用自定义解码器插件处理 HEIF 时,如果设置了跳过磁盘缓存策略,Glide无法优先使用自定义解码器对图片进行解码,而会默认使用自带的解码器,这可能导致 HEIF 图像解码异常。此时,建议您采取以下操作进行排查和处理:

    1. 检查缓存策略:请确保没有设置跳过磁盘缓存的相关策略,如.diskCacheStrategy(DiskCacheStrategy.RESOURCE).diskCacheStrategy(DiskCacheStrategy.NONE)。这样 Glide 才能将图像保存到磁盘缓存,并使用自定义解码器进行解码。

    2. 清理缓存:如果之前已经设置了跳过磁盘缓存策略,或者存在旧的缓存数据,建议清理缓存。通过清理缓存,可以删除旧的缓存文件,以防止其对解码效果产生影响。您可以使用 Glide 提供的缓存清理方法或手动删除相关缓存文件。