当前位置: 首页 > news >正文

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

文章目录

      • 卡片图片的限制
      • 项目结构
      • 卡片 UI:用 memory:// 显示图片
      • FormAbility:下载图片 → 写入共享内存 → 推送更新
      • 显示本地图片(无需下载)
      • memory:// 协议原理
      • 关键注意事项
      • 写在最后

卡片里显示图片这件事比我想象的要麻烦一点。卡片跑在独立的渲染进程里,没法直接访问网络,也没法直接读你应用的文件路径。想显示动态图片,要走一个专门的内存共享机制——memory://协议。
今天把这套图片更新方案从头到尾讲清楚。

卡片图片的限制

先说为什么不能直接用Image('https://...')或者Image('/data/...')

  1. 卡片渲染进程是沙盒:卡片 UI 代码跑在一个受限进程里,没有网络权限,也读不了宿主应用的私有目录
  2. Image 组件有限制:卡片里的Image只支持应用包内的资源($r())和memory://协议,不支持 HTTP URL
  3. 数据传递有大小限制:卡片数据通过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.etsletstorageWidgetImageUpdate=newLocalStorage();@Entry(storageWidgetImageUpdate)@Componentstruct WidgetImageUpdateCard{@LocalStorageProp('text')text:ResourceStr=$r('app.string.loading');// loaded 标志:true 时显示共享内存图片,false 时显示默认占位图@LocalStorageProp('loaded')loaded:boolean=false;// 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%')// 核心:loaded=true 时用 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}from'@kit.FormKit';import{Want}from'@kit.AbilityKit';import{BusinessError}from'@kit.BasicServicesKit';import{http}from'@kit.NetworkKit';import{image}from'@kit.ImageKit';constTAG='WidgetImageFormAbility';exportdefaultclassWidgetImageFormAbilityextendsFormExtensionAbility{onAddForm(want:Want):formBindingData.FormBindingData{// 卡片创建时,先显示本地占位图状态constformData:Record<string,boolean|string>={'loaded':false,'text':'图片加载中...'};returnformBindingData.createFormBindingData(formData);}onFormEvent(formId:string,message:string):void{// 收到卡片的 message 事件constmsg:Record<string,string>=JSON.parse(message);if(msg.info==='refreshImage'){// 下载网络图片并更新到卡片this.downloadAndUpdateImage(formId);}}privateasyncdownloadAndUpdateImage(formId:string):Promise<void>{constimageUrl='https://example.com/sample.png';// 替换为实际图片URLtry{// 1. 创建 HTTP 实例下载图片consthttpRequest=http.createHttp();constresponse=awaithttpRequest.request(imageUrl,{method:http.RequestMethod.GET,expectDataType:http.HttpDataType.ARRAY_BUFFER,connectTimeout:10000,readTimeout:10000});httpRequest.destroy();// 2. 将 ArrayBuffer 转成 PixelMapconstimageSource=image.createImageSource(response.resultasArrayBuffer);constpixelMap=awaitimageSource.createPixelMap({editable:false,desiredSize:{width:200,height:200}});// 3. 构造带图片的 FormBindingData// imgName 是共享内存的 key,卡片用 memory://imgName 访问constimgName='downloaded_image';constformData:Record<string,boolean|string|image.PixelMap>={'loaded':true,// 通知 UI 图片已加载完成'imgName':imgName,// 图片的 key'text':'图片更新成功',[imgName]:pixelMap// 以 key 为属性名存放 PixelMap};constformInfo=formBindingData.createFormBindingData(formData);// 4. 推送数据,卡片自动显示新图片awaitformProvider.updateForm(formId,formInfo);console.info(`${TAG}: 图片更新成功`);}catch(error){consterr=errorasBusinessError;console.error(`${TAG}: 图片下载失败${err.code}:${err.message}`);// 失败时推送错误状态consterrorData:Record<string,boolean|string>={'loaded':false,'text':'图片加载失败,请重试'};awaitformProvider.updateForm(formId,formBindingData.createFormBindingData(errorData));}}}

显示本地图片(无需下载)

如果只是想更新本地的应用资源图片,也可以不走网络:

// FormAbility 里直接用 $r 引用应用内图片onUpdateForm(formId:string):void{// 用 PixelMap 方式传递应用包内图片constcontext=this.context;// 方式一:读取 rawfile 目录里的图片context.resourceManager.getRawFileContent('images/banner.png').then((data:Uint8Array)=>{constimageSource=image.createImageSource(data.bufferasArrayBuffer);returnimageSource.createPixelMap();}).then((pixelMap:image.PixelMap)=>{constimgName='local_image';constformData:Record<string,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是图片的 key,formData[imgName] = pixelMap把 PixelMap 对象放进去,memory://+imgName才能找到这个图片。

// 正确写法constimgName='myImage';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的写法比较反直觉,记住就好。

http://www.jsqmd.com/news/840070/

相关文章:

  • 对比直接使用官方API与通过Taotoken调用的成本体感差异
  • NotebookLM标签管理正在淘汰旧范式!2024 Q3最新实践白皮书首发:支持多源引用+版本快照+权限继承的下一代标签协议
  • Pearcleaner终极指南:彻底清理Mac应用残留的免费开源工具
  • 创业团队如何利用多模型聚合平台优化产品开发流程
  • 中小团队如何利用Taotoken实现大模型API成本集约化管理
  • STM32CubeIDE静态库实战:从创建、编译到跨工程调用的完整避坑指南(附F401工程)
  • Windows 创建软链接/目录联接命令
  • 抖音批量下载神器:三步搞定无水印视频下载,告别手动烦恼
  • 告别无声播放!UE5中为MediaPlayer视频添加声音的完整指南(含MediaSound组件详解)
  • 接口自动化工具类模板 + 必备 requirements 依赖清单
  • 在VMware虚拟机Ubuntu 20.04上,5分钟搞定PyBullet安装与第一个仿真程序
  • 钻井“自动化”的终点就是钻井自主化的起点
  • 鲁L蒲公英5.15股市日记:既然有风险,为何还强做?
  • 终极指南:5步解锁完整Koikatu游戏体验的HF Patch安装方案
  • 新闻从业者必读的NotebookLM避坑手册(含3类高发误用场景与合规红线)
  • XFCE桌面效率提升:自动光标跟随焦点窗口插件详解
  • 实测Taotoken多模型聚合调用的响应延迟与稳定性观感
  • Debian12 新手上路:从虚拟机搭建到系统调优全指南
  • 初次使用Taotoken控制台管理API密钥与查看账单的直观体验
  • 深度学习立体匹配:从MC-CNN架构解析到工程实践优化
  • Scalpel:精准代码修改利器,编译时源码替换实战指南
  • 5分钟快速上手:用particles.js为网站添加惊艳粒子特效
  • NotebookLM赋能康复医学研究:3天构建个性化循证分析工作流的实操指南
  • Consul-K8s实战:Kubernetes与Consul服务网格的无缝集成指南
  • 使用pip安装openai库并配置Taotoken实现Python快速接入大模型
  • 用C++手搓一个能下赢你的五子棋AI:从零实现博弈树与α-β剪枝
  • Linux驱动调试利器:debugfs接口设计与实现详解
  • LabVIEW PC端软件开发:架构设计、性能优化与工程化实践
  • Flutter聊天界面开发实战:flutter_chat_ui核心架构与高级定制指南
  • NVM for Windows终极指南:如何轻松管理多个Node.js版本 [特殊字符]