ImageKnife
專門為OpenHarmony打造的一款圖像加載緩存庫,致力于更高效、更輕便、更簡單。
簡介
OpenHarmony的自研版本:
- 支持內(nèi)存緩存,使用LRUCache算法,對圖片數(shù)據(jù)進行內(nèi)存緩存。
- 支持磁盤緩存,對于下載圖片會保存一份至磁盤當中。
- 支持進行圖片變換: 支持圖像像素源圖片變換效果。
- 支持用戶配置參數(shù)使用:( 例如:配置是否開啟一級內(nèi)存緩存,配置磁盤緩存策略,配置僅使用緩存加載數(shù)據(jù),配置圖片變換效果,配置占位圖,配置加載失敗占位圖等)。
- 推薦使用ImageKnifeComponent組件配合ImageKnifeOption參數(shù)來實現(xiàn)功能。
- 支持用戶自定義配置實現(xiàn)能力參考ImageKnifeComponent組件中對于入?yún)mageKnifeOption的處理。

下載安裝
ohpm install @ohos/imageknife
使用說明
1.依賴配置
在entrysrcmainetsentryabilityEntryAbility.ts中做如下配置初始化全局ImageKnife實例:
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import { ImageKnife } from '@ohos/imageknife'
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', (err, data) = > {
});
// 初始化全局ImageKnife
ImageKnife.with(this.context);
// 后續(xù)訪問ImageKnife請通過:ImageKnifeGlobal.getInstance().getImageKnife()方式
}
}
2.加載普通圖片
接下來我們來寫個簡單實例看看:
import { ImageKnifeComponent, ImageKnifeOption } from '@ohos/imageknife'
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
@State option: ImageKnifeOption = {
loadSrc: $r('app.media.icon')
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
ImageKnifeComponent({ imageKnifeOption: this.option })
.width(300)
.height(300)
}.width('100%')
}.height('100%')
}
}
非常簡單,僅需定義一個ImageKnifeOption數(shù)據(jù)對象,然后在你需要的UI位置,加入ImageKnifeComponent自定義組件就可以加載出一張圖像了。
3.加載SVG圖片
加載svg其實和普通流程沒有區(qū)別,只要將 loadSrc: $r('app.media.jpgSample'),``改成一張 loadSrc: $r('app.media.svgSample'), svg類型圖片即可。
4.加載GIF圖片
加載GIF其實和普通流程也沒有區(qū)別只要將 loadSrc: $r('app.media.jpgSample'),``改成一張 loadSrc: $r('app.media.gifSample'), GIF圖片即可。
5.自定義Key
因為通常改變標識符比較困難或者根本不可能,所以ImageKnife也提供了 簽名 API 來混合(你可以控制的)額外數(shù)據(jù)到你的緩存鍵中。 簽名(signature)適用于媒體內(nèi)容,也適用于你可以自行維護的一些版本元數(shù)據(jù)。
將簽名傳入加載請求
imageKnifeOption = {
loadSrc: 'https://aahyhy.oss-cn-beijing.aliyuncs.com/blue.jpg',
signature: new ObjectKey(new Date().getTime().toString())
}
詳細樣例請參考SignatureTestPage文件
代碼示例
進階使用
如果簡單的加載一張圖像無法滿足需求,我們可以看看ImageKnifeOption這個類提供了哪些擴展能力。
ImageKnifeOption參數(shù)列表
| 參數(shù)名稱 | 入?yún)?nèi)容 | 功能簡介 |
|---|---|---|
| loadSrc | string | PixelMap |
| mainScaleType | ScaleType | 設(shè)置主圖展示樣式(可選) |
| strategy | DiskStrategy | 設(shè)置磁盤緩存策略(可選) |
| dontAnimateFlag | boolean | gif加載展示一幀(可選) |
| placeholderSrc | PixelMap | Resource |
| placeholderScaleType | ScaleType | 設(shè)置占位圖展示樣式(可選) |
| errorholderSrc | PixelMap | Resource |
| errorholderSrcScaleType | ScaleType | 設(shè)置失敗占位圖展示樣式(可選) |
| retryholderSrc | PixelMap | Resource |
| retryholderScaleType | ScaleType | 設(shè)置重試占位圖展示樣式(可選) |
| thumbSizeMultiplier | number 范圍(0,1] | 設(shè)置縮略圖占比(可選) |
| thumbSizeDelay | number | 設(shè)置縮略圖展示時間(可選) |
| thumbSizeMultiplierScaleType | ScaleType | 設(shè)置縮略圖展示樣式(可選) |
| displayProgress | boolean | 設(shè)置是否展示下載進度條(可選) |
| canRetryClick | boolean | 設(shè)置重試圖層是否點擊重試(可選) |
| onlyRetrieveFromCache | boolean | 僅使用緩存加載數(shù)據(jù)(可選) |
| isCacheable | boolean | 是否開啟一級內(nèi)存緩存(可選) |
| gif | { // 返回一周期動畫gif消耗的時間 loopFinish?: (loopTime?) => void // gif播放速率相關(guān) speedFactory?: number // 直接展示gif第幾幀數(shù)據(jù) seekTo?: number } | GIF播放控制能力(可選) |
| transformation | BaseTransform | 單個變換(可選) |
| transformations | Array | 多個變換,目前僅支持單個變換(可選) |
| allCacheInfoCallback | IAllCacheInfoCallback | 輸出緩存相關(guān)內(nèi)容和信息(可選) |
| signature | ObjectKey | 自定key(可選) |
| drawLifeCycle | IDrawLifeCycle | 用戶自定義實現(xiàn)繪制方案(可選) |
| imageSmoothingEnabled | boolean | 抗鋸齒是否開啟屬性配置,設(shè)置為false時,imageSmoothingQuality失效 |
| imageSmoothingQuality | AntiAliasing | 抗鋸齒屬性配置 |
其他參數(shù)只需要在ImageKnifeOption對象上按需添加即可。
這里我們著重講一下 自定義實現(xiàn)繪制方案 。為了增強繪制擴展能力,目前ImageKnifeComponent使用了Canvas的渲染能力作為基礎(chǔ)。在此之上為了抽象組件繪制表達。我將圖像的狀態(tài)使用了 IDrawLifeCycle繪制生命周期進行表達 ,
大致流程 展示占位圖->展示網(wǎng)絡(luò)加載進度->展示縮略圖->展示主圖->展示重試圖層->展示失敗占位圖

ImageKnifeComponent內(nèi)部,責任鏈實現(xiàn)。 用戶參數(shù)設(shè)置->全局參數(shù)設(shè)置->自定義組件內(nèi)部設(shè)置
采用責任鏈的好處是,用戶可以通過自定義繪制,重新繪制圖層。如果不想繪制也可以通過預(yù)制回調(diào)獲取繪制流程信息。

場景1:默認的展示不滿足需求,需要加個圓角效果。
代碼如下:
import { ImageKnifeComponent } from '@ohos/imageknife'
import { ImageKnifeOption } from '@ohos/imageknife'
import { ImageKnifeDrawFactory } from '@ohos/imageknife'
@Entry
@Component
struct Index {
@State imageKnifeOption1: ImageKnifeOption = {
// 加載一張本地的jpg資源(必選)
loadSrc: $r('app.media.jpgSample'),
// 占位圖使用本地資源icon_loading(可選)
placeholderSrc: $r('app.media.icon_loading'),
// 失敗占位圖使用本地資源icon_failed(可選)
errorholderSrc: $r('app.media.icon_failed'),
// 繪制圓角30,邊框5,邊框"#ff00ff".用戶自定義繪制(可選)
drawLifeCycle:ImageKnifeDrawFactory.createRoundLifeCycle(5,"#ff00ff",30)
};
build(){
Scroll() {
Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) {
ImageKnifeComponent({ imageKnifeOption: this.imageKnifeOption1 })
.width(300)
.height(300)
}
}
.width('100%')
.height('100%')
}
}
ImageKnifeDrawFactory.createRoundLifeCycle(5,"#ff00ff",30) 我們深入查看源碼可以發(fā)現(xiàn),實際上是對IDrawLifeCycle接口的部分實現(xiàn),這里我介紹一下IDrawLifeCycle。
IDrawLifeCycle的返回值代表事件是否被消費,如果被消費接下來組件內(nèi)部就不會處理,如果沒被消費就會傳遞到下一個使用者。目前消費流程(用戶自定義-> 全局配置定義->組件內(nèi)部默認定義) *
所以我們在當數(shù)據(jù)是一張PixelMap的時候(目前jpg png bmp webp svg返回的都是PixelMap,gif返回GIFFrame數(shù)組),我們返回了true。消費了事件,代表這個繪制流程用戶自定義完成。

由于IDrawLifeCycle實現(xiàn)較為冗長,我們封裝了ImageKnifeDrawFactory工廠,提供了網(wǎng)絡(luò)下載百分比效果、圓角、橢圓添加邊框等能力。下面我們就再看看使用工廠封裝之后的場景代碼。
場景2: 網(wǎng)絡(luò)下載百分比效果展示
當進行加載網(wǎng)絡(luò)圖片時,可能需要展示網(wǎng)絡(luò)下載百分比動畫。但是默認的動畫又不能滿足需求,這個時候我們就需要自定義網(wǎng)絡(luò)下載百分比效果。代碼如下:
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
import { ImageKnifeGlobal,ImageKnife,ImageKnifeDrawFactory,LogUtil } from '@ohos/imageknife'
import abilityAccessCtrl,{Permissions} from '@ohos.abilityAccessCtrl';
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
//.. 刪除不必要代碼
windowStage.loadContent('pages/index', (err, data) = > {
});
// 初始化ImageKnifeGlobal和ImageKnife
ImageKnife.with(this.context);
// 全局配置網(wǎng)絡(luò)加載進度條 使用ImageKnifeGlobal.getInstance().getImageKnife()訪問ImageKnife
ImageKnifeGlobal.getInstance().getImageKnife().setDefaultLifeCycle(ImageKnifeDrawFactory.createProgressLifeCycle("#10a5ff", 0.5))
}
}
這里大家可能會問,為什么會將這個IDrawLifeCycle放在AbilityStage里面實現(xiàn)?
這是因為網(wǎng)絡(luò)下載百分比進度很多時候都是全局通用,如果有需要全局配置的自定義展示方案。推薦在AbilityStage里面,往ImageKnife的setDefaultLifeCycle函數(shù)中注入,即可將ImageKnifeComponent中的默認繪制方案替換。
在這里我們實現(xiàn)的效果如下圖所示。

高級用法
以上簡單使用和進階使用都是經(jīng)過一層自定義組件封裝之后形成的,RequestOption封裝成了ImageKnifeOption,繪制部分封裝成了自定義組件ImageKnifeComponent。
如果用戶其實并不關(guān)心繪制部分,或者說想用自己的通用方案對自定義組件ImageKnifeComponent重構(gòu)都是可以的。
下面我們會著重指導用戶如何復(fù)用圖片加載邏輯,重構(gòu)自定義組件ImageKnifeComponent。
首先我們先看看RequestOption構(gòu)建的內(nèi)容,如下所示:
數(shù)據(jù)加載
RequestOption構(gòu)建:
請查閱下文接口內(nèi)容:[RequestOption接口方法]
了解了RequestOption的參數(shù)內(nèi)容后,我們可以參考ImageKnifeComponent組件代碼進行分析。
從imageKnifeExecute()函數(shù)入口,首先我們需要構(gòu)建一個RequestOption對象,let request = new RequestOption(), 接下來就是按需配置request對象的內(nèi)容,最后使用 ImageKnifeGlobal.getInstance().getImageKnife()?.call(request)發(fā)送request執(zhí)行任務(wù)即可。
是不是很簡單,而其實最重要的內(nèi)容是就是: 按需配置request對象的內(nèi)容 為了更好理解,我舉例說明一下:
場景一: 簡單加載一張圖片
let request = new RequestOption();
// (必傳)
request.load("圖片url")
// (可選 整個request監(jiān)聽回調(diào))
.addListener({callback:(err:BusinessError|string, data:ImageKnifeData) = > {
// data 是ImageKnifeData對象
if(data.isPixelMap()){
// 這樣就獲取到了目標PixelMap
let pixelmap = data.drawPixleMap.imagePixelMap;
}
return false;
})
let compSize:Size = {
width: this.currentWidth,
height:this.currentHeight
}
// (必傳)這里setImageViewSize函數(shù)必傳組件大小,因為涉及到圖片變換效果都需要適配圖像源和組件大小
request.setImageViewSize(compSize)
// 最后使用ImageKnife的call函數(shù)調(diào)用request即可
let imageKnife:ImageKnife|undefined = ImageKnifeGlobal.getInstance().getImageKnife();
if(imageKnife != undefined){
imageKnife.call(request)
}
其他場景,可以按需加載
比如我需要配置 占位圖 只需要 在request對象創(chuàng)建好之后,調(diào)用 placeholder 函數(shù)即可
request.placeholder(this.imageKnifeOption.placeholderSrc, (data) = > {
console.log('request.placeholder callback')
this.displayPlaceholder(data)
})
再比如 我對緩存配置有要求,我要禁用內(nèi)存緩存,調(diào)用 skipMemoryCache 函數(shù)即可
request.skipMemoryCache(true)
這里只是簡單介紹部分使用,更多的內(nèi)容請參考 按需加載 原則,并且可以參考ImageKnifeComponent源碼或者根據(jù)文檔自行探索實現(xiàn)。
接口說明
RequestOption用戶配置參數(shù)
| 方法名 | 入?yún)?/th> | 接口描述 |
|---|---|---|
| load(src: string | PixelMap | Resource) |
| setImageViewSize(imageSize: { width: number, height: number }) | imageSize:{width: number, height: number } | 傳入顯示圖片組件的大小,變換的時候需要作為參考 |
| diskCacheStrategy(strategy: DiskStrategy) | strategy:DiskStrategy | 配置磁盤緩存策略 NONE SOURCE RESULT ALL AUTOMATIC |
| placeholder(src: PixelMap | Resource, func?: AsyncSuccess) | src: PixelMap |
| errorholder(src: PixelMap | Resource, func?: AsyncSuccess) | src: PixelMap |
| retryholder(src: PixelMap | Resource, func?: AsyncSuccess) | src: PixelMap |
| addListener(func: AsyncCallback) | func: AsyncCallback | 配置整個監(jiān)聽回調(diào),數(shù)據(jù)正常加載返回,加載失敗返回錯誤信息 |
| thumbnail(sizeMultiplier:number, func?: AsyncSuccess) | sizeMultiplier:number, func?: AsyncSuccess | 設(shè)置縮略圖比例,縮略圖返回后,加載并展示縮略圖 |
| addProgressListener(func?: AsyncSuccess) | func?: AsyncSuccess | 設(shè)置網(wǎng)絡(luò)下載百分比監(jiān)聽,返回數(shù)據(jù)加載百分比數(shù)值 |
| addAllCacheInfoCallback(func: IAllCacheInfoCallback) | func: IAllCacheInfoCallback | 設(shè)置獲取所有緩存信息監(jiān)聽 |
| skipMemoryCache(skip: boolean) | skip: boolean | 配置是否跳過內(nèi)存緩存 |
| retrieveDataFromCache(flag: boolean) | flag: boolean | 配置僅從緩存中加載數(shù)據(jù) |
| signature | ObjectKey | 自定義key |
同時支持[圖片變換相關(guān)]接口。
ImageKnife 啟動器/門面類
| 方法名 | 入?yún)?/th> | 接口描述 |
|---|---|---|
| call(request: RequestOption) | request: RequestOption | 根據(jù)用戶配置參數(shù)具體執(zhí)行加載流程 |
| preload(request: RequestOption) | request: RequestOption | 根據(jù)用戶配置參數(shù)具體執(zhí)行預(yù)加載流程 |
| pauseRequests() | 全局暫停請求 | |
| resumeRequests() | 全局恢復(fù)暫停 |
緩存策略相關(guān)
| 使用方法 | 類型 | 策略描述 |
|---|---|---|
| request.diskCacheStrategy(new ALL()) | ALL | 表示既緩存原始圖片,也緩存轉(zhuǎn)換過后的圖片 |
| request.diskCacheStrategy(new AUTOMATIC()) | AUTOMATIC | 表示嘗試對本地和遠程圖片使用適合的策略 |
| request.diskCacheStrategy(new DATA()) | DATA | 表示只緩存原始圖片 |
| request.diskCacheStrategy(new NONE()) | NONE | 表示不緩存任何內(nèi)容 |
| request.diskCacheStrategy(new RESOURCE()) | RESOURCE | 表示只緩存轉(zhuǎn)換過后的圖片 |
AntiAliasing類型展示效果
| 使用方法 | 類型 | 策略描述 |
|---|---|---|
| AntiAliasing.FIT_HIGH | String | 圖像抗鋸齒設(shè)置為高畫質(zhì) |
| AntiAliasing.FIT_MEDIUM | String | 圖像抗鋸齒設(shè)置為中畫質(zhì) |
| AntiAliasing.FIT_LOW | String | 圖像抗鋸齒設(shè)置為低畫質(zhì) |
ScaleType類型展示效果
| 使用方法 | 類型 | 策略描述 |
|---|---|---|
| ScaleType.FIT_START | int | 圖像位于用戶設(shè)置組件左上角顯示,圖像會縮放至全部展示 |
| ScaleType.FIT_END | int | 圖像位于用戶設(shè)置組件右下角顯示,圖像會縮放至全部展示 |
| ScaleType.FIT_CENTER | int | 圖像位于用戶設(shè)置組件居中,圖像會縮放至全部展示 |
| ScaleType.CENTER | int | 圖像居中展示,不縮放 |
| ScaleType.CENTER_CROP | int | 圖像的寬高長度,短的部分縮放至組件大小,超出的全部裁剪 |
| ScaleType.FIT_XY | int | 圖像拉伸至組件大小 |
| ScaleType.CENTER_INSIDE | int | 如果圖像大于組件則執(zhí)行FIT_CENTER,小于組件則CENTER |
| ScaleType.NONE | int | 如果不想適配,直接展示原圖大小 |
圖片變換相關(guān)
| 使用方法 | 類型 | 相關(guān)描述 |
|---|---|---|
| request.centerCrop() | CenterCrop | 可以根據(jù)圖片文件,目標顯示大小,進行對應(yīng)centerCrop |
| request.centerInside() | CenterInside | 可以根據(jù)圖片文件,目標顯示大小,進行對應(yīng)centerInside |
| request.fitCenter() | FitCenter | 可以根據(jù)圖片文件,目標顯示大小,進行對應(yīng)fitCenter |
| request.blur() | BlurTransformation | 模糊處理(圖片分辨率較大建議傳遞第二個參數(shù)將圖片進行縮小) |
| request.ightnessFilter() | BrightnessFilterTransformation | 亮度濾波器 |
| request.contrastFilter() | ContrastFilterTransformation | 對比度濾波器 |
| request.cropCircle() | CropCircleTransformation | 圓形剪裁顯示 |
| request.cropCircleWithBorder() | CropCircleWithBorderTransformation | 圓環(huán)展示 |
| request.cropSquare() | CropSquareTransformation | 正方形剪裁 |
| request.crop() | CropTransformation | 自定義矩形剪裁 |
| request.grayscale() | GrayscaleTransformation | 灰度級轉(zhuǎn)換 |
| request.invertFilter() | InvertFilterTransformation | 反轉(zhuǎn)濾波器 |
| request.pixelationFilter() | PixelationFilterTransformation | 像素化濾波器 |
| request.rotateImage() | RotateImageTransformation | 圖片旋轉(zhuǎn) |
| request.roundedCorners() | RoundedCornersTransformation | 圓角剪裁 |
| request.sepiaFilter() | SepiaFilterTransformation | 烏墨色濾波器 |
| request.sketchFilter() | SketchFilterTransformation | 素描濾波器 |
| request.mask() | MaskTransformation | 遮罩 |
| request.swirlFilter() | SwirlFilterTransformation | 扭曲濾波器 |
| request.kuwaharaFilter() | KuwaharaFilterTransform | 桑原濾波器 |
| request.toonFilter() | ToonFilterTransform | 動畫濾波器 |
| request.vignetteFilter() | VignetteFilterTransform | 裝飾濾波器 |

setLruCacheSize
setLruCacheSize(size: number,memory:number): void
設(shè)置圖片文件緩存的大小上限,size單位為張數(shù),memory單位為字節(jié),提升再次加載同源圖片的加載速度,特別是對網(wǎng)絡(luò)圖源會有較明顯提升。 如果不設(shè)置則默認為100張,100MB。緩存采用內(nèi)置的LRU策略。 size為0則代表不限制緩存張數(shù),memory為0則代表不限制緩存大小。 建議根據(jù)應(yīng)用實際需求,設(shè)置合理緩存上限,數(shù)字過大可能導致內(nèi)存占用過高,可能導致OOM異常。
參數(shù):
| 參數(shù)名 | 類型 | 必填 | 說明 |
|---|---|---|---|
| size | number | 是 | 圖片文件的緩存張數(shù),單位為張。只支持正整數(shù),0 |
| memory | number | 是 | 圖片文件的緩存大小,單位為字節(jié)。只支持正數(shù),0 |
示例:
//EntryAbility.ets
import { InitImageKnife } from '...imageknife'
export default class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
InitImageKnife.init(this.context);
let imageKnife: ImageKnife | undefined = ImageKnifeGlobal.getInstance().getImageKnife()
if (imageKnife != undefined) {
//設(shè)置全局內(nèi)存緩存大小張數(shù)
imageKnife.setLruCacheSize(100, 100 * 1204 * 1024)
}
}
}
約束與限制
在下述版本驗證通過: DevEco Studio 4.1(4.1.3.520)--SDK:API11( 4.1.0.63) DevEco Studio 4.1(4.1.3.418)--SDK:API11( 4.1.0.56) DevEco Studio 4.1(4.1.3.322)--SDK:API11( 4.1.0.36) DevEco Studio 4.0(4.0.3.700)--SDK:API10( 4.0.10.15)
HSP場景適配:
在使用ImageKnifeComponent進行加載圖片時, 提供的ImageKnifeOption配置類新增了可選參數(shù)context, 在HSP場景下需要傳入正確的context, 才能保證三方庫后續(xù)正確獲取Resource資源。
在使用RquestOption進行加載圖片時, 提供的RquestOption配置類新增了接口setModuleContext(moduleCtx:common.UIAbilityContext), 在HSP場景下需要傳入正確的context, 才能保證三方庫后續(xù)正確獲取Resource資源。
非HSP場景不影響原功能, ImageKnifeOption配置類新增的可選參數(shù)context可以不傳, RquestOption配置類新增的接口可以不調(diào)用。
更多鴻蒙開發(fā)知識已經(jīng)更新在[gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md]前往參考。

遺留問題
1.目前只支持一種圖片變換效果。
2.目前svg和gif動圖不支持變換效果。
補充說明
SVG標簽說明
使用版本為(SVG)1.1,當前支持的標簽列表有:
- a
- circla
- clipPath
- defs
- ellipse
- feBlend
- feColorMatrix
- feComposite
- feDiffuseLighting
- feDisplacementMap
- feDistantLight
- feFlood
- feGaussianBlur
- feImage
- feMorphology
- feOffset
- fePointLight
- feSpecularLighting
- feSpotLight
- feTurbulence
- filter
- g
- image
- line
- linearGradient
- mask
- path
- pattern
- polygon
- polyline
- radialGradient
- rect
- stop
- svg
- text
- textPath
- tspan
- use
審核編輯 黃宇
-
鴻蒙
+關(guān)注
關(guān)注
60文章
2963瀏覽量
45926 -
OpenHarmony
+關(guān)注
關(guān)注
33文章
3952瀏覽量
21120
發(fā)布評論請先 登錄
知乎開源“智能預(yù)渲染框架” 幾行代碼實現(xiàn)鴻蒙應(yīng)用頁面“秒開”
貨拉拉開源兩款三方庫,為鴻蒙應(yīng)用高效開發(fā)貢獻力量
鴻蒙非侵入式彈窗新解法,企查查正式開源“QuickDialog”彈窗組件庫
鴻蒙開發(fā)案例:【圖像加載緩存庫ImageKnife】
評論