当前位置: 首页 > 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.cnnetsun.cn/news/2437474.html

相关文章:

  • Python机器学习库精选指南:best-of-ml-python项目深度解析与应用
  • SSH 远程登录协议
  • 避开STC8H-ADC的五个常见坑:时钟配置、通道切换与结果读取的注意事项
  • MetaClaw:开源元数据提取工具的设计原理与实战应用
  • 企业如何通过统一api网关管理内部多个ai模型调用
  • 嵌入式开发调试实战:从硬件信号到软件逻辑的完整解决方案
  • MySQL-进阶篇-视图/存储过程/触发器
  • 别再乱改node_modules了!pdfjs-dist字体加载警告的三种正确解决姿势
  • 解决Win11家庭版运行软件程序提示【管理员已阻止你运行此应用】
  • 别再只盯着NXP和Impinj了!盘点5款国产超高频RFID芯片的‘独门绝技’
  • AList搭建好了,下一步怎么用?手把手教你用RaiDrive在Windows上挂载WebDAV本地磁盘
  • CAXA 直线命令
  • AI Agent 项目学习笔记(二):Spring AI 与 ChatClient 主链路解析
  • codex出现Reconnecting和stream disconnected before completion:stream closed before response.complete解决方案
  • 紧急通知:FAO 2024渔业AI伦理新规已生效!NotebookLM合规使用红线清单(含数据脱敏、模型可解释性、渔民知情权三重校验表)
  • 新时代的信息茧房
  • 开源威胁检测工具openclaw-nie-guard部署与实战指南
  • 保姆级图解:用MMDetection3D复现SMOKE3D时,DLA34骨干网络的特征图到底怎么传?
  • 终极指南:5步掌握Rusted PackFile Manager打造Total War模组
  • 如何高效解密QQ音乐文件:QMCDump工具完整使用指南
  • 5步解锁显卡隐藏性能:NVIDIA Profile Inspector全面指南
  • 5分钟快速上手:用FakeLocation实现Android应用级虚拟定位
  • 如何免费获取米哈游11款游戏字体:完整安装与创意应用指南
  • 如何快速部署FastGithub:终极GitHub加速配置指南
  • 基于Python+OpenCV+MediaPipe的手势识别实战:从环境搭建到实时标注
  • 微信读书笔记助手完整教程:3分钟掌握高效阅读笔记技巧
  • 终极B站会员购抢票神器:5分钟掌握自动化抢票完整攻略
  • 从BERT到GPT-4:大语言模型的技术演进与应用实践
  • 嵌入式调试器核心原理与实战技巧:从JTAG到HardFault排查
  • 利用Taotoken多模型能力为智能客服场景选型