Bootstrap

2024最新鸿蒙开发面试题合集(一)-HarmonyOS Next Developer Beta6(API 12)

前端卷的不行,岗位也少,花了一个星期把HarmonyOS应用开发者基础认证HarmonyOS应用开发者高级认证拿下了,除了概念性内容不要硬背,理解为主。有需要题库的可以点这,是9月份最新搜集的题目,共计188题,均分85,没信心考过的可以私信联系博主,这个高级认证很重要但有点难考,尤其是6月23日更新之后的题目,取消了10道判断题且增加10道题,改为40道单选(40分)+20道多选(60分),很多公司都是要6.23之后的高级认证,初级认证可以不考但高级认证是鸿蒙开发岗位的入场券,没有这个证就相当于没有从业资格,除非你是本就是安卓或者ios开发

现在的官网文档和特性更新很快很频繁,所以指不定啥时候就增加/删除一些东西了,有错误很正常,提醒我修改下。

1. HarmonyOS应用打包后的文件扩展名是?

打包后的文件扩展名为.hap(HarmonyOS Ability Package),这是HarmonyOS应用的标准包格式

2. 页面和自定义组件生命周期有哪些?

页面和自定义组件生命周期说明

有@Entry装饰器的@component组件的生命周期

  • onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
  • onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
  • onBackPress:当用户点击返回按钮时触发。

有@Entry装饰器和无@Entry装饰器@Component组件都有的生命周期

  • aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其 build()函数之前执行。
  • onDidBuild:API12新增,组件 build()函数执行完成之后回调该接口,不建议在 onDidBuild函数中更改状态变量、使用 animateTo等功能,这会导致不稳定的UI表现。
  • aboutToDisappearaboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改会导致应用程序行为不稳定。

3. 如何进行数据持久化?

应用数据持久化

  1. 用户首选项(Preferences):这是一种轻量级的配置数据持久化方式,适用于保存应用配置信息、用户偏好设置等。它通过文本形式保存数据,并且数据会全量加载到内存中,因此访问速度快,但不适合存储大量数据。
  2. 键值型数据库(KV-Store):适用于存储结构简单的数据,如商品名称和价格、员工工号和出勤状态等。键值型数据库以“键值对”的形式组织数据,适合数据关系不复杂的场景。
  3. 关系型数据库(RelationalStore):基于SQLite,适用于存储包含复杂关系的数据,如学生信息、雇员信息等。关系型数据库提供了一系列SQL操作,如增删改查等。

4. 如何进行全局状态管理?

应用全局的UI状态存储

  1. @Provide+@Consume装饰器
    适用场景:适用于整个组件树而言“全局”的状态共享,且该状态改动不频繁的场景。
    工作原理:通过在最顶层组件中使用 @Provide装饰器提供状态,其他需要共享状态的组件通过 @Consume装饰器获取该状态 。
    优点:减少了状态传递的层级,提升了代码的可维护性和可拓展性。
    注意事项:确保状态的生命周期与组件树的生命周期一致,避免不必要的UI刷新。
  2. AppStorage
    适用场景:适用于整个应用而言“全局”的变量或应用的主线程内多个 UIAbility实例间的状态共享。
    工作原理:AppStorage与应用的进程绑定,由UI框架在应用程序启动时创建,当应用进程终止,AppStorage被回收。
    优点:适用于需要在整个应用中共享状态的场景。
    注意事项:确保状态的生命周期与应用进程一致,避免在应用退出后仍有状态存在。
  3. LocalStorage
    适用场景:适用于单个Ability而言“全局”的变量,主要用于不同页面间的状态共享。
    工作原理:LocalStorage的生命周期由应用程序决定,当应用释放最后一个指向 LocalStorage的引用时,LocalStorage被垃圾回收。
    优点:适用于需要在单个UIAbility中不同页面间共享状态的场景。
    注意事项:确保状态的生命周期与应用程序的生命周期一致,避免在应用退出后仍有状态存在。

5. LocalStorage在应用重启后数据会消失吗?

页面级UI状态存储

会,因为LocalStorage 是一种用于页面或组件级别的数据存储方式,它允许开发者在页面或组件的生命周期内存储和检索数据。LocalStorage 的数据存储在内存中,因此它的读写速度相对较快。但是,当应用重启后,LocalStorage 中的数据会丢失。

6. 父子组件如何通信?

@Prop装饰器@Link装饰器@Provide和@Consume装饰器@Event装饰器@Parame装饰器@Provider装饰器和@Consumer装饰器
当前(API 12)状态管理有两个版本 @Component@ComponentV2

  1. 父子单向数据传递 @State+@Prop
    @Prop装饰的变量可以和父组件建立单向的同步关系。@Prop装饰的变量是可变的,但是变化不会同步回其父组件。
  2. 父子双向数据传递 @State+@Link@objectLink+@Link
    子组件中被 @Link装饰的变量与其父组件中对应的数据源建立双向数据绑定。
  3. 跨组件通信 @Provide装饰器和 @Consume装饰器
    @Provide@Consume,应用于与后代组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于 @Prop@Link@Provide@Consume摆脱参数传递机制的束缚,实现跨层级传递。
  4. @Observed装饰器和 @ObjectLink装饰器
    对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就要用到 @Observed/@ObjectLink装饰器
    注意:@ObjectLink装饰器不能在 @Entry装饰的自定义组件中使用@ObjectLink 装饰的变量不能被赋值,只能对其属性进行赋值操作

7. 兄弟组件如何通信?

  1. 通过公共父组件传递
    如果两个组件是同一个父组件的子组件,可以通过父组件来传递数据或事件。父组件可以作为中介,将一个子组件的数据或事件传递给另一个子组件。

  2. 使用全局状态管理
    使用全局状态管理(如 AppStorage、LocalStorage)来存储共享数据。兄弟组件可以独立地读取和更新这个全局状态,从而实现通信。

8. 如何实现页面间的通信?

  1. 使用 @Provide@Consume装饰器(见6.3)
  2. 使用路由跳转传参
import { router } from '@kit.ArkUI';
router.pushUrl({
  url: 'pages/Detail', // 目标url
   params: paramsInfo // 添加params属性,传递自定义参数
 })
 // 返回指定页面并携带参数
 router.back({
  url: 'pages/Home',
  params: {
    info: '来自Home页'
  }
});
  1. 使用导航跳转传参
this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.pushPathByName("PageOne", "PageOne Param")

9. Navigation组件跳转和router跳转有什么区别?

官方文档写了很多,捡几个我觉得比较重要的写的

    • Navigation:是路由容器组件,适用于模块内和跨模块的路由切换,一次开发,多端部署场景。Router位于页面栈管理节点 stage 下面,不提供导航容器的概念。
    • NavigationRouter都支持跳转传参,但 Router对象中暂不支持方法变量。
    • Navigation:支持清理指定路由,页面栈没有上限,可以无限跳转。Router不支持清理指定路由且页面栈最大为32,页面栈到达32之后必须清除之后才能继续跳转。
    • Navigation:支持自定义转场动画和共享元素转场动画。 Router:仅支持简单自定义转场动画。
    • Navigation:支持通过 setInterception 方法设置路由拦截。Router:不支持路由拦截。
    • Navigation:支持沉浸式页面和模态嵌套路由。Router:不支持,需要通过窗口配置实现沉浸式页面。

总而言之,Navigation 组件在功能上更具丰富性和灵活性,特别是在处理复杂的导航结构、动效和路由管理方面。
Router 则提供了更基础的路由跳转功能,适合简单的路由需求。开发者可以根据应用的具体需求和设计选择最合适的路由方案。

具体的区别如下表:

业务场景NavigationRouter
一多能力支持,Auto模式自适应单栏跟双栏显示不支持
跳转指定页面pushPath & pushDestinationpushUrl & pushNameRoute
跳转HSP中页面支持支持
跳转HAR中页面支持支持
跳转传参支持支持
获取指定页面参数支持不支持
传参类型传参为对象形式传参为对象形式,对象中暂不支持方法变量
跳转结果回调支持支持
跳转单例页面支持支持
页面返回支持支持
页面返回传参支持支持
返回指定路由支持支持
页面返回弹窗支持,通过路由拦截实现showAlertBeforeBackPage
路由替换replacePath & replacePathByNamereplaceUrl & replaceNameRoute
路由栈清理clearclear
清理指定路由removeByIndexes & removeByName不支持
转场动画支持支持
自定义转场动画支持支持,动画类型受限
屏蔽转场动画支持全局和单次支持 设置pageTransition方法duration为0
geometryTransition共享元素动画支持(NavDestination之间共享)不支持
页面生命周期监听UIObserver.on(‘navDestinationUpdate’)UIObserver.on(‘routerPageUpdate’)
获取页面栈对象支持不支持
路由拦截支持通过setInterception做路由拦截不支持
路由栈信息查询支持getState() & getLength()
路由栈move操作moveToTop & moveIndexToTop不支持
沉浸式页面支持不支持,需通过window配置
设置页面标题栏(titlebar)和工具栏(toolbar)支持不支持
模态嵌套路由支持不支持

10. HarmonyOS与Android和iOS有什么区别?

HarmonyOS 是华为开发的一个开源、分布式的操作系统。它设计用于多种设备,包括智能手机、平板电脑、智能电视和物联网设备。与Android和iOS的主要区别在于:

  • 分布式架构:HarmonyOS支持跨设备无缝协作,允许设备之间共享硬件资源。
  • 性能:HarmonyOS优化了任务调度和内存管理,提高了性能和响应速度。
  • 安全性:HarmonyOS采用了多层次的安全策略,包括数据加密和安全启动。
  • 生态系统:HarmonyOS正在构建自己的应用生态系统,鼓励开发者使用Ark Ts和ArkUI框架。

11. 什么是Ability?

Ability是应用/服务所具备的能力的抽象,一个Module可以包含一个或多个 Ability ,在鸿蒙系统中,Ability提供了对 Ability生命周期、上下文环境等调用管理的能力,包括 Ability创建、销毁、转储客户端信息等

鸿蒙系统中的 Ability主要分为两种类型:UIAbilityExtensionAbility

  1. UIAbility
  • 定义 :包含UI界面,提供展示UI的能力,主要用于和用户交互 。
  • 创建 :在模块中添加UIAbility时,选中对应的模块,单击鼠标右键,选择New > Ability,设置Ability名称,选择是否在设备主屏幕上显示该功能的启动图标,单击Finish完成Ability创建 。
  1. ExtensionAbility
  • 定义 :提供特定场景的扩展能力,满足更多的使用场景 。
  • 创建 :在模块中添加ExtensionAbility时,选中对应的模块,单击鼠标右键,选择不同的场景类型(如Accessibility、EmbeddedUIExtensionAbility等) 。当前仅Application工程支持创建ExtensionAbility。设置Ability名称,单击Finish完成ExtensionAbility创建。

此外,Ability是Ability模块的基类,提供系统配置更新回调和系统内存调整回调 。Ability的继承关系包括UIAbility和ExtensionAbility等具体类.

总之,Ability是鸿蒙系统中用于管理应用能力的核心组件,通过不同类型的Ability可以实现不同的功能需求。

12. ArkUI框架有哪些特点?

ArkUI框架是鸿蒙(HarmonyOS)中的一个重要组件框架,具有以下几个特点:

  1. 组件树结构 : ArkUI框架通过布局组件和基础组件构建界面描述树(组件树),其中基础组件为叶子节点,布局组件为中间节点 。当用户进行交互时,会触发界面修改,通过重新渲染组件树来实现应用界面更新 。
  2. 数据与UI更新过程 : ArkUI框架的数据处理过程和UI更新过程是分开进行的。数据处理过程中,主要是对状态数据进行更新,并通过标脏过程确定布局最小影响范围,减少不必要的重新布局 。UI更新过程包括组件标脏、布局、测量和渲染等阶段 。
  3. 布局组件 : ArkUI框架提供了多种布局组件,如Row、Column、Stack、Flex、List、Grid、RelativeContainer等。开发者可以根据场景选择合适的布局组件,以优化性能 。例如,线性布局(Row、Column)适用于横向或纵向排列组件,而弹性布局(Flex)适用于需要弹性排列的场景。
  4. 性能优化 : ArkUI框架在性能优化方面做了很多工作。例如,通过减少不必要的组件嵌套和节点数量,降低布局测算的复杂度,从而提升性能。开发者可以使用DevEco Studio提供的工具(如Profiler和ArkUI Inspector)来查看性能瓶颈和组件树结构,进一步优化应用性能。
  5. 状态管理 : ArkUI框架支持状态管理最佳实践,通过有效的状态管理减少无效的UI更新操作,提升性能。例如,在状态变量变化导致UI更新时,只更新部分组件,而不是重新渲染整个界面。

13. 跨设备通信的方式有哪些?

HarmonyOS支持多种跨设备通信方式,包括:

  • 分布式软总线:一种高性能的通信机制,允许设备之间建立直接连接,进行数据传输。
  • 蓝牙:使用标准的蓝牙技术进行设备间的通信。
  • WLAN:通过WLAN网络实现设备间的通信。
  • 远程服务调用:通过分布式任务调度实现跨设备的服务调用。

14. 如何实现应用的后台运行?

  • 后台服务:使用后台服务(如BackgroundService)来执行不需要用户直接交互的任务。
  • 定时任务:通过系统提供的定时任务机制(如AlarmService)来周期性执行后台任务。
  • 事件监听:注册系统事件,如网络变化、电量变化等,以在特定事件发生时唤醒应用进行处理。

15. Ability是如何与用户交互的?

  • 界面显示:Ability可以包含一个或多个AbilitySlice,用于显示UI界面并与用户进行交互。
  • 事件处理:Ability可以处理用户的输入事件,如触摸、按键等。
  • 数据绑定:Ability可以使用数据绑定机制,将UI组件与数据模型绑定,实现数据的自动更新和交互。
  • 通知:Ability可以通过系统通知机制向用户发送通知,即使应用不在前台运行。

16. 如何实现应用的多语言支持?

  • 资源文件:为每种语言创建资源文件(如string.json),并在里面定义所有可本地化的字符串。
  • 资源引用:在代码中使用资源ID引用字符串,而不是硬编码文本。
  • 系统设置:应用会自动根据系统设置的语言环境加载相应的资源文件。
  • 动态切换:支持在应用运行时切换语言,并动态更新UI。

17. 分布式数据库是如何实现数据同步的?

  • 分布式事务:确保跨设备的数据库操作具有原子性、一致性、隔离性和持久性。
  • 数据版本控制:为数据添加版本号,确保同步时数据的一致性。
  • 冲突解决策略:定义冲突解决策略,处理并发操作导致的数据冲突。
  • 网络状态感知:根据网络状态智能同步数据,优化同步效率和流量使用。

18. 如何优化应用的性能?

  • 内存管理:合理分配和释放内存,避免内存泄漏。
  • 后台优化:合理使用后台服务和定时任务,避免不必要的后台运行。
  • UI渲染优化:使用轻量级的UI组件,减少布局复杂度,优化渲染性能。
  • 资源优化:压缩图片和媒体资源,减少应用的体积和加载时间。

19. HarmonyOS中的权限管理模型是怎样的?

  • 权限声明:应用在config.json中声明所需的权限。
  • 权限申请:在应用运行时,根据需要动态申请权限。
  • 权限检查:在执行敏感操作前,检查是否已获得相应权限。
  • 权限分组:系统将权限分为不同的组,便于管理和申请。

20. LazyForEach是什么?

LazyForEach 是一个用于高效渲染列表的组件或功能,它允许开发者在用户滚动列表时才加载和渲染列表项,而不是一次性渲染整个列表。这种按需渲染的方式可以显著提高应用的性能,特别是在处理大量数据时。

21. LazyForEach的工作原理是什么?

LazyForEach 的工作原理通常是基于用户的滚动位置来动态地创建和销毁列表项的组件实例。当用户滚动到列表的某个部分时,LazyForEach 会加载并渲染那些即将进入视图的列表项,同时可能会卸载那些滚出视图的列表项,以节省内存和计算资源。

22. Router.replace()方法的作用是什么?和Router.pushUrl()方法有什么区别?

Router.replace()方法用于替换当前路由,并将目标路由压入栈顶。与Router.pushUrl()方法不同,Router.replace()方法不会保留当前路由,而是直接替换掉当前路由。

23. 如何实现应用的沉浸式模式?

沉浸式模式是指应用界面呈现出沉浸式的全屏模式,不留任何系统UI,用户只能看到应用内容。在沉浸式模式下,应用的UI元素会被覆盖,但系统状态栏、导航栏、键盘等系统UI依然可见。以下是实现步骤

  1. 设置窗口属性:
    在应用的入口Ability中,可以通过设置窗口属性来实现沉浸式模式。这通常涉及到配置窗口特性(Window Features)来隐藏状态栏和导航栏。
  2. 使用系统API:
    鸿蒙OS提供了API来控制系统UI的显示和隐藏。你可以在应用的代码中调用这些API来实现沉浸式效果。
  3. 配置应用的配置文件:
    在应用的config.json或其他配置文件中,可以声明应用需要的窗口特性,如ohos:immersive。
  4. 动态切换:
    应用可以根据用户的交互或特定场景动态地进入或退出沉浸式模式。这可能涉及到监听用户的手势或其他事件来切换UI状态。
  5. 适配不同设备:
    不同的设备可能有不同的屏幕和系统UI,因此在实现沉浸式模式时,需要考虑不同设备的适配问题。

24. 如何获取屏幕的安全区域?

  1. 可以通过设置组件的expandSafeArea属性来获取获取UIWindow:首先,你需要获取到当前页面的UIWindow实例。
  2. 调用getSafeArea方法:通过UIWindow实例调用getSafeArea方法来获取安全区域的Rect对象。
    示例:
import ohos.aafwk.ability.AbilitySlice;
import ohos.aafwk.content.Intent;
import ohos.agp.window.UIWindow;
import ohos.agp.utils.Rect;
public class MyAbilitySlice extends AbilitySlice {
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        setUIContent(new SurfaceLayout(this));
        UIWindow window = getUIWindow();
        if (window != null) {
            // 获取安全区域
            Rect safeArea = window.getSafeArea();
            // 在这里可以使用safeArea对象,它包含了安全区域的位置和尺寸信息
            // 例如,可以使用safeArea.left, safeArea.top, safeArea.right, safeArea.bottom
        }
    }
}

25. ArkTs是什么?

ArkTs介绍

ArkTS是HarmonyOS优选的主力应用开发语言。保持了TypeScript的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。
ArkTS的主要特点包括:

  • 静态类型检查:ArkTS在编译时进行类型检查,这有助于在代码运行前发现和修复错误,提高代码的稳定性和性能。
  • 声明式UI:ArkTS定义了声明式UI描述,允许开发者以更简洁、更自然的方式开发跨端应用。
  • 状态管理:ArkTS提供了多维度的状态管理机制,使得与UI相关联的数据可以在组件内使用,也可以在不同组件层级间传递,支持单向和双向数据流。
  • 渲染控制:ArkTS支持条件渲染、循环渲染和数据懒加载,允许开发者根据应用的不同状态渲染UI内容。
  • 兼容性:ArkTS兼容TS/JavaScript生态,开发者可以使用TS/JS进行开发或复用已有代码。
  • 并发机制:ArkTS支持轻量化的并发机制,允许开发者编写并发代码,提高应用的性能和响应速度。

26. ArkTs与TypeScript有什么区别?(答5点以上)

ArkTs官方文档

ArkTS 是基于 TypeScript 开发的框架,但是有一些限制和差异。ArkTS 旨在提供更严格的类型检查和优化的代码性能,同时确保与 HarmonyOS 的开发环境和特性兼容。以下是 ArkTS 与 TypeScript 的差异:

  • 不支持使用对象字面量声明类型。
  • 不支持使用 var关键字。
  • 不支持使用 in运算符。
  • 不支持导入断言。
  • 不支持使用 any类型。
  • 不支持使用 import赋值表达式。
  • 不支持使用 require导入

具体区别:

  1. 生成器函数:ArkTS 不支持 TypeScript 中的生成器函数(使用 function* 定义的函数),应使用 asyncawait 机制进行并行任务处理 。
  2. 参数解构:在函数参数中使用解构赋值是 TypeScript 的特性,ArkTS 不支持参数解构,需要显式传递参数 。
  3. 函数内声明函数:TypeScript 允许在函数内部声明新的函数,而 ArkTS 不支持在函数内声明函数,应使用 lambda 函数代替 。
  4. new.target:ArkTS 不支持 new.target 元属性,这是 TypeScript 中用于反射的属性 。
  5. 确定赋值断言:TypeScript 中的 ! 确定赋值断言在 ArkTS 中不被支持,应初始化变量或使用其他方式确保赋值 。
  6. 原型上的赋值:ArkTS 不支持在对象的原型上进行赋值,这与 TypeScript 不同 。
  7. globalThis:ArkTS 不支持 globalThis,这是 TypeScript 中用于获取全局对象的属性 。
  8. Function.prototype.applyFunction.prototype.callFunction.prototype.bind:ArkTS 不支持这些函数,它们在 TypeScript 中用于控制函数的 this 绑定 。
  9. instanceofas 类型保护:ArkTS 不支持 is 运算符,必须使用 instanceof 运算符替代,并且在使用之前,必须使用 as 运算符将对象转换为需要的类型 。
  10. 接口继承类:在 TypeScript 中,接口可以继承类,但在 ArkTS 中,接口只能继承接口 。
  11. 构造函数类型:ArkTS 不支持使用构造函数类型,应改用 lambda 函数 。
  12. enum 声明合并:ArkTS 不支持 enum 声明合并,所有相关的枚举成员必须在同一个声明中 。
  13. 命名空间作为对象:ArkTS 不支持将命名空间用作对象,可以使用类或模块替代 。
  14. 非声明语句在命名空间中:ArkTS 中,命名空间用于定义标志符可见范围,不支持命名空间中的非声明语句 。
  15. import default as ...:ArkTS 不支持 import default as ... 语法,应使用显式的 import ... from ... 语法 。
  16. requireimport 赋值表达式:ArkTS 不支持通过 require 导入,也不支持 import 赋值表达式,应使用 import 语法 。
  17. ambient 模块声明:ArkTS 不支持 declare module 语法,应直接导入需要的内容 。
  18. new.target:ArkTS 不支持 new.target 元属性,这是 TypeScript 中用于反射的属性 。
  19. Function.prototype.applyFunction.prototype.callFunction.prototype.bind:ArkTS 不支持这些函数,它们在 TypeScript 中用于控制函数的 this 绑定 。
  20. as const 断言:ArkTS 不支持 as const 断言,这是 TypeScript 中用于标注字面量的相应字面量类型的语法 。
  21. any:ArkTS 不支持any类型, 应使用更具体的类型替代 。

27. @Provider和@Consumer vs @Provide和@Consume的区别?

能力V2 装饰器@Provider 和@ConsumerV1 装饰器@Provide 和@Consume
本地初始化允许本地初始化,当找不到@Provider 的时候使用本地默认值。禁止本地初始化,当找不到对应的@Provide 时候,会抛出异常。
支持类型支持 function。不支持 function。
观察能力仅能观察自身赋值变化,如果要观察嵌套场景,配合@Trace 一起使用。观察第一层变化,如果要观察嵌套场景,配合@Observed 和@ObjectLink 一起使用。
alias 和属性名alias 是唯一匹配的 key,如果缺省 alias,则默认属性名为 alias。alias 和属性名都为 key,优先匹配 alias,匹配不到可以匹配属性名。
从父组件初始化禁止。允许。
支持重载默认开启,即@Provider 可以重名,@Consumer 向上查找最近的@Provider。默认关闭,即在组件树上不允许有同名@Provide。如果需要重载,则需要配置 allowOverride。

28. @Prop和@ObjectLink装饰器有什么区别?

1.用途

  • @Prop装饰器:主要用于在组件之间传递数据,将父组件的值传递给子组件。它定义了子组件的属性,可以接收来自父组件的赋值。@ObjectLink用于建立对象之间的链接,通常用于在组件内部或组件之间共享和同步状态。它可以将一个对象的属性与另一个对象的属性进行链接,当一个对象的属性发生变化时,另一个对象的属性也会自动更新。
  1. 数据传递方式
  • @Prop:是单向的数据传递,从父组件到子组件。父组件可以设置子组件的 @Prop属性值,但子组件不能直接修改这个值。@ObjectLink是双向的数据传递,父组件和子组件都可以修改子组件的 @ObjectLink属性值。
  1. 性能
  • @Prop会深拷贝数据,具有拷贝的性能开销,性能低于 @ObjectLink详见官方文档

29. ForEach和LazyForEach的区别?

ForEach和LazyForEach都是用于渲染列表的装饰器,它们的区别在于:

  • ForEach:渲染列表时,会将列表中的每一项都渲染一次,适用于列表项数量较少的情况。
  • LazyForEach:渲染列表时,只渲染当前可见的列表项,适用于列表项数量较多的情况。

30. UIAbility的生命周期有哪些?

官方文档说明

UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态,如下图所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

31. H5如何与HarmonyOS应用(webView)进行通信?官方文档

应用侧可以通过runJavaScript()方法异步调用前端页面的JavaScript相关函数,并通过Promise方式返回脚本执行的结果。runJavaScript需要在loadUrl完成后,比如onPageEnd中调用。

使用Web组件将应用侧代码注册到前端页面中,注册完成之后,前端页面中使用注册的对象名称就可以调用应用侧的函数,实现在前端页面中调用应用侧方法。注册应用侧代码有两种方式,一种在Web组件初始化调用,使用javaScriptProxy()接口。另外一种在Web组件初始化完成后调用,使用registerJavaScriptProxy()接口。

32. 如何实现图片上传?

有两种方式原生和Web组件:

  • 原生:使用上传下载模块(ohos.request)的上传接口将本地文件(图片)上传,需声明权限:ohos.permission.INTERNET。
    代码示例:
// pages/xxx.ets
import { common } from '@kit.AbilityKit';
import fs from '@ohos.file.fs';
import { BusinessError, request } from '@kit.BasicServicesKit';

// 获取应用文件路径
let context = getContext(this) as common.UIAbilityContext;
let cacheDir = context.cacheDir;

// 新建一个本地应用文件
let file = fs.openSync(cacheDir + '/test.txt', fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
fs.writeSync(file.fd, 'upload file test');
fs.closeSync(file);

// 上传任务配置项
let header = new Map<Object, string>();
header.set('key1', 'value1');
header.set('key2', 'value2');
let files: Array<request.File> = [
//uri前缀internal://cache 对应cacheDir目录
  { filename: 'test.txt', name: 'test', uri: 'internal://cache/test.txt', type: 'txt' }
]
let data: Array<request.RequestData> = [{ name: 'name', value: 'value' }];
let uploadConfig: request.UploadConfig = {
  url: 'https://xxx',
  header: header,
  method: 'POST',
  files: files,
  data: data
}

// 将本地应用文件上传至网络服务器
try {
  request.uploadFile(context, uploadConfig)
    .then((uploadTask: request.UploadTask) => {
      uploadTask.on('complete', (taskStates: Array<request.TaskState>) => {
        for (let i = 0; i < taskStates.length; i++) {
          console.info(`upload complete taskState: ${JSON.stringify(taskStates[i])}`);
        }
      });
    })
    .catch((err: BusinessError) => {
      console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
    })
} catch (error) {
  let err: BusinessError = error as BusinessError;
  console.error(`Invoke uploadFile failed, code is ${err.code}, message is ${err.message}`);
}
  • 使用axios上传:使用axios上传文件,需安装axios依赖。
    注意事项
  1. 上传文件需要单独导入FormData模块
  2. 当前版本只支持 Stage 模型
  3. 上传类型支持uri和ArrayBuffer,uri支持“internal”协议类型和沙箱路径,仅支持"internal"协议类型,"internal://cache/"为必填字段,示例: internal://cache/path/to/file.txt;沙箱路径示例:cacheDir + ‘/hello.txt’
  4. 请求的表单数据值为string类型
  5. 支持设置多部分表单数据的数据名称和数据类型类型
  6. 上传参数context:当uri为沙箱路径,无需传参context;若uri为“internal”协议类型,必须传参context

当上传的内容为ArrayBuffer时,用法如下

import axios from '@ohos/axios'
import { FormData } from '@ohos/axios'
import fs from '@ohos.file.fs';

// ArrayBuffer
let formData = new FormData()
let cacheDir = getContext(this).cacheDir
try {
  // 写入
  let path = cacheDir + '/hello.txt';
  let file = fs.openSync(path, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE)
  fs.writeSync(file.fd, "hello, world"); // 以同步方法将数据写入文件
  fs.fsyncSync(file.fd); // 以同步方法同步文件数据。
  fs.closeSync(file.fd);

  // 读取
  let file2 = fs.openSync(path, 0o2);
  let stat = fs.lstatSync(path);
  let buf2 = new ArrayBuffer(stat.size);
  fs.readSync(file2.fd, buf2); // 以同步方法从流文件读取数据。
  fs.fsyncSync(file2.fd);
  fs.closeSync(file2.fd);

  formData.append('file', buf2);
  // formData.append('file', buf2, { filename: 'text.txt', type: 'text/plain'}); 设置多部分表单数据的数据名称和数据类型类型
} catch (err) {
  console.info('err:' + JSON.stringify(err));
}
// 发送请求
axios.post<string, AxiosResponse<string>, FormData>(this.uploadUrl, formData, {
  headers: { 'Content-Type': 'multipart/form-data' },
  context: getContext(this),
  onUploadProgress: (progressEvent: AxiosProgressEvent): void => {
  console.info(progressEvent && progressEvent.loaded && progressEvent.total ? Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%');
},
}).then((res: AxiosResponse) => {
  console.info("result" + JSON.stringify(res.data));
}).catch((error: AxiosError) => {
  console.error("error:" + JSON.stringify(error));
})

当上传的uri时,用法如下

import axios from '@ohos/axios'
import { FormData } from '@ohos/axios'

let formData = new FormData()
formData.append('file', 'internal://cache/blue.jpg')
// formData.append('file', cacheDir + '/hello.txt'); uri支持传入沙箱路径

// 发送请求
axios.post<string, AxiosResponse<string>, FormData>('https://www.xxx.com/upload', formData, {
  headers: { 'Content-Type': 'multipart/form-data' },
  context: getContext(this),
  onUploadProgress: (progressEvent: AxiosProgressEvent): void => {
    console.info(progressEvent && progressEvent.loaded && progressEvent.total ? Math.ceil(progressEvent.loaded / progressEvent.total * 100) + '%' : '0%');
  },
}).then((res: AxiosResponse<string>) => {
  console.info("result" + JSON.stringify(res.data));
}).catch((err: AxiosError) => {
  console.error("error:" + JSON.stringify(err));
})

33.hap、har、hsp三者的区别?

  • HAP(Harmony Ability Package)是应用安装和运行的基本单元。HAP包是由代码、资源、第三方库、配置文件等打包生成的模块包,其主要分为两种类型:entry和feature。(又称ability)
  • HAR(Harmony Archive)是静态共享包,可以包含代码、C++库、资源和配置文件。通过HAR可以实现多个模块或多个工程共享ArkUI组件、资源等相关代码。(又称static library, 静态共享包)
  • HSP(Harmony Shared Package)是动态共享包,可以包含代码、C++库、资源和配置文件,通过HSP可以实现代码和资源的共享。HSP不支持独立发布,而是跟随其宿主应用的APP包一起发布,与宿主应用同进程,具有相同的包名和生命周期。(又称shared library, 动态共享包)

34. 鸿蒙常用的装饰器有哪些?

  • @State 定义状态,当前组件能使用
  • @Prop 父子组件通信(特点:子组件数据不能修改)
  • @Link 父子组件通信(特点:子组件数据可以修改)
  • @Observed 和 @ObjectLink 父子组件通信(特点:嵌套第二层数据修改可以达到响应式,之前方案不行)
  • @Provide 和 @Consume 祖孙组件通信
  • @Builder 和 @BuilderParam 父子组件通信,通信组件数据
  • @Watch 监视数据的变化(第一次不会触发)

35. 如何启动一个 ability?

通过 context 对象的 startAbility 方法官方文档:


import { common, Want } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';

context = getContext(this) as common.UIAbilityContext; // UIAbilityContext

const want: Want = {
  deviceId: '', // deviceId为空表示本设备
  bundleName: 'com.example.system', // AppScope/app.json5 中找
  abilityName: 'SecondAbility', // 去对应的ability内部找module.json5
  // moduleName: 'entry' // moduleName非必选
  parameters: { // 携带参数

  }
};
this.context.startAbility(want, (err: BusinessError) => {
  if (err.code) {
    // 显式拉起Ability,通过bundleName、abilityName和moduleName可以唯一确定一个Ability
    console.error(`Failed to startAbility. Code: ${err.code}, message: ${err.message}`);
  }
});

36. 显示 want 和 隐式 want 的区别?

官方文档

  • 显式Want:在启动目标应用组件时,调用方传入的want参数中指定了abilityName和bundleName,称为显式Want。
    显式Want通常用于应用内组件启动,通过在Want对象内指定本应用Bundle名称信息(bundleName)和abilityName来启动应用内目标组件。当有明确处理请求的对象时,显式Want是一种简单有效的启动目标应用组件的方式。
    例如:打开其他窗口
  • 隐式Want:在启动目标应用组件时,调用方传入的want参数中未指定abilityName,称为隐式Want。
    当需要处理的对象不明确时,可以使用隐式Want,在当前应用中使用其他应用提供的某个能力,而不关心提供该能力的具体应用。隐式Want使用skills标签来定义需要使用的能力,并由系统匹配声明支持该请求的所有应用来处理请求。例如,需要打开一个链接的请求,系统将匹配所有声明支持该请求的应用,然后让用户选择使用哪个应用打开链接。
    例如:将pdf文件传递给其他应用窗口
    总的来说
  • 显示want和隐式want的区别在于有无abilityName。有就是显示want,没有就是隐式want
  • 显示want主要用于当前应用窗口跳转,隐式want打开其他应用的窗口

37. 三层架构是什么?

官方文档
三层架构为了“一次开发,多端部署”,项目结构采用三层架构
三层工程结构如下:

  • commons(公共能力层):用于存放公共基础能力集合(如工具库、公共配置等)。commons层可编译成一个或多个HAR包或HSP包,只可以被products和features依赖,不可以反向依赖。
  • features(基础特性层):开发页面、组件(HAR包或HSP包)。
  • products(产品定制层):定义phone\pad两个ability,引用 features 的包和 commons 的包完成应用功能

38. 优化内存有哪些方法?

官方文档

  1. 使用onMemoryLevel监听内存变化
等级说明
MEMORY_LEVEL_MODERATE0系统内存适中。系统可能会开始根据 LRU 缓存规则杀死进程。
MEMORY_LEVEL_LOW1系统内存比较低。此时应该去释放掉一些不必要的资源以提升系统的性能。
MEMORY_LEVEL_CRITICAL2系统内存很低。此时应当尽可能地去释放任何不必要的资源,因为系统可能会杀掉所有缓存中的进程,并且开始杀掉应当保持运行的进程,比如后台运行的服务。
  1. 使用LRUCache优化ArkTS内存
    例如:我们搜索租房列表可以无限加载租房数据,这样数据会越来越多,我们使用LRUCacheUtil来管理数据
  2. 使用生命周期管理优化ArkTS内存
    例如:aboutToDisappear中销毁订阅事件,清除定时器等
    4.使用purgeable优化C++内存

39. 多线程实现方式TaskPoll和Worker的区别?

  1. TaskPool和Worker均支持多线程并发能力。由于TaskPool的工作线程会绑定系统的调度优先级,并且支持负载均衡(自动扩缩容),而Worker需要开发者自行创建,存在创建耗时以及不支持设置调度优先级,故在性能方面使用TaskPool会优于Worker,因此大多数场景推荐使用TaskPool。
  2. TaskPool偏向独立任务维度,该任务在线程中执行,无需关注线程的生命周期,超长任务(大于3分钟且非长时任务)会被系统自动回收;而Worker偏向线程的维度,支持长时间占据线程执行,需要主动管理线程生命周期。

40. 音视频的组件的使用方式?

  • 视频组件 Video
controller: VideoController = new VideoController()
Video({
  src: $rawfile('test.mp4'),
  previewUri: $r('app.media.startIcon'),
  controller: this.controller
})
.width('100%')
.height(200)
.autoPlay(true)
.controls(true)
//配置音频渲染参数并创建AudioRenderer实例,音频渲染参数的详细信息可以查看AudioRendererOptions。
import { audio } from '@kit.AudioKit';
let audioStreamInfo: audio.AudioStreamInfo = {
  samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率
  channels: audio.AudioChannel.CHANNEL_2, // 通道
  sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式
  encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式
};
let audioRendererInfo: audio.AudioRendererInfo = {
  usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION,
  rendererFlags: 0
};
let audioRendererOptions: audio.AudioRendererOptions = {
  streamInfo: audioStreamInfo,
  rendererInfo: audioRendererInfo
};
audio.createAudioRenderer(audioRendererOptions, (err, data) => {
  if (err) {
    console.error(`Invoke createAudioRenderer failed, code is ${err.code}, message is ${err.message}`);
    return;
  } else {
    console.info('Invoke createAudioRenderer succeeded.');
    let audioRenderer = data;
  }
});
// 调用on('writeData')方法,订阅监听音频数据写入回调。
import { BusinessError } from '@kit.BasicServicesKit';
import { fileIo } from '@kit.CoreFileKit';
let bufferSize: number = 0;
class Options {
  offset?: number;
  length?: number;
}
let path = getContext().cacheDir;
//确保该路径下存在该资源
let filePath = path + '/StarWars10s-2C-48000-4SW.wav';
let file: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
let writeDataCallback = (buffer: ArrayBuffer) => {
  let options: Options = {
    offset: bufferSize,
    length: buffer.byteLength
  }
  fileIo.readSync(file.fd, buffer, options);
  bufferSize += buffer.byteLength;
}
audioRenderer.on('writeData', writeDataCallback);
//调用start()方法进入running状态,开始渲染音频。
import { BusinessError } from '@kit.BasicServicesKit';
audioRenderer.start((err: BusinessError) => {
  if (err) {
    console.error(`Renderer start failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer start success.');
  }
});
//调用stop()方法停止渲染。
import { BusinessError } from '@kit.BasicServicesKit';
audioRenderer.stop((err: BusinessError) => {
  if (err) {
    console.error(`Renderer stop failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer stopped.');
  }
});
// 调用release()方法销毁实例,释放资源。
import { BusinessError } from '@kit.BasicServicesKit';
audioRenderer.release((err: BusinessError) => {
  if (err) {
    console.error(`Renderer release failed, code is ${err.code}, message is ${err.message}`);
  } else {
    console.info('Renderer released.');
  } 
});
;