【鸿蒙实战开发】ImageKnife组件解析

199 篇文章 8 订阅
198 篇文章 0 订阅

前言

图片是 UI 界面的重要元素之一,图片加载速度及效果直接影响应用体验。ArkUI 开发框架提供了丰富的图像处理能力,如图像解码、图像编码、图像编辑及基本的位图操作等,满足了开发者日常开发所需。但随着产品需求的日益增长,基本的图像处理能力已不能胜任某些比较复杂的应用场景,如无法直接获取缓存图片、无法配置占位图、无法进行自定义 PixelMap 图片变换等。为增强 ArkUI 开发框架的图像处理能力,ImageKnife 组件应运而生,本期我们将为大家带来 ImageKnife 的介绍。

一、ImageKnife简介

ImageKnife 是一个参考 Glide 框架进行设计,并基于 eTS 语言实现的图片处理组件,让开发者能轻松且高效地进行图片开发。而 Glide 是一个快速高效的图片加载库,它注重于平滑的滚动,提供了易用的 API,高性能、可扩展的图片解码管道,以及自动的资源池技术。

功能方面,ImageKnife 提供了自定义图片变换、占位图等图片处理能力,几乎满足了开发者进行图片处理的一切需求。性能方面,ImageKnife 采用 LRU 策略实现二级缓存,可灵活配置,有效减少内存消耗,提升了应用性能。使用方面,ImageKnife 封装了一套完整的图片加载流程,开发者只需根据 ImageKnifeOption 配置相关信息即可完成图片的开发,降低了开发难度,提升了开发效率。如图 1 所示,是 ImageKnife 加载图片的整体流程。

image.png

二、ImageKnife实现原理

下面我们将为大家介绍 ImageKnife 加载图片过程中每个环节的实现原理,让大家更深刻地认识 ImageKnife 组件。图 2 是 ImageKnife 加载图片的时序图:

image.png

2.1 用户配置信息

在加载图片前,用户需根据自身需求配置相应的参数,包括图片路径、图片大小、占位图及缓存策略等。ImageKnife 提供了 RequestOption 类,用于封装用户配置信息的接口,如图 3 所示列举了部分接口供大家参考:

image.png

通过 imageKnifeExecute() 方法获取用户配置信息,然后执行 ImageKnife.call(request),正式启动图片加载任务。相关实现代码如下:


imageKnifeExecute() {
  // 首先需要确保获取ImageKnife单例对象
  if(ImageKnife){
  }else{
    ImageKnife = globalThis.exports.default.data.imageKnife;
  }
  // 生成配置信息requestOption
  let request = new RequestOption();
  // 配置必要信息和回调
  this.configNecessary(request);
  // 配置缓存相关信息   
  this.configCacheStrategy(request);
  // 配置显示信息和回调 
  this.configDisplay(request);
  // 启动ImageKnife执行请求
  ImageKnife.call(request);
}

2.2 加载图片

加载图片过程是 ImageKnife 组件的核心部分,如图 4 所示,包含占位图填充、缓存实现及图片解码三个环节。下面我们将为大家分别介绍每个环节的实现。

image.png

2.2.1 占位图填充

占位图就是图片加载过程中页面上的过渡效果,通常表现形式是在页面上待加载区域填充灰色的占位图,可以使得页面框架不会因为加载失败而变形。ImageKnife 提供了占位图功能,开发者可在 RequestOption 中配置是否启动占位图任务。如图 5 所示是占位图工作流程,执行图片加载任务后,占位图会填充加载页面。如果图片解析成功则将页面上填充的占位图替换为待加载的图片。如果图片解析失败,则将页面上填充的占位图替换为“图片解析失败占位图”。

image.png

相关实现代码如下:


// 占位图解析成功
placeholderOnComplete(imageKnifeData: ImageKnifeData) {
// 主图未加载成功,并且未加载失败  显示占位图  主图加载成功或者加载失败后=>不展示占位图
  if (!this.loadMainReady && !this.loadErrorReady && !this.loadThumbnailReady) {
        this.placeholderFunc(imageKnifeData)
  }
}
// 加载失败 占位图解析成功
errorholderOnComplete(imageKnifeData: ImageKnifeData) {
  // 如果有错误占位图 先解析并保存在RequestOption中 等到加载失败时候进行调用
  this.errorholderData = imageKnifeData;
  if (this.loadErrorReady) {
    this.errorholderFunc(imageKnifeData)
  }

2.2.2 缓存实现

缓存是图片加载过程中最关键的环节,缓存机制直接影响了图片加载速度及图片滚动效果,开发者可通过以下方法来灵活配置缓存策略。

image.png

为了保障图片的加载速度,ImageKnife 通过使用 Least Recently Used(最近最少使用)清空策略来实现内存缓存及磁盘缓存。如图 7 所示,在图片加载过程中,CPU 会首先读取内存缓存中的数据,如果读取到图片资源则直接显示图片,否则读取磁盘缓存数据。如果在磁盘缓存上仍然没有读取到数据,则可判定为该图片为网络图片,这时需要将网络图片解码后再进行显示(后面章节会详细介绍),并将解码后的图片文件缓存至磁盘。

image.png

下面我们将分别介绍两种缓存机制的具体实现。

(1)内存缓存

内存缓存,就是指当前程序运行内存分配的临时存储器,当我们使用 ImageKnife 加载图片时,这张图片会被缓存到内存当中,只要在它还没从内存中被清除之前,下次再加载这张图片都会直接从内存中读取,而不用重新从网络或硬盘上读取,大幅度提升图片的加载效率。ImageKnife 内存缓存的实现,需控制最大空间(maxsize),以及目前占用空间(size),相关实现代码如下:


// 移除较少使用的缓存数据
trimToSize(tempsize: number) {
  while (true) {
    if (tempsize < 0) {
      this.map.clear()
      this.size = 0
      break
    }
    if (this.size <= tempsize || this.map.isEmpty()) {
      break
    }
    var delkey = this.map.getFirstKey()
    this.map.remove(delkey)
    this.size--
  }
}

// 缓存数据最大值
maxSize(): number{
  return this.maxsize
}

// 设置缓存数据量最大值
resize(maxsize: number) {
  if (maxsize < 0) {
    throw new Error('maxsize <0 & maxsize invalid');
  }
  this.maxsize = maxsize
  this.trimToSize(maxsize)
}

// 清除缓存
evicAll() {
  this.trimToSize(-1)
}

(2)磁盘缓存

默认情况下,磁盘缓存的是解码后的图片文件,需防止应用重复从网络或其他地方下载和读取数据。ImageKnife 磁盘缓存的实现,主要依靠 journal 文件对缓存数据进行保存,保证程序磁盘缓存内容的持久化问题。相关实现代码:


//读取journal文件的缓存数据
readJournal(path: string) {
  var fileReader = new FileReader(path)
  var line: string = ''
  while (!fileReader.isEnd()) {
    line = fileReader.readLine()
    line = line.replace('\n', '').replace('\r', '')
    this.dealwithJournal(line)
  }
  this.fileUtils.deleteFile(this.journalPathTemp)
  this.trimToSize()
}

//根据LRU算法删除多余缓存数据
private trimToSize() {
  while (this.size > this.maxSize) {
    var tempkey: string = this.cacheMap.getFirstKey()
    var fileSize = this.fileUtils.getFileSize(this.dirPath + tempkey)
    if (fileSize > 0) {
      this.size = this.size - fileSize
    }
    this.fileUtils.deleteFile(this.dirPath + tempkey)
    this.cacheMap.remove(tempkey)
    this.fileUtils.writeData(this.journalPath, 'remove ' + tempkey + '\n')
  }
}

//清除所有disk缓存数据
cleanCacheData() {
  var length = this.cacheMap.size()
  for (var index = 0; index < length; index++) {
    this.fileUtils.deleteFile(this.dirPath + this.cacheMap[index])
  }
  this.fileUtils.deleteFile(this.journalPath)
  this.cacheMap.clear()
  this.size = 0
}

2.2.3 图片解码

当我们使用 ImageKnife 去加载一张图片的时候,并不是将原始图片直接显示出来,而是会进行图片解码后再显示到页面。图片解码就是将不同格式的图片(包括 JPEG、PNG、GIF、WebP、BMP)解码成统一格式的 PixelMap 图片文件。ImageKnife 的图片解码能力依赖的是 ArkUI 开发框架提供的 ImageSource 解码能力。通过 import image from '@ohos.multimedia.image’导入 ArkUI 开发框架的图片能力,并调用 createImageSource() 方法获取,实现代码如下:

import image from '@ohos.multimedia.image'

2.3 显示图片

获取到 PixelMap 解码文件后,接下来就是将它渲染到应用界面上。ImageKnife 的图片渲染能力依赖的是 ArkUI 开发框架提供的 image 组件的渲染能力。由于 eTS 是声明式的,我们无法直接获得 Image 组件的对象,需要依赖 ArkUI 开发框架的 @State 能力绑定输入参数,在改变属性对象之后,通知 UI 组件重新渲染,达到图片显示的效果,相关代码如下:


import image from '@ohos.multimedia.image'

export class TransformUtils {
  static centerCrop(buf: ArrayBuffer, outWidth: number, outHeihgt: number,
                    callback?: AsyncTransform<Promise<PixelMap>>) {
    // 创建媒体解码imageSource
    var imageSource = image.createImageSource(buf as any);
    // 获取图片信息
    imageSource.getImageInfo()
      .then((p) => {
        var sw;
        var sh;
        var scale;
        var pw = p.size.width;
        var ph = p.size.height;
        // 根据centerCrop规则控制缩放比例
        if (pw == outWidth && ph == outHeihgt) {
          sw = outWidth;
          sh = outHeihgt;
        } else {
          if (pw * outHeihgt > outWidth * ph) {
            scale = outHeihgt / ph;
          } else {
            scale = outWidth / pw;
          }
          sw = pw * scale;
          sh = ph * scale;
        }
        var options = {
          editable: true,
          rotate: 0,
          desiredRegion: { size: { width: sw, height: sh },
            x: pw / 2 - sw / 2,
            y: ph / 2 - sh / 2,
          },
        }
        if (callback) {
          // 回调,创建相关配置pixelmap
          callback('', imageSource.createPixelMap(options));
        }
      })
      .catch((error) => {
        callback(error, null);
      })
  }
}

注:@State 装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的 build 方法进行 UI 刷新。

三、ImageKnife实战

通过上文的介绍,相信大家对 ImageKnife 组件有了深刻的了解。下面我们将创建一个 ImageKnife_Test 项目,为大家展示 ArkUI 开发框架中 ImageKnife 组件的使用。通过将 ImageKnife 组件下载至项目中,然后根据 ImageKnifeOption 配置相关信息,即可完成 GIF 图片的加载。

3.1 创建项目

如图 8 所示,在 DevEco Studio 中新建 ImageKnife_Test 项目,项目类型选择 Application,语言选择 eTS,点击 Finish 完成创建。

image.png

3.2 添加依赖

成功创建项目后,接下来就是将 ImageKnife 组件下载至项目中。

首先,我们需找到 .npmrc 配置文件,并在文件中添加 @ohos 的 scope 仓库地址:@ohos:registry=https://repo.harmonyos.com/npm/,如图 9 所示:

image.png

配置好 npm 仓库地址后,如图 10 所示,在 DevEco Studio 的底部导航栏,点击“Terminal”(快捷键 Alt+F12),键入命令:npm install @ohos/imageknife 并回车,此时 ImageKnife 组件会被自动下载至项目中。下载完成后工程根目录下会生成 node_modules/@ohos/imageknife 目录。

image.png

3.3 编写逻辑代码

ImageKnife 组件成功下载至项目中后,接下来就是逻辑代码编写,这里我们将为大家介绍两种使用方式:

方式一:首先初始化全局 ImageKnife 实例,然后在 app.ets 中调用 ImageKnife.with() 进行初始化,相关代码如下:


import {ImageKnife} from '@ohos/imageknife'
export default {
  data: {
    imageKnife: {} // ImageKnife
  },
  onCreate() {
    this.data.imageKnife = ImageKnife.with();
  },
  onDestroy() {
  },
}
然后在页面index.ets中使用imageknife,相关代码如下:
@Entry
@Component
struct Index {
  build() {

  }

  // 页面初始化完成,生命周期回调函数中 进行调用ImageKnife
  aboutToAppear() {
    let requestOption = new RequestOption();
  requestOptin.load($r('app.media.IceCream'))
  .addListener((err,data) => {
      //加载成功/失败回调监听
    })
    ...
  ImageKnife.call(requestOption)
  }
}

var ImageKnife;
var defaultTemp = globalThis.exports.default
if (defaultTemp != undefined) {
  ImageKnife = defaultTemp.data.imageKnife;
}

方式二:在 index.ets 中,直接使用 ImageKnifeOption 作为入参,并配合自定义组件 ImageKnifeComponent 使用,相关代码如下:


import {ImageKnifeOption} from '@ohos/imageknife'

@Entry
@Component
struct Index {
  @State imageKnifeOption1: ImageKnifeOption =
    {
      loadSrc: $r('app.media.gifSample'),
      size: { width: 300, height: 300 },
      placeholderSrc: $r('app.media.icon_loading'),
      errorholderSrc: $r('app.media.icon_failed')
    };
  build() {
    Scroll() {
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
        ImageKnifeComponent({ imageKnifeOption: $imageKnifeOption1 })
      }
    }
    .width('100%')
    .height('100%')
  }
}

写在最后

有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)文档用来跟着学习是非常有必要的。

这份鸿蒙(HarmonyOS NEXT)文档包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。

希望这一份鸿蒙学习文档能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!

获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

鸿蒙(HarmonyOS NEXT)5.0最新学习路线

在这里插入图片描述

有了路线图,怎么能没有学习文档呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。

获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习文档

《鸿蒙 (OpenHarmony)开发入门教学视频》

在这里插入图片描述

《鸿蒙生态应用开发V3.0白皮书》

在这里插入图片描述

《鸿蒙 (OpenHarmony)开发基础到实战手册》

OpenHarmony北向、南向开发环境搭建

在这里插入图片描述

《鸿蒙开发基础》

●ArkTS语言
●安装DevEco Studio
●运用你的第一个ArkTS应用
●ArkUI声明式UI开发
.……
在这里插入图片描述

《鸿蒙开发进阶》

●Stage模型入门
●网络管理
●数据管理
●电话服务
●分布式应用开发
●通知与窗口管理
●多媒体技术
●安全技能
●任务管理
●WebGL
●国际化开发
●应用测试
●DFX面向未来设计
●鸿蒙系统移植和裁剪定制
……
在这里插入图片描述

《鸿蒙进阶实战》

●ArkTS实践
●UIAbility应用
●网络案例
……
在这里插入图片描述

获取以上完整鸿蒙HarmonyOS学习文档,请点击→纯血版全套鸿蒙HarmonyOS学习文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值