卡片里放图片?用 memory:// 协议才是正确打开方式

卡片里放图片?用 memory:// 协议才是正确打开方式 文章目录卡片图片的限制项目结构卡片 UI用 memory:// 显示图片FormAbility下载图片 → 写入共享内存 → 推送更新显示本地图片无需下载memory:// 协议原理关键注意事项写在最后卡片里显示图片这件事比我想象的要麻烦一点。卡片跑在独立的渲染进程里没法直接访问网络也没法直接读你应用的文件路径。想显示动态图片要走一个专门的内存共享机制——memory://协议。今天把这套图片更新方案从头到尾讲清楚。卡片图片的限制先说为什么不能直接用Image(https://...)或者Image(/data/...)卡片渲染进程是沙盒卡片 UI 代码跑在一个受限进程里没有网络权限也读不了宿主应用的私有目录Image 组件有限制卡片里的Image只支持应用包内的资源$r()和memory://协议不支持 HTTP URL数据传递有大小限制卡片数据通过FormBindingData传递不能直接传二进制图片数据官方给的解决方案是在FormExtensionAbility里下载图片并写入共享内存然后把共享内存的 key文件名通过updateForm传给卡片 UI卡片 UI 用memory://key访问图片。项目结构StageServiceWidgetCards/ └── entry/src/main/ets/ ├── widgetimageupdate/ │ ├── pages/ │ │ └── WidgetImageUpdateCard.ets ← 卡片 UI │ └── widgetimageabilify/ │ └── WidgetImageFormAbility.ets ← 卡片 Ability └── pages/ └── Index.ets ← 主页面卡片 UI用 memory:// 显示图片// entry/src/main/ets/widgetimageupdate/pages/WidgetImageUpdateCard.etsletstorageWidgetImageUpdatenewLocalStorage();Entry(storageWidgetImageUpdate)Componentstruct WidgetImageUpdateCard{LocalStorageProp(text)text:ResourceStr$r(app.string.loading);// loaded 标志true 时显示共享内存图片false 时显示默认占位图LocalStorageProp(loaded)loaded:booleanfalse;// imgName 是共享内存里图片的 key由 FormAbility 传入LocalStorageProp(imgName)imgName:ResourceStr$r(app.string.imgName);build(){Column(){Column(){Text(this.text).fontColor(#FFFFFF).opacity(0.9).fontSize(12).textOverflow({overflow:TextOverflow.Ellipsis}).maxLines(1).margin({top:8%,left:10%})}.width(100%).height(50%).alignItems(HorizontalAlign.Start)Row(){Button(){Text(刷新图片).fontColor(#45A6F4).fontSize(12)}.width(120).height(32).margin({top:30%,bottom:10%}).backgroundColor(#FFFFFF).borderRadius(16).onClick((){// 发送 message 事件让 FormAbility 去下载新图片postCardAction(this,{action:message,params:{info:refreshImage// FormAbility 会识别这个参数}});})}.width(100%).height(40%).justifyContent(FlexAlign.Center)}.width(100%).height(100%)// 核心loadedtrue 时用 memory:// 协议否则显示本地占位图.backgroundImage(this.loaded?memory://this.imgName// memory:// imgName 从共享内存读图片:$r(app.media.ImageDisp)// 未加载时显示默认图).backgroundImageSize(ImageSize.Cover)}}FormAbility下载图片 → 写入共享内存 → 推送更新// entry/src/main/ets/widgetimageupdate/widgetimageabilify/WidgetImageFormAbility.etsimport{formBindingData,FormExtensionAbility,formProvider}fromkit.FormKit;import{Want}fromkit.AbilityKit;import{BusinessError}fromkit.BasicServicesKit;import{http}fromkit.NetworkKit;import{image}fromkit.ImageKit;constTAGWidgetImageFormAbility;exportdefaultclassWidgetImageFormAbilityextendsFormExtensionAbility{onAddForm(want:Want):formBindingData.FormBindingData{// 卡片创建时先显示本地占位图状态constformData:Recordstring,boolean|string{loaded:false,text:图片加载中...};returnformBindingData.createFormBindingData(formData);}onFormEvent(formId:string,message:string):void{// 收到卡片的 message 事件constmsg:Recordstring,stringJSON.parse(message);if(msg.inforefreshImage){// 下载网络图片并更新到卡片this.downloadAndUpdateImage(formId);}}privateasyncdownloadAndUpdateImage(formId:string):Promisevoid{constimageUrlhttps://example.com/sample.png;// 替换为实际图片URLtry{// 1. 创建 HTTP 实例下载图片consthttpRequesthttp.createHttp();constresponseawaithttpRequest.request(imageUrl,{method:http.RequestMethod.GET,expectDataType:http.HttpDataType.ARRAY_BUFFER,connectTimeout:10000,readTimeout:10000});httpRequest.destroy();// 2. 将 ArrayBuffer 转成 PixelMapconstimageSourceimage.createImageSource(response.resultasArrayBuffer);constpixelMapawaitimageSource.createPixelMap({editable:false,desiredSize:{width:200,height:200}});// 3. 构造带图片的 FormBindingData// imgName 是共享内存的 key卡片用 memory://imgName 访问constimgNamedownloaded_image;constformData:Recordstring,boolean|string|image.PixelMap{loaded:true,// 通知 UI 图片已加载完成imgName:imgName,// 图片的 keytext:图片更新成功,[imgName]:pixelMap// 以 key 为属性名存放 PixelMap};constformInfoformBindingData.createFormBindingData(formData);// 4. 推送数据卡片自动显示新图片awaitformProvider.updateForm(formId,formInfo);console.info(${TAG}: 图片更新成功);}catch(error){consterrerrorasBusinessError;console.error(${TAG}: 图片下载失败${err.code}:${err.message});// 失败时推送错误状态consterrorData:Recordstring,boolean|string{loaded:false,text:图片加载失败请重试};awaitformProvider.updateForm(formId,formBindingData.createFormBindingData(errorData));}}}显示本地图片无需下载如果只是想更新本地的应用资源图片也可以不走网络// FormAbility 里直接用 $r 引用应用内图片onUpdateForm(formId:string):void{// 用 PixelMap 方式传递应用包内图片constcontextthis.context;// 方式一读取 rawfile 目录里的图片context.resourceManager.getRawFileContent(images/banner.png).then((data:Uint8Array){constimageSourceimage.createImageSource(data.bufferasArrayBuffer);returnimageSource.createPixelMap();}).then((pixelMap:image.PixelMap){constimgNamelocal_image;constformData:Recordstring,boolean|string|image.PixelMap{loaded:true,imgName:imgName,text:本地图片,[imgName]:pixelMap};returnformProvider.updateForm(formId,formBindingData.createFormBindingData(formData));});}memory:// 协议原理关键注意事项1.memory://只能在卡片里用普通应用页面的Image组件不认memory://这个协议是卡片渲染引擎专属的。2. PixelMap 要作为属性值传入 FormBindingData这是最容易搞错的地方。imgName是图片的 keyformData[imgName] pixelMap把 PixelMap 对象放进去memory://imgName才能找到这个图片。// 正确写法constimgNamemyImage;constformData{imgName:imgName,// 告诉 UI 用哪个 key[imgName]:pixelMap// 以这个 key 存放 PixelMap};// 错误写法少了 PixelMap 数据constformData{imgName:imgName// UI 读到了 key但内存里没有图显示空白};3. 图片不会自动清理共享内存里的图片在卡片刷新后不会立即释放多次更新后可能积累很多。生产环境里要注意图片大小建议不超过 1MB。4. 网络请求要放在 onFormEvent 里FormExtensionAbility只有 5 秒存活时间不要在onAddForm里做耗时的网络请求最好在onFormEvent里按需下载。写在最后卡片图片这块确实绕核心逻辑是FormAbility 当中间人下好图片转成 PixelMap 塞进共享内存卡片 UI 用memory://协议取图。搞清楚这个模式之后写起来其实不难就是 FormBindingData 里那个[imgName]: pixelMap的写法比较反直觉记住就好。