式 JPEG(Progressive JPEG)支持:網(wǎng)絡(luò)流式解碼原理與 Android 實(shí)戰(zhàn)指南)
移動(dòng)開(kāi)發(fā)圖像處理【免費(fèi)下載鏈接】frescoAn Android library for managing images and the memory they use.項(xiàng)目地址https://gitcode.com/gh_mirrors/fr/fresco點(diǎn)擊查看免費(fèi)下載漸進(jìn)式 JPEGProgressive JPEG是提升弱網(wǎng)環(huán)境下圖片體驗(yàn)的關(guān)鍵技術(shù)圖片在下載過(guò)程中先顯示模糊的完整輪廓再隨數(shù)據(jù)到達(dá)逐步變清晰。本文以 Fresco 官方文檔 progressive-jpegs.md 為骨架結(jié)合倉(cāng)庫(kù)內(nèi)imagepipeline模塊的源碼實(shí)現(xiàn)與 showcase 示例應(yīng)用講解如何在 Fresco 中開(kāi)啟漸進(jìn)式渲染、其底層解碼鏈路如何工作以及有哪些可配置項(xiàng)與限制。讀完本文你將掌握構(gòu)建漸進(jìn)式 JPEG 圖片請(qǐng)求的完整寫(xiě)法并理解DecodeProducer、ProgressiveJpegParser、ProgressiveJpegConfig在其中的角色。一、漸進(jìn)式 JPEG 是什么與普通 JPEG 的區(qū)別JPEG 圖片有兩種常見(jiàn)的編碼組織方式基線Baseline編碼圖像的掃描數(shù)據(jù)從上到下按行順序存放必須等整張圖的數(shù)據(jù)全部到達(dá)后才能完整解碼顯示。漸進(jìn)式Progressive編碼圖像數(shù)據(jù)被拆分成多次掃描scan第一次掃描只包含粗糙的整體信息后續(xù)每次掃描逐步補(bǔ)充細(xì)節(jié)。解碼端每完成一次掃描就可以渲染出一版質(zhì)量更高的圖片。Fresco 利用這一特性支持對(duì)通過(guò)網(wǎng)絡(luò)加載的漸進(jìn)式 JPEG 圖片進(jìn)行流式解碼Scans of the image will be shown in the view as you download them. Users will see the quality of the image start out low and gradually become clearer.圖片掃描結(jié)果會(huì)隨著下載進(jìn)度逐幀顯示在視圖上用戶(hù)會(huì)看到圖片質(zhì)量由低到高逐漸變清晰。需要強(qiáng)調(diào)的是這個(gè)能力有兩個(gè)前提條件文檔明確說(shuō)明僅適用于網(wǎng)絡(luò)圖片本地圖片如res、assets、本地文件是一次性完整解碼的不存在漸進(jìn)的需要。圖片本身必須是漸進(jìn)式編碼并非所有 JPEG 都是漸進(jìn)式編碼對(duì)于基線編碼的 JPEG無(wú)論下載多慢都無(wú)法做到漸進(jìn)顯示——數(shù)據(jù)不完整時(shí)根本無(wú)法解碼出任何一版畫(huà)面。這兩個(gè)條件在源碼中同樣有印證DecodeProducer會(huì)根據(jù) URI 是否為網(wǎng)絡(luò)地址來(lái)選擇不同的解碼器詳見(jiàn)下文第三節(jié)。二、構(gòu)建圖片請(qǐng)求顯式開(kāi)啟漸進(jìn)式渲染在當(dāng)前版本中漸進(jìn)式渲染必須由開(kāi)發(fā)者顯式開(kāi)啟ImageRequestBuilder提供了對(duì)應(yīng)的方法。文檔給出的標(biāo)準(zhǔn)寫(xiě)法如下Uri uri; ImageRequest request ImageRequestBuilder.newBuilderWithSource(uri) .setProgressiveRenderingEnabled(true) .build(); DraweeController controller Fresco.newDraweeControllerBuilder() .setImageRequest(request) .setOldController(mSimpleDraweeView.getController()) .build(); mSimpleDraweeView.setController(controller);關(guān)鍵點(diǎn)說(shuō)明ImageRequestBuilder.newBuilderWithSource(uri)創(chuàng)建圖片請(qǐng)求構(gòu)建器.setProgressiveRenderingEnabled(true)開(kāi)啟漸進(jìn)式渲染。查看 ImageRequestBuilder.java 的實(shí)現(xiàn)該方法只是將布爾值寫(xiě)入構(gòu)建器內(nèi)部的mProgressiveRenderingEnabled字段并返回 builder 自身鏈?zhǔn)秸{(diào)用最終由build()生成攜帶該標(biāo)志的ImageRequest通過(guò)Fresco.newDraweeControllerBuilder()將請(qǐng)求與視圖關(guān)聯(lián)setOldController傳入舊的 controller 以便復(fù)用最后mSimpleDraweeView.setController(controller)生效ImageRequest的toString()中也會(huì)攜帶該標(biāo)志便于調(diào)試時(shí)確認(rèn)請(qǐng)求配置見(jiàn) ImageRequest.java。文檔同時(shí)說(shuō)明目前setImageURI快捷方式還不支持漸進(jìn)式圖片We hope to add support for using progressive images withsetImageURIin a future release.。也就是說(shuō)凡是需要漸進(jìn)式渲染的圖片都必須走上述顯式構(gòu)造ImageRequest的完整流程而不能使用簡(jiǎn)化 API。三、底層原理DecodeProducer 如何選擇漸進(jìn)式解碼路徑開(kāi)啟漸進(jìn)式渲染后圖片請(qǐng)求會(huì)進(jìn)入 Fresco 的生產(chǎn)者Producer流水線。負(fù)責(zé)解碼的DecodeProducer是這條鏈路的核心節(jié)點(diǎn)其類(lèi)注釋明確寫(xiě)道Progressive JPEGs are decoded progressively as new data arrives.漸進(jìn)式 JPEG 會(huì)隨新數(shù)據(jù)到達(dá)而被漸進(jìn)解碼???DecodeProducer.kt 的produceResults方法它根據(jù)請(qǐng)求的 URI 是否為網(wǎng)絡(luò)地址構(gòu)造兩種不同的解碼器非網(wǎng)絡(luò) URI本地文件、資源等創(chuàng)建LocalImagesProgressiveDecoder。它的getIntermediateImageEndOffset直接返回整個(gè)編碼圖片的大小qualityInfo固定為質(zhì)量 0 且非良好質(zhì)量、非完整質(zhì)量——即本地圖片不做漸進(jìn)解碼與文檔Local images are decoded at once完全一致網(wǎng)絡(luò) URI含自定義網(wǎng)絡(luò) URI通過(guò)ImageRequestBuilder.isCustomNetworkUri判斷創(chuàng)建NetworkImagesProgressiveDecoder并注入一個(gè)ProgressiveJpegParser負(fù)責(zé)解析 JPEG 字節(jié)流和ProgressiveJpegConfig負(fù)責(zé)決策何時(shí)解碼、解碼到第幾次掃描。也就是說(shuō)DecodeProducer是是否走漸進(jìn)式解碼的分流器本地圖片永遠(yuǎn)走一次性解碼網(wǎng)絡(luò)圖片才有機(jī)會(huì)利用漸進(jìn)式掃描。四、ProgressiveJpegParser逐字節(jié)掃描 JPEG 的有限狀態(tài)機(jī)NetworkImagesProgressiveDecoder的漸進(jìn)能力底層依賴(lài) ProgressiveJpegParser.java 這個(gè)解析器。它的職責(zé)是隨著網(wǎng)絡(luò)數(shù)據(jù)分塊到達(dá)持續(xù)解析 JPEG 字節(jié)流識(shí)別出已經(jīng)完整到達(dá)的掃描scan并告訴調(diào)用方現(xiàn)在可以解碼到哪個(gè)位置。從源碼看它是一個(gè)典型的有限狀態(tài)機(jī)用整數(shù)常量表示解析狀態(tài)狀態(tài)含義READ_FIRST_JPEG_BYTE期望讀到0xFFJPEG 起始標(biāo)記的第一個(gè)字節(jié)READ_SECOND_JPEG_BYTE已讀到0xFF期望讀到0xD8SOI 標(biāo)記READ_MARKER_FIRST_BYTE_OR_ENTROPY_DATA下一個(gè)字節(jié)可能是熵編碼數(shù)據(jù)或標(biāo)記首字節(jié)READ_MARKER_SECOND_BYTE上一個(gè)字節(jié)是0xFF可能是某個(gè)標(biāo)記的開(kāi)始READ_SIZE_FIRST_BYTE/READ_SIZE_SECOND_BYTE讀取段segment長(zhǎng)度的兩個(gè)字節(jié)NOT_A_JPEG已確認(rèn)不是 JPEG 數(shù)據(jù)關(guān)鍵實(shí)現(xiàn)細(xì)節(jié)校驗(yàn)合法性任何以0xFFD8開(kāi)頭的字節(jié)序列才被認(rèn)為是合法 JPEG構(gòu)造器注釋treats any sequence of bytes starting with 0xFFD8 as a valid jpeg image否則進(jìn)入NOT_A_JPEG狀態(tài)后續(xù)所有parseMoreData調(diào)用直接返回false持續(xù)增量解析調(diào)用方解碼器每收到一塊新數(shù)據(jù)就調(diào)用一次parseMoreData(encodedImage)解析器從上次停下的位置mBytesParsed繼續(xù)而不是重復(fù)掃描整個(gè)緩沖區(qū)識(shí)別掃描邊界解析到 SOSMARKER_SOS標(biāo)記時(shí)記一次新掃描開(kāi)始解析到 EOIMARKER_EOI標(biāo)記時(shí)則記錄圖片結(jié)束遇到0xFF00轉(zhuǎn)義字節(jié)或重復(fù)0xFF時(shí)按 JPEG 規(guī)范跳過(guò)避免把熵編碼數(shù)據(jù)誤判為標(biāo)記產(chǎn)出解碼建議getBestScanNumber()返回當(dāng)前已完整到達(dá)的最佳掃描編號(hào)getBestScanEndOffset()返回應(yīng)該截取的數(shù)據(jù)偏移——解碼器會(huì)在掃描邊界處截?cái)鄶?shù)據(jù)進(jìn)行部分解碼見(jiàn) DecodeProducer.ktgetIntermediateImageEndOffset返回bestScanEndOffset確保每個(gè)中間幀都是完整可用的畫(huà)面而不是半張撕裂圖。解析過(guò)程使用 16KBBUFFER_SIZE的池化緩沖流讀取數(shù)據(jù)并復(fù)用ByteArrayPool以降低內(nèi)存分配開(kāi)銷(xiāo)。五、何時(shí)解碼、解碼到第幾幀ProgressiveJpegConfig 的決策邏輯ProgressiveJpegParser只負(fù)責(zé)解析而要不要現(xiàn)在解碼、解碼到哪一幀、當(dāng)前幀質(zhì)量如何則由 ProgressiveJpegConfig.java 決定。這是一個(gè)接口定義了四個(gè)核心方法方法作用boolean decodeProgressively(ImageRequest imageRequest)判斷該請(qǐng)求是否應(yīng)嘗試漸進(jìn)解碼int getNextScanNumberToDecode(ImageRequest imageRequest, int scanNumber)給定當(dāng)前已解碼的掃描編號(hào)返回下一個(gè)應(yīng)解碼的掃描編號(hào)QualityInfo getQualityInfo(ImageRequest imageRequest, int scanNumber)返回某次掃描對(duì)應(yīng)的質(zhì)量信息是否足夠好、是否完整質(zhì)量long getTimeBetweenPartialResultsMs(ImageRequest imageRequest)兩次中間結(jié)果向上游傳播的最小時(shí)間間隔默認(rèn) 100 毫秒DEFAULT_TIME_BETWEEN_PARTIAL_RESULTS_MS 100用于節(jié)流避免每一幀都立即上屏導(dǎo)致頻繁重繪倉(cāng)庫(kù)提供的默認(rèn)實(shí)現(xiàn)是 SimpleProgressiveJpegConfig.java它通過(guò)內(nèi)部接口DynamicValueConfig暴露兩個(gè)可定制點(diǎn)getScansToDecode(ImageRequest)指定應(yīng)該解碼哪些掃描編號(hào)的列表。默認(rèn)返回空列表含義是每個(gè)掃描都允許被解碼If no specific scans to decode are provided, every scan is allowed to be decodedgetGoodEnoughScanNumber(ImageRequest)指定質(zhì)量足夠好的掃描編號(hào)。默認(rèn)值為 0getQualityInfo判斷邏輯為當(dāng)前掃描編號(hào) goodEnoughScanNumber即為isOfGoodEnoughQuality而isOfFullQuality恒為false因?yàn)樽罱K完整質(zhì)量幀由最終結(jié)果事件攜帶。NetworkImagesProgressiveDecoder的解碼調(diào)度邏輯見(jiàn) DecodeProducer.kt可以概括為收到新的中間數(shù)據(jù)塊IS_PARTIAL_RESULT且圖片格式為 JPEG后先調(diào)用parseMoreData解析若解析出的最佳掃描編號(hào)bestScanNumber不大于上次已調(diào)度的掃描編號(hào)說(shuō)明這一幀已經(jīng)解碼過(guò)直接跳過(guò)若bestScanNumber小于配置要求的下一掃描編號(hào)getNextScanNumberToDecode且尚未讀到 EOI 結(jié)束標(biāo)記說(shuō)明還沒(méi)到配置允許解碼的門(mén)檻幀繼續(xù)等待更多數(shù)據(jù)否則記錄lastScheduledScanNumber安排解碼任務(wù)把這一版部分結(jié)果作為中間幀onIntermediateImageSet交給上層渲染。這套門(mén)檻幀 結(jié)束標(biāo)記的雙重判斷保證了既不會(huì)漏掉關(guān)鍵幀也不會(huì)在圖片還有后續(xù)掃描時(shí)提前終止?jié)u進(jìn)流程。六、完整示例showcase 應(yīng)用中的漸進(jìn)式 JPEG 演示官方文檔推薦直接查看 showcase 示例應(yīng)用中的 ImageFormatProgressiveJpegFragment.java。該 Fragment 是一個(gè)可交互的漸進(jìn)式 JPEG 演示頁(yè)它提供 4 個(gè)不同的測(cè)試圖片來(lái)源小/中/大尺寸樣本圖以及一個(gè)慢速漸進(jìn)式 JPEG 示例createPJPEGSlow()用于模擬不同網(wǎng)速下的漸進(jìn)加載通過(guò)SwitchCompat開(kāi)關(guān)實(shí)時(shí)切換漸進(jìn)式渲染的開(kāi)啟/關(guān)閉渲染時(shí)通過(guò) Vito 新 API 展示圖片該示例使用VitoView.show等價(jià)配置為ImageOptions.create().progressiveRendering(mProgressiveRenderingEnabled)對(duì)應(yīng)舊 Drawee API 的setProgressiveRenderingEnabled注冊(cè)BaseImageListener在onIntermediateImageSet中間幀與onFinalImageSet最終幀回調(diào)中取出QualityInfo把每次渲染的時(shí)間戳、isOfGoodEnoughQuality、isOfFullQuality、quality逐行打印到調(diào)試輸出區(qū)——你可以直觀看到同一張圖片被解碼渲染了多少個(gè)漸進(jìn)幀、每個(gè)幀的質(zhì)量評(píng)分是多少?gòu)亩?yàn)證本文前述的掃描機(jī)制。倉(cāng)庫(kù)中還附帶了該功能的真實(shí)演示視頻 01-progressive-jpegs.mp4可以直觀感受低質(zhì)量輪廓 → 逐漸清晰 → 最終高清的完整過(guò)程該視頻同時(shí)被官網(wǎng)文檔引用為/static/videos/01-progressive-jpegs.mp4。七、注意事項(xiàng)與限制綜合文檔說(shuō)明與源碼實(shí)現(xiàn)使用漸進(jìn)式 JPEG 時(shí)需要注意以下幾點(diǎn)必須顯式開(kāi)啟漸進(jìn)式渲染不會(huì)默認(rèn)啟用必須通過(guò)ImageRequestBuilder.setProgressiveRenderingEnabled(true)顯式請(qǐng)求setImageURI等簡(jiǎn)化 API 目前不支持漸進(jìn)式渲染。僅網(wǎng)絡(luò)圖片有效本地圖片在DecodeProducer中會(huì)被分流到LocalImagesProgressiveDecoder一次性完整解碼不存在漸進(jìn)過(guò)程。依賴(lài)圖片本身的編碼格式只有漸進(jìn)式編碼的 JPEG 才能漸進(jìn)顯示基線編碼 JPEG 在網(wǎng)絡(luò)圖片場(chǎng)景下也只會(huì)等待全部數(shù)據(jù)到達(dá)后一次性顯示。圖片仍需等待足夠的數(shù)據(jù)即使開(kāi)啟了漸進(jìn)式渲染首次畫(huà)面也要等到 JPEG 頭部與第一次掃描的數(shù)據(jù)到達(dá)后才能出現(xiàn)掃描次數(shù)越少如兩次掃描漸進(jìn)效果越生硬但等待時(shí)間也越短。節(jié)流與質(zhì)量門(mén)檻可調(diào)默認(rèn)兩次中間幀的最小間隔為 100ms通過(guò)自定義ProgressiveJpegConfig如配置scansToDecode、goodEnoughScanNumber可以控制解碼哪些幀、何時(shí)把結(jié)果標(biāo)記為足夠好從而在流暢度與清晰度之間做取舍。配合進(jìn)度條等視覺(jué)反饋效果更佳showcase 示例中同時(shí)疊加了ProgressBarDrawable顯示下載進(jìn)度中間幀與進(jìn)度條協(xié)同能給用戶(hù)更完整的加載感知。結(jié)語(yǔ)漸進(jìn)式 JPEG 是 Fresco 圖片流水線中一個(gè)開(kāi)箱即用但又高度可定制的特性一句setProgressiveRenderingEnabled(true)即可啟用底層則由ProgressiveJpegParser字節(jié)流狀態(tài)機(jī)、ProgressiveJpegConfig解碼決策器與DecodeProducer網(wǎng)絡(luò)/本地分流三者協(xié)同完成流式解碼。理解這條鏈路后你既可以按官方文檔快速接入也能通過(guò)自定義ProgressiveJpegConfig針對(duì)業(yè)務(wù)場(chǎng)景微調(diào)漸進(jìn)幀的渲染節(jié)奏。深入閱讀 DecodeProducer.kt 與 ProgressiveJpegParser.java并結(jié)合 showcase 示例動(dòng)手調(diào)試是掌握這一能力的最快路徑。贊分享移動(dòng)開(kāi)發(fā)圖像處理【免費(fèi)下載鏈接】frescoAn Android library for managing images and the memory they use.項(xiàng)目地址https://gitcode.com/gh_mirrors/fr/fresco點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Front-End-Checklist 圖像性能清單漸進(jìn)式 JPEGProgressive JPEG編碼實(shí)踐、檢測(cè)與代碼審查指南Front End Checklist 圖像性能清單漸進(jìn)式 JPEGProgressive JPEG編碼實(shí)踐、檢測(cè)與代碼審查指南 漸進(jìn)式 JPEGPro如何通過(guò)Fresco漸進(jìn)式JPEG流式加載優(yōu)化大型圖片顯示體驗(yàn)如何通過(guò)Fresco漸進(jìn)式JPEG流式加載優(yōu)化大型圖片顯示體驗(yàn) Fresco是Facebook開(kāi)源的Android圖片加載庫(kù)專(zhuān)為優(yōu)化內(nèi)存管理和提升圖片顯示性能移動(dòng)開(kāi)發(fā)圖像處理ExoPlayer 漸進(jìn)式播放Progressive Playback容器格式支持全解析與恒定比特率 Seek 實(shí)戰(zhàn)ExoPlayer 漸進(jìn)式播放Progressive Playback容器格式支持全解析與恒定比特率 Seek 實(shí)戰(zhàn) ExoPlayer 可以在不依賴(lài)任何自音視頻移動(dòng)開(kāi)發(fā)上一篇螞蟻百靈開(kāi)源Ring-mini-2.01.4B激活參數(shù)實(shí)現(xiàn)7B級(jí)推理性能MoE架構(gòu)改寫(xiě)效率規(guī)則下一篇Gauge插件系統(tǒng)深度解析如何擴(kuò)展你的測(cè)試能力創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考