 Coil(coil-core)的 OpenHarmony 鴻蒙化適配實(shí)戰(zhàn))
Compose Multiplatform 三方庫(kù) Coilcoil-core的 OpenHarmony 鴻蒙化適配實(shí)戰(zhàn)上游 3.3.0 LRU 強(qiáng)弱雙級(jí)緩存 會(huì)話式 JSON 橋 DevEco 模擬器三頁(yè)簽實(shí)測(cè)庫(kù)版本coil-kt/coil 1731511v3.3.0 線Apache 2.0驗(yàn)證環(huán)境Kotlin 2.2.21-1.0.0鴻蒙定制版kotlinx-serialization 1.9.1-1.0.0DevEco Studio 26.0.0DevEco 模擬器HarmonyOS 7.0.0API 26前面幾篇把日歷、圖表這些 CMP 庫(kù)搬上鴻蒙之后這次輪到圖片加載生態(tài)的地基coil-kt/coil。Coil 3 號(hào)稱Kotlin Multiplatform 時(shí)代的圖片加載庫(kù)名字就來(lái)自CoroutineImageLoaderAndroid/iOS/Desktop 全平臺(tái)覆蓋。查了 CPF-KMP-CMP 官方清單和 AtomGit鴻蒙上同樣沒(méi)有它的位置——于是接著做。先說(shuō)邊界圖片加載庫(kù)的完整鏈路是網(wǎng)絡(luò)請(qǐng)求 → 解碼 → 緩存 → 顯示其中網(wǎng)絡(luò)ktor/okhttp和顯示Compose Canvas兩端都和平臺(tái)強(qiáng)耦合本次不搬。但鏈路中間那段——LRU 強(qiáng)/弱雙級(jí)內(nèi)存緩存、MIME 解析、請(qǐng)求尺寸模型、緩存鍵組合——是純粹的 Kotlin 數(shù)據(jù)結(jié)構(gòu)與算法恰恰是圖片加載庫(kù)最容易寫錯(cuò)的部分驅(qū)逐時(shí)機(jī)、字節(jié)記賬、弱引用生命周期。結(jié)論先說(shuō)上游核心源碼零邏輯修改僅兩處編譯依賴替換下文詳述Kotlin/Native 編出雙 ABI.soDevEco 模擬器三頁(yè)簽實(shí)測(cè)跑通44 個(gè)單測(cè)全綠30 上游 14 橋。*先睹為快DevEco 模擬器實(shí)測(cè)。左內(nèi)存緩存頁(yè)LRU 驅(qū)逐與弱引用回退全部由 Kotlin/Native 側(cè)的上游 RealMemoryCache 驅(qū)動(dòng)右MIME 解析頁(yè)上游 MimeTypeMap 的 100 類型擴(kuò)展名表在鴻蒙側(cè)原樣生效*一、先看清楚圖片加載庫(kù)的值錢部分在哪照例先翻上游源碼分布。coil-core 的 commonMain 按包分目錄內(nèi)容對(duì)平臺(tái)的依賴memory/MemoryCache接口 StrongMemoryCacheWeakMemoryCacheRealMemoryCache純 Kotlin atomicfu 鎖 WeakReferenceutil/LruCache/LruMutableMapLRU 核心、mimeTypes100 類型擴(kuò)展名表、collections/logging/contexts幾乎為零size/Size/Dimension/Scale/Precision純 Kotlinnetwork/、decode/、transform/請(qǐng)求執(zhí)行、平臺(tái)解碼器、變換ktor/okhttp、Skia/Bitmap重度平臺(tái)耦合Compose 層AsyncImage等Canvas重度耦合又是熟悉的格局容易寫錯(cuò)的全在中段平臺(tái)耦合全在兩端。LRU 緩存這種東西自己手寫一遍不難寫對(duì)很難——驅(qū)逐到哪個(gè)字節(jié)數(shù)停被強(qiáng)緩存驅(qū)逐的條目去哪答案弱引用層繼續(xù)存活直到 GCsetMaxSize收縮時(shí)要不要立即驅(qū)逐答案要這些語(yǔ)義上游用 8 個(gè)單測(cè)文件鎖死了白送不抄是浪費(fèi)。所以路線不變緩存/解析/尺寸/鍵模型原樣復(fù)用網(wǎng)絡(luò)與繪制兩端不做。二、整體鏈路ArkTS (Index.ets / CoilApi.ets) │ import coilNative from libcoil.so ▼ coilNative.call({op:createCache,maxSizeBytes:2097152}) libcoil.so ← C NAPI 薄層字符串進(jìn)、字符串出 │ extern C OhosCoilCall / OhosCoilFree ▼ libohoscmpcoil.so ← Kotlin/NativeohosArm64 / ohosX64 │ CoilBridge解析 op → 定位 cache id → 調(diào)用上游 → JSON 序列化 ▼ 上游 RealMemoryCache / StrongMemoryCache / WeakMemoryCache LruCache / MimeTypeMap / Size / MemoryCache.Key零邏輯修改 兩處編譯依賴替換Poko → 手寫atomicfu 鎖 → 本地 shim工程結(jié)構(gòu)cmp-coil-demo/ ├── coil/ # 庫(kù)模塊上游 vendored shims │ └── src/ │ ├── commonMain/kotlin/coil3/ ← 上游原樣memory/util/size/annotation │ ├── commonMain/kotlin/coil3/util/SynchronizedObject.kt ← 新增 expect │ ├── jvmMain/kotlin/ ← JVM actual供單測(cè)在 JVM 跑 │ ├── ohosMain/kotlin/ ← ohos actualPlatformContext 等 │ └── commonTest/kotlin/ ← 上游單測(cè) 30 個(gè)原樣搬入 ├── example/nativeApp/ # CoilBridge CoilExport產(chǎn)出 libohoscmpcoil.so ├── example/ohosApp/ # DevEco 工程三頁(yè)簽 Demo └── scripts/build-so.ps1 # 單測(cè) 雙 ABI 一鍵構(gòu)建三、橋協(xié)議會(huì)話式 啞圖記賬圖表篇確立了有狀態(tài)庫(kù)走會(huì)話式協(xié)議的范式緩存直接沿用createCache拿整數(shù) cache id后續(xù) 10 個(gè)帶 id 的操作free釋放。registry 是 .so 進(jìn)程里的HashMapInt, MemoryCache。有個(gè)圖片緩存特有的設(shè)計(jì)點(diǎn)要想清楚緩存值是什么上游MemoryCache.Value包著Image可繪制的位圖對(duì)象而鴻蒙側(cè)真實(shí)位圖只能存在 ArkTS 的 PixelMap 里Kotlin/Native 側(cè)沒(méi)有 Skia。硬要把位圖搬過(guò)橋就得做字節(jié)流序列化 像素格式轉(zhuǎn)換純粹為了 demo 得不償失。所以橋協(xié)議里set存的是啞圖BridgeImage——一個(gè)寬/高/字節(jié)數(shù)可控的記賬對(duì)象privateclassBridgeImage(overridevalwidth:Int,overridevalheight:Int,overridevalsize:Long,overridevalshareable:Booleantrue,):Image{overridefundraw(canvas:Canvas){}}真實(shí)位圖由 ArkTS 側(cè)持有Kotlin 側(cè)只做緩存行為的記賬哪個(gè) key 進(jìn)來(lái)了、占了多少字節(jié)、什么時(shí)候被驅(qū)逐、被驅(qū)逐后弱引用層還能不能命中。這些恰是上游緩存最值得驗(yàn)證的行為語(yǔ)義——圖片本身不過(guò)是測(cè)試數(shù)據(jù)。協(xié)議上get命中時(shí)返回imageId/width/height/bytes/extrasArkTS 拿著 imageId 去自己的映射表里找真圖即可。這個(gè)啞圖記賬模式對(duì)所有值是平臺(tái)對(duì)象、邏輯在公共層的庫(kù)都通用。{op:createCache,maxSizeBytes:2097152}→{id:1,initialMaxSize:2097152}{op:set,id:1,key:https://example.com/cat.jpg,imageId:0,width:800,height:600,extras:{scale:FIT}}→{ok:true,size:1920000}{op:get,id:1,key:https://example.com/cat.jpg}→{hit:true,imageId:0,width:800,height:600,bytes:1920000,extras:{scale:FIT}}createCache完整走上游MemoryCache.BuildermaxSizeBytes定長(zhǎng)、maxSizePercent按平臺(tái)內(nèi)存比例上游默認(rèn) 0.15模擬器 512MB 內(nèi)存算出 80530636 字節(jié)——單測(cè)里就用這個(gè)值鎖 Builder 鏈路的正確性、strongReferencesEnabled/weakReferencesEnabled兩個(gè)開(kāi)關(guān)透?jìng)鳌K?、適配過(guò)程4.1 上游搬入兩處編譯依賴替換零邏輯修改這次上游文件不要求全部一字節(jié)不動(dòng)有兩處編譯依賴替換不改任何行為先坦白替換一Poko→ 手寫equals/hashCode/toString。上游MemoryCache.Key和測(cè)試用FakeImage用Poko編譯期生成這些方法的注解避免手寫樣板。Poko 是一個(gè)獨(dú)立的 KSP 處理器為兩個(gè)類引入整套代碼生成管線不值得。手寫這三個(gè)方法是無(wú)腦的機(jī)械勞動(dòng)語(yǔ)義與注解生成完全一致——Key本來(lái)就是數(shù)據(jù)類語(yǔ)義key 字符串 extras 映射equals逐字段比較。替換二atomicfu 鎖 → 本地 expect/actual shim。RealMemoryCache里的并發(fā)保護(hù)用kotlinx.atomicfu的SynchronizedObject/synchronized。atomicfu 對(duì)鴻蒙定制 ohosArm64/ohosX64 target 沒(méi)有預(yù)編譯 klibtransform 插件也只覆蓋官方 target。解法是本地 shim// commonMain —— 簽名與 atomicfu 完全一致publicexpectopenclassSynchronizedObject(){publicfunlockImpl()publicfununlockImpl()}publicinlinefunTsynchronized(lock:SynchronizedObject,block:()-T):T{lock.lockImpl()try{returnblock()}finally{lock.unlockImpl()}}// ohosMain —— 自旋鎖NAPI JS 線程是唯一常規(guī)調(diào)用方publicactualopenclassSynchronizedObjectactualconstructor(){privatevalspinAtomicInt(0)actualfunlockImpl(){while(!spin.compareAndSet(0,1)){}}actualfununlockImpl(){spin.store(0)}}ohos actual 用kotlin.concurrent.atomics.AtomicInt自旋鎖JVM actual 用ReentrantLock單測(cè)跑 JVM語(yǔ)義與上游對(duì)齊。RealMemoryCache.kt的 import 行從 atomicfu 改指本地同名 shim——全庫(kù)唯一一行非原樣的改動(dòng)。除了這兩處memory/、util/、size/、annotation/下的文件全部原樣搬入包括那 100 行的 MIME 擴(kuò)展名映射表和 5 個(gè)上游單測(cè)文件30 個(gè)用例。4.2 WeakReferenceexpect/actual 再來(lái)一次WeakMemoryCache的核心機(jī)制是WeakReferenceEntry——強(qiáng)緩存驅(qū)逐后條目靠弱引用續(xù)命直到 GC 才真正消失。JVM 有java.lang.ref.WeakReferenceKotlin/Native 的kotlin.native.ref.WeakReference在新內(nèi)存模型下同樣可用但 commonMain 里沒(méi)有公共 API。老朋友 expect/actual// commonMainpublicexpectclassWeakRefT:Any(referred:T){publicfunget():T?}// jvmMainpublicactualclassWeakRefT:Anyactualconstructor(referred:T):java.lang.ref.WeakReferenceT(referred){publicactualoverridefunget():T?super.get()}注意 Kotlin/Native 的WeakReference構(gòu)造后要用executeAfterGarbageCollection之類手段才能觀察到回收——JVM 單測(cè)里System.gc()后弱引用判空這個(gè)上游測(cè)試語(yǔ)義照常工作ohos 側(cè) demo 不依賴 GC 時(shí)機(jī)弱引用回退靠強(qiáng)緩存驅(qū)逐但對(duì)象仍被 images 表強(qiáng)持有來(lái)演示見(jiàn) 4.4。4.3 橋接層JVM 單測(cè)把語(yǔ)義鎖死CoilBridge.kt放 commonMain14 個(gè)橋接單測(cè)全部在 JVM 跑。大部分 op 是直接的參數(shù)搬運(yùn)三個(gè)語(yǔ)義點(diǎn)值得記錄LRU 驅(qū)逐 → 弱引用回退本次適配最核心的驗(yàn)證點(diǎn)。maxSize 只夠放一張圖第二張進(jìn)來(lái)把第一張從強(qiáng)緩存擠掉、趕進(jìn)弱引用層get第一張依然命中——這是上游RealMemoryCache的招牌行為TestfunlruEvictionTriggersWeakFallback(){validnewCache(40000)call({op:set,id:$id,key:a,imageId:1,bytes:40000})call({op:set,id:$id,key:b,imageId:2,bytes:40000})// a 被從強(qiáng)緩存擠掉、進(jìn)了弱緩存get 依然命中上游弱引用回退valacall({op:get,id:$id,key:a})assertTrue(str(a,hit).toBoolean())// 但強(qiáng)緩存 size 只剩 bvalstatscall({op:cacheStats,id:$id})assertEquals(40000,str(stats,size))}等等“啞圖被 images 表強(qiáng)持有怎么會(huì)進(jìn)弱引用層還能命中”這正是啞圖記賬的巧處上游WeakMemoryCache的弱引用包的是RealStrongMemoryCache.Entry含著啞圖橋側(cè) images 表持有的是另一個(gè)引用——弱引用層命中取決于 Entry 對(duì)象是否仍可達(dá)。橋的 images 表確實(shí)讓 Entry 間接保持強(qiáng)可達(dá)所以弱引用層穩(wěn)定命中demo 語(yǔ)義被驅(qū)逐的圖在 ArkTS 側(cè)還有引用時(shí)緩存仍能找回。這演示的是上游文檔里弱引用回退的語(yǔ)義而非性能特征——真正 GC 語(yǔ)義的驗(yàn)證在 JVM 單測(cè)側(cè)靠WeakReference直接斷言ohos 側(cè)不賭 GC 時(shí)機(jī)。keys計(jì)數(shù)含弱引用層。setMaxSize收縮后被驅(qū)逐的兩個(gè) key 在cacheKeys里仍然計(jì)入上游keys strong.keys weak.keys。第一次跑這個(gè)斷言時(shí)我以為 count 會(huì)減實(shí)際不減——這是正確語(yǔ)義不是 bug條目還活著只是降到弱層。get的 extras 是 Value 自帶、不是 Key 的。上游MemoryCache.Value里的extras是MapString, Any存入時(shí)快照Key的extras是MapString, String參與相等判定。橋協(xié)議里兩個(gè)都有set時(shí)傳的 extras 同時(shí)成為 Key extras 與 Value extrasget返回的是 Value 側(cè)快照。測(cè)試鎖死同 key 不同 extras 的get不命中Key 不相等。異常處理照例Kotlin 側(cè)try全部Throwable包成{error: 類名: 消息}返回free一個(gè)不存在的 id、未知 op 都有測(cè)試覆蓋。4.4 NAPI 層與編譯部署C 層沿用誰(shuí)分配誰(shuí)釋放的 82 行薄層OhosCoilCall返回的 C 字符串用OhosCoilFree釋放CMake 鏈接entry/libs/abi/libohoscmpcoil.so。構(gòu)建腳本照 koalaplot 那套# 1. 全部 JVM 單測(cè)44 個(gè)30 上游 14 橋.\scripts\build-so.ps1# 內(nèi)含 :coil:jvmTest :example:nativeApp:jvmTest# 2. 雙 ABI release .soarm64 2.6 MB / x86_64 2.5 MB 部署到 ohosApp# build-so.ps1 一并完成 linkReleaseShared 拷貝# 3. hvigor 打 hap安裝啟動(dòng)powershell-File example\ohosApp\build-hap.ps1 powershell-File example\ohosApp\install-run.ps1產(chǎn)物驗(yàn)證雙 ABI.so里確認(rèn)導(dǎo)出符號(hào)OhosCoilCall/OhosCoilFreestrings搜索二進(jìn)制即可確認(rèn) CName 導(dǎo)出HAP 6.3 MB。五、運(yùn)行效果DevEco 模擬器實(shí)測(cè)冷啟動(dòng) hilog 的CoilNapi/CoilDemotag 下每條請(qǐng)求/響應(yīng)的頭部與耗時(shí)都有記錄全鏈路可追溯。5.1 內(nèi)存緩存頁(yè)LRU 驅(qū)逐 弱引用回退活演示2MB 雙級(jí)緩存預(yù)填 5 張圖后逐張存入一張觸發(fā)驅(qū)逐。三個(gè)統(tǒng)計(jì)卡實(shí)時(shí)顯示 Kotlin 側(cè)上報(bào)的強(qiáng)緩存占用/容量上限/條目數(shù)強(qiáng)弱*內(nèi)存緩存頁(yè)統(tǒng)計(jì)數(shù)字全部來(lái)自 Kotlin/Native 側(cè) RealMemoryCache 的實(shí)時(shí)狀態(tài)條目列表的驅(qū)逐順序按上游 LRU 語(yǔ)義排列*操作按鈕的語(yǔ)義對(duì)照“get 最老條目”演示弱引用回退——最老條目早已被 LRU 驅(qū)逐出強(qiáng)緩存get卻依然命中弱層續(xù)命日志區(qū)打出hittrue與圖寬高“容量減半”走trimToSize強(qiáng)緩存立刻驅(qū)逐到目標(biāo)字節(jié)但count不降弱層仍持有“清空”后 size 與 count 同時(shí)歸零clear是真清。每一擊操作日志實(shí)時(shí)追加行為與第 4.3 節(jié)單測(cè)鎖死的語(yǔ)義一一對(duì)應(yīng)。5.2 MIME 解析頁(yè)100 類型表原樣生效輸入框任意 URL/擴(kuò)展名mimeTypeop 解析下方速查表實(shí)時(shí)跑 6 個(gè)典型用例大寫.PNG、帶#fragment的 jpg、無(wú)擴(kuò)展名*MIME 解析頁(yè)上游 MimeTypeMap 的 URL 解析截?cái)U(kuò)展名、忽略查詢串與 fragment與 100 類型擴(kuò)展名映射在鴻蒙側(cè)零改動(dòng)生效*單測(cè)鎖的邊界行為在頁(yè)面上一眼可查https://example.com/photo.PNG?w100→image/png擴(kuò)展名大小寫不敏感pic.jpg#fragment→image/jpegfragment 不影響無(wú)擴(kuò)展名 URL →foundfalse。5.3 Size / Key 頁(yè)模型組合與字符串化三組對(duì)照卡Size(400)半定尺寸widthPx400、heightPx 缺失、isDefinedtrue、Size.ORIGINAL雙軸 Undefined、isOriginaltrue、MemoryCache.Key的 extras 組合與toString展示*Size/Key 頁(yè)上游 Dimension 模型的 Pixels/Undefined 二態(tài)與 Key 的 extras 快照、字符串化在鴻蒙側(cè)原樣可查*六、踩坑記#坑現(xiàn)象解法1atomicfu 無(wú) ohos klibRealMemoryCache編譯不過(guò)本地SynchronizedObjectexpect/actual shimJVMReentrantLockohosAtomicInt 自旋import 行改指本地——全庫(kù)唯一非原樣行2Poko代碼生成不可用Key/FakeImage缺 equals/hashCode/toString手寫三個(gè)方法語(yǔ)義與注解生成一致3Kotlin/Native 無(wú) JVM 式 GC 單測(cè)語(yǔ)義弱引用回收時(shí)機(jī)不可控JVM 單測(cè)靠WeakReference直接斷言 GC 語(yǔ)義ohos demo 用強(qiáng)持有下弱層命中演示回退語(yǔ)義不賭 GC 時(shí)機(jī)4keys計(jì)數(shù)預(yù)期錯(cuò)setMaxSize收縮后 count 不減疑似 bug上游keys strong.keys weak.keys弱層條目仍計(jì)入——正確語(yǔ)義測(cè)試預(yù)期修正5gradlew 啟動(dòng)器要 JAVA_HOMEorg.gradle.java.home在 Gradle 起來(lái)后才生效構(gòu)建/IDE 全鏈路統(tǒng)一gradle.properties 腳本內(nèi)先設(shè)JAVA_HOME再調(diào) gradlew6真實(shí)位圖過(guò)橋成本高PixelMap 序列化 像素格式轉(zhuǎn)換純?yōu)?demo 服務(wù)啞圖記賬模式Kotlin 側(cè)只記賬寬高/字節(jié)/extras真圖留 ArkTS 側(cè)七、FAQQ1為什么只搬緩存不搬網(wǎng)絡(luò)請(qǐng)求和 AsyncImage兩端都是平臺(tái)強(qiáng)耦合網(wǎng)絡(luò)層綁定 ktor/okhttp鴻蒙側(cè)有 ohos.net.http 原生方案直接在 ArkTS 拉流更順顯示層綁定 Skia/Compose CanvasArkUI Image 組件天生干這個(gè)。中段的緩存/解析/尺寸/鍵模型是純 Kotlin恰是自己寫容易錯(cuò)的部分。上游語(yǔ)義用 44 個(gè)單測(cè)鎖死ArkTS 側(cè)網(wǎng)絡(luò)拉圖 libcoil.so做緩存記賬即可拼出完整鏈路。Q2啞圖記賬的弱引用回退是真語(yǔ)義嗎上游WeakMemoryCache弱引用命中依賴 Entry 可達(dá)性。demo 的 images 表持有啞圖導(dǎo)致弱層穩(wěn)定命中演示的是對(duì)象仍被引用時(shí)弱層續(xù)命的文檔語(yǔ)義真正 GC 后弱層判空的語(yǔ)義在 JVM 單測(cè)里靠WeakReference直接鎖。要在 ohos 側(cè)觀察 GC 回收需要kotlin.native.ref.Cleaner或手動(dòng)GC.collect()配合demo 有意不賭時(shí)機(jī)。Q3多緩存實(shí)例怎么管理createCache每次返回新 idregistry 多實(shí)例并存。Demo 全局一個(gè)緩存、頁(yè)面aboutToAppear建、aboutToDisappearfree要模擬內(nèi)存緩存 磁盤緩存前置的多級(jí)結(jié)構(gòu)可以建多個(gè)實(shí)例各自配 maxSize。Q4maxSizePercent在鴻蒙上按什么算走上游Builder.maxSizePercent(context, 0.15)context 是 ohos 側(cè)單例PlatformContext.INSTANCE默認(rèn)按平臺(tái)總內(nèi)存 15% 折算模擬器 512MB → 80530636 字節(jié)單測(cè)鎖了這個(gè)數(shù)。要精確到當(dāng)前應(yīng)用可用內(nèi)存可在 ohos actual 里接ohos.app.ability.ApplicationManager——屬于后續(xù)增量。Q5這個(gè)切片對(duì)真實(shí)圖片加載器ImageKnife 等有什么用ImageKnife 等 FlutterOH/ArkTS 圖片庫(kù)最缺的恰是經(jīng)過(guò)大規(guī)模生產(chǎn)驗(yàn)證的緩存語(yǔ)義。本適配把 Coil 的雙級(jí)緩存行為原樣搬到.soArkTS 圖片庫(kù)可以直接過(guò)橋取用請(qǐng)求前置get判命中下載后set記賬內(nèi)存吃緊trimToSize頁(yè)面銷毀free。語(yǔ)義不用重寫上游演進(jìn)免費(fèi)跟進(jìn)。八、總結(jié)這次 Coil 適配給這套上游零邏輯修改路線又添了兩塊拼圖值是平臺(tái)對(duì)象、邏輯在公共層的庫(kù)有了標(biāo)準(zhǔn)解法。啞圖記賬模式讓緩存行為驅(qū)逐/回退/記賬在 Kotlin 側(cè)完整驗(yàn)證值本體留在 ArkTS——這個(gè)模式對(duì)數(shù)據(jù)庫(kù)、偏好存儲(chǔ)、任意容器型庫(kù)都通用。依賴替換有下限。atomicfu 鎖與Poko這兩處替換是編譯依賴級(jí)別的簽名一致、語(yǔ)義一致上游邏輯零改動(dòng)。判斷一個(gè)替換是否越界看它的單測(cè)是否還全綠——30 個(gè)上游單測(cè)原樣通過(guò)就是零邏輯修改的證明。會(huì)話式橋協(xié)議二次復(fù)用。圖表篇的create → 帶 id 操作 → free范式平移到緩存場(chǎng)景零成本說(shuō)明這個(gè)協(xié)議設(shè)計(jì)對(duì)有狀態(tài)庫(kù)已趨成熟。驗(yàn)收閉環(huán)44 個(gè)單測(cè)全綠30 上游 14 橋 DevEco 模擬器三頁(yè)簽實(shí)測(cè) hilog 全鏈路可追溯 雙 ABI.so導(dǎo)出符號(hào)確認(rèn)。適配成果將推至https://atomgit.com/oh-tpc/coil含雙 ABI.so、完整 ArkTS Demo 與雙語(yǔ) README。圖片加載生態(tài)的地基打好了下一篇可以順著往網(wǎng)絡(luò)/解碼方向啃。本文代碼與資源適配倉(cāng)庫(kù)https://atomgit.com/oh-tpc/coil上游原庫(kù)https://github.com/coil-kt/coil1731511v3.3.0 線Apache 2.0Demo 入口倉(cāng)庫(kù)example/ohosAppDevEco Studio 26.0.0 直接打開(kāi)構(gòu)建腳本倉(cāng)庫(kù)scripts/build-so.ps1、example/ohosApp/build-hap.ps1