那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

通過Glide組件的GIF能力解讀Glide加載資源的過程

DRXU_gh_019562b ? 來源:HarmonyOS開發者 ? 作者: HarmonyOS ? 2021-08-20 10:17 ? 次閱讀

HarmonyOS Glide組件是一款非常優秀的圖片處理工具,不僅支持多種格式圖片的加載,而且采用磁盤緩存和內存緩存方式實現圖片的預加載,同時還能指定圖片緩存大小,節省內存。本文將通過介紹Glide組件的GIF能力,來解讀Glide加載資源的過程。

通過以上視頻可以看到,一張網絡上的GIF圖片已經被成功下載,并且展示到Image控件上了。

我們到底做了什么?實際上核心的代碼就只有這一段而已:

Glide.with(classcontext) .asGif() .load(uri) .into(image);雖說只有這簡簡單單的一段代碼,但大家可能不知道的是,Glide在背后幫我們默默執行了成噸的工作。下面,我們將圍繞著這段簡單的代碼,來解讀Glide加載GIF的過程。

一、加載過程與數據轉換

在開始解讀Glide加載GIF的過程之前,先說明一下圖片的加載過程以及圖片加載過程中的數據轉換,便于后面對整個過程的理解。如下所示,是GIF的加載過程:

10fddf72-00ed-11ec-9bcf-12bb97331649.png

如下所示,是GIF加載過程中的數據轉換:

113d68ae-00ed-11ec-9bcf-12bb97331649.png

1、load狀態傳入的model類型

2、request狀態獲取的數據類型

3、原數據經過decoder和transcode之后的數據類型

4、transformation變換

5、animation加載動畫實現

二、Glide.With()

with()方法是Glide類中的一組靜態方法,用于獲取RequestManager對象。Glide.with(Context)流程如下所示:

115d9a2a-00ed-11ec-9bcf-12bb97331649.png

1.通過Glide.get(context)初始化Glide2.通過GlideBuilder初始化各項配置3.返回requestManagerRetriever對象4.調用RequestManagerRetriever中的get方法,通過RequestManagerFactory中的build()方法創建并返回了RequestManager,用于管理Glide的請求。

三、Glide.asGif()

通過asGif()方法,規定了最后資源轉化類型為 GifDrawable。如果加載的資源不是GIF,則將操作失敗。這里需要注意的是如果加載的是GIF文件,即使沒有使用asGif()方法,但只要配合DraweeView使用,最終解析還是會走GIF流程。如果用戶希望解析的GIF顯示為一張單幀圖片,那么一定要在asBitmap ()方法中聲明需求,讓Glide知道需要的僅僅是一張單幀圖片而非GIF。

四、Glide.load()

load()方法用于創建一個目標為Drawable的圖片加載請求,傳入需要加載的資源(String,URL,URI等)。由于with()方法返回的是一個RequestManager對象,那么很容易就能想到,load()方法是在RequestManager類當中。通過調用asDrawable()方法,創建一個目標為Drawable的圖片加載請求RequestBuilder。load方法比較簡單,流程也比較清晰,主要是保存用戶傳入的參數,包括load傳入的model和RequestOption構建的參數都會被記錄保存,用于后續構建Request使用。如下所示:

116cf5e2-00ed-11ec-9bcf-12bb97331649.png

五、Glide.into()

如果說前面都是在準備開胃小菜的話,那么現在終于要進入主菜了,因為into()方法是整個Glide圖片加載流程中邏輯最復雜的地方,into()方法的作用是在子線程中網絡請求解析圖片,并回到主線程中繪制圖片。由于into()過程非常復雜,所以我們將這部分拆分為三個小節進行講解。

1.資源加載Into()方法從load()創建的圖片加載請求RequestBuilder開始。資源加載過程中,通過onSizeReady()函數獲取image控件的寬和高。如果已知控件寬、高則直接進入onSizeReady函數執行后續任務。如果控件寬、高未知,則會在ViewTarget中進行監聽回調,待控件擁有寬高之后再執行onSizeReady函數和后續任務。

119dac0a-00ed-11ec-9bcf-12bb97331649.png

進入engine.load函數后。首先通過loadFromMemory()函數,加載activeResource中的緩存資源,如果activeResource沒有找到資源,則會通過loadFromLruCache()方法,到LruCache緩存中尋找資源。如果通過以上方法都沒有找到緩存資源,則會開啟新的任務進行加載。在waitForExistingOrStartNewJob()方法中創建EngineJob和DecodeJob,然后通過EngineJob執行DecodeJob,解析任務。如下圖所示:

11e7551c-00ed-11ec-9bcf-12bb97331649.png

2.資源解析

完成資源加載之后,Glide會進入資源解析,通過decodeResourceWithList()方法獲取對應的解析器。代碼如下所示

private Resource《ResourceType》 decodeResourceWithList( DataRewinder《DataType》 rewinder,int width,int height,Options options,List《Throwable》 exceptions) throws GlideException { Resource《ResourceType》 result = null; for (int i = 0, size = decoders.size(); i 《 size; i++) { // 循環去獲取對應的解析器 ResourceDecoder《DataType, ResourceType》 decoder = decoders.get(i); try { DataType data = rewinder.rewindAndGet(); if (decoder.handles(data, options)) { data = rewinder.rewindAndGet(); result = decoder.decode(data, width, height, options); } } catch (IOException | RuntimeException | OutOfMemoryError e) { } } return result;}

然后通過DataType、ResourceType來尋找具體實現類,發現byteBufferGifDecoder的decode才是真正的執行者。

/* GIFs */.append( Registry.BUCKET_GIF, InputStream.class, GifDrawable.class, new StreamGifDecoder(imageHeaderParsers, byteBufferGifDecoder, arrayPool))ByteBufferGifDecoder byteBufferGifDecoder = new ByteBufferGifDecoder(context, imageHeaderParsers, bitmapPool, arrayPool);

下面是ByteBufferGifDecoder的資源解析過程,解析完成后會生成一個GifDrawable回調資源。

// 生成GifDecoder GIF的解析工作是GifDecoder承擔的 GifDecoder gifDecoder = gifDecoderFactory.build(provider, header, byteBuffer, sampleSize); gifDecoder.setDefaultBitmapConfig(config); gifDecoder.advance(); PixelMap firstFrame = gifDecoder.getNextFrame(); // 此處生成 gifDrawable GifDrawable gifDrawable = new GifDrawable(context, gifDecoder, unitTransformation, width, height, firstFrame); return new GifDrawableResource(gifDrawable);

如果成功獲取resource就執行回調通知,onResourceReady()用于將圖片顯示到DraweeView上。

public void onResourceReady(@NotNull Z resource, @Nullable Transition《? super Z》 transition) { if (transition == null || !transition.transition(resource, this)) { setResourceInternal(resource); } else { maybeUpdateAnimatable(resource); }}

如果resource繼承了Animatable,就會觸發animatable.start()進行GIF的加載和繪制。

private void maybeUpdateAnimatable(@Nullable Z resource) { if (resource instanceof Animatable) { animatable = (Animatable) resource; // GIFDrawable繼承了Animatable所以接下來GIF流程查看GIFDrawable.java animatable.start(); } else { animatable = null; }}

3.GIF加載和繪制GIF的加載和繪制就是通過將GIF解析成一張張的單幀圖片,然后再將單幀圖片循環不停地繪制到canvas上,從而實現動畫效果。GIF加載和繪制的序列圖如下:

11f461ee-00ed-11ec-9bcf-12bb97331649.png

3.1GIF加載Glide 加載 GIF 的原理就是將GIF 解碼成多張圖片進行無限輪播,每幀切換都是一次圖片加載請求,當加載到新的一幀數據時會對舊的一幀數據進行清除,然后再繼續下一幀數據的加載請求,以此類推。在GIF加載和繪制的序列圖中可以看到,ImageViewTarget中的onResourceReady觸發onStart() =》realStart()=》startRunning()。當GIF為單張圖片的時候就直接繪制。當GIF為多張圖片就先加載第一張,然后注冊frameLoader的回調。

private void startRunning() { if (state.frameLoader.getFrameCount() == 1) { invalidateSelf(); } else if (!isRunning) { isRunning = true; state.frameLoader.subscribe(this); invalidateSelf(); }else{ } } // 注冊frameLoader的回調 void subscribe(FrameCallback frameCallback) { boolean start = callbacks.isEmpty(); callbacks.add(frameCallback); if (start) { start(); } }到這里,就是整個GIF加載的關鍵了,通過loadNextFrame加載GIF的下一幀。

private void loadNextFrame() { isLoadPending = true; // 獲取解析器當前幀到下一幀的延遲時間 int delay = gifDecoder.getNextDelay(); // 獲取系統當前時間+延時時間 long targetTime = SystemClock.uptimeMillis() + delay; // 將GIF的當前幀往后+1 gifDecoder.advance(); // 創建出DelayTarget任務 next = new DelayTarget(handler, gifDecoder.getCurrentFrameIndex(), targetTime); // 啟動DelayTarget requestBuilder.apply(signatureOf(getFrameSignature())).load(gifDecoder).into(next); }

然后進入DelayTarget類中執行onSourceReady()方法,使用EventHandler將PixelMap的resource傳到主線程上,用于定時發送解析好的資源。

public void onResourceReady( PixelMap resource, @Nullable Transition《? super PixelMap》 transition) { this.resource = resource; InnerEvent innerEvent = InnerEvent.get(FrameLoaderCallback.MSG_DELAY, this); // 使用handler發送消息,此處會將解析好的資源定時發送FrameLoaderCallback handler.sendTimingEvent(innerEvent, targetTime); }FrameLoaderCallback是EventHandler的實現類,用于接收EventHandler發送過來的任務,并觸發onFrameReady函數。

private class FrameLoaderCallback extends EventHandler{ static final int MSG_DELAY = 1; static final int MSG_CLEAR = 2; @Synthetic FrameLoaderCallback() { super(EventRunner.getMainEventRunner()); } @Override protected void processEvent(InnerEvent event) { if (event.eventId == MSG_DELAY) { DelayTarget target = (DelayTarget) event.object // 接收到消息,觸發onFrameReady函數 onFrameReady(target); return; } else if (event.eventId == MSG_CLEAR) { DelayTarget target = (DelayTarget) event.object; requestManager.clear(target); } return; } }當上一幀加載完成后, GifFrameLoader類中的onFrameReady(target)方法觸發繪制的回調操作,然后進入加載GIF的下一幀。同時,會通過FrameLoaderCallback.MSG_CLEAR對舊的一幀數據進行清除。清除完后再次通過loadNextFrame()加載下一幀,實現了GIF循環不停去加載下一幀的這個流程,直到加載完整個GIF。

void onFrameReady(DelayTarget delayTarget) { // 觸發了 GifDrawable.java的繪制回調操作 if (delayTarget.getResource() != null) { recycleFirstFrame(); DelayTarget previous = current; current = delayTarget; for (int i = callbacks.size() - 1; i 》= 0; i--) { FrameCallback cb = callbacks.get(i); // 注冊在GifFrameLoader的GifDrawable會接收onFrameReady回調通知 cb.onFrameReady(); } if (previous != null) { // 這里將上一個target給清理了 InnerEvent innerEvent = InnerEvent.get(FrameLoaderCallback.MSG_CLEAR, previous); handler.sendEvent(innerEvent); } } // 加載下一幀,構成了gif的循環不停的地去執行這個流程 loadNextFrame(); }3.2GIF繪制GIF繪制,就是將解析后的圖片通過invalidateSelf()方法通知DraweeView進行重繪。在繪制過程中invalideDraweeView通過調用GifDrawable的drawToCanvas()方法將圖片繪制到Canvas上。GifDrawable類中的onFrameReady()調用的invalidateSelf()函數用于執行繪制任務

public void onFrameReady() { // 如果沒有找到Callback的實現控件就停止繪制最后一幀 if (findCallback() == null) { stop(); invalidateSelf(); return; } // 執行繪制流程 invalidateSelf(); if (getFrameIndex() == getFrameCount() - 1) { // 循環次數計數 loopCount++; } // 非無限循環并且達到設置最大值停止gif if (maxLoopCount != LOOP_FOREVER && loopCount 》= maxLoopCount) { stop(); } }public void invalidateSelf(){ final Callback callback = getHmCallback(); if(callback!=null){ // 這里的callback就是注冊Callback函數的組件,此處是DraweeView callback.invalidateDrawable(this); }else{ }}通過調用setImageElement(((RootShapeElement) resource))方法,實現Callback接口

protected void setResource(@Nullable Element resource) { if(resource instanceof PixelMapElement) { view.setPixelMap(((PixelMapElement) resource).getPixelMap()); }else if(resource instanceof RootShapeElement){ view.setImageElement(((RootShapeElement) resource)); } }public void setImageElement(Element element) { if(element == null){ // 如果設置的內容為null 則去刷新圖片并且清空之前的東西 invalidate(); return; } super.setImageElement(element); element.setCallback(this::onChange); if(element instanceof RootShapeElement){ // 將組件注冊到RootShapeElement中 ((RootShapeElement) element).setHmCallback(this); } }最后通過drawToCanvas()方法生成空白PixelMap交給GifDrawable繪制,并根據scaleMode()方法重新設置最后生成圖像的位置。

private void init(Context context) { setBindStateChangedListener(this); addDrawTask(this::drawToCanvas); setTouchEventListener(this::onTouchEvent); } private void drawToCanvas(Component component, Canvas canvas) { if(getImageElement() instanceof RootShapeElement){ RootShapeElement rootShapeElement = (RootShapeElement) getImageElement(); int rw = rootShapeElement.getIntrinsicWidth(); int rh = rootShapeElement.getIntrinsicHeight(); int cw = component.getWidth(); int ch = component.getHeight(); PixelMap.InitializationOptions opts = new PixelMap.InitializationOptions(); opts.size = new Size(rw, rh); opts.pixelFormat = PixelFormat.ARGB_8888; opts.editable = true; PixelMap gifmap = PixelMap.create(opts); // 生成空白PixelMap交給GifDrawable繪制 applyDrawToCanvas(gifmap); RectFloat src = new RectFloat(0,0,cw,ch); // 根據scaleMode重新設置最后生成圖像的位置 RectFloat dst = scaleTypeFixed(gifmap,component); PixelMapHolder pixelMapHolder = new PixelMapHolder(gifmap); canvas.drawPixelMapHolderRect(pixelMapHolder, src, dst, getGifDrawPaint()); } }private void applyDrawToCanvas(PixelMap targetBitmap){ BITMAP_DRAWABLE_LOCK.lock(); try { Canvas canvasRootShape = new Canvas(new Texture(targetBitmap)); // 將canvas交給RootShapeElement,gifDrawable會調用RootShapeElement的drawToCanvas 進行繪制 getImageElement().drawToCanvas(canvasRootShape); clear(canvasRootShape); } finally { BITMAP_DRAWABLE_LOCK.unlock(); } }至此,整個GIF的流程就走了一遍。

六、課題延伸

因為GIF加載過程其實是無限循環加載單張圖片的過程,其實對系統的性能消耗還是非常大的。所以在使用GIF的時候,一定要堅持用完之后及時釋放資源。在這里因為HarmonyOS的生命周期和Android有所不同,所以在DraweeView開放了stopGif()方法,當你的GIF不打算用之后,請務必先調用stopGif(),防止內存泄露。

重要提示:

1、目前必須配合DraweeView使用GIF。

2、如果Glide使用了生命周期較長的上下文,例如applicationContext,則在GIF頁面結束時調用繪制視圖的stopGif方法停止Glide,以減少資源浪費。

3.如果您想使用Glid的GIF能力,但原生Image不支持此功能,因為Image和Element是獨立的,不能使用Element重繪。要支持GIF,您需要自定義Image。具體可以參考DraweeView的實現

編輯:jq

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 內存
    +關注

    關注

    8

    文章

    3055

    瀏覽量

    74336
  • GIF
    GIF
    +關注

    關注

    0

    文章

    24

    瀏覽量

    6623
  • HarmonyOS
    +關注

    關注

    79

    文章

    1982

    瀏覽量

    30579

原文標題:淺談HarmonyOS Glide組件的GIF能力

文章出處:【微信號:gh_019562b5fb4b,微信公眾號:gh_019562b5fb4b】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    EE-240: ADSP-BF533 Blackfin加載過程

    電子發燒友網站提供《EE-240: ADSP-BF533 Blackfin加載過程.pdf》資料免費下載
    發表于 01-05 10:00 ?0次下載
    EE-240: ADSP-BF533 Blackfin<b class='flag-5'>加載</b><b class='flag-5'>過程</b>

    HarmonyOS Web開發性能優化指導

    加載速度。 預下載:預下載指在頁面加載之前提前下載所需的資源,以避免在頁面加載過程中資源下載導致的阻塞和耗時。
    發表于 12-06 08:41

    靜態與動態載荷下具有粘彈性密封劑光伏組件的力學特性分析

    對光伏組件和建筑集成光伏系統均需要進行機械性能評估,這一評估是確保這些系統長期功能性和優化商業產品的關鍵步驟。通過機械加載、非均勻機械加載和動態機械
    的頭像 發表于 11-19 01:03 ?362次閱讀
    靜態與動態載荷下具有粘彈性密封劑光伏<b class='flag-5'>組件</b>的力學特性分析

    通過DaVinci TMS320DM644x的串行接口加載基本應用程序

    電子發燒友網站提供《通過DaVinci TMS320DM644x的串行接口加載基本應用程序.pdf》資料免費下載
    發表于 10-16 11:52 ?0次下載
    <b class='flag-5'>通過</b>DaVinci TMS320DM644x的串行接口<b class='flag-5'>加載</b>基本應用程序

    如何提高TLV61046A在啟動時的加載能力

    電子發燒友網站提供《如何提高TLV61046A在啟動時的加載能力.pdf》資料免費下載
    發表于 09-25 11:34 ?1次下載
    如何提高TLV61046A在啟動時的<b class='flag-5'>加載</b><b class='flag-5'>能力</b>

    OMAPL138/C6748 ROM引導加載程序資源和常見問題解答

    電子發燒友網站提供《OMAPL138/C6748 ROM引導加載程序資源和常見問題解答.pdf》資料免費下載
    發表于 09-04 09:31 ?0次下載
    OMAPL138/C6748 ROM引導<b class='flag-5'>加載</b>程序<b class='flag-5'>資源</b>和常見問題解答

    解讀PyTorch模型訓練過程

    PyTorch作為一個開源的機器學習庫,以其動態計算圖、易于使用的API和強大的靈活性,在深度學習領域得到了廣泛的應用。本文將深入解讀PyTorch模型訓練的全過程,包括數據準備、模型構建、訓練循環、評估與保存等關鍵步驟,并結合相關數字和信息進行詳細闡述。
    的頭像 發表于 07-03 16:07 ?1164次閱讀

    鴻蒙ArkTS聲明式組件:Progress

    進度條組件,用于顯示內容加載或操作處理等進度。
    的頭像 發表于 06-27 14:48 ?622次閱讀
    鴻蒙ArkTS聲明式<b class='flag-5'>組件</b>:Progress

    鴻蒙ArkTS聲明式組件:LoadingProgress

    用于顯示加載動效的組件
    的頭像 發表于 06-24 16:53 ?712次閱讀
    鴻蒙ArkTS聲明式<b class='flag-5'>組件</b>:LoadingProgress

    鴻蒙ArkTS聲明式組件:Image

    Image為圖片組件,常用于在應用中顯示圖片。Image支持加載[PixelMap]、[ResourceStr]和[DrawableDescriptor]類型的數據源,支持png、jpg、bmp、svg和gif類型的圖片格式。
    的頭像 發表于 06-23 20:32 ?1088次閱讀
    鴻蒙ArkTS聲明式<b class='flag-5'>組件</b>:Image

    機械載荷測試模擬光伏組件在實際應用過程中抗風壓、抗沖擊能力

    光伏組件的日益高功率化、組件尺寸增大成為趨勢,組件在工廠處理、運輸、安裝過程中會收到各種各樣的力,在戶外會受到風、雪、冰等重物的壓力,如果組件
    的頭像 發表于 05-16 08:32 ?1014次閱讀
    機械載荷測試模擬光伏<b class='flag-5'>組件</b>在實際應用<b class='flag-5'>過程</b>中抗風壓、抗沖擊<b class='flag-5'>能力</b>

    純血鴻蒙開發教程-運行時動態加載頁面提升性能

    下面示例應用通過Navigation組件常規加載與動態加載的對比,介紹如何在跳轉時觸發加載方法,實現按需
    發表于 05-10 20:52

    鴻蒙原生應用元服務開發-Web相關說明

    Web組件用于在應用程序中顯示Web頁面內容,為開發者提供頁面加載、頁面交互、頁面調試等能力。 頁面加載:Web組件提供基礎的前端頁面
    發表于 05-10 15:03

    HarmonyOS實戰開發-如何使用全局狀態保留能力彈窗來實現評論組件

    使用了LazyForEach進行數據懶加載,LazyForEach懶加載可以通過設置cachedCount屬性來指定緩存數量,同時搭配組件復用能力
    發表于 05-07 15:06

    【AWTK使用經驗】加載和釋放外部圖片

    AWTK是基于C語言開發的跨平臺GUI框架。《AWTK使用經驗》系列文章將介紹開發AWTK過程中一些常見問題與解決方案,例如:如何加載外部資源?如何設計自定義進度條?這些都會在系列文章進行解答。
    的頭像 發表于 04-26 08:25 ?518次閱讀
    【AWTK使用經驗】<b class='flag-5'>加載</b>和釋放外部圖片
    属虎和属猴牛人做生意| 互博百家乐官网现金网| 足球比分直播| 无锡百家乐官网的玩法技巧和规则| 德州扑克英语| 星河百家乐现金网| 响水县| 全讯网百导| 百家乐官网技巧网址| 本溪市| 老虎机派通娱乐| 24山消砂| 百家乐官网正网开户| 大发888游戏破解秘籍| 百家乐好的平台| 昆明百家乐官网装修装潢有限公司| 大发888倾家荡产| 网上百家乐平台下载| 百家乐官网网上赌博网| 丹东亿酷棋牌世界官方下载| 百家乐平台注册送现金| 百家乐官网书包| bet365备用器| 百家乐切入法| 免费百家乐官网预测软件| 百家乐官网投注科学公式| 德州扑克游戏| 网上赌百家乐的玩法技巧和规则| 百苑百家乐官网的玩法技巧和规则 | 君怡百家乐官网的玩法技巧和规则| 网上百家乐官网新利| 大发888娱乐城加速器| 任我赢百家乐自动投注分析系统| 什么百家乐官网九宫三路| 百家乐官网赢的秘诀| 大发888娱乐城 真钱| 澳门百家乐娱乐场开户注册| 24山在风水中的作用| 百家乐官网套装| 皇冠网百家乐官网赢钱| 鸿博投注|