鸿蒙跨平台框架ArkUI-X

01

引言

目前,移动端主流跨平台方案有Flutter、React Native、uni-app等等,还有刚推出不久的Compose-Multiplatform,真所谓是百花齐放。这些框架各有特点,技术实现各有差异,比如Flutter通过Dart编写的UI描述对接Flutter渲染引擎,React Native 则是借助大前端成熟的发展背景,利用JS引擎生成UI描述,渲染时转化为原生控件,复用了原生渲染能力。至于选择哪种框架实现跨平台取决于项目的具体需求、开发团队的技能和偏好。今天我们探索一个新的框架——ArkUI-X。

02

ArkTS、ArkUI、ArkUI-X

在探索ArkUI-X之前,先了解一下ArkTS、ArkUI、ArkUI-X三者关系:

  • ArkTS 是华为基于TypeScript自研的开发语言,主要用于Harmony应用层开发。ArkTS在保持原 TS 基本语法风格的基础上,对 TS 的动态类型特性施加了更严格的约束,引入静态类型,减少运行时的类型检查,有助于性能提升;

  • ArkUI 是一套声明式UI开发框架。包含一系列UI组件(Text、Image)、状态管理(State、LocalStorage)、界面绘制、交互事件以及实时界面预览工具;

  • ArkUI-X 进一步将 ArkUI 扩展到多个 OS 平台,目前支持 OpenHarmony、HarmonyOS、Android、iOS,后续会逐步增加更多平台支持。

简单来说,开发者基于ArkUI框架,使用ArkTS语言进行编码,构建OpenHarmony/HarmonyOS应用,为了让应用运行到Android iOS上,利用 ArkUI-X 框架实现各OS平台的适配和构建。接下来我们将创建一个 ArkUI-X 简易Demo,通过Demo来探索ArkUI如何绘制组件到屏幕,ArkUI-X如何扩展ArkUI到Android平台,ArkUI-X如何与Android系统能力交互。

03

快速上手

3.1 环境搭建

  1. DevEco Studio

首先从DevEco Studio官方网站(https://developer.huawei.com/consumer/cn/deveco-studio/)下载并安装DevEco Studio,需要选择4.0.0以上版本,以支持 ArkUI-X 套件。然后在DevEco Studio内部,分别下载HarmonyOS SDK 和 ArkUI-X,非合作企业开发者下载OpenHarmony SDK 和 ArkUI-X。

下载OpenHarmony SDK示例:

1.png

下载ArkUI-X示例:2.png

  1. Android Studio

因为最终要打出Android的apk实现跨平台,所以需要安装Android Studio。安装步骤对客户端小伙伴都已经轻车熟路了,强调一点:需要额外配置ANDROID_HOME(Android SDK安装路径)到系统环境变量中,这是步骤一中DevEco Studio编译项目的必备条件之一。

  1. Xcode 同理,使用Xcode导入iOS项目,打IPA格式的安装包。

3.2 创建工程

完成上述的环境搭建后,就可以创建工程了。可以通过 ArkUI-X 基础模板进行创建。 3.png
创建工程

创建完成后,得到这样一个工程结构:

4.png
项目结构
  • entry:存放的是应用程序入口、核心业务逻辑以及资源文件。跨平台的公共源代码放在entry下,这点和纯Harmony项目结构一致;

  • .arkui-x/android:是一个标准的Android项目结构。但目前文件还不完整,需要Build App/Hap之后,才会生成更为完整的Android项目;

  • .arkui-x/iOS: 是一个标准的iOS项目结构,包含project.pbxproj。

Build App/Hap之后,在build目录下生成的后缀名.hap文件,可安装到HarmonyOS平台上。其它平台需要单独打包,.arkui-x下的Android项目导入到Android Studio中打出.apk;.arkui-x下的iOS项目导入到Xcode中打出.IPA。至此完成三个平台的打包工作。也就是说,在Build App过程中,ArkUI-X框架会把entry里公用ArkTS代码和resource,打入到各平台的安装包或项目文件中。

应用调试方面,目前只支持HarmonyOS调试,Android和iOS暂不支持ArkTS调试。

其实上述的环境搭建、新建项目、编译打包和安装调试,还有另一种方式实现——ACE Tools,是一套为ArkUI-X开发者提供的命令行工具。详情参见:ACE Tools快速指南(https://gitee.com/arkui-x/docs/blob/master/zh-cn/application-dev/quick-start/start-with-ace-tools.md)

04

窥探ArkUI的绘制过程

在了解 ArkUI-X 实现跨平台之前,我们先简单过一遍ArkTS编写的UI界面,是如何绘制到OpenHarmony/HarmonyOS上的,以上面的Demo为例:

// Index.ets
@Entry
@Component
struct Index {
  @State message: string = '今天星期五'
  
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
    }
    .height('100%')
  }
}

Build App / Hap 之后得到hap包,和Android apk类似,对hap包进行解压得到modules.abc字节码文件 (Ark Byte Code),再对字节码文件进行16进制解析,可以看到这样一段代码:

class Index extends ViewPU {
    constructor(parent, params, __localStorage, elmtId = -1, paramsLambda = undefined, extraInfo) {
        super(parent, __localStorage, elmtId, extraInfo);
        this.__message = new ObservedPropertySimplePU('今天星期五', this, "message");
        this.setInitiallyProvidedValue(params);
    }
    setInitiallyProvidedValue(params: Index_Params) {
        if (params.message !== undefined) {
            this.message = params.message;
        }
    }
    aboutToBeDeleted() {
        this.__message.aboutToBeDeleted();
        SubscriberManager.Get().delete(this.id__());
    }
    private __message: ObservedPropertySimplePU<string>;

    initialRender() {   
        // 编译器根据ArkTS中对组件的描述部分,重新生成js
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Row.create();
            Row.height('100%');
        }, Row);
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Column.create();
            Column.width('100%');
        }, Column);
        this.observeComponentCreation2((elmtId, isInitialRender) => {
            Text.create(this.message);
            Text.fontSize(50);
            Text.fontWeight(FontWeight.Bold);
        }, Text);
        Text.pop();
        Column.pop();
        Row.pop();
    }
}

可以看出,我们用@Component编写的组件,经过ArkCompiler编译后,会生成一个继承自ViewPU的js类,这个过程和kotlin经过kotlin编译器生成java有点类似。ViewPU位于ArkUI框架的arkui_ace_engine

(https://gitee.com/openharmony/arkui_ace_engine/tree/master)仓, 部分代码如下:

// 位于 frameworks/bridge/declarative_frontend/state_mgmt/src/lib/partial_update/pu_view.ts
abstract class ViewPU extends PUV2ViewBase implements IViewPropertiesChangeSubscriber, IView {

  constructor(parent: IView, localStorage: LocalStorage, 
              elmtId: number = UINodeRegisterProxy.notRecordingDependencies, 
              extraInfo: ExtraInfo = undefined) {
    super(parent, elmtId, extraInfo); 
    this.id_ = SubscriberManager.MakeId() : elmtId;
    if (localStorage) {
      this.localStorage_ = localStorage;
    }
    SubscriberManager.Add(this);
  }
    
  public initialRenderView(): void {
    this.obtainOwnObservedProperties();
    this.initialRender();
    ...
  }
    
  // 编译器生成的js类会实现该方法,主要是对组件的描述
  protected abstract initialRender(): void;
    
  // implements IMultiPropertiesChangeSubscriber UI状态变化回调
  viewPropertyHasChanged(varName: PropertyInfo, dependentElmtIds: Set<number>): void {
    if (dependentElmtIds.size && !this.isFirstRender()) { // 第一次走initialRenderView()
      if (!this.dirtDescendantElementIds_.size && !this.runReuse_) {
        this.markNeedUpdate(); // 更新UI 最终调到C++
      }
      ...
    }
    
    let cb = this.watchedProps.get(varName);
    if (cb && typeof cb === 'function') {
      // ArkTS中@Prop @Watch('xxx') value 的invoke
      cb.call(this, varName);
    }
  }
}

从上面ViewPU的部分代码,可以提取几点信息:

  1. 构造方法第一个参数parent: IView,说明整个UI结构中存在子父组件概念,其内部维护一个子父组件关系链,这点和其它UI框架一样;

  2. 构造逻辑依次是:

  • 生成element Id,用于局部刷新;

  • 定义localStorage对象,用于页面状态共享;

  • SubscriberManager.Add(this),添加订阅,监听状态变化。

状态变化后,回调viewPropertyHasChanged,更新UI并执行@Watch装饰器逻辑。

更多实现可查看源码,当然只需要看看流程即可,因为代码的commit频次比较高,每次打开看细节都可能有所变化,而且还存在很多同名v2类、v2方法。总之,ViewPU 是所有组件的基类,ViewPU 继承自 PUV2ViewBasePUV2ViewBase 继承自 NativeViewPartialUpdate。从下面代码块的注释得知,UI渲染将在 C++ 里面实现,将通过NAPINAPIJNI 类似)完成jsC++的交互。

// 位于 frameworks/bridge/declarative_frontend/state_mgmt/src/lib/puv2_common/puv2_view_base.ts 和 puv2_view_native_base.d.ts
// implemented in C++  for release
abstract class PUV2ViewBase extends NativeViewPartialUpdate {
    ...
}

/**
 * NativeViewPartialUpdate aka JSViewPartialUpdate C++ class exposed to JS
 *  all definitions in this file are framework internal
 */
declare class NativeViewPartialUpdate {
  constructor();
  markNeedUpdate(): void; // 更新UI
  finishUpdateFunc(elmtId: number): void;
  ...
  static create(newView: NativeViewPartialUpdate): void;
}

markNeedUpdate()主要负责UI刷新,追踪一下它的实现逻辑:

// 位于 frameworks/bridge/declarative_frontend/jsview/js_view.cpp
void JSViewPartialUpdate::JSBind(BindingTarget object)
{
    JSClass<JSViewPartialUpdate>::Declare("NativeViewPartialUpdate");

    JSClass<JSViewPartialUpdate>::StaticMethod("create", &JSViewPartialUpdate::Create, opt);
    JSClass<JSViewPartialUpdate>::Method("markNeedUpdate", &JSViewPartialUpdate::MarkNeedUpdate);
}

void JSViewPartialUpdate::MarkNeedUpdate()
{
    needsUpdate_ = ViewPartialUpdateModel::GetInstance()->MarkNeedUpdate(viewNode_);
}
// 位于 frameworks/bridge/declarative_frontend/jsview/models/view_partial_update_model_impl.cpp
bool ViewPartialUpdateModelImpl::MarkNeedUpdate(const WeakPtr<AceType>& node)
{
    auto weakElement = AceType::DynamicCast<ComposedElement>(node);
    auto element = weakElement.Upgrade();
    if (element) {
        element->MarkDirty();
    }
    return true;
}

ComposedElementpipeline 记录组件信息的对象,weakElement.Upgrade()ComposedElement 放入 pipeline 中,最终通过图形渲染引擎(OpenGL ES、Skia)完成显示。详细代码参考frameworks/core/pipeline/base/composed_element.cpp

05

Android跨平台的实现

以上是一个hap包通过ArkUI完成渲染的大致过程,回到跨平台ArkUI-X,相同的ArkTS代码是如何运行在Android设备上的呢?

打开Android项目,看到assets下存放着ArkCompiler编译产物,和上一节中对.hap包解压后得到的文件一模一样。这是在编译环节中,编译脚本copy一份modules.abc字节码和resource到Android工程下,作为Android应用资源,打包时将以assets形式打入apk。

src/main/assets/arkui-x
    ├── entry
    |   ├── ets
    |   |   ├── modules.abc
    |   |   └── sourceMaps.map
    |   ├── resouces.index
    |   ├── resouces
    |   └── module.json
    └── systemres

如何在Android上执行modules.abc字节码呢?打开libs,发现ArkUI相关的so动态库和jar包。

libs
    ├── armabi-v7a
    |   ├── libarkui_android.so
    |   └── libhilog.so
    └── arkui_android_adapter.jar

其中libarkui_android.soarkui_ace_enginearkui_napifoundation/appframeworkarkui_for_android... 所编译出的动态库,是运行和界面渲染的必要环境。另一个arkui_android_adapter.jar的功能是:Android Application需要继承arkui_android_adapter.jar包所提供的StageApplicationStageApplication用于初始化资源路径以及加载配置信息。Activity需要继承arkui_android_adapter.jar包所提供的StageActivityStageActivity主要功能是将Android中Activity的生命周期与Harmony中Ability的生命周期进行映射。除此之外,arkui_android_adapter.jar适配了系统平台能力,如粘贴板、软键盘、字体、存储、日志。这里有个疑问:ArkUI-X是如何实现与Android系统之间的交互呢?ArkTSJava没有相互调用的能力,为了实现ArkTSJava交互,需要ArkTSC++交互,C++再与Java交互,调用链为ArkTS  -> NAPI -> C++ -> JNI -> Java,反之亦然,看起来十分复杂。ArkUI-X提供一套桥接能力,对于开发者来说,并不用关心这些封装逻辑,实际开发过程中,就像是ArkTSJava直接交互。

下面通过粘贴板的例子,探究它的具体实现过程。我们给系统粘贴板设置数据——'明天星期六',使用ArkTS实现如下,看看最终是如何调到Android Framework 给粘贴板设置数据的api:ClipboardManager#setPrimaryClip()

import pasteboard from '@ohos.pasteboard';

Button('拷贝到粘贴板')
  .onClick(() => {
    let pasteData: pasteboard.PasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, '明天星期六')
    let systemPasteboard: pasteboard.SystemPasteboard = pasteboard.getSystemPasteboard()
    systemPasteboard.setData(pasteData)
  })

这段代码对接ArkUI的调用链和上一节中渲染UI类似,就不再赘述了。粘贴板相关api 在 arkui_ace_engine中定义成抽象接口,如下:

// 位于 arkui_ace_engine/frameworks/core/common/clipboard/clipboard.h
namespace OHOS::Ace {
    class Clipboard : public AceType {
        DECLARE_ACE_TYPE(Clipboard, AceType);
    public:
        ~Clipboard() override = default;
        
        virtual void SetData(
            const std::string& data, CopyOptions copyOption = CopyOptions::InApp, bool isDragData = false) = 0;
    }
}

ArkUI-X 分平台对接口进行不同实现,Android的实现在arkui_for_android仓库中,其定义和实现如下,可见粘贴板设置数据api SetData()最终到ClipboardJni::SetData(data)

// 位于 arkui_for_android/capability/java/jni/clipboard/clipboard_impl.h
#include "core/common/clipboard/clipboard.h"

namespace OHOS::Ace::Platform {
    class ClipboardImpl final : public Clipboard {
    public:
        explicit ClipboardImpl(const RefPtr<TaskExecutor>& taskExecutor) : Clipboard(taskExecutor) {}
        ~ClipboardImpl() override = default;
    
        void SetData(
            const std::string& data, CopyOptions copyOption = CopyOptions::InApp, bool isDragData = false) override;
    }
}
// 位于 arkui_for_android/capability/java/jni/clipboard/clipboard_impl.cpp
#include "adapter/android/capability/java/jni/clipboard/clipboard_impl.h"
#include "adapter/android/capability/java/jni/clipboard/clipboard_jni.h"

namespace OHOS::Ace::Platform {
    void ClipboardImpl::SetData(const std::string& data, CopyOptions copyOption, bool isDragData)
    {
         // 对 SetData 实现,最终ClipboardJni::SetData(data)
        taskExecutor_->PostTask(
            [data] { ClipboardJni::SetData(data); }, TaskExecutor::TaskType::PLATFORM, "ArkUI-XClipboardImplSetData");
    }
}

再通过JNI实现C++Java交互:

// 位于 arkui_for_android/capability/java/jni/clipboard/clipboard_jni.cpp
static const char CLIPBOARD_PLUGIN_CLASS_NAME[] = "ohos/ace/adapter/capability/clipboard/ClipboardPluginBase";
static const JNINativeMethod METHODS[] = {
    { .name = "nativeInit", .signature = "()V", .fnPtr = reinterpret_cast<void*>(ClipboardJni::NativeInit) },
};
static const char METHOD_SET_DATA[] = "setData";
static const char SIGNATURE_SET_DATA[] = "(Ljava/lang/String;)V";

// JNI_OnLoad
bool ClipboardJni::Register(std::shared_ptr<JNIEnv> env)
{
    // 动态注册 nativeInit 方法,java侧调用
    jclass clazz = env->FindClass(CLIPBOARD_PLUGIN_CLASS_NAME);
    bool ret = env->RegisterNatives(clazz, METHODS, ArraySize(METHODS)) == 0;
    return true;
}

void ClipboardJni::NativeInit(JNIEnv* env, jobject object)
{
    jclass clazz = env->GetObjectClass(object);
    g_pluginMethods.setData = env->GetMethodID(clazz, METHOD_SET_DATA, SIGNATURE_SET_DATA);
}


bool ClipboardJni::SetData(const std::string& data)
{
    auto env = JniEnvironment::GetInstance().GetJniEnv();
    jstring jData = env->NewStringUTF(data.c_str());
    // 反射调用 ClipboardPluginAosp.java setData方法
    env->CallVoidMethod(g_clipboardObj.get(), g_pluginMethods.setData, jData);
    if (jData != nullptr) {
        env->DeleteLocalRef(jData);
    }
    return true;
}

最终通过ClipboardManager#setData() ,将ArkTS中设置的内容,给到Android的系统粘贴板。

// 位于 arkui_for_android 仓库打出的 arkui_android_adapter.jar 包中
public class ClipboardPluginAosp extends ClipboardPluginBase {
    private final ClipboardManager clipManager;
    
    public ClipboardPluginAosp(Context context) {
        this.clipManager = (ClipboardManager context.getSystemService(Context.CLIPBOARD_SERVICE);
        nativeInit();
    }

    @Override
    public void setData(String data) {
        if (clipManager != null) {
            ClipData clipData = ClipData.newPlainText(null, data);
            clipManager.setPrimaryClip(clipData);
        }
    }
}

iOS平台的实现和Android平台类似,原理都是相通的。

06

小结

通过这一章,我们学到了ArkUI-X的环境搭建、项目创建和打包流程,探索了ArkTS编写的项目,编译后字节码文件如何与ArkUI对接,了解了ArkUI-X在Android平台上的实现方案,以及ArkUI-X如何适配系统平台能力。ArkUI-X 属于后来者,设计之初应该借鉴过其它跨平台方案,汲取了优秀设计,才形成目前的形态。今后在跨平台的实现上,我们又多了一种选择。

参考
  • ArkUI-X仓库地址:https://gitee.com/arkui-x

  • ArkUI-arkui_ace_engine仓库地址:https://gitee.com/openharmony/arkui_ace_engine

  • 深入理解arkui_ace_engine:https://juejin.cn/post/7305235970286485515

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关于更新字段为空值——MybatisPlus框架

背景&#xff1a;我们在项目开发过程中&#xff0c;可能会经常遇到这样的问题&#xff0c;某个前端的字段&#xff0c;用户把原本有值的改为空值了&#xff0c;用户的意愿肯定是要去更新的&#xff0c;前端此时会把这个字段传"null"或空字符串&#xff0c;但我们后端…

CherryStudio调用DeepSeek API实现AI对话

目录 一、CherryStudio是什么&#xff1f;二、下载安装CherryStudio三、调用DeepSeek API&#xff08;以华为云为例&#xff09;1.新建服务模型2.获取API Key和API 地址3.添加模型检查连接 四、体验刚建立成功的deepseek五、总结 一、CherryStudio是什么&#xff1f; CherrySt…

夜莺监控 v8.0 新版通知规则 | 对接钉钉告警

对新版本通知规则还不太了解的用户可以阅读文章&#xff1a;《夜莺监控巨大革新&#xff1a;抽象出通知规则&#xff0c;增强告警通知的灵活性》。下面我们将以钉钉通知为例&#xff0c;介绍如何使用新版通知规则来对接钉钉通知。 上图是通知规则对接钉钉通知的示意逻辑图。 在…

pycharm找不到conda可执行文件

conda 24.9.2 在pycharm的右下角就可以切换python解释器了

第六课:数据库集成:MongoDB与Mongoose技术应用

本文详细介绍了如何在Node.js应用程序中集成MongoDB数据库&#xff0c;并使用Mongoose库进行数据操作。我们将涵盖MongoDB在Ubuntu 20系统中的安装、Bash命令的CRUD操作、Mongoose数据建模&#xff08;Schema/Model&#xff09;、关联查询与聚合管道&#xff0c;以及实战案例—…

小谈java内存马

基础知识 &#xff08;代码功底不好&#xff0c;就找ai优化了一下&#xff09; Java内存马是一种利用Java虚拟机&#xff08;JVM&#xff09;动态特性&#xff08;如类加载机制、反射技术等&#xff09;在内存中注入恶意代码的攻击手段。它不需要在磁盘上写入文件&#xff0c…

Swift系列01-Swift语言基本原理与设计哲学

本文将深入探讨Swift的核心原理、设计理念以及与Objective-C的对比 1. Swift与Objective-C的架构差异分析 Swift和Objective-C尽管可以无缝协作&#xff0c;但它们的架构设计存在本质差异。 1.1语言范式 Objective-C是一种动态语言&#xff0c;建立在C语言之上并添加了Smal…

解决:Word 保存文档失败,重启电脑后,Word 在试图打开文件时遇到错误

杀千刀的微软&#xff0c;设计的 Word 是个几把&#xff0c;用 LaTex 写完公式&#xff0c;然后保存&#xff0c;卡的飞起 我看文档卡了很久&#xff0c;就关闭文档&#xff0c;然后 TMD 脑抽了重启电脑 重启之后&#xff0c;文档打不开了&#xff0c;显示 杀千刀的&#xff…

把握好自己的节奏, 别让世界成为你的发条匠

我见过凌晨两点还在回复工作群消息的职场妈妈&#xff0c;也见过凌晨三点抱着手机刷短视频的年轻人。 地铁站台的上班族永远在狂奔&#xff0c;连刚会走路的小孩都被早教班塞满了日程表。 现如今生活节奏快&#xff0c;像一只巨大的发条&#xff0c;每个人都被拧得紧紧的&#…

大型语言模型训练的三个阶段:Pre-Train、Instruction Fine-tuning、RLHF (PPO / DPO / GRPO)

前言 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 当前的大型语言模型训练大致可以分为如下三个阶段&#xff1a; Pre-train&#xff1a;根据大量可获得的文本资料&#…

LeetCode 解题思路 12(Hot 100)

解题思路&#xff1a; 定义三个指针&#xff1a; prev&#xff08;前驱节点&#xff09;、current&#xff08;当前节点&#xff09;、nextNode&#xff08;临时保存下一个节点&#xff09;遍历链表&#xff1a; 每次将 current.next 指向 prev&#xff0c;移动指针直到 curre…

【情境领导者】评估情境——什么是准备度

本系列是看了《情境领导者》一书&#xff0c;结合自己工作的实践经验所做的学习笔记。 准备度是指完成某一项特定的工作所表现出来的能力和意愿水平。 能力&#xff1a;是个人或组织在某一项特定工作或活动所表现出的知识、经验与技能。 意愿&#xff1a;是指个人或组织完成某一…

【GoTeams】-3:构建api、重构错误码

本文目录 1. 构建api梳理调用关系api包的作用路由梳理注册Register代码语法 2. 重构错误码 1. 构建api 首先复制project-user&#xff0c;改名为project-api&#xff0c;放在总的路径下&#xff0c;然后在工作区中进行导入。 运行命令go work use .\project-api\新建工作区之…

clickhouse ppt

《了解ClickHouse&#xff1a;快速数据查询的利器》 大家好&#xff0c;今天我们要介绍一个特别适合处理大规模数据分析任务的数据库系统——ClickHouse。它是一个开源的列式存储数据库管理系统&#xff08;DBMS&#xff09;&#xff0c;专门针对在线分析处理&#xff08;OLAP…

深度学习---卷积神经网络

一、卷积尺寸计算公式 二、池化 池化分为最大池化和平均池化 最常用的就是最大池化&#xff0c;可以认为最大池化不需要引入计算&#xff0c;而平均池化需要引出计算&#xff08;计算平均数&#xff09; 每种池化还分为Pooling和AdaptiveAvgPool Pooling(2)就是每2*2个格子…

HCIA-路由重分布

一、路由重分布是指在同一个网络中&#xff0c;将一种路由协议所学习到的路由信息导入到另一种路由协议中的技术&#xff0c;实现通信。 二、实验 1、配置 AR1AR2AR3sy sy AR1 int g 0/0/1 ip add 192.168.1.254 24 int g 0/0/0 ip add 10.1.1.1 24 rip version 2 net 192.16…

测试用例详解

一、通用测试用例八要素   1、用例编号&#xff1b;    2、测试项目&#xff1b;   3、测试标题&#xff1b; 4、重要级别&#xff1b;    5、预置条件&#xff1b;    6、测试输入&#xff1b;    7、操作步骤&#xff1b;    8、预期输出 二、具体分析通…

langchain系列(九)- LangGraph 子图详解

一、导读 环境&#xff1a;OpenEuler、Windows 11、WSL 2、Python 3.12.3 langchain 0.3 langgraph 0.2 背景&#xff1a;前期忙碌的开发阶段结束&#xff0c;需要沉淀自己的应用知识&#xff0c;过一遍LangGraph 时间&#xff1a;20250307 说明&#xff1a;技术梳理&#…

Kotlin和Java区别

哈哈哈&#xff0c;前段时间&#xff0c;面试的时候&#xff0c;突然问到我Kotlin和Java的区别&#xff0c;一下子把我问懵逼了&#xff0c;确实没遇到问这个的&#xff0c;想了下&#xff0c;说了下Kotlin的编译时空检查机制&#xff0c;代码更简洁&#xff0c;很多封装好的AP…

ADSP21569 SPORT使用静态配置驱动方法

ADSP Driver除了可以使用提供API配置&#xff0c;还可以使用静态配置文件进行配置 1&#xff0c;ADI的API比如SPORT驱动API 截图如下所示 可以查看帮助文档获取并使用&#xff0c;调用简洁方便&#xff0c;但不会直观与寄存器联系&#xff0c;不利于调试 2、驱动静态配置可以找…