鸿蒙 Form Kit(卡片开发服务)

news2025/6/3 8:49:55

Form Kit(卡片开发服务)

鸿蒙应用中,Form / Card / Widget 都翻译为“卡片”

Form Kit(卡片开发服务)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片,以达到服务直达、减少跳转层级的体验效果。卡片常用于嵌入到其他系统应用(例如桌面)中作为其界面显示的一部分,并支持拉起页面、发送消息等基础的交互能力。

桌面上长按某个应用图标,弹出菜单中点击“服务卡片”,即可预览并添加该应用提供的服务卡片

卡片的约束

为防止卡片被恶意使用,Form Kit 要求卡片使用方应用只能是系统应用(一般是桌面应用) ,为确保使用体验以及控制功耗,系统对 ArkTS 卡片的能力做了以下约束:

  1. 当导入模块时,仅支持导入标识“支持在 ArkTS 卡片中使用”的模块。
  2. 不支持导入共享包。
  3. 不支持使用 native(C++) 语言开发。
  4. 仅支持声明式范式的部分组件、事件、动效、数据管理、状态管理和 API 能力。
  5. 卡片的事件处理和使用方的事件处理是独立的,建议在使用方支持左右滑动的场景下卡片内容不要使用左右滑动功能的组件,以防手势冲突影响交互体验。
  6. 暂不支持断点调试能力。
  7. 暂不支持 Hot Reload 热重载。
  8. 暂不支持 setTimeOut / setInterval()。
创建卡片
  • 选择一个模块,比如我选择 entry,右键
  • 新建-Service Widget-Static Widget/Dynamic Widget (静态/动态卡片)
  • 然后选择卡片类型,填写卡片名字大小等等信息
  • 注意:多个卡片共享一个 Ability name,所以多张卡片都是一个 Ability name

静态卡片
以渲染后的最后一帧为静态图片呈现,不支持动画、动态数据、自定义事件,只能使用 FormLink 组件跳转到 提供方

动态卡片
内容可以动态改变的卡片,支持动画、动态数据、自定义事件等,只是系统资源消耗要大于静态卡片

isDynamic 配置项赋值为 false 就是静态卡片,否则就是动态卡片

相关模块

比如我创建的卡片名字是 MyWidgetCard ,卡片的 Ability name 是 EntryFormAbility

下面的 4 个文件都是和卡片密切相关的

  • src/main/ets/entryformability/EntryFormAbility.ets 卡片创建、销毁、刷新等生命周期回调
  • src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片业务代码
  • src/main/resources/base/profile/form_config.json 配置卡片的细节设定
  • src/main/module.json5 注册 FormExtensionAbiltiy

在 MyWidgetCard 上写卡片的业务代码时,样式按照在正常的组件逻辑即可,虽然卡片有 2*22*4 等不同大小

相关进程

在真机调试时,需要注意卡片相关代码运行于不同的进程中,必需筛选正确的进程才能看到相关日志输出内容:

  • UIAbility:运行于 App 主进程中,进程名就是当前应用的 bundleName
  • FormExtensionAbility :运行在独立的卡片扩展进程中,进程名形如:bundleName:form
  • WidgetCard :运行于系统进程中,进程名为 com.ohos.formrenderservice
卡片生命周期

EntryFormAbility.ets

export default class EntryFormAbility extends FormExtensionAbility {
  // 使用方创建卡片时触发,提供方需要返回卡片数据绑定对象
  onAddForm(want: Want) {
    return formData;
  }

  // 使用方将临时卡片转换为常态卡片触发,提供方需要做相应的处理
  onCastToNormalForm(formId: string) {}

  // 若卡片支持定时更新/定点更新/卡片使用方主动请求更新功能,则提供方需要重写该方法以支持数据更新
  onUpdateForm(formId: string) {}

  // 需要配置formVisibleNotify为true,且为系统应用才会回调
  onChangeFormVisibility(newStatus: Record<string, number>) {}

  // 若卡片支持触发事件,则需要重写该方法并实现对事件的触发
  onFormEvent(formId: string, message: string) {}

  // 当对应的卡片删除时触发的回调,入参是被删除的卡片ID
  onRemoveForm(formId: string) {}

  // 当前formExtensionAbility存活时更新系统配置信息时触发的回调
  onConfigurationUpdate(config: Configuration) {}

  // 卡片提供方接收查询卡片状态通知接口,默认返回卡片初始状态
  onAcquireFormState(want: Want) {}
}

注意:FormExtensionAbility 进程不能常驻后台,即在卡片生命周期回调函数中无法处理长时间的任务,在生命周期调度完成后会继续存在 10 秒,如 10 秒内没有新的生命周期回调触发则进程自动退出。

卡片的三种动作

卡片行为/事件 (这里只提供动作,不传递数据)

针对静态卡片,ArkTS 卡片提供了 FormLink 组件用于卡片内部和提供方应用间的交互。(不举例了)

针对动态卡片,ArkTS 卡片中提供了 postCardAction( ) 接口用于卡片内部和提供方应用间的交互,当前支持 router、message 和 call 三种类型的动作(action):

  • router 动作:可以使用 router 事件跳转到指定 UIAbility(拉起窗口),并通过 router 事件刷新卡片内容。
  • call 动作:可以使用 call 事件拉起指定 UIAbility 到后台(不会拉起窗口),并通过 call 事件刷新卡片内容。
  • message 动作:可以使用 message 拉起 FormExtensionAbility(8 秒哥,8 秒后进程死去),并通过 FormExtensionAbility 刷新卡片内容。

在这里插入图片描述

router 动作

在卡片中使用 postCardAction 接口发起 router 动作,能够快速拉起卡片提供方应用的指定 UIAbility 或 Page,因此 UIAbility 或 Page 较多的应用往往会通过卡片提供不同的跳转按钮,实现一键直达的效果。

// src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片的UI页面
@Entry
@Component
struct MyWidgetCard {
  build() {
    Button('拉起UIAbility-发起router动作').onClick(_ => {
      postCardAction(this, {
        action: 'router', //发起一个router动作,即拉起一个UIAbility到前台
        abilityName: 'EntryAbility', //启动卡片提供方应用中的某个UIAbility
      })
    })
  }
}

call 动作

可以借助卡片,实现和应用在前台时相同的功能。例如音乐卡片,卡片上提供播放、暂停等按钮,点击后将触发音乐应用的对应功能。在卡片中使用 postCardAction 接口的 call 动作,能够将卡片提供方应用的指定的 UIAbility 拉到后台。同时,call 动作提供了调用应用指定方法、传递数据的功能,使应用在后台运行时可以通过卡片上的按钮执行不同的功能。

// src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片的UI页面
@Entry
@Component
struct MyWidgetCard {
  build() {
    Button('发起call动作').onClick(_ => {
      postCardAction(this, {
        action: 'call', //发起一个call动作,即调用一个UIAbility的方法,但保持UIAbility在后台运行
        abilityName: 'EntryAbility', //启动卡片提供方应用中的某个UIAbility
        params: {
          method: 'play' //必需参数
        }
      })
    })
  }
}
// src/main/ets/entryability/EntryAbility.ets  卡片提供方UIAbility
import {
  AbilityConstant,
  ConfigurationConstant,
  UIAbility,
  Want,
} from "@kit.AbilityKit";
import { rpc } from "@kit.IPCKit";

class MyParcelable implements rpc.Parcelable {
  marshalling(dataOut: rpc.MessageSequence): boolean {
    return true; //数据序列化
  }

  unmarshalling(dataIn: rpc.MessageSequence): boolean {
    return true; //数据反序列化
  }
}

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    //开始监听卡片发来的call动作,  callee指代当前正在后台运行的UIAbility实例
    this.callee.on("play", (dataFromCard: rpc.MessageSequence) => {
      let data = dataFromCard.readString(); //读取卡片所在的进程发来的数据,默认都是JSON字符串
      console.log("--读取到卡片发来的数据:", data);
      return new MyParcelable();
    });
  }
}

message 动作

在卡片页面中可以通过 postCardAction 接口触发 message 事件拉起 FormExtensionAbility (8 秒哥) ,然后由 FormExtensionAbility 执行相关操作。

// src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片的UI页面
@Entry
@Component
struct MyWidgetCard {
  build() {
    Button('发起message动作').onClick(_ => {
      postCardAction(this, {
        action: 'message', //发起一个message动作,即给指定的FormExtensionAbility发消息
        // abilityName: 'EntryAbility',  //每个卡片对应的FormExtensionAbility是固定的,可以省略不声明
      })
    })
  }
}

刷新卡片内容

卡片刷新原理

用户点击卡片,发起三种动作之一,由 Ability 提供更新后的数据,从而实现卡片内容的“手动更新”;
也可以在卡片的配置文件中指定时间点或时间间隔,实现卡片内容的定点或定时“自动更新”。

卡片提供方可以调用 updateForm() 接口为卡片提供“卡片绑定数据”,卡片管理服务以 LocalStorage 形式转交给卡片,从而可以实现卡片的页面刷新效果。

let data = formBindingData.createFormBindingData( {k1: v1,   k2: v2, ...} )
formProvider.updateForm( formId, data )
router 动作刷新卡片

在使用 postCardAction 接口发起 router 动作时,需要在 FormExtensionAbility 中的 onAddForm 生命周期回调中更新 formId:

效果:点击卡片后变化天气,分冷热启动显示不同值

// src/main/ets/mywidget/pages/MyWidgetCard.ets  卡片的UI页面
let storage = new LocalStorage()

@Entry(storage)
@Component
struct MyWidgetCard {
  @LocalStorageProp('weather') weather: string = '晴'

  build() {
    Column() {
      Text(this.weather)
      Button('发起router动作').onClick(_ => {
        postCardAction(this, {
          action: 'router',
          abilityName: 'EntryAbility',
          //给目标UIAbility提供一些启动参数
          params: {
            location: 'Beijing', //自定义参数1
            time: '2998/06/01', //自定义参数2
          }
        })
      })
    }
  }
}
// src/main/ets/entryability/EntryAbility.ets 卡片提供方UIAbility
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.log(
      "--EntryAbility.onCreate: 冷启动",
      JSON.stringify(want.parameters)
    );
    if (want.parameters?.["formID"]) {
      //如果是卡片拉起的UIAbility,则want中存在formID
      let fid = String(Number(want.parameters["formID"]));
      let data = formBindingData.createFormBindingData({
        weather: "多云",
      });
      formProvider.updateForm(fid, data);
    }
  }

  onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.log(
      "--EntryAbility.onNewWant: 热启动",
      JSON.stringify(want.parameters)
    );
    if (want.parameters?.["formID"]) {
      //如果是卡片拉起的UIAbility,则want中存在formID
      let fid = String(Number(want.parameters["formID"]));
      let data = formBindingData.createFormBindingData({
        weather: "雷雨",
      });
      formProvider.updateForm(fid, data);
    }
  }
}
call 动作刷新卡片

在卡片中可以通过 postCardAction()接口触发 call 动作拉起 UIAbility 到后台,然后由 UIAbility 使用 updateForm( )方法携带数据刷新卡片内容。

formID 线路图

因为 formProvider.updateForm()需要卡片 id 才能干活

  1. call 获取 formID 需要从 FormExtensionAbibility 获取
  2. 获取到 formID 后传递给卡片的 UI 页面,写在 params 上
  3. 然后在把 formID 传递到卡片提供者的 UIAbility,有 id 就可以操作卡片了

来吧,分 3 步,效果是按钮显示 formID,点击刷新随机数

获取 formID 传递给卡片的 UI 页面

// src/main/ets/entryformability/EntryFormAbility.ets 卡片的Ability
export default class EntryFormAbility extends FormExtensionAbility {
  onAddForm(want: Want) {
    console.log(
      "--FormExtensionAbility.onAddForm:一个卡片被添加到卡片使用方",
      JSON.stringify(want)
    );
    let formId = want.parameters?.["ohos.extra.param.key.form_identity"];

    // formProvider.setFormNextRefreshTime(String(formId), 5)

    return formBindingData.createFormBindingData({ curFormId: formId });
  }
}

卡片页面的构建和传递 formID 给卡片提供方 UIAbility

// src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片页面
let storage = new LocalStorage()

@Entry(storage)
@Component
struct MyWidgetCard {
  @LocalStorageProp('myName') myName: string = ''
  @LocalStorageProp('curFormId') formID: string = ''

  build() {
    Column() {
      Text('---' + this.myName)
      Button(this.formID).onClick(_ => {
        postCardAction(this, {
          action: 'call',
          abilityName: 'EntryAbility',
          params: {
            method: 'play',
            curFormId: this.formID
          }
        })
      })
    }
  }
}
// src/main/ets/entryability/EntryAbility.ets  卡片提供方UIAbility
class MyParcelable implements rpc.Parcelable {
  marshalling(dataOut: rpc.MessageSequence): boolean {
    return true; //数据序列化
  }

  unmarshalling(dataIn: rpc.MessageSequence): boolean {
    return true; //数据反序列化
  }
}

interface DataFromCard {
  method: string;
  curFormId: string;
}

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    console.log("--PlayerAbility.onCreate: 冷启动");
    //开始监听卡片发来的call动作,  callee指代当前正在后台运行的UIAbility实例
    this.callee.on("play", (data) => {
      let dataFromCard = JSON.parse(data.readString()) as DataFromCard;

      //给卡片发送更新后的数据
      let fid = dataFromCard.curFormId;
      let dataToCard = formBindingData.createFormBindingData({
        myName: Math.floor(Math.random() * 100).toString(),
      });
      formProvider.updateForm(fid, dataToCard);

      return new MyParcelable(); //此处返回此对象仅仅为了满足TS语法检查
    });
  }
}
message 动作刷新卡片

在卡片页面中可以通过 postCardAction 接口触发 message 事件拉起 FormExtensionAbility,然后由 FormExtensionAbility 执行相关操作。

// src/main/ets/mywidget/pages/MyWidgetCard.ets 卡片页面
let storage = new LocalStorage()

@Entry(storage)
@Component
struct MyWidgetCard {
  @LocalStorageProp('myCount') count: number = 0

  build() {
    Column() {
      Button(this.count.toString()).onClick(_ => {
        postCardAction(this, {
          action: 'message',
          // abilityName: 'EntryAbility',
        })
      })
    }
  }
}
// src/main/ets/entryformability/EntryFormAbility.ets 卡片的Ability
export default class EntryFormAbility extends FormExtensionAbility {
  //卡片主动给FormExtensionAbility发来message动作
  onFormEvent(formId: string, message: string) {
    console.log(
      "--FormExtensionAbility.onFormEvent:一个卡片发来一个事件",
      formId,
      message
    );
    let data = formBindingData.createFormBindingData({
      //把普通的JS对象封装为“卡片绑定数据”对象
      myCount: Math.floor(Math.random() * 100),
    });
    formProvider.updateForm(formId, data);
  }
}

定时/定点刷新

除了前面的三个特定 action 可以手动更新卡片内容,卡片框架提供了如下三种按时间刷新卡片的方式:

  1. 定时刷新:卡片使用方在一定时间间隔内调用 requestForm()从而触发 onUpdateForm 的生命周期回调函数实现卡片内容的刷新。可以在 form_config.json 配置文件的 updateDuration 字段中进行设置。例如,可以将刷新时间设置为每小时一次。

  2. 定点刷新:表示在每天的某个特定时间点自动刷新卡片内容。可以在 form_config.json 配置文件中的 scheduledUpdateTime 字段中进行设置。例如,可以将刷新时间设置为每天的上午 10 点 30 分。

  3. 下次刷新:表示指定卡片的下一次刷新时间。可以通过调用 setFormNextRefreshTime()接口来实现。最短刷新时间为 5 分钟。例如,可以在接口调用后的 5 分钟内刷新卡片内容。

定时刷新

至少要等半个小时才能看到效果,及其不方便

// src/main/ets/mywidget/pages/MyWidgetCard.ets

let storage = new LocalStorage()

@Entry(storage)
@Component
struct MyWidgetCard {
  @LocalStorageProp('myTime') time1: string = ''

  build() {
    Column() {
      Text('--' + this.time1)
    }
  }
}
// src/main/ets/entryformability/EntryFormAbility.ets

export default class EntryFormAbility extends FormExtensionAbility {
  //卡片使用方根据form_config.json配置申请刷新卡片内容
  onUpdateForm(formId: string) {
    console.log(
      "--FormExtensionAbility.onUpdateForm:一个卡片需要更新内容",
      formId
    );
    let data = formBindingData.createFormBindingData({
      myTime: new Date().toLocaleString(),
    });
    formProvider.updateForm(formId, data);
  }
}
// src/main/resources/base/profile/form_config.json
{
  "forms": [
    {
      "name": "widget",
      "isDynamic": true, // 只有动态卡片才能更新内容
      "updateEnabled": true, //启用卡片内容更新功能
      "scheduledUpdateTime": "22:00", // 启用定时更新时,定点更新自动失效
      "updateDuration": 1 // 定时更新时间间隔,取值范围1~336,此处的 1 代表 1*30分钟
    }
  ]
}
定点刷新

这个设定时间后可以马上看到效果,除了 form_config.json 和定时刷新不一样,其他的都一样

// src/main/resources/base/profile/form_config.json
{
  "forms": [
    {
      "name": "widget",
      "isDynamic": true, // 只有动态卡片才能更新内容
      "updateEnabled": true, // 启用卡片内容更新功能
      "scheduledUpdateTime": "10:30", // 定点更新时间
      "updateDuration": 0 // 定时更新时间间隔设定为0,才能启用定点更新
    }
  ]
}
下次刷新

注意

  1. 定时刷新有配额限制,每张卡片每天最多通过定时方式触发刷新 50 次,定时刷新包含卡片配置项 updateDuration 和调用 setFormNextRefreshTime()方法两种方式,当达到 50 次配额后,无法通过定时方式再次触发刷新,刷新次数会在每天的 0 点重置。
  2. 当前定时刷新使用同一个计时器进行计时,因此卡片定时刷新的第一次刷新会有最多 30 分钟的偏差。比如第一张卡片 A(每隔半小时刷新一次)在 3 点 20 分添加成功,定时器启动并每隔半小时触发一次事件,第二张卡片 B(每隔半小时刷新一次)在 3 点 40 分添加成功,在 3 点 50 分定时器事件触发时,卡片 A 触发定时刷新,卡片 B 会在下次事件(4 点 20 分)中才会触发。
  3. 定时刷新和定点刷新仅在屏幕亮屏情况下才会触发,在灭屏场景下仅会记录刷新动作,待亮屏时统一进行刷新。
// src/main/ets/entryformability/EntryFormAbility.ets
export default class EntryFormAbility extends FormExtensionAbility {
  onFormEvent(formId: string, message: string): void {
    formProvider.setFormNextRefreshTime(formId, 5);
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2394712.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

算力卡上部署OCR文本识别服务与测试

使用modelscope上的图像文本行检测和文本识别模型进行本地部署并转为API服务。 本地部署时把代码中的检测和识别模型路径改为本地模型的路径。 关于模型和代码原理可以参见modelscope上这两个模型相关的页面&#xff1a; iic/cv_resnet18_ocr-detection-db-line-level_damo iic…

KWIC—Implicit Invocation

KWIC—Implicit Invocation ✏️ KWIC—Implicit Invocation 文章目录 KWIC—Implicit Invocation&#x1f4dd;KWIC—Implicit Invocation&#x1f9e9;KWIC&#x1f9e9;核心组件&#x1f9e9;ImplementationScheme⚖️ 隐式调用 vs 显式调用对比 &#x1f31f; 总结 &#x…

Visual Studio 2022 发布独立的 exe 文件

我们在用 Visual Studio 2022 写好一个 exe 程序之后&#xff0c;如果想把这个拿到其他地方运行&#xff0c;需要把 exe 所在的文件夹一起拿过去。 编译出来的 exe 文件需要其他几个文件一同放在同一目录才能运行&#xff0c;原因在于默认情况下&#xff0c;Visual Studio 是把…

11.4java语言执行浅析4

编译成字节码&#xff08;.class 文件&#xff09; 使用 javac 命令将源代码编译为 Java 字节码&#xff08;bytecode&#xff09; 它不是机器码&#xff0c;而是 JVM 能理解的中间语言&#xff08;字节码&#xff09;&#xff0c;具有平台无关性。 编译过程简要&#xff1…

python分配方案数 2023年信息素养大赛复赛/决赛真题 小学组/初中组 python编程挑战赛 真题详细解析

python分配方案数 2023全国青少年信息素养大赛Python编程挑战赛复赛真题解析 博主推荐 所有考级比赛学习相关资料合集【推荐收藏】1、Python比赛 信息素养大赛Python编程挑战赛 蓝桥杯python选拔赛真题详解

《信号与系统》第 5 章 离散时间傅里叶变换

5.0 引言 这一章将介绍并研究离散时间傅里叶变换&#xff0c;这样就完整地建立了傅里叶分析方法。 5.1 非周期信号的表示&#xff1a;离散时间傅里叶变换 5.1.1 离散时间傅里叶变换的导出 在第4章看到&#xff1a;一个连续时间周期方波的傅里叶级数可以看成一个包络函数的采…

动态IP与区块链:重构网络信任的底层革命

在数字经济蓬勃发展的今天&#xff0c;网络安全与数据隐私正面临前所未有的挑战。动态IP技术与区块链的深度融合&#xff0c;正在构建一个去中心化、高可信的网络基础设施&#xff0c;为Web3.0时代的到来奠定基础。 一、技术碰撞&#xff1a;动态IP与区块链的天然契合 动态I…

uniapp使用Canvas生成电子名片

uniapp使用Canvas生成电子名片 工作中有生成电子名片的一个需求&#xff0c;刚刚好弄了发一下分享分享 文章目录 uniapp使用Canvas生成电子名片前言一、上代码&#xff1f;总结 前言 先看效果 一、上代码&#xff1f; 不对不对应该是上才艺&#xff0c;哈哈哈 <template…

世冠科技亮相中汽中心科技周MBDE会议,共探汽车研发数字化转型新路径

近日&#xff0c;中汽中心2025年科技周MBDE前沿应用主题会议在天津成功举办。本次会议以“智汇津门共探MBDE前沿应用新征程”为主题&#xff0c;聚焦基于模型的数字工程&#xff08;MBDE&#xff09;方法论在汽车复杂系统研发中的创新实践与跨领域协同&#xff0c;旨在推动行业…

Linux笔记---线程

1. 线程的介绍 1.1 线程的概念 基本定义&#xff1a; 线程&#xff08;Thread&#xff09;是操作系统能够进行运算调度的最小单位。它被包含在进程&#xff08;Process&#xff09;之中&#xff08;或者说是进程的一部分、对进程的划分&#xff09;&#xff0c;是进程中的实际…

MCP架构深度解析:从基础原理到核心设计

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 持续学习&#xff0c;不断…

数据库暴露--Get型注入攻击

1.背景知识 1.1Post、Get的对比 特性GET 方法POST 方法HTTP 方法类型GETPOST数据位置URL 查询字符串(?key=value)请求体(Request Body)数据可见性明文显示在 URL 和浏览器历史中不可见(除非开发者工具查看)数据长度限制受 URL 长度限制(通常约 2048 字符)无明确限制(…

AI炼丹日志-26 - crawl4ai 专为 AI 打造的爬虫爬取库 上手指南

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇&#xff1a; MyBatis 更新完毕目前开始更新 Spring&#xff0c;一起深入浅出&#xff01; 大数据篇 300&#xff1a; Hadoop&…

ESP32-idf学习(四)esp32C3驱动lcd

一、前言 屏幕是人机交互的重要媒介&#xff0c;而且现在我们产品升级的趋势越来越高大尚&#xff0c;不少产品都会用lcd来做界面&#xff0c;而esp32c3在一些项目上是可以替代主mcu&#xff0c;所以驱动lcd也是必须学会的啦 我新买的这块st7789&#xff0c;突然发现是带触摸…

【python】uv管理器

uv是一个速度极快的 Python 包和项目管理器&#xff0c;用 Rust 编写。 安装 安装uv之前&#xff0c;确保你的电脑不需要安装了python 在Windows下&#xff0c;可以使用官方的脚本直接安装 powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.…

戴尔AI服务器订单激增至121亿美元,但传统业务承压

戴尔科技121亿美元的AI服务器订单&#xff0c;不仅超过了公司整个2025财年的AI服务器出货量&#xff0c;更让其AI订单积压达到144亿美元的历史高位。 戴尔科技最新财报显示&#xff0c;AI服务器需求的爆炸式增长正在重塑这家老牌PC制造商的业务格局&#xff0c;但同时也暴露出…

如何手搓扫雷(待扩展)

文章目录 一、扫雷游戏分析与设计1.1 扫雷游戏的功能说明1.2 游戏的分析和设计1.2.1 数据结构的分析1.2.2 文件结构设计 二、扫雷游戏的代码实现三、扫雷游戏的扩展总结 一、扫雷游戏分析与设计 扫雷游戏网页版 1.1 扫雷游戏的功能说明 使用控制台&#xff08;黑框框的程序&a…

俄军操作系统 Astra Linux 安装教程

安装 U盘制作 Rufus 写盘工具&#xff1a;https://rufus.ie/ Astra Linux ISO 镜像文件&#xff1a;https://dl.astralinux.ru/astra/stable/2.12_x86-64/iso/ 准备一个8g以上的u盘&#xff0c;打开Rufus写盘工具&#xff0c;选择下载的iso镜像&#xff0c;写入u盘&#xff…

第三方软件评测机构如何助力软件品质提升及企业发展?

第三方软件评测机构与软件开发者及使用者无直接关联&#xff0c;它们提供全方位的检测和公正的评价服务。这样的评测可以展现客观的成效&#xff0c;对提升软件的品质具有显著影响&#xff0c;且在软件产业中发挥着至关重要的角色。 评测的客观性 独立第三方机构与软件开发者…

Python打卡训练营Day40

DAY 40 训练和测试的规范写法 知识点回顾&#xff1a; 彩色和灰度图片测试和训练的规范写法&#xff1a;封装在函数中展平操作&#xff1a;除第一个维度batchsize外全部展平dropout操作&#xff1a;训练阶段随机丢弃神经元&#xff0c;测试阶段eval模式关闭dropout 作业&#x…