简历相关问题
选择图片——拷贝到缓存目录
选择图片
- 用photoAccessHelper创建一个图片选择器的行为PhotoSelectOptions
- 约束选择图片的条件,选择图片的最大数量,及图片类型
- 在用photoAccessHelper创建一个图片选择器PhotoViewPicker
- 用异步方法photoPicker.select()获取用户的选择结果,返回的是获取用户选择的第一张图片的 URI(统一资源标识符)
拷贝图片到缓存目录
为了只能上传存在缓存目录的文件
预览模拟器中的文件
/data/app/el2/100/base/你的项目的boundle Name 即可 /haps/entry/cache
-
获取上下文对象
-
拼接生成唯一的图片名称 例如:时间戳.jpg
-
用filo.openSync()来获取选择到的uri的读取权限
-
通过缓存路径+文件名 拼接出完整的路径,生成图片储存的路径
-
用fileIo.copyFileSync()将待复制的文件复制到指定路径
申请权限-录音-播放录音
申请权限
配置
- module.json5中声明权限
- string.json说明申请权限的原因
封装的权限管理文件
录音
- 指定录音文件存储的沙箱路径,一般是采用filesdir文件目录+m4a格式进行拼接,因为cachedir会有容量限制,满了会存在覆盖问题
- fileIo.openSync()获得文件的相关权限
- 用media创建avRecord录音实例
- 用其中的prepare方法设置录音的相关配置信息(音频输入源,音频声道数,音频比特率,采样率,音频路径),仅支持m4a格式
- 开始录音start()方法,结束录音release()方法
播放
1,用media创建一个Avplayer实例
2,采用AvPlayer的on方法监听音频状态改变来触发回调函数
3,状态有初始化,播放,停止等,设置当前处于状态触发avPlayer的什么方法
4,通过uiabilityContext获取沙箱地址filesdir,采用fileIO.openSync获取这个地址的权限
5,结束播放时用release释放资源,防止内存泄漏
录音可以通过AVRecorder和AudioCapturer来实现。两者区别主要在支持录制声音的格式不同和控制录音文件的细小粒度不同上。AVRecorder会简单一些,AudioCapturer会复杂一些-还可以搭配ai语音功能使用
AVRecorder主要有以下这些状态:
类型 | 说明 |
---|---|
'idle' | 闲置状态。 |
'prepared' | 参数设置完成 |
'started' | 正在录制。 |
'paused' | 录制暂停。 |
'stopped' | 录制停止。 |
'released' | 录制资源释放。 |
'error' | 错误状态。 |
鸿蒙如何和网页端通信?
-
如果是应用的话,使用web组件和对应的controller的一些接口,如 runJavaScript()和registerJavaScriptProxy
-
如果是元服务的话,使用AtomicServiceWeb来实现,因为2025年1月22日后不支持使用web。还有AtomicServiceWeb没有了web中的如 runJavaScript()和registerJavaScriptProxy接口,但是它一样可以通过页面的url进行参数的传递和鸿蒙端提供了js sdk,也可以很方便的让h5端调用鸿蒙端的功能
在鸿蒙中entryAbility 与 UIability的区别
概念与用途
- EntryAbility:是应用程序的入口 Ability,主要用于标识应用的入口点,负责启动应用的初始逻辑和界面展示等工作,每个应用至少有一个 EntryAbility,用于引导用户进入应用的主要功能界面,类似于传统安卓应用中的 Launcher Activity。
- UIAbility:侧重于提供用户界面相关的功能和交互,是构建应用用户界面的基础能力单元,可以包含多个页面和界面元素,用于实现应用的各种具体功能界面,如设置界面、详情展示界面等。
生命周期
-
EntryAbility
- 其生命周期与应用的启动和初始化过程紧密相关,在应用启动时创建,负责完成一些全局的初始化工作,如加载配置信息、初始化数据库连接等。
- 当应用被关闭或系统资源紧张需要回收时,EntryAbility 会经历相应的销毁过程。
-
UIAbility
-
生命周期与具体的 UI 界面展示和交互相关,当 UIAbility 所对应的界面需要显示时,会经历创建、启动等过程。
-
当界面被隐藏或切换到其他界面时,会进入暂停或停止状态;当界面再次显示时,又会相应地恢复到活跃状态
-
有做过华为支付吗?
需要企业资质、需要在AGC平台上开通服务。
- 商户客户端请求商户服务器创建商品订单。
- 商户服务器按照商户模型调用Payment Kit服务端直连商户预下单或平台类商户/服务商预下单接口。
- 华为支付服务端返回预支付ID(prepayId)。
- 商户服务端组建订单信息参数orderStr返回给商户客户端。
- 商户客户端调用requestPayment接口调起Payment Kit支付收银台。
- Payment Kit客户端展示收银台。
- 用户通过收银台完成支付,Payment Kit客户端会收到支付结果信息并请求Payment Kit服务端处理支付。
- Payment Kit服务端成功受理支付订单并异步处理支付。
- Payment Kit服务端将支付结果返回给Payment Kit客户端。
- Payment Kit客户端展示支付结果页。
- 用户关闭支付结果页后Payment Kit客户端会返回支付状态给商户客户端。
- 支付处理完成后,Payment Kit服务端会调用回调接口返回支付结果信息给商户服务端。
- 商户服务端收到支付结果回调响应后,使用SM2验签方式对支付结果进行验签。
说一下多线程
HarmonyOS中的生命周期
页面生命周期
- onpageshow:页面每次显示时触发,包括路由过程、应用进入前台等场景。例如,用户从后台切换应用到前台,或者通过路由跳转到该页面时,此方法会被调用
- onpagehide:页面每次隐藏时触发,包括路由过程、应用进入后台等场景。比如用户按下主页键将应用切换到后台,或者通过路由跳转到其他页面时,该页面的 onpagehide 方法会被执行
- onbackpress:当用户点击返回按钮时触发。如果返回值为 true,表示页面自己处理返回逻辑,不进行页面路由;返回 false 则表示使用默认的路由返回逻辑,不设置返回值时按照 false 处理
组件生命周期
- abouttoappear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其 build () 函数之前执行。在该函数中可以修改变量,更改将在后续执行 build () 函数中生效
- abouttodisappear:在自定义组件析构销毁之前执行。在此函数中不允许改变状态变量,特别是 @link 变量的修改可能会导致应用程序行为不稳定
UIAbility 生命周期
- create 状态:在应用加载过程中,UIAbility 实例创建完成时触发,系统会调用 oncreate () 回调。可以在该回调中进行页面初始化操作,例如变量定义、资源加载等,用于后续的 UI 展示
- windowstagecreate 状态:UIAbility 实例创建完成之后,在进入 foreground 之前,系统会创建一个 windowstage。windowstage 创建完成后会进入 onwindowstagecreate () 回调,可以在该回调中设置 UI 加载、设置 windowstage 的事件订阅,如获焦 / 失焦、可见 / 不可见等事件
- foreground 状态:当 UIAbility 实例切换至前台时触发,对应于 onforeground () 回调。在 onforeground () 中可以申请系统需要的资源,或者重新申请在 onbackground 中释放的资源.
- background 状态:当 UIAbility 实例切换至后台时触发,对应于 onbackground () 回调。在该回调中可以释放 UI 界面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等.
- windowstagedestroy 状态:在 UIAbility 实例销毁之前,会先进入 onwindowstagedestroy 回调,可以在该回调中释放 UI 界面资源
- destroy 状态:在 UIAbility 实例销毁时触发,可以在 ondestroy () 回调中进行系统资源的释放、数据的保存等操作
用Entry和Navigation装饰的页面有哪些区别
- @Entry装饰的页面是应用的入口页面,通常用于展示应用的初始界面,而Navigation组件是一个导航容器,挂载在单个页面下,支持跨模块的动态路由。
- @Entry页面具有通用的生命周期方法,而Navigation组件里的页面不执行onPageShow、onPageHide等生命周期回调。
HarmonyOS中里面有几种包,分别有什么作用
HarmonyOS中有三种类型的包:HAP(HarmonyOS Ability Package)、HAR(Harmony Archive)、HSP(Harmony Shared Package)。
- HAP是应用安装和运行的基本单元,分为entry和feature两种类型。
- HAR是静态共享包,用于代码和资源的共享。
- HSP是动态共享包,用于应用内共享代码和资源。
简单介绍一下Stage模型
- Stage模型是HarmonyOS应用开发的基础架构,它提供了面向对象的开发方式,规范化了进程创建的方式,并提供组件化开发机制。
- Stage模型的组件天生具备分布式迁移和协同的能力,支持多设备形态和多窗口形态,重新定义了应用能力边界。
HarmonyOS中的动画
HarmonyOS提供了多种动画能力,包括属性动画、显式动画、转场动画、路径动画和粒子动画。
如何进行路由页面传参
在HarmonyOS中,可以通过router.pushUrl方法跳转到目标页面,并携带参数。在进入被分享页面时,通过router.getParams()来获取
传递的数据。此外,还可以使用LocalStorage等在页面间共享状态。
ArkTS和TS的区别有哪些区别
ArkTS是HarmonyOS优选的主力应用开发语言,它保持了TypeScript的基本风格,同时通过规范定义强化开发期静态检查和分析,提升程序执行稳定性和性能。ArkTS与TS的主要区别在于ArkTS是静态类型的,而TS支持动态类型。ArkTS在编译时进行类型检查,有助于在代码运行前发现和修复错误。
常见装饰器
-
@State:@State装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。
-
@Prop:@Prop装饰的变量可以和父组件建立单向同步关系,@Prop装饰的变量是可变的,但修改不会同步回父组件。深拷贝。
-
@Link:@Link装饰的变量可以和父组件建立双向同步关系,子组件中@Link装饰变量的修改会同步给父组件中建立双向数据绑定的数据源,父组件的更新也会同步给@Link装饰的变量。
-
@Provide/@Consume:@Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定。
-
@Observed:@Observed装饰class,需要观察多层嵌套场景的class需要被@Observed装饰。单独使用@Observed没有任何作用,需要和@ObjectLink、@Prop联用。
-
@ObjectLink:@ObjectLink装饰的变量接收@Observed装饰的class的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。
-
AppStorage是应用程序中的一个特殊的单例LocalStorage对象,是应用级的数据库,和进程绑定,通过@StorageProp和@StorageLink装饰器可以和组件联动。
-
AppStorage是应用状态的“中枢”,将需要与组件(UI)交互的数据存入AppStorage,比如持久化数据PersistentStorage和环境变量Environment。UI再通过AppStorage提供的装饰器或者API接口,访问这些数据。
-
框架还提供了LocalStorage,AppStorage是LocalStorage特殊的单例。LocalStorage是应用程序声明的应用状态的内存“数据库”,通常用于页面级的状态共享,通过@LocalStorageProp和@LocalStorageLink装饰器可以和UI联动。
防抖和节流
-
防抖是一种通过延迟执行函数,确保在连续高频触发的事件中,函数仅执行一次的技术。其核心逻辑是:若事件在指定时间间隔内被重复触发,则取消前一次的执行,并重新开始计时
-
防抖应用场景
- 搜索框输入联想:等待用户停止输入后再发起请求,避免频繁请求。
- 窗口大小调整(resize) :在窗口调整结束后计算布局,而非每次像素变化都触发。
- 按钮防重复提交:防止用户快速点击按钮导致多次提交。
-
具体实现
-
function debounce(func, delay) { let timer; return function (...args) { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, delay); }; }
-
-
节流是一种通过限制函数执行频率的技术,确保在指定时间间隔内,函数最多执行一次。其核心逻辑是:无论事件触发多频繁,函数始终按固定节奏执行。
-
节流应用场景
- 搜索框输入联想:等待用户停止输入后再发起请求,避免频繁请求。
- 窗口大小调整(resize) :在窗口调整结束后计算布局,而非每次像素变化都触发。
- 按钮防重复提交:防止用户快速点击按钮导致多次提交。
-
具体实现
-
function throttle(func, interval) { let lastTime = 0; return function (...args) { const now = Date.now(); if (now - lastTime >= interval) { func.apply(this, args); lastTime = now; } }; }
-
Class 和 interface的区别
-
Interface 只能定义类型,class可以定义类型和保护功能实现
-
interface可以同时继承多个接口,class只能同时继承一个父类
-
工作中两个都用,比如用class来封装了一些工具库 avplayer、首选项、全屏-沉浸式、axios等
鸿蒙的 router 和 Navigation 的对比
- router最多页面栈为32个,Navigation 无限制
- Navigation 支持一多开发,Auto模式自适应单栏跟双栏显示
- Navigation 支持获取指定页面参数
- Navigation 清理指定路由
- Navigation 支持路由拦截
能力对比
业务场景 | Navigation | Router |
---|---|---|
一多能力 | 支持,Auto模式自适应单栏跟双栏显示 | 不支持 |
跳转指定页面 | pushPath & pushDestination | pushUrl & pushNameRoute |
跳转HSP中页面 | 支持 | 支持 |
跳转HAR中页面 | 支持 | 支持 |
跳转传参 | 支持 | 支持 |
获取指定页面参数 | 支持 | 不支持 |
传参类型 | 传参为对象形式 | 传参为对象形式,对象中暂不支持方法变量 |
跳转结果回调 | 支持 | 支持 |
跳转单例页面 | 支持 | 支持 |
页面返回 | 支持 | 支持 |
页面返回传参 | 支持 | 支持 |
返回指定路由 | 支持 | 支持 |
页面返回弹窗 | 支持,通过路由拦截实现 | showAlertBeforeBackPage |
路由替换 | replacePath & replacePathByName | replaceUrl & replaceNameRoute |
路由栈清理 | clear | clear |
清理指定路由 | removeByIndexes & removeByName | 不支持 |
转场动画 | 支持 | 支持 |
自定义转场动画 | 支持 | 支持,动画类型受限 |
屏蔽转场动画 | 支持全局和单次 | 支持 设置pageTransition方法duration为0 |
geometryTransition共享元素动画 | 支持(NavDestination之间共享) | 不支持 |
页面生命周期监听 | UIObserver.on('navDestinationUpdate') | UIObserver.on('routerPageUpdate') |
获取页面栈对象 | 支持 | 不支持 |
路由拦截 | 支持通过setInercption做路由拦截 | 不支持 |
路由栈信息查询 | 支持 | getState() & getLength() |
路由栈move操作 | moveToTop & moveIndexToTop | 不支持 |
沉浸式页面 | 支持 | 不支持,需通过window配置 |
设置页面标题栏(titlebar)和工具栏(toolbar) | 支持 | 不支持 |
模态嵌套路由 | 支持 | 不支持 |
页面下拉刷新和页面上拉加载
-
下拉刷新可以使用Refresh组件,它提供了onStateChange和onRefreshing事件 用来实现下拉刷新的业务
-
List、Scroll、Grid、WaterFall等组件都提供了上拉加载更多事件,比如List组件的onReachEnd事件就是
响应式布局
-
断点
- 在 在UIAbility的onWindowStageCreate生命周期回调中监听 窗口尺寸变化事件,获取到当前窗口大小
- 因为窗口大小单位是px,需要调用px2vp函数转成vp
- 然后存到AppStorage中
- 最后页面 使用 AppStorage 即可
-
媒体查询
- 主要通过 mediaquery 结合 断点来使用
-
栅格布局
-
通过 GridRow 和 GridCol来实现
-
一列分成了12份, 结合栅格组件默认提供xs、sm、md、lg四个断点
-
断点续传
鸿蒙发送网络请求有两套方案
Request , 我们使用的axios就是 基于它封装的
RCP ,Remote Communication Kit(远场通信服务)是华为提供的HTTP发起数据请求的NAPI封装 目前新项目再推动它
断点续传
-
利用了远场通信 RemoteCommunicationKit
-
发送网络请求,利用TransferRange的from 和 to属性 进行截取下载内容,拼接到文件上即可
双向证书校验
用于验证服务端和客户端之间的身份和数据完整性,确保通信的安全性。
-
导入远场通信模块和文件读写模块
-
import { rcp } from '@kit.RemoteCommunicationKit'; import { fileIo } from '@kit.CoreFileKit';
-
-
使用文件读写模块 读取存在客户端的证书
-
// 读取 fileIo.read(file.fd // 存到字符串 content中 // 将读取的数据转换为字符串 let content = String.fromChar
-
-
然后调用给远场通信的configuration 方法设置到 security.certificate.content 属性中
-
request.configuration = { security: { certificate: { content: content,
-
项目优化
-
图片懒加载:列表里的图片滑到可见区域再加载,减少内存占用。
-
数据缓存:用
Preferences
或数据库缓存首页数据,下次启动先展示缓存再刷新。 -
减少布局嵌套:用@Builder代替自定义组件,多用线性布局,少用flex 等弹性布局
-
线程管理:把JSON解析、图片解码丢到
Worker
线程,防止主线程卡顿。 -
内存泄漏排查:用DevEco Studio的Profiler工具,发现有个页面退出后监听器没注销,赶紧加了
onPageHide
里的释放逻辑。
LazyForEach 如何实现更新
-
数据源绑定:
LazyForEach
需要与实现了IDataSource
接口的数据源(如LazyDataSource
)绑定。当数据源发生变化(增、删、改)时,框架会自动触发更新。 -
观察者模式:数据源通过
DataChangeListener
通知LazyForEach
数据变更。只有实际变化的项会触发局部更新,而非重新渲染整个列表。
多线程
有做过华为支付吗?
需要企业资质、需要在AGC平台上开通服务。
-
商户客户端请求商户服务器创建商品订单。
-
商户服务器按照商户模型调用Payment Kit服务端直连商户预下单或平台类商户/服务商预下单接口。
-
华为支付服务端返回预支付ID(prepayId)。
-
商户服务端组建订单信息参数orderStr返回给商户客户端。
-
商户客户端调用requestPayment接口调起Payment Kit支付收银台。
-
Payment Kit客户端展示收银台。
-
用户通过收银台完成支付,Payment Kit客户端会收到支付结果信息并请求Payment Kit服务端处理支付。
-
Payment Kit服务端成功受理支付订单并异步处理支付。
-
Payment Kit服务端将支付结果返回给Payment Kit客户端。
-
Payment Kit客户端展示支付结果页。
-
用户关闭支付结果页后Payment Kit客户端会返回支付状态给商户客户端。
-
支付处理完成后,Payment Kit服务端会调用回调接口返回支付结果信息给商户服务端。
-
商户服务端收到支付结果回调响应后,使用SM2验签方式对支付结果进行验签。
鸿蒙开发中手动签名和自动签名的区别?
在鸿蒙应用开发中,手动签名和自动签名是两种不同的应用签名方式,主要用于确保应用的安全性和完整性。以下是它们的区别:
手动签名
手动签名是指开发者手动配置签名证书并生成签名文件的过程。
特点:
完全手动操作:开发者需要自己生成签名证书、配置签名信息,并在构建应用时手动选择签名文件。
灵活性高:可以根据需求自定义签名证书和配置。
适用于正式发布:通常用于正式发布的应用,确保签名证书的安全性和可控性。
优点:
签名证书完全由开发者控制,安全性高。
适用于正式发布场景。
缺点:
操作复杂,需要手动配置和管理签名证书。
容易因配置错误导致签名失败。
自动签名
自动签名是指开发工具(如DevEco Studio)自动为应用生成临时签名证书并签名的过程。
特点:
自动化操作:开发工具自动生成临时签名证书,并在构建应用时自动签名。
适用于开发和调试:通常用于开发和测试阶段,方便快速构建和运行应用。
无需手动配置:开发者无需关心签名证书的生成和管理。
步骤:
在DevEco Studio中创建项目时,默认启用自动签名。
构建或运行应用时,工具会自动使用临时签名证书进行签名。
优点:
操作简单,无需手动配置签名信息。
适合开发和调试阶段,提升开发效率。
缺点:
签名证书由工具自动生成,安全性较低。
不适用于正式发布场景。
主要区别
对比项 | 手动签名 | 自动签名 |
---|---|---|
操作方式 | 手动生成和配置签名证书 | 开发工具自动生成临时签名证书 |
适用场景 | 正式发布 | 开发和调试 |
安全性 | 高(签名证书由开发者控制) | 低(签名证书由工具自动生成) |
灵活性 | 高(可自定义签名证书和配置) | 低(签名证书和配置由工具自动管理) |
操作复杂度 | 复杂(需手动配置和管理) | 简单(无需开发者干预) |
使用建议
开发和调试阶段:使用自动签名,方便快速构建和运行应用。
正式发布阶段:使用手动签名,确保签名证书的安全性和可控性。
总结:
手动签名和自动签名的主要区别在于操作方式和适用场景。手动签名适合正式发布,安全性高但操作复杂;自动签名适合开发和调试,操作简单但安全性较低。开发者应根据实际需求选择合适的签名方式。
断点续传总结
断点续传是一种在网络传输中实现中断后从中断点继续传输的技术,常用于文件上传或下载场景。以下是断点续传的核心要点总结:
- 核心原理
分块传输:将文件分成多个小块,每次传输一块。
记录进度:服务器和客户端分别记录已传输的块,中断后从中断点继续传输。
唯一标识:通过文件唯一标识(如MD5值)确保传输的是同一文件。
- 实现步骤
文件分块:
- 将文件按固定大小(如1MB)分块。
记录进度:
- 客户端记录已上传/下载的块,服务器记录已接收的块。
请求续传:
- 客户端发送续传请求,携带已传输的块信息。
校验文件:
- 通过文件唯一标识(如MD5)校验文件一致性。
继续传输:
- 从中断点继续传输剩余块。
合并文件:
- 传输完成后,将分块合并为完整文件。
- 关键技术
HTTP Range 请求:
客户端通过Range
头指定需要传输的字节范围。
服务器通过Content-Range
头返回指定范围的数据。
文件校验:
使用MD5、SHA等哈希算法校验文件完整性。
并发传输:
通过多线程或异步IO实现多块同时传输,提升效率。
- 优点
节省资源:避免重复传输已完成部分,节省带宽和时间。
提升用户体验:网络中断后无需重新开始传输。
支持大文件传输:通过分块传输降低单次传输压力。
- 应用场景
大文件上传(如云存储、视频上传)。
大文件下载(如软件安装包、视频下载)。
网络不稳定的环境(如移动网络)。
- 实现示例
客户端:
发送Range
头请求指定字节范围:
http
复制
GET /file.zip HTTP/1.1Range: bytes=500-999
记录已下载的块信息(如保存到本地文件或数据库)。
服务器:
解析Range
头,返回指定范围的数据:
http
复制
HTTP/1.1 206 Partial ContentContent-Range: bytes 500-999/10000Content-Length: 500
记录已接收的块信息。
- 注意事项
文件一致性:确保传输过程中文件未被修改。
并发控制:多线程传输时需处理资源竞争问题。
错误处理:网络中断或服务器异常时需重试机制。
总结:
断点续传通过分块传输和记录进度,解决了大文件传输和网络不稳定的问题,提升了传输效率和用户体验。其核心在于分块、记录、校验和续传,结合HTTP Range请求和文件校验技术,广泛应用于文件上传和下载场景。
在鸿蒙中router跳转到下一个页面会触发哪个生命周期
在鸿蒙(HarmonyOS)应用开发中,使用 router
进行页面跳转时,会涉及到相关页面(UIAbilitySlice
)的生命周期回调方法触发。以下为你详细介绍在不同情况下页面跳转所触发的生命周期回调。
基础生命周期回调触发情况
当使用 router
跳转到下一个页面(即新的 UIAbilitySlice
)时,主要会涉及到当前页面和目标页面的生命周期变化。
当前页面(即将离开的页面)
当调用路由跳转时,当前页面会进入非活动或不可见状态,依次触发以下生命周期回调:
-
onInactive
- 触发时机:当前页面失去焦点但仍可见时触发。例如,在跳转过程中,当前页面被部分覆盖或者即将隐藏。
- 作用:通常可用于暂停一些与用户交互相关的操作,比如暂停动画、取消定时器等。
-
onBackground
- 触发时机:当前页面完全不可见时触发,意味着它已经被新页面覆盖。
- 作用:可在此处释放一些与页面显示相关的资源,像停止音频播放、释放占用的系统资源等。
目标页面(即将进入的页面)
目标页面会经历创建和显示的过程,依次触发以下生命周期回调:
-
onStart
- 触发时机:目标页面首次被创建时调用,且整个生命周期仅调用一次。
- 作用:一般用于进行初始化操作,例如加载布局、初始化数据等。
-
onForeground
- 触发时机:目标页面进入前台并可见时触发。
- 作用:可以执行一些与界面显示相关的操作,如启动动画、更新界面数据等。
-
onActive
-
触发时机:目标页面处于活动状态,即可以与用户进行交互时触发。
-
作用:可在此处理用户交互相关的初始化工作,比如为按钮设置点击监听器等。
-
@objectprop @objectlink
在鸿蒙 ArkTS 开发中,@ObjectProp
和 @ObjectLink
用于处理对象类型数据的单向和双向数据绑定,下面为你详细介绍这两个装饰器:
@ObjectProp
作用
@ObjectProp
是用于单向数据绑定的装饰器,它允许父组件将一个对象类型的数据传递给子组件,子组件可以使用该对象的数据,但不能直接修改该对象来影响父组件的数据。如果子组件修改了 @ObjectProp
装饰的对象,这种修改仅在子组件内部生效,不会同步到父组件。
@ObjectLink
作用
@ObjectLink
用于实现对象类型数据的双向数据绑定。它允许子组件和父组件共享同一个对象的引用,当子组件修改 @ObjectLink
装饰的对象时,父组件中的对应对象也会同步更新;反之,父组件修改对象时,子组件也能感知到变化。
总结
@ObjectProp
用于对象类型数据的单向传递,子组件无法通过修改该对象影响父组件。
@ObjectLink
用于对象类型数据的双向绑定,子组件和父组件共享对象引用,一方修改会同步到另一方。
Map地图
Map Kit(地图服务)为开发者提供强大而便捷的地图能力,助力全球开发者实现个性化显示地图、位置搜索和路径规划等功能,轻松完成地图构建工作
地图功能是一种为用户提供地理信息和位置服务的重要工具。由于位置服务涉及隐私,我们需要拿到华为通过允许的uid,才可开发该功能,通过AGC打开该应用的天气服务开关开启天气服务功能.主要用到了鸿蒙自带的地图组件MapConpoment,其中地图显示需要地图的初始化参数,map options定义中心点坐标,相机位置,支持某种手势以及回调函数获取地图控制器以操作地图。
setMapType设置地图类型,比如:标准地图,空地图,地形图等。其中呢,可以通过地图显示自己的位置,通过安全控件locationButton来获取临时权限,或申请永久权限权限以开启“我的位置”功能。调用mapController对象的setMyLocationEnabled方法启用“我的位置”功能。
地图的前后台切换:根据页面的生命周期onPageshow(),onPageHide()来申请/释放资源
地图搜索功能是现代地图应用中至关重要的一项业务,主要为用户提供了便捷高效的地理信息查询服务,有两种开发技术:
第一种采用接口的方式向后端请求获取位置坐标系实现定位到搜索位置
第二种:用Map.kit自带的Poi功能,实现关键字搜索,周边搜索,自动补全,地点详情等功能
地点详情:创建地点详情参数,调用queryLocation方法拉起地点详情
创建地点选取参数,调用chooseLocation方法拉起地点选取页。
创建行政区划选择请求参数,调用selectDistrict方法拉起行政区划选择页。
鸿蒙地图 API 用到的坐标系主要有以下两种:
-
WGS84 坐标系:是一种国际上广泛使用的地理坐标系,也是鸿蒙地图 API 在海外地图开发中主要使用的坐标系。例如,当开发面向海外用户的地图应用时,地图数据会以 WGS84 坐标系为基础进行展示和交互,如定位用户位置、显示地图上的地点等,都将以该坐标系下的经纬度来表示具体的地理位置.
-
GCJ02 坐标系:即国测局坐标系,主要用于国内的地图应用。在鸿蒙系统中,如果应用的使用场景主要是在国内,那么地图 API 会使用 GCJ02 坐标系来确保地图数据的准确性和合规性,像在国内进行地图导航、查询周边兴趣点等功能时,所涉及的坐标数据均以 GCJ02 坐标系为基准.
地点详情展示控件功能主要由sceneMap命名空间下的queryLocation方法提供,更多接口及使用方法请参见接口文档。
接口名 | 描述 |
---|---|
LocationQueryOptions | 查询地点详情的参数。 |
queryLocation(context: common.UIAbilityContext, options: LocationQueryOptions): Promise | 查询地点详情。 |
地点选取控件功能主要由sceneMap命名空间下的chooseLocation方法提供,更多接口及使用方法请参见接口文档。
接口名 | 描述 |
---|---|
LocationChoosingOptions | 地点选取的参数。 |
chooseLocation(context: common.UIAbilityContext, options: LocationChoosingOptions): Promise<LocationChoosingResult> | 地点选取。 |
LocationChoosingResult | 地点选取的返回结果。 |
获取地理位置
developer.huawei.com/consumer/cn…
地址解析
街道 -> 经纬度 ->
逆地址解析
经纬度 -> 街道
import { geoLocationManager } from '@kit.LocationKit';
import { promptAction } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';
import { Logger } from 'basic';
@Builder
function AddressBuilder() {
Address()
}
// 获取当前位置信息
function getCurrentLocationInfo() {
const requestInfo: geoLocationManager.LocationRequest = {
'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
'scenario': geoLocationManager.LocationRequestScenario.UNSET,
'timeInterval': 1,
'distanceInterval': 0,
'maxAccuracy': 0
};
try {
geoLocationManager.getCurrentLocation(requestInfo)
.then((location: geoLocationManager.Location) => {
// https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-geolocationmanager-V5#location
// promptAction.showToast({ message: JSON.stringify(location) });
// Logger.log(location, "成功")
let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest =
{ "latitude": location.latitude, "longitude": location.longitude, "maxItems": 6 };
try {
geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest).then((data) => {
Logger.log(data, "中文地址")
})
.catch((error: BusinessError) => {
console.error('promise, getAddressesFromLocation: error=' + JSON.stringify(error));
});
} catch (err) {
console.error("errCode:" + JSON.stringify(err));
}
})
.catch((err: BusinessError) => {
console.error(`Failed to get current location. Code is ${err.code}, message is ${err.message}`);
});
} catch (err) {
console.error(`Failed to get current location. Code is ${err.code}, message is ${err.message}`);
}
}
@Component
struct Address {
build() {
NavDestination() {
Column() {
Text("地址")
LocationButton({
icon: LocationIconStyle.LINES,
text: LocationDescription.CURRENT_LOCATION,
buttonType: ButtonType.Normal
})
.padding({
top: 12,
bottom: 12,
left: 24,
right: 24
})
.onClick((event: ClickEvent, result: LocationButtonOnClickResult) => {
if (result === LocationButtonOnClickResult.SUCCESS) {
// 免去权限申请和权限请求等环节,获得临时授权,获取位置信息授权
getCurrentLocationInfo();
// promptAction.showToast({ message: '获取到权限!' })
} else {
// 你故意改按钮 不像正常 获取位置的按钮 可能失败!!
promptAction.showToast({ message: '获取位置信息失败!' })
}
})
LocationButton({
icon: LocationIconStyle.LINES,
text: LocationDescription.CURRENT_LOCATION,
buttonType: ButtonType.Normal
})
.padding({
top: 12,
bottom: 12,
left: 24,
right: 24
})
.onClick(async (event: ClickEvent, result: LocationButtonOnClickResult) => {
if (result === LocationButtonOnClickResult.SUCCESS) {
const location = await MKLocation.getCurrentLocationInfo()
const res = await MKLocation.getAddressesFromLocation(location)
AlertDialog.show({ message: JSON.stringify(res, null, 2) })
} else {
// 你故意改按钮 不像正常 获取位置的按钮 可能失败!!
promptAction.showToast({ message: '获取位置信息失败!' })
}
})
}
.width("100%")
.height("100%")
.justifyContent(FlexAlign.Center)
}
}
}
class MKLocation {
static getCurrentLocationInfoParams: geoLocationManager.LocationRequest = {
'priority': geoLocationManager.LocationRequestPriority.FIRST_FIX,
'scenario': geoLocationManager.LocationRequestScenario.UNSET,
'timeInterval': 1,
'distanceInterval': 0,
'maxAccuracy': 0
};
// 获取经纬度
static async getCurrentLocationInfo() {
return geoLocationManager.getCurrentLocation(MKLocation.getCurrentLocationInfoParams)
}
// 将经纬度 转 街道 逆地理解析
static async getAddressesFromLocation(location: geoLocationManager.Location) {
let reverseGeocodeRequest: geoLocationManager.ReverseGeoCodeRequest =
{ "latitude": location.latitude, "longitude": location.longitude, "maxItems": 6 };
return geoLocationManager.getAddressesFromLocation(reverseGeocodeRequest)
}
// 将街道转成经纬度 地理解析
}
天气服务
Weather Service Kit(天气服务)是鸿蒙生态下的一个数据提供服务,Weather Service Kit融合了多家气象行业TOPs供应商,提供专业、精准、稳定的超本地化天气预报服务,帮助开发者为用户提供更贴心的本地生活服务,支撑行业客户防灾减灾、降本增效。
通过AGC打开该应用的天气服务开关开启天气服务功能
获取详细位置:(可选)申请位置权限:获取当前位置的天气信息,需要调用Location Kit服务getCurrentLocation方法获取当前位置经纬度信息。getAddressesFromLocationName方法获取指定城市的经纬度信息
准备工作:通过weatherService的weatherRequester对象存放需求,其中需要两个参数:经纬度和查询目的的数据集合;比如实况天气,天气指数,潮汐等。
请求数据:采用异步方法wetherService的getWeather传入需求获得当前天气信息以及预警信息
服务卡片
)提供一种界面展示形式,可以将应用的重要信息或操作前置到服务卡片(以下简称“卡片”),以达到服务直达、减少跳转层级的体验效果。
亮点/特征
- 服务直达:将元服务/应用的重要信息以卡片形式展示在桌面,用户可以通过快捷手势使用卡片,通过轻量交互行为实现服务直达、减少层级跳转的目的。
- 永久在线:提供定时、代理等多种卡片刷新机制,实现卡片永久在线。
- 受限管控:卡片支持的组件、事件、动效、数据管理、状态管理和API能力均进行了一定限制,保障性能、功耗及安全可靠。
暂时无法在飞书文档外展示此内容
ArkUI的两大开发范式及区别
类Web开发范式:采用经典的Html,css,js三段开发方式,即用html搭建静态布局,css设置样式,js处理逻辑。该范式更符合前端开发者的使用习惯,可以快速将已有的web应用改造成方舟框架应用
声明式开发范式:采用基于ts超集而来的ArkTS语法,从组件,动画 和状态管理三个维度提供Ui渲染能力
生命周期(页面,组件,Uiability)
- 组件:
用@component修饰的ui单元,可以组合多个系统组件实现UI复用,生命周期有aboutToAppear()和aboutToDisappear()
- 页面:
即应用的UI页面,可以由一个或多个组件组成,@entry装饰的组件为页面的入口,即页面的根节点,一个页面有且仅有一个@entry,只有被@entry修饰的组件才能调用页面的生命周期,生命周期除了组件的还有onPageshow(),onpageHide(),onbackpress()
- 应用:
create 状态:在应用加载过程中,UIAbility 实例创建完成时触发,系统会调用 oncreate () 回调。可以在该回调中进行页面初始化操作,例如变量定义、资源加载等,用于后续的 UI 展示
windowstagecreate 状态:UIAbility 实例创建完成之后,在进入 foreground 之前,系统会创建一个windowstage。windowstage 创建完成后会进入 onwindowstagecreate () 回调,可以在该回调中设置 UI 加载、设置 windowstage 的事件订阅,如获焦 / 失焦、可见 / 不可见等事件
foreground 状态:当 UIAbility 实例切换至前台时触发,对应于 onforeground () 回调。在 onforeground () 中可以申请系统需要的资源,或者重新申请在 onbackground 中释放的资源.
background 状态:当 UIAbility 实例切换至后台时触发,对应于 onbackground () 回调。在该回调中可以释放 UI 界面不可见时无用的资源,或者在此回调中执行较为耗时的操作,例如状态保存等.
windowstagedestroy 状态:在 UIAbility 实例销毁之前,会先进入 onwindowstagedestroy 回调,可以在该回调中释放 UI 界面资源
destroy 状态:在 UIAbility 实例销毁时触发,可以在 ondestroy () 回调中进行系统资源的释放、数据的保存等操作
用Entry和Navigation装饰的页面有哪些区别
-
@Entry装饰的页面是应用的入口页面,通常用于展示应用的初始界面,而Navigation组件是一个导航容器,挂载在单个页面下,支持跨模块的动态路由。
-
@Entry页面具有通用的生命周期方法,而Navigation组件里的页面不执行onPageShow、onPageHide等生命周期回调。
简单介绍一下Stage模型
-
Stage模型是HarmonyOS应用开发的基础架构,它提供了面向对象的开发方式,规范化了进程创建的方式,并提供组件化开发机制。
-
Stage模型的组件天生具备分布式迁移和协同的能力,支持多设备形态和多窗口形态,重新定义了应用能力边界。
谈谈对Ability的理解,怎么跳转
Ability是应用所具备能力的抽象,也是应用的重要组成部分.ability是系统调度应用的最小单元,能够完成一个独立功能的组件。一个应用可以包含一个或者多个Ability。
跳转分为两种:
同模块内的Ability跳转
实现步骤:
1,导入common和want
2,获取上下文对象
3,在合适的地方声明want参数,把want参数中的abilityName换成目标abilityName,然后调用上下文对象的startability方法,把want参数传入即可实现跳转
跨ability跳转
在want参数中,除了要修改abilityName之外,还要修改模块名,然后调用上下文对象的startability方法,把want参数传入即可实现跳转
forEach和LazyForeach区别?
1.数据源类型:oForEach:直接接受一个数组作为数据源。oLazyForEach:接受一个实现了IDataSource接口
的对象作为数据源。
2.渲染策略:oForEach:一次性渲染所有数据项,适用于数据量较少的情况。oLazyForEach:按需渲染数据项,
只渲染可视区域内的数据项,适用于数据量较大的情况,提升性能。
3.内存使用:oForEach:会一次性加载所有的数据项,内存使用较高。oLazyForEach:根据可视区域按需加载数
据项,并回收滑出可视区域的数据项,内存使用较低。
4.组件复用:oForEach:没有内置的组件复用机制。LazyForEach:类似于原生开发中的cell复用机制,滑出可
视区域的组件会被回收,新出现的组件优先使用复用池中的组件。
5.性能优化:oForEach:适用于数据量较少且性能要求不高的场景。oLazyForEach:适用于数据量较大且性能要
求较高的场景,显著提升页面性能和用户体验。
6.数据监听:oForEach:没有专门的数据监听机制,依赖于数组变化触发的UI 更新。oLazyForEach:需要通过DataChangeListener来监听数据变化,手动通知数据变动以刷新UI。
layforeach的使用限制
-
LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载
-
容器组件内使用LazyForEach的时候,只能包含一个LazyForEach。以List为例,同时包含ListItem、ForEach、LazyForEach的情形是不推荐的;同时包含多个LazyForEach也是不推荐的。
-
LazyForEach在每次迭代中,必须创建且只允许创建一个子组件,以List为例,在listItem之外,不允许有其他组件;生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件
-
键值生成器必须针对每个数据生成唯一的值
项目优化
-
图片懒加载:列表里的图片滑到可见区域再加载,减少内存占用。
-
数据缓存:用
Preferences
或数据库缓存首页数据,下次启动先展示缓存再刷新。 -
减少布局嵌套:用@Builder代替自定义组件,多用线性布局,少用flex 等弹性布局
-
线程管理:把JSON解析、图片解码丢到
Worker
线程,防止主线程卡顿。 -
内存泄漏排查:用DevEco Studio的Profiler工具,发现有个页面退出后监听器没注销,赶紧加了
onPageHide
里的释放逻辑。
lazyforeach中key重复
lazyforeach
可能是一种具有延迟执行特性的遍历方式,它可能会根据具体的实现情况,在真正需要访问元素的时候才执行遍历操作。
当key
重复出现的问题
- 渲染效率问题:如果
key
重复,渲染效率会受到影响。框架无法准确地根据key
来判断元素的变更情况。例如,在更新列表时,框架可能会错误地复用元素或者重新渲染不必要的元素,导致性能下降。 - 状态关联问题:如果列表元素有与之关联的内部状态(如在组件中通过@
State
等方式设置的状态),key
重复可能会导致状态关联混乱。当更新列表时,因为key
不能唯一标识元素,可能会出现状态被错误地应用到其他元素上的情况。
解决办法:
确保key
的唯一性:
-
最直接的方法是为每个元素生成一个唯一的
key
。如果列表中的元素有一个自然的唯一标识符(如数据库中的主键、对象的唯一 ID 等),可以使用这个标识符作为key
。 -
如果没有自然的唯一标识符,可以考虑使用一些辅助函数来生成唯一的
key
。例如,在 JavaScript 中,可以使用uuid
库来生成通用唯一识别码(UUID)作为key
。
Work taskpool区别
- “Work” 更侧重于表示具体的工作内容或任务本身,它是一个相对比较抽象的概念,可以是一段代码逻辑、一个函数功能,或者是分配给某个线程 / 进程的具体事务。它强调的是要做的事情是什么,比如对数据进行排序、对文件进行读取等操作。
- “Task Pool” 是一种管理任务的机制和数据结构。它关注的是任务的存储、调度和分配。任务池就像是一个任务的 “容器”,本身并不直接涉及具体的工作内容,而是负责将任务有条理地组织起来,以便有效地分配给执行单元(如线程、进程)来完成。
-
功能和职责
-
Work:
- 它的功能主要是定义了具体需要完成的工作。例如,在一个图像编辑软件中,“Work” 可能包括调整图像亮度的算法、裁剪图像的功能等。这些 “Work” 直接与软件的功能实现相关,是实现软件各种业务逻辑的基本单元。
- 每个 “Work” 单元通常有自己独立的输入(如要处理的数据)和输出(如处理后的结果)。以一个简单的数学计算函数为例,输入是数字,输出是计算后的结果,这个函数的 “Work” 就是完成特定的数学运算。
-
Task Pool:
- 任务池的主要职责是管理任务的生命周期。它包括接收任务并将其放入池中(添加任务),根据一定的策略(如优先级、先来先服务等)从池中取出任务,以及跟踪任务的状态(如等待执行、正在执行、已完成)。
-
-
在编程流程中的角色
-
Work:
- 在编程的前期设计阶段,开发者会先确定需要完成哪些 “Work”,也就是软件需要具备哪些功能。这些 “Work” 的定义会影响到程序的架构和模块划分。例如,如果软件需要支持多种数据格式的文件读取,那么每种数据格式的读取工作就会被设计成一个独立的 “Work” 单元,可能会封装在不同的函数或类中。
- 在编码阶段,“Work” 通过具体的代码实现。开发人员需要编写代码来完成每个工作单元的逻辑,包括处理输入、执行操作和生成输出。在测试阶段,“Work” 也是测试的基本对象,需要验证每个工作单元是否按照预期完成任务。
-
Task Pool:
-
在程序的架构设计中,当涉及到多任务处理或者并发编程时,才会考虑引入任务池。任务池的设计需要考虑程序的任务负载、执行单元的数量(如线程数)、任务的类型等因素。
-
在编码阶段,需要实现任务池的添加任务、取出任务、调度策略等功能。在运行时,任务池作为一个中间层,协调任务和执行单元之间的关系。它的性能和调度策略的合理性会直接影响到整个程序的效率和响应速度。例如,一个设计不佳的任务池可能导致某些任务长时间等待执行,或者执行单元频繁切换任务而浪费资源。
-
-
遍历渲染三参
1,数据源:数组类型,可以设置为空数组,此时不会创建子组件
2,组件生成函数:
1,为数组中的每个元素创建对应的组件
2,item参数,为将要渲染的数组的数组元素
3,index参数,键值索引
3,键值生成函数
为数据源的每个数组项生成唯一且持久的键值
沉浸式导航栏
所谓沉浸式就是为用户提供了一种无信息干扰,高度专注的导航体验,使导航栏能够与应用内容更好地融合,让用户沉浸在应用中。主要实现方式是在Entryability中的onWindowStageCreate的生命周期中获得最新的上下文对象,用于创建最新的窗口对象,其中有setWindowLayoutFullScreen的一个方法用于开启沉浸式。通常我们会将它封装起来使用,只需调用这个对象对其进行初始化就可以完成,保证了代码的整洁性也提高了代码的可读性。
键盘避让模式
在移动终端上,可视化空间有限。键盘避让模式可以让我们有效的解决在获取输入框焦点键盘弹出的同时,内容依然不会受到影响。实现方式也比较简单,用到TextInput的defaultFocus属性打开这个模式。也可以设置它的详细避让模式用到setKeyboardAvoidMode设置内容压缩还是上抬
LocalStorage AppStorage PersistentStorage
LocalStorage是页面级的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。也可以在UIability内,页面间共享状态,是一个局部状态管理器,他修饰的变量保存在内存中,是非持久化状态,退出程序后会消失
Appstorage是一个全局状态管理器。一个应用可能有很多个UIability,Appstorage可以在多个UIability共享数据,保存在内存中国,也是非持久化状态。
PersistentStorage修饰的变量是保存在磁盘中的,是持久化状态,退出应用后依然存在。PersistentStorage是应用程序中的可选单例对象,此对象的作用是持久化存储选定的Appstorage属性,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同
不同浏览器对 Local Storage 的容量限制有所不同,一般在 5MB - 10MB 左右。
子线程和主线程如何通信
在华为的鸿蒙系统开发中,主线程通常处理与UI相关的任,子线程执行耗时的后台任务,比如网络请求,文件读写等
主线程与子线程通讯包括有Emitter,Worker,EventHandler和EventRunner
Emitter的实现步骤包括
-
创建Emitter实例,在需要接收事件的线程中订阅事件
-
在触发事件的线程中发送事件
Worker的实现步骤
-
创建Worker实例,并指定执行的脚本或代码
-
在主线程中发送消息给worker线程,启动任务
-
worker线程完成任务后,可以发送消息回主线程,主线程根据消息内容更新Ui
EventHandler和EventRunner
-
EventHandler用于在主线程上投递innerEvent事件或Runnable任务到异步线程上处理
-
eventRunner是一种事件循环器
-
循环处理从该EventRunner创建的新线程的事件队列中获取InnerEvent事件或Runnable任务
首选项value的长度限制
-
在鸿蒙开发中,首选项(Preferences)用于存储和检索简单的数据,如应用程序的配置信息、用户偏好设置等。它提供了一种轻量级的持久化存储机制。
-
首选项数据存储在文件系统中,并且可以通过键 - 值(key - value)对的形式进行访问。这些键 - 值对的数据类型包括基本数据类型,如整数、字符串、布尔值等。
关于 value 长度限制的细节
-
鸿蒙官方文档没有明确提及首选项中 value 长度的严格固定限制。然而,从实际应用和存储机制角度考虑,其长度受到设备存储资源的限制。
-
存储在首选项中的数据最终会占用设备的存储空间,就像其他文件存储一样。如果 value 的长度过长,可能会导致存储文件过大,进而影响存储效率,甚至可能在极端情况下导致存储不足的问题。
Promise如何使用
Promise主要用于处理异步进程的情况,解决了耗时操作嵌套(回调函数地狱),多个并发请求的问题。
promise.then是promise实例的回调函数,接收两个回调函数作为参数,第一个回调函数是promise对象的状态变为resolve时调用,即请求成功;第二个是请求失败的情况,,即rejeccted;返回的是另一个promise对象,后面可以继续.then
相关Api
promise.resolve() 返回新的状态为resolve的promise对象
promise.rejecct() 返回新的状态为reject的promise对象
promise.all() 等待所有异步都请求成功在返回
promise.allSettled() 数组的异步依次返回成功结果,其中一个失败也不会影响其他数据
promise.race() 数组的异步哪个结果返回快就是那个结果,不管成功或者失败
axios的原理,以及用法
axios是一个基于Promise的Http客户端,用于浏览器和node.js中发送http请求
原理
创建请求配置对象:包含请求的方法(get,post等),url,请求头,请求体等信息,利用XMLHttpRequest对象或浏览器的fetchApi发送请求
处理请求的响应:包括解析响应数据,处理错误状态.返回一个Promise对象,以便进行异步操作的处理和链式调用方法
http请求工具封装
-
在终端下载三方包axios,配置网络权限
-
创建一个axios实例,并配置一些全局的默认值,比如baseURL,timeout等
-
为axios实例添加请求拦截器,用于请求发送前进行某些操作,比如添加token,设置请求头等,同样还可以添加响应拦截器来处理响应数据.比如错误码,统一返回数据格式等
-
根据实际需求封装具体的请求方法,如get,post,put等,这些方法接收URL,请求参数,请求头,请求体,,并返回一个含响应数据,状态码和可能异常的封装对象.并导出这些方法供其他模块使用
图案密码锁用
特点
输入方式:用户通过手指在图案密码锁组件的各自区域内滑动,连接特定顺序的格子来创建密码
验证机制:当用户在再次按照相同的顺序连接格子时,系统会根据输入的顺序与预设的密码进行对比,以验证用户身份
界面表现:图案密码组件通常会以图形化的方式展示格子,并在用户选择格子时显示选中的状态,比如UI改变
实现
组件支持:从harmonyOS的Api9开始起,方舟编译器支持图案密码的组件开发
属性与事件:这个组件提供了很多属性与方法用于自定义组件的外观和行为。比如:设置格子大小,边框宽度以及监听密码输入完成等事件
HAP,HAR,HSP
- HAP:应用安装运行的基本单元,支持在配置文件中声明abilities等 组件,支持在配置文件中声明pages页面
应用场景
entry:应用的主模块,用于实现应用的入口界面,入口图标,主特性功能
feature:应用的特性模块,用于实现应用的特性功能
- HAR:静态共享包。编译态复用,不支持在配置文件中声明abilities组件,不支持配置文件中声明pages页面,支持navigation组件导航
应用场景
作为二方库,发布到ohpm私仓,供公司内部其他应用依赖使用
作为三方库,发布到ohpm中心仓,供其他应用以来使用
- HSP动态共享包,运行时复用,不支持在配置文件中声明abilites组件,支持在配置文件中声明页面
应用场景
多模块共用的代码,资源可以使用HSP提高代码的 可复用性和可维护性
元服务分包预加载
多线程,和 promise区别
多线程
指在程序运行时可以执行多个线程的能力。每个线程都是一个轻量级的进程,他们共享相同的内存空间,并且可以执行不同的任务。在多核的现代计算机,多线程可以充分利用硬件资源,提高程序的性能。多线程编程通常用于处理耗时的任务,比如网络请求,文件读写等
Promise
promise是一种编程模式,在js中广泛采用,用来解决异步操作中的回调地狱问题。promise提供了一种更加优雅的方式来处理异步操作的结果,它代表了一个最终响应操作完成或者失败的promise值。promise可以被链接起来,使得异步操作可以向同步操作一样按顺序编写
区别
概念层次:多线程是操作系统提供的并行执行能力;promise是一种编程模式,用于简化异步操作
用途:多线程用于实现任务的并发执行,提高程序效率;promise用于组织和简化异步逻辑,避免回调嵌套
语言支持:多线程依赖于底层操作系统的支持;promise则是语言层面或库方面提供的功能
uni-app
uni - app 是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到 iOS、Android、Web(H5)、以及各种小程序(微信 / 支付宝 / 百度 / 字节跳动 / QQ 等)多个平台。
主要特点
跨平台能力:它采用了一套代码多端运行的理念,大大减少了开发不同平台应用时的重复工作量。
性能优化:它使用了虚拟 DOM(Document Object Model)技术,和传统的 DOM 操作相比,虚拟 DOM 可以更高效地更新页面。
Uni-ui
uni - ui 是基于 Vue.js 和 uni - app 框架的跨平台 UI 组件库。
特点
-
跨平台兼容:uni - ui 的最大优势在于其跨平台特性。在编写代码时,基本不需要针对不同平台(如微信小程序和 H5)进行大量的样式和功能调整。因为它基于 uni - app 的跨平台能力,能够自动适配不同的运行环境,大大节省了开发成本。
-
主题定制方便:可以轻松地定制组件的主题颜色、字体等样式。通过修改全局的样式变量,就能够使整个应用的 UI 风格保持一致,满足个性化的设计需求。
-
性能优化:经过了优化处理,在加载速度和运行效率上有一定的保障。组件的代码结构合理,减少了不必要的资源占用,在低性能设备上也能有较好的表现。
分包
分包是一种优化应用性能和加载速度的技术。当应用体积变大时,如果将所有代码和资源都放在一个主包中,会导致应用首次启动时加载时间过长。分包就是将应用按照功能模块或者页面等划分成多个子包,在需要的时候才加载相应的子包,从而提高应用的启动速度和响应性能。
分类
-
普通分包 - 普通使用
- 进入小程序的时候,先下载主包、下载其某个分包
-
独立分包
- 不需要先下载主包,只下载单独分包
-
分包预下载
- 进入到分包A,设置提前先下载好分包B
-
分包异步化
- 解决分包之间互不能引用的问题!!
总结
-
普通分包可以引用主包的内容-因为加载普通分包之前,先加载主包
-
独立分包无法使用所有其他的包-包括主包。独立主包被下载和运行
-
普通分包无法直接引用其他分包(除了主包之外)的资源
-
想要解决 以上问题, 可以常用 分包 异步化来解决!!
-
分包预下载 - 设置预览分包A的时候,提前下载分包B
封装全局的builder(WarpBuilter)
wrapBuilder是一个模板函数,返回一个WrappedBuilder对象。
限制条件
wrapBuilder方法只能传入全局@Builder方法。
wrapBuilder方法返回的WrappedBuilder对象的builder属性方法只能在struct内部使用。
录音模块
录音业务:
- 点击按钮黑色变成蓝色, 2. 录音声纹动态变化
二: 再次点击按钮结束录音
1.UI发生变化, 2. 生成录音文件 3.生成一条数据,记录到数据库对应的数据表中。
一: 新建页面,搭建静态页面。
二:申请录音权限:申请要在model.json5和string.json文件中配置一些信息,进入录音页面,,配置完成后使用permission这个API调Request permission()这个方法会弹出一个半模态申请面板,会有同意和拒接,所以会用到if判断语句,同意后就直接进入录音,拒接后,就使用promptAction.showDialag弹出一个对话框,对话框中有确认和取消,所以会用到if判断语句,如果用户点击确认,就使用路由route.back退出当前页面。返回上一页,如果用户点击取消,则重启打开半模态申请面板,取消则退出页面,通过则进入录音模块。
三:进入录音模块:要实现UI的变化和录音的功能,所以这里采用的是UI和功能相分离的思想,
先做UI的变化,来帮助我对业务进行划分和设计,这种我可以轻松定义好用的状态和函数来实现功能。
主要业务是:录音按钮颜色发送改变, 录音声波动态变化
是当点击录音按钮,按钮颜色发生变化,这里使用三用运算符完成,并给按钮添加onclick事件,在onclick事件中进行判断,判断的是当正在录音时
点击则取消录音,当再次点击则开始录音。声波是使用数组中的Math.Random生成随机数,将随机数赋值给每一个组成声纹的柱状图的高,使用
ForEach进行规定数量的渲染即可完成布局,当点击录音按钮时使用定时器setInternval在每隔500毫米生成一个声纹的柱状图,,录音结束后也要使用clearInterval清除定时器和使用组件属性visibility隐藏波纹,由于声波是写在一个单独的组件,这里用到了父子的单向通讯@prop完成数据传参,
完成UI布局,
四:录音功能的信息配置:
1.首选指定录音文件存储的沙箱路径(指的是录音文件的存储的位置)———而这个路径是通过getContext获取上下文动态拼接完成的
- 适应fileIo.opensyn获取访问沙箱文件权限
3.使用medio.creatAvRecoder创建录音实例
4.指定录音相关的配置信息(1.主要有音频的输入源,音频比特率,音频采样率等)
五:开始录音
调用avRecorder.start()开始录音
调用avRecorder?.release()结束录音
六:创建数据库,将生成的录音文件写入到数据表中
使用relatiaoStore.getRdbstore创建数据库。进行
通过store.insert将录音信息进行插入
store.query进行查询
视频录制。
AVRecorder,
AVRecorderState音视频录制的状态机:idle'空闲状态,prepared'准备状态。'started'开始录制状态,录制暂停状态,录制停止状态,录制资源释放状态
1.使用media.createAVRecorder创建AVRecorder实例,实例创建完成进入idle状态。
2.设置业务需要的监听事件,监听状态变化及错误上报avRecorder.on创建回调函数进行操作。正确怎么处理,错误弹出什么文字
3.配置视频录制参数,调用prepare()接口,此时进入prepared状态
配置视频的文件格式
视频分辨率的宽和高
视频帧率等
4.调用getInputSurface()接口,接口的返回值SurfaceID用于传递给视频数据输入源模块
输入源模块通过SurfaceID可以获取到Surface,通过Surface可以将视频数据流传递给AVRecorder
5.初始化视频数据输入源。该步骤需要在输入源模块完成,需要创建录像输出流,包括创建Camera对象、获取相机列表、创建相机输入流等,
6.开始录制:调用camera.VideoOutput.start接口启动录制
7.暂停录制,调用pause()接口,此时AVRecorder进入paused状态,同时暂停输入源输入数据。
8.恢复录制,调用resume()接口
9.停止录制,调用stop()接口
播放音频:
我们项目中有一个功能是使用者可以通过带读单词的方式来学习专业单词的发音并自己录音交给AI检测其中带读部分我们采用的是有道在线发音API,这个API是免费使用的,只需要给他传一个单词,他就能生成-个mp3的发音文件。
在鸿蒙原生应用中有一个API叫做AVPlay,这个API可以实现音频源的播放。所以只需要将有道在线播放源设置给它,就能实现在应用内播放音频了。AVPlay有一个播放控制状态机,用好这个状态机就能很好的控制音频的播放,包括,播放,停止,循环播放等功能。
AVPlay也通常用来开发音乐播放器。比如,汽水,酷狗这些APP音乐播放器中的功能用AVPlay都可以实现
Avplayer:
1.idle:空闲 2.intialized 初始化状态 3.prepared 准备好状态 4.playing 正在播放状态 5.completed播放完毕状态 6.paused暂停状态 7.停止状态 8.released 销毁状态 9.error出错状态
- 当我们打开页面的时候会触发aboutToAppear生命周期,立即执行,
- 在aboutToAppear生命周期函数里面,主要是创建播放器实例,监听播放器实例状态,因为我们要用到里面的一种状态,就要把前面的所有状态都要走一遍,因为我们要用到播放状态,就要把空闲状态,初始化状态,准备好状态,正在播放状态都用的,在空闲状态的时候,监听URl变化,这个URl是每个新闻也简单URL,在初始化状态的时候调用avplayer.prepare这个方法,准备完毕后,进入播放状态,调用avplayer.play()开始播放,如果播放完毕后实现自动循环播放,就在播放完毕后状态后,再次进入开始播放状态,再调一次avplayer.play()播放,就可以实现自动循环播放了
3.设置播放的URL:就是把有道在线播放源设置给它
4.但这里要有个注意点,我们在监听的时候,没有进入销毁或者停止状态里,是因为在哪里执停止播放的话,关闭当前页面,有时候还会播放,针对这个情况,我们是在生命周期aboutToDisappera中,进行销毁,并将整个avplay这个使用重新赋值为null,就解决了停止播放的这个问题
项目中图片上传是怎么做的?
1、跳转到相册app/原地拍照
通过鸿蒙内置的picker模块photoSelectOptions创建出一个实例,配置上传类型、数据
使用PhotoViewPicker创建图片选择器拉起相册app,通过select方法await获取选中图片的地址
2、拷贝至应用沙箱(鸿蒙不能直接上传,需要先拷贝到app沙箱中)
准备文件后缀、文件名、文件沙箱的路径将选中的图片以只读的模式通过fs.copyFileSync拷贝至文件中得到沙箱路径
3、上传图片
最后调用后端接口通过request.uploadFile方法上传
需要传入2个参数,一个是上下文,一个是上传文件配置参数,注意在参数的header里面要设置Content-Type为multipart/from-data,表示当前上传的是一个多部分表单数据,上传时必须要设置的,后端根据这个类型去做文件的接收处理
通过监听2个事件,progress和fail,其中progress事件会返回上传文件的总大小和当前上传的大小,我们可以用来做上传进度的计算和展示,当时碰到一个问题,就是([ˈkʌstəm])customDialogController弹窗不会根据调用方法的状态属性值改变而变化的,后来通过emitter这个api解决跨线程通讯问题
如果上传有错误,可以在fail监听事件中获取错误
⚠️:从相册中选一个文件是没有权限读取的,需要利用上下文对象以及fs这个api将文件拷贝到应用沙箱中,通过internal://cache/加上沙箱中的文件名才能正常做上传操作
需要开启internet权限
图片下载用的是 request.downloadFile
http的封装
http请求工具封装可以使用官方提供的@ohos.net.http模块,也可以使用前端流行的axios封装。
使用axios封装首先在Terminal窗口中,执行如下命令安装三方包
引入axios:ohpm install @ohos/axis
开通网络权限
需要配置ohos.permission.INTERNET权限,在工程目录entry\src\main中找到module.json5文件,配置网络请求权限。
封装的步骤:
首先,我们创建一个axios实例,并配置一些全局的默认值,比如baseURL(基础URL)、timeout(超时时间)等。
然后,为axios实例添加请求拦截器,用于在请求发送前进行某些操作,比如添加token、设置请求头等。同样地,添加响应拦截器来处理响应数据。比如处理错误码、统一返回数据格式等。
最后,根据实际需求封装具体的请求方法,如get、post、put、delete等,这些方法接收URL、请求参数、请求头和请求体,并返回一个包含响应数据、状态码和可能异常的封装对象,并导出这些封装好的方法供其他模块使用。
通过以上步骤,我们可以有效地封装axios,实现HTTP请求的统一管理,增强代码的复用性和可维护性。同时,通过拦截器的使用,我们可以灵活地处理请求和响应,满足不同的业务需求。
数据库创建
首选项:一种Key-value键值型持久化存储数据的方案 , 应用首选项preferences持久化将文件保存在应用沙箱内部,通过context获取其路径
1.导入Preferences模块
2.通过getPreferencesSync创建一个实例
3.写入数据:通过putSync方法将数据写入Preferences实例,可通过flush将Preferences实例持久化可以封装起来,同时封装getSync、deleteSync等方法获取数据、删除数据,使用时直接调用方法即可
关系型数据库:是一种可以存储复杂关系数据的持久化方案 ,
首先通过relationalStore模块获取一个getRdbStore,用来操作数据库
建库、建表获取到RdbStore实例后对数据库进行增删改查等操作,建库时要配置storeConfig方法设置库名、安全等级等
获取到RdbStore后,调用insert方法像目标表中插入一行数据,
//创建谓词relationalStore.RdbPredicates(['predɪkeɪts]-破ruai 滴 kei 题 斯)
通过谓词指定的实例对象,调用update方法修改数据,调用delete方法删除数据,
//predicates.equalTo([ˈi:kwəl]-衣库哦TO)
store.query
根据谓词指定的条件查询数据,调用query方法,返回一个ResultSet结果集,
小结果是一个数据合集,默认指向第-1个记录,有效数据从0开始,结合while循环调用goToNextRow方法
获取要的结果当应用完成查询数据操作,不再使用结果集时,请及时调用close方法关闭结果集,放系统为其分配的内存
区别:
首选项是一种轻量级的本地存储解决方案,不支持存储嵌套对象,undefinde和null,内置类型;
AppStorage是应用级的全局状态共享
而关系型数据库则是一种结构化的数据库,适用于存储大量数据并支持复杂查询和关联操作。
数据存储大小不同
页面的传值方式
router.pushURL(params:)传数据
aboutToAppear中接收router.getParams接收数据
Appstorage
LocalStorage
emitter
1、如果页面间存在父与子关系,我们使用@prop和@link来实现 父传子的数据,使用回调函数的方式实现子传父
2、如果某俩个页面在关系树上,比如爷-父-子关系可以使用@provide@cusome
3、如何俩个页面毫无关联,我们使用emitter这个核心api来完成跨页面传值,Appstorage、localStorage、首选项都可以
4、路由传递参数
RichText 富文本兼容性的处理
在next版本中直接把富文本字符串交给RichText显示时,在真机和模拟器上文字显示会特别的小,需要把需要显示的富文本字符串使用一段html结构,并且使用一个<div style="font-size:34px"来包括着这段富文本字符串才能显示正常。
webview 加载html文件
1.我们项目中有一个用户隐私协议页面使用的是html编写的,这个文件存在项目的rawfile文件夹中
2.我们主要使用了 $rawfile()方法来读取html文件的路径,然后交给webview组件来显示
高亮显示代码怎么做的?
我们项目中有一个需要把语法关键词进行高亮显示的需求。通过研究,可以通过在前端的html中引入一个高亮的js插件可以实现。所以我们采取的解决方案如下
1.请前端同学写了一个html页面,引入了这个高亮插件,在html文件中暴露了一个js方法A
2.我在鸿蒙应用中使用Web组件结合WebView控制器来加载这个html文件
3.当Web组件加载完html页面后会自动触发onPageEnd 方法,在里面我们使用WebView控制器的runJavaScript函数来动态的执行html网页中的js方法,传入我们想要高亮显示的文本即可完成高亮显示功能
数据埋点
数据埋点功能是用来收集用户访问App中各个页面的痕迹数据的。可以通过这些数据分析来判断用户的访问喜好,公司通过对这些数据的分析从而可以让制定更好的营销策略
我们项目的数据埋点,主要用在收集用户访问哪类题目的频率比较高。更好的把一些高频类目推送给用户观看。
主要的实现思路是:
- 在项目中统一封装了一个类,包含四个方法:①进入页面时间收集 ② 离开页面时间收集 ③ 当前访问的题目+id收集 ④ 将收集到的数据发送给服务
- 当然因为每次访问都将数据发送给服务器,将会因为很频繁的访问服务器而给服务器带来很大的访问压力,所以我们优化成了,每收集到20条数据才统一发送给服务器,当然这个数量是可以动态调整的
- 我们也可以考虑把这些数据持久化到应用的首选项中,这样就算应用退出后再打开,也不会让数据丢失
沉浸式模式的封装
1.鸿蒙内置模块window中有个方法getLastWindow 获取到当前窗口
利用窗口调用setWindowLayoutFullScreen(true)全屏
- 将app沉浸式后 , 结构会和状态栏重叠 , 获取安全区高度(getWindowAvoidArea) 设置容器padding
存appStorage 方便使用 , 背景色和状态栏不协调 , 设置状态栏文字颜色(setWindowSystemBarProperties)
上传文件怎么做的?
项目中图片上传是怎么做的?
文件上传主要借助于鸿蒙request这个原生api的uploadFile方法。
它需要传入两个参数,一个是当前应用的上下文,一个是上传文件的配置参数,比如:接收文件的后端接
口,文件路径设置等,还有在header里面要设置Content-Type为multipart/form-data,表示当前上传的
是一个多部分表单数据,这是做上传时必须要设置的,后端会根据这个类型去做文件的接收处理。
request还有两个事件可以被我们监听,分别是progress和fail,其中progress事件会返回上传文件的总
大小和当前上传的大小,我们可以用来做上传进度的计算和展示,当时碰到一个问题,就是
CustomDialogController弹窗是不会因为调用方的状态属性值改变而改变的,后来通过研究使用emitter
这个api来做的跨线程通信解决的。
如果上传有错误,可以在fail的监听事件中来获取错误信息
有两个需要特别注意的地方是:
1.我们从相册中选择一个文件是没有权限读取的,需要利用上下文对象以及fs这个api将相册的文件拷贝
到应用沙箱中,然后通过internal://cache/再加上沙箱中的文件名才能正常做上传操作
2.需要再你的应用中开启intelnet访问权限
录制音频
我们使用的是AudioCapturer录制 .wav格式的音频文件通过解析接口确定的上传文件格式、声道数、音频采样深度、采样率、编码格式参数的要求对比另外一个接口AvRecorder ([rrko:da(r)])也可以录制音频,但不能设置采样深度,最后选用AudioCapturer
通过内置的audio模块createAudiocapturer创建音频捕获器
同时通过fs模块opensync创建空文件并设置文件的读写权限(指定缓存路径、文件名及后缀)按下录音键start方法开始录制-只要获取到声音音频采集器启动,文件通过二进制流数据结合while循环不同的写入文件,采集器停止,跳出循环停止写入数据,stop方法停止录制,最后释放资源
⚠️注意:
1.只能在真机上测试
2、必须先停止写入文件 在停止录制音频
3、接口中的contentType类型拼写要完全与请求报文中一致
解析评测流程:
目前还没有鸿蒙的sdk,暂时我们将用户录音完成后的文件(缓存到文件沙箱)通过http协议方式上传至云知声AI口语测评接口接收到文件会根据录音中的发音进行完整度、流利度、标准度的打分响应完成后将数据解析后回显给用户
音频播放
我们项目中有一个功能是使用者可以通过带读单词的方式来学习专业单词的发音并自己录音交给AI检测其中带读部分我们采用的是有道在线发音API,这个API是免费使用的,只需要给他传一个单词,他就能生成-个mp3的发音文件。
在鸿蒙原生应用中有一个API叫做AVPlay,这个API可以实现音频源的播放。所以只需要将有道在线播放源设置给它,就能实现在应用内播放音频了。AVPlay有一个播放控制状态机,用好这个状态机就能很好的控制音频的播放,包括,播放,停止,循环播放等功能。
AVPlay也通常用来开发音乐播放器。比如,汽水,酷狗这些APP音乐播放器中的功能用AVPlay都可以实现
Avplayer:
1.idle:空闲 2.intialized 初始化状态 3.prepared 准备好状态 4.playing 正在播放状态 5.completed播放完毕状态 6.paused暂停状态 7.停止状态 8.released 销毁状态 9.error出错状态
- 当我们打开页面的时候会触发aboutToAppear生命周期,立即执行,
- 在aboutToAppear生命周期函数里面,主要是创建播放器实例,监听播放器实例状态,因为我们要用到里面的一种状态,就要把前面的所有状态都要走一遍,因为我们要用到播放状态,就要把空闲状态,初始化状态,准备好状态,正在播放状态都用的,在空闲状态的时候,监听URl变化,这个URl是每个新闻也简单URL,在初始化状态的时候调用avplayer.prepare这个方法,准备完毕后,进入播放状态,调用avplayer.play()开始播放,如果播放完毕后实现自动循环播放,就在播放完毕后状态后,再次进入开始播放状态,再调一次avplayer.play()播放,就可以实现自动循环播放了
3.设置播放的URL:就是把有道在线播放源设置给它
4.但这里要有个注意点,我们在监听的时候,没有进入销毁或者停止状态里,是因为在哪里执停止播放的话,关闭当前页面,有时候还会播放,针对这个情况,我们是在生命周期aboutToDisappera中,进行销毁,并将整个avplay这个使用重新赋值为null,就解决了停止播放的这个问题
AI口语评测
我们项目中有一个功能是专门给使用者做发音评测的,能根据使用者的发音来实现对发音流畅度,标准度,完整度的评分。从而帮助使用者改善自己的发音,
首先使用者调用我们的录音功能完成录音文件的生成后,我们会将这个录音文件通过http方式传输给一个语音评测接口,这个接口收到录音文件后,会分析录音中的发音流畅度,标准度,完整度,然后将评分响应回来,我们再回显在页面上。
因为鸿蒙刚出来,现在很多AI语音评测公司还没有开发出鸿蒙SDK,所有只能选择http传输方式来提交录音文件,当时对接了很多家语音评测公司,都表示现在还没有SDK,看过我们的功能需求后,表示要开启鸿蒙SDK的开发了。
14.录制音频文件
我们项目中有一个功能是专门用来供使用者朗读单词并将自己录音交。其中录音部分,我们采用的是鸿蒙原生接口AudioCapture来.wav音频文件的录制的。因为我们在调用其他公司录音解析AI接口的时候,他们的接口对 声道数、音频采样深度、音频采样率、音频编码格式均有要求
而AudioCapture正好支持这些参数的设置,能录制出一个满足AI解析接口要求的音频来。所以我们采用了AudioCapture来实现。
当然鸿蒙录制音频的原生接口除了AudioCapture之外还有AVRecorder这个接口也能录制音频,但是当时我们测试了这个接口,它录制出来的音频格式不满足AI解析接口的要求,因为它不能设置音频采样深度
15.下拉刷新
1.next版本中可以直接使用Refresh组件结合它的 onRefreshing 方法就可以实现下拉刷新功能,其中我们需要自定定义一个boolen类型的变量用来控制Refresh组件的刷新结束
2.其中下拉时的加载中效果、弹簧效果,Refresh组件都已经内部实现,无需开发者关注
page=1 发请求 请求回来的数据 赋值=
16.上拉加载
1.上拉加载更多其实是移动端页面用来做分页的功能的一种场景方式
2.具体我们可以使用List组件结合它的onReachEnd方法来实现,因为onReachEnd是当List中的内容被用户拉倒最下方时会被触发,所以我们可以onReachEnd方法中调用数据接口,传入新的分页参数(page++)来获取到最新分页的数据即可完成上拉加载更多的功能
请求回来的数据push进去
17.一多模式
是指编写一套代码,可以多在多个不同设备上运行,我们在一种设备上编写好代码并运行后没问题,运行到其他设备上就会出现问题,比如屏幕尺寸不同,最显而易见的布局会错乱,系统的设备能力也不同,考虑到智能穿戴设备是否具有定位功能,智慧屏是否具备摄像头呀,所以,所以针对多个设备适配一套代码的问题我们采用一多模式开发,首先要解决尺寸问题,我们采用了自适应布局和响应布局;因为窗口变化较大,自适应布局导致页面内容异常,就需要结合响应式布局来调整,响应式布局能够根据不同设备的屏幕尺寸和分辨率自动调整UI布局的设计方法;响应式布局实现采用的断点BreakPointType,媒体查询mediaquery ,栅格布局GridRo,Gridcloumw等,适配系统能力是通过canIUse接口判断设备是否支持某系统能力。
一套代码,一次开发上架,多端按需部署,快速高效的开发支持多端设备形态的应用。
适配:
- 不同设备间的屏幕尺寸、色彩风格等存在差异,页面如何适配。
- 不同设备的系统能力有差异,如智能穿戴设备是否具备定位能力、智慧屏是否具备摄像头等,功能如何兼容。
页面适配:
自适应布局和响应式布局:
注意:窗口变化较大,自适应布局导致页面内容异常,就需要结合响应式布局来调整
响应式布局:断点、媒体查询、栅格布局
响应式布局是指能够根据不同设备的屏幕尺寸和分辨率自动调整UI布局的设计方法
系统能力适配:需要考虑到适配不同范类的应用
- 部署模型A:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成相同的HAP(或HAP组合)。
- 部署模型B:不同类型的设备上按照一定的工程结构组织方式,通过一次编译生成不同的HAP(或HAP组合)。
- 默认设备(一般为手机)、平板
- 车机、智慧屏
- 智能穿戴(手表, 眼镜)
适配系统能力
使用canIUse接口判断设备是否支持某系统能力
通过import动态导入controller,配合try/catch判断
18.三层框架
是什么:
一多模式下 官方推荐的项目结构
默认情况下 一个entry打天下(单模块)
为什么:
模块结构清晰
怎么做:
-
公共层 没有ability 封装一些类
-
业务层 没有ability 写具体业务(最后在入口层展示)
-
入口层 有ability 需要展示页面
三层架构:
common([ˈkɒmən]):公共能力层-用于存放公共基础能力合集,比如工具库,公共配置等
features(['fi:tʃəz]):基础特性层-用于存放应用中相对独立的各个功能的UI以及业务实现
product([ˈprɒdʌkt]):产品定制层-用于针对不同设备形态进行功能和特性集成,作为应用入口
19.普通登录和华为账号三方登录(为什么要缓存token)
普通登录:
用户名/密码-校验符合格式要求=>请求接口发给服务端,并返回token,将数据存储到AppStorage中并结合persistenstorage持久化,跳转到首页
为什么要缓存token
1、根据查询接口文档,确定项目接口除了登录接口之外,其他接口都需要在请求头中携带一个token才能正常访问到数据
2、登录接口请求成功后,响应数据中会返回一个token,所以我们在登录成功后需要将token缓存起来方便后面其他接口请求数据时使用(通过AppStorage获取登录时存在的token)
3、当在使用http模块请求接口时,我们在header参数中携带token给服务器,如果服务器验证token不存在或者失效的时候,会返回一个401标记,客户端通过判断如果是401标记则跳转到登录页面让用户重新登录
三方登录:
-
准备
- 在编辑器中生成p12
- 在编辑器中生成csr
- 在agc平台生成cer
- 在agc平台生成p7b
- 将上面3个文件配置到编辑器中(两个密码 1个别名)
- 配置公钥指纹(验证签名有效)
- 生成的clientId配置到编辑器项目
- 申请scope权限([skəʊp])
-
流程
-
利用华为账号服务中一键面板登录(authentication类)获取code([ɔ:ˌθentɪ'keɪʃn]-奥shen体kei深)
-
将code发请求给后端
- 请求成功后 提示
- 跳转
- 存用户信息
-
20.无状态组件和无渲染组件的封装
封装无状态组件
TitleBar UI结构相同,数据不同
根据不同的页面设置不同的返回图标、标题等,具体显示的内容可以定义成一个成员变量通过通信方式传入,
如果数据不变化,就通过传递参数,如果数据变化通过@prop和@Link等装饰器修饰接收数据
封装无渲染LoadingCom组件
重复的部分自定义了一个加载中动效的UI,不同的部分是UI,通过尾随闭包和BuilderParam进行接收
封装的核心思想:
-
先看出来重复的部分 => 封装
-
辨别出哪部分是相同的(不变的) 哪部分是不同的(变化的)
-
相同的(不变的)部分 直接封装进组件/函数内部 不同的(变化的)部分需要传入
-
组件
-
组件组成部分
- UI: 结构+样式
- 逻辑: 数据+修改数据的逻辑
-
封装组件的两种思想
-
将重复的UI结构封装进去 传入数据 => GridRowList({ list: [] }) => 无状态组件(只有结构) => 简单
-
将重复的逻辑封装进去 传入结构 => CheckLandscape组件 => 无渲染组件/有状态组件/状态服用组件(只有逻辑) => 难度
-
-
-
CheckLandscape组件的封装 => 横屏检测组件 => 传入你需要控制的结构
-
发现了相同的部分 就是横屏状态以及改变横屏状态的逻辑 封装进组件内部
-
发现了不同的部分 就是每次想要控制的结构UI 传入UI通过@BuilderParam
-
使用组件时 需要传入一个Builder函数 => 仅仅只是能够展示传入的组件
- 可以在调用builder函数时传入参数 => 想把组件内部的landStateObj传出去
-
组件内部调用Builder函数时 传递横屏状态 => 使用组件时可以拿到横屏状态做自己的逻辑
-
-
21长按语音搜索转文字
- 首先要创建一个音频采集器AudioCapture进行语音的录制,
- 创建录音文件,给文件读写的权限
- 在长按开始时候,需要调用麦克风录制声音,得到一个音频文件
- 在长按的过程中,麦克风会不断地接收声音,写入音频文件中
- 在长按结束时候,把音频文件里的内容转文字(转文字有以下几个逻辑)
转文字的逻辑
- 先导入底层转文字的API
- 调用一个方法,创建引擎
- 设置回调函数(其中onResult函数最重要,第二个参数是结果)通过回调函数的参数拿到转换出文字的结果
- 调用一个方法,启动引擎,开始转换文字
- 写入音频流到引擎里面,转换
- 最后将转换好的文字展示在输入框中
22.服务卡片
首先创建一个卡片组件service widget ([ˈwɪdʒɪt]-喂句特)
要在卡片中显示真实商品图片
通过卡片的生命周期 FormExtensionAbility 下载图片资源,并处理为标准的数据格式
通过调用formProvider.updateForm下发内容------ (ability需要自己拿数据 处理成标准数据 下发给page)
卡片组件中通过@localStorage获取下发数据,然后使用image组件通过入参([ˈmeməri])memory://fileName来进行远端内存图片显示----- 其中fileName需要和EntryFormAbility传递对象{ formImages: {key: fd} } 中的 key相对应
1、卡片显示真实照片
FormExtensionAbility 负责下载图片资源,并处理为标准的数据格式
然后通过调用formProvider.updateForm方法下发内容
卡片组件中通过 @localStorage获取下发的数据,然后使用Image组件通过入参memory://fileName来进行远端内存图片显示,其中fileName需要和EntryFormAbility传递对象{ formImages: {key: fd} } 中的 key相对应
2、卡片动态更新内容
在卡片组件中触发message事件,在FormExtensionAblility的onFormEvent钩子中监听事件,然后执行卡片的updateForm生命周期方法传入要更新的数据即可
3、点击卡片唤起特定页
卡片组件点击之后通过postCardAction触发router事件并携带参数
在应用的UIAbility中接收router事件,解析参数完成跳转
如果UIAbility已在后台运行,在收到Router事件后会触发onNewWant生命周期回调
应用未打开,在收到router事件后会触发onCreate生命周期函数并解析参数,然后onWindowStageCreate函数则根据参数打开不同页面
注意:已经启动时只执行 onNewWant钩子函数第一次启动时存下来 windowStage实例,二次启动时直接在onNewWant钩子中通过windowState实例手动调用onWindowStageCreate方法 执行判断逻辑
23.emitter线程通信方案
线程通信方案
很多时候通信时 两个组件毫无关系 如果组件之间有关系
父子关系 @Prop @Link 回调函数
父子孙 @Provide @Consume
发消息 emitter.emit({ eventId: ''}, 数据)
收消息 emitter.on({ eventId: ''} , (data) => {})
(主要的做法就是在request.on里面的progress中把回调函数拿到的数据交给发送事件emitter.emit传入两个值一个是eventId,一个是回调函数的数据,然后在使用进程展示的页面用订阅事件emitter.on去订阅传入eventId和回调函数,回调函数获取发送事件中拿到的数据然后在页面进行渲染,这里有一个需要注意的就是emitter拿到的数据类型是需要我们自己定义的,就是需要我们在页面中对emitter的数据用interface定义类型)
24.位置服务
使用LocationButton获取地理位置
准备LocationButton按钮控件-提供获取位置组件结构
注册点击事件,获取位置信息临时授权,完成临时授权
用户允许,通过内置位置服务;geoLocationManage的([ˈkʌrənt])getCurrentLocation方法获取经纬度位置
通过逆地理编码转化继续调用geoLocationmanage的getAddressesFromLocation方法将经纬度转换成文字地址-渲染到页面
25.支付流程
-
新增订单(后续所有的操作都是基于订单) => 请求后端接口
-
支付
-
携带订单id 请求后端支付接口(web组件的src)
-
后端接受到请求后 会整合该笔订单所有信息 请求支付宝
-
支付宝会返回一个该笔订单的支付链接
-
由于我们使用的时web组件 这个支付链接会被打开 进入到支付流程
-
支付完成后 支付宝会返回一个支付结果的链接
-
通过web组件的onInterceptRequest 可以监听到链接
-
只要结果的那次链接 处理链接中的参数 payResult
-
true/false 控制页面展示
-
26.地图服务
-
申请agc地图服务
-
利用MapComponent组件展示地图([kəmˈpəʊnənt]) 设置地图初始化参数,设置地图中心点坐标及层级 地图初始化回调,获取地图控制类,用来操作地图
- MapComponent({ mapOptions: , mapCallback: (err, controller) => {} })
- controller就是地图控制器 后续所有方法都在他身上
-
利用控制器绘制折线,准备一段折线,利用addPolyLine方法添加折线到地图上
- controller.addPolyline ([ˈpɒli]-跑利)绘制折线
-
利用navi模块按需求获取对应的坐标点,在地图上使用路径节点利用绘制折线方法完成绘制
- 得到步行的坐标点/开车的坐标点/公交车的坐标点 navi.getDrivingRoutes([ˈdraɪvɪŋ] ['ru:ts]-入t词)
27.图片上传(下载)
1、跳转到相册app/原地拍照
通过鸿蒙内置的picker模块photoSelectOptions创建出一个实例,配置上传类型、数据
使用PhotoViewPicker创建图片选择器拉起相册app,通过select方法await获取选中图片的地址
2、拷贝至应用沙箱(鸿蒙不能直接上传,需要先拷贝到app沙箱中)
准备文件后缀、文件名、文件沙箱的路径将选中的图片以只读的模式通过fs.copyFileSync拷贝至文件中得到沙箱路径
3、上传图片
最后调用后端接口通过request.uploadFile方法上传
需要传入2个参数,一个是上下文,一个是上传文件配置参数,注意在参数的header里面要设置Content-Type为multipart/from-data,表示当前上传的是一个多部分表单数据,上传时必须要设置的,后端根据这个类型去做文件的接收处理
通过监听2个事件,progress和fail,其中progress事件会返回上传文件的总大小和当前上传的大小,我们可以用来做上传进度的计算和展示,当时碰到一个问题,就是([ˈkʌstəm])customDialogController弹窗不会根据调用方法的状态属性值改变而变化的,后来通过emitter这个api解决跨线程通讯问题
如果上传有错误,可以在fail监听事件中获取错误
⚠️:从相册中选一个文件是没有权限读取的,需要利用上下文对象以及fs这个api将文件拷贝到应用沙箱中,通过internal://cache/加上沙箱中的文件名才能正常做上传操作
需要开启internet权限
图片下载用的是 request.downloadFile
28.权限管理
1.权限全流程
默认应用等级和权限接口的等级是相对应的 应用等级成为APL 俗称就是一个权限等级的划分
应用等级划分为:
1.normal 这个标识普通应用
2.system basic 这个是系统服务
3.system_core 这个是核心系统服务 (这是操作系统能力 也就是你只要在鸿蒙中运行他就是支持的,但是目前看下来基本只会用到前面里两个等级)
权限等级:
1.normal
2.system basic
3.system_core (同理)
他们的等级是一一对应的 如果normal跨级访问system basic权限的接口
1.第一步
首先就需要看 你申请的这个权限是否支持跨等级访问 这个官方有一个acl名单 标明了你这个等级的app是否支持那些权限 一般我们的应用默认都是normal
2.第二步
如果是支持的 要看使用这个权限的授权方式是userAgent 还是 system_grant 如果是userAgent 需要弹出一个授权弹框去找用户交互 让用户来手动授权 如果是system_grant 这个这个权限的你就可以直接声明权限直接去使用 不需要弹框告诉用户
3.第三步
要在modle.JSON5 文件中去声明权限(类似请求的那个权限就是写在那个文件里,具体怎么写 要看文档)
4.第四步
要自动生成签名 不然会导致你不能使用一些权限(所以切记 这个必须要生成,不然默认为你没有这个权限去访问这个写内容 ,他就相当于一个token ,有token才能访问这个内容)
5.最后
接下来就是正常套路了 先判断他是否拥有这个权限 如果拥有 然后再去调用申请这个系统的权限接口,这个调用的时候如果时userAgent 需要弹窗
通知和其他权限有点区别
通知权限直接申请即可
29.骨架屏
是什么:
是网速慢的时候,后端数据无法及时响应,页面结构无法展示,需要一个替代结构,让用户感知自己网速不行,提升用户体验
封装骨架屏结构-一个小小的宽度不同的骨架组件组成
请求数据显显示=> 数据回来后不显示
利用linearGradient+translate+animation+onAppear实现骨架闪光效果
组件内部定义一个变量接收外部数据
层叠-提供2个相同宽高的盒子
下面的盒子设置渐变linearGradient-及平移x轴-100%
上面的盒子onAppear瞬间平移x轴100%
添加动画 无限次循环
30.页面跳转router 和 Navigation 的区别
Navigation主要用于页面内的导航和转场动效,并提供多种显示模式和样式。Router则更侧重于全局的页面路由管理,通过URL地址进行页面间的跳转和数据传递。
Navigation 比router(最多32个页面)更高级,也是用来在页面之间跳转的,但可以处理更复杂的情况,比如动画效果、多级页面等。router已经不怎么改进了,但Navigation还在不断更新,考虑到应用当前或以后可能出现的复杂场景,那就用Navigation。只需要简单的页面跳转就用router。
补充:在使用NavRouter进行页面跳转的时候必须要放另一个子组件也就是NavDestination([ˌdestɪˈneɪʃn]-带斯题内身)
Navigation声明式导航 router编程式导航
统一管理和控制 | 动态加载和扩展性 | 多设备适配 | 路由管理和路由栈 | ||
---|---|---|---|---|---|
Navigation | 提供了更高级的页面管理和控制能力,可以作为Page页面的根组件,通过路由进行页面切换 | 支持动态加载,可以动态添加,删除,修改页面,增加灵活性和可扩展性,跳转更加灵活 | 可以,但没有Navigation更直接方便 | ||
router | 也能实现页面跳转,更多的是关注URL地址解析和页面之间数据传递 | 可以 |
31.组件、页面及应用的生命周期
组件:
aboutToAppear
aboutToDisappear
页面:
aboutToAppear
aboutToDisappear
onPageShow 页面每次显示触发
onPageHide 页面每次隐藏式触发
onBackPress 用户点击返回按钮触发
应用:
NnCreate:Ability创建时回调。,执行初始化业务逻辑操作。
·onDestory:Ability生命周期回调,在销毁时回调,执行资源清理等操作。
·onWindowStageCreate:当WindowStage创建后调用。
·onWindowStageDestory:当WindowStage销毁后调用。
·onForeground:Ability生命周期回调,当应用从后台转到前台时触发。
·onBackground:Ability生命周期回调,当应用从前台转到后台时触发
32. LocalStorage&AppStorage&PersistentStorage分别介绍一下?
LocalStorage是一个UIAbility,多个页面之间共享数据
AppStorage 是 多个 UIAbility 可以跨模块共享数据
PersistentStorage 是 可以数据持久化,退出应用后不会清掉数据
33. 使用过哪些第三方库?分别介绍一下
三方库(别人封装好的函数axios 别人封装好的组件)
axios
abner/log
dayjs
lodash
canlendar 日历组件
crypto-js
34.HAP、HSP、HAR区别
HAP是鸿蒙应用的基本组成单元,具有自己的Ability,不同的设备类型有不同的HAP。
HSP是一个动态共享包,用于在不同HAP之间共享公共的页面或功能,体积相对较小。(没有Ability)
HAR是静态共享包,用于在HAP之间共享静态资源,性能相对较好(没有Ability)
有无UIAbility
-
有, HAP(必须有)
-
没有, 共享包(被别人用 不需要UIAbility)
-
HAR 使用时多次拷贝 独立
-
HSP 使用时只有一份
-
35.了解过多线程吗?和 Promise 区别是什么?
相同点:都可以同时做事
不同点:
1.Promise是语言层面的特征,多线程是操作系统层面的特征
2.Promise主要是处理异步代码的执行结果 多线程更多的是关注代码执行的效率和性能
(Promise只能处理异步代码的同时 多线程任何代码都可以并发)
3.Promise相比于多线程 太简单了 .then拿结果, 多线程涉及到创建线程 线程管理
36.项目中遇到过哪些坑?
1.首选项使用异步方法的时候会出现运行紊乱的问题,当异步方法文件还没写完,如果去读就会读取失败,建议使用同步方法
2.文件操作的时候,如果使用异步方法执行会出现紊乱的问题,建议改为使用同步方法
3.async方法忘记使用await调用出现执行结果紊乱
4.AVPlayer播放在线mp3文件时,http协议的url播放不了,但是https协议的url可以正常播放
5.AudioCapturer实例化的时候,在win模拟器上实例化报错,真机可以
6.上传文件时直接读取系统相册文件没有权限,上传失败,需要把系统相册图片通过fs拷贝到应用程序缓存中使用internel://cache方式来读取就能成功
7.RichText富文本兼容性问题
8.跨线程通讯问题 ->头像上传百分比进度不更新->使用emitter解决
9.Web组件高度兼容性问题:Web组件加载完html后,再调用writeCode方法写入内容,显示不出来内容,在Web组件上增加固定高度可以解决
37.如何解决代码冲突
代码冲突通常发生在多人协作开发时,当两个或更多的开发者修改了同一个文件的同一部分,并且这些修改在合并时不能自动解决时。以下是一些解决代码冲突的基本步骤:
- 了解冲突: 首先,你需要了解冲突的原因。查看版本控制系统(如Git)提供的冲突信息,这通常会在合并失败时显示。
- 备份你的工作: 在解决冲突之前,确保你已经备份了所有未提交的更改。这样,如果解决冲突的过程中出现问题,你可以轻松地恢复到之前的状态。
- 手动解决冲突: 打开有冲突的文件,你会看到类似于以下的冲突标记:
你需要手动编辑这些文件,决定保留哪些更改。通常,你会保留你自己的更改,同时考虑合并另一个开发者的更改(如果它们是有益的)。删除所有的冲突标记(<<<<<<<、======= 和 >>>>>>>)。
- 测试你的更改: 在解决冲突后,确保你的代码仍然按预期工作。运行你的测试套件,确保没有引入新的错误。
- 提交你的更改: 一旦你解决了所有的冲突并测试了你的代码,你可以提交你的更改。在提交消息中,清楚地说明你解决了哪些冲突以及你是如何解决的。
- 拉取最新的更改(可选): 如果你在解决冲突时,其他开发者提交了新的更改,你可能需要再次拉取最新的代码,并重新解决可能出现的任何新的冲突。
- 使用工具辅助: 许多IDE(如IntelliJ IDEA、Visual Studio Code等)和版本控制系统(如Git)都提供了图形化界面来帮助你解决冲突。这些工具通常可以高亮显示冲突的部分,并允许你更轻松地选择保留哪些更改。
- 保持沟通: 与你的团队成员保持沟通是非常重要的。如果你不确定如何处理某个冲突,或者你不确定某个更改是否应该被保留,不要犹豫,向你的同事寻求帮助。
- 学习如何避免冲突: 虽然冲突是不可避免的,但有一些策略可以帮助你减少它们的发生。例如,使用特性分支(feature branches)来隔离你的工作,定期拉取最新的代码,以及尽早地解决小的冲突,而不是让它们积累起来。
38.鸿蒙 和H5怎么通信的?
目前在做的项目正在做的阶段混合开发,具有跨平台能力,快速迭代
使用鸿蒙原生和Web开发相结合,使用了原生JSBridge模块([brɪdʒ]-不瑞句)
我们考虑到个人信息页多端使用,使用的web开发嵌入到鸿蒙原生应用中,
但个人资料页数据及修改网页端的数据需要从鸿蒙原生侧获取到对应的能力
我们在鸿蒙原生侧封装了拍照服务类以及相册服务类,
然后通过Web组件src加载H5端网页,通过Web组件的控制器的([ˈredʒɪstə(r)]- ruai 句斯特)registerJavaScriptProxy([ˈprɒksi]-破绕可西)方法将封装的能力注入到网页中,在网页中调用获取到相应的能力,这个方法有3个必传,
第一个参数是注入网页端的能力,将封装好的拍照及获取相册的能力通过回调函数方式传入(方法名:回调函数),
第二个参数是注册对象的名称string
第三个是传给网页端能力对应的方法名称组成的数组
网页端通过注册对象的名称.对应的方法直接调用获取到原生侧的能力,会有类型问题,在网页端建一个d.ts声明类型文件,将原生侧的类型cv过去即可
原生端也可以直接调用h5端的方法 runJavascript,在这个方法里面传入方法调用传参数就可以,
需要注意的点:如果原生端的sdk方法是个异步方法,在h5端无法及时得到结果,此时需要再用原生反调h5进行传递结果
但注意获取到能力执行逻辑,需要在原生侧查看效果网页端无法执行逻辑
就会很尴尬,打印无法观看,我们在网页端index.html文件中引入了一个js插件,然后在手机端配置Eruda (艾瑞嘚)手机调试面板工具,可以在原生端查看打印
webview的性能优化(怎么加快webview的响应速度)
- 可以通过prepareForPageLoad()来预解析或者预连接将要加载的页面
- 能够预测到Web组件将要加载的页面或者即将要跳转的页面。可以通过prefetchPage()来预加载即将要加载页面
- 可以通过prefetchResource()预获取将要加载页面中的post请求。在页面加载结束时,可以通过clearPrefetchedResource()清除后续不再使用的预获取资源缓存
- 预编译生成编译缓存 可以通过precompileJavaScript()在页面加载前提前生成脚本文件的编译缓存。
39.应用权限的分类?
40. 网络请求模块是怎么做到的(axios封装)?
网络请求模块的封装主要涉及到以下几个关键步骤:
- 引入网络请求库: 首先,需要引入一个网络请求库,比如常用的axios。这个库提供了丰富的API来进行HTTP请求。
- 创建网络请求实例: 使用网络请求库(如axios)的create方法创建一个实例。在这个实例中,可以设置一些默认的配置,如baseURL(基础URL)、timeout(请求超时时间)等。例如,const instance = axios.create({baseURL: "example.com", timeout: 5000});。
- 配置请求 拦截器: 请求拦截器可以在请求发送前进行一些预处理操作,比如添加请求头、设置token、显示加载动画等。通过instance.interceptors.request.use方法可以添加请求拦截器。
- 配置响应 拦截器: 响应拦截器可以在接收到服务器响应后进行一些后处理操作,比如对响应数据进行格式化、处理错误等。通过instance.interceptors.response.use方法可以添加响应拦截器。
- 导出封装好的网络请求函数: 将封装好的网络请求实例或者函数导出,供其他模块使用。这样,其他模块只需要调用这个函数,并传入相应的配置参数,就可以发起网络请求了。
- 使用封装好的网络请求模块: 在其他模块中,通过引入封装好的网络请求模块,并调用其导出的函数来发起网络请求。例如,import { request } from "./network/request"; request({ url: '/api/data' }).then(res => { console.log(res); }).catch(err => { console.log(err); });。
- 错误处理和 日志记录: 在封装过程中,还需要考虑错误处理和日志记录的问题。比如,在请求拦截器和响应拦截器中,可以添加错误处理的逻辑,并记录相应的日志信息,以便于后续的问题排查和定位。
41.开发项目流程
42.上架APP的流程
-
创建应用: 在AppGallery Connect(AGC)创建鸿蒙应用(注意创建应用包名要与项目包名一致)
-
调测应用:在主菜单栏单击Build(构建)-> 生成私钥(.p12)和证书请求文件(.csr)
-
申请发布证书和发布Profile文件: 在AGC平台点击用户与访问, 左侧点击证书管理,再点击还右侧新增证书) 在弹框中填写证书名称、选择证书类型为调试证书(.cer),选取我们在第一步生成的·csr文件,最后点击提交调试证书下载的本地
-
打包HarmonyOS应用: 在DevEco Studio中配置好发布证书和发布Profile,然后编译生成软件包(HAP)。
-
发布应用: 调试完毕并打包后,可以在AGC提交上架申请。
43.线程池 TaskPool
44.har包项目里有没有用过?怎么把工具类和组件导出来?
引用本地模块 源码
在需要引入本地模块源码的模块的oh-package.json5中设置源码依赖项,即entry模块的oh-package.json5中,添加如下配置:
- "dependencies": {
- "folder": "file:path/to/foo" // 此处也可以是以当前oh-package.json5所在目录为起点的相对路径
- }
依赖设置完成后,需要执行ohpm install命令安装依赖包,模块foo的源码会安装在entry模块的oh_modules目录下。
- ohpm install
引用ohpm仓中的 HAR
在需要引入三方包的模块的oh-package.json5中设置三方包依赖,配置示例如下:
- "dependencies": {
- "@ohos/lottie": "^2.0.0"
- }
依赖设置完成后,需要执行ohpm install命令安装依赖包,依赖包会安装到该模块的oh_modules目录下。
- ohpm install
引用本地 HAR /HSP包
-
在需要引入三方包的模块的oh-package.json5中设置本地HAR/HSP包。以HAR/HSP包在工程根目录下为例,配置示例如下(实际配置时请以HAR/HSP包实际目录为准):
-
引用HAR:
- "dependencies": {
- "package": "file:path/to/package.har" // 此处也可以是以当前oh-package.json5所在目录为起点的相对路径
- }
-
引用HSP:
- "dependencies": {
- "package": "file:path/to/package.tgz" // 此处也可以是以当前oh-package.json5所在目录为起点的相对路径
- }
-
-
依赖设置完成后,需要执行ohpm install命令安装依赖包,依赖包会安装在该模块的oh_modules目录下。
- ohpm install
另外,在安装或卸载共享包时,可在模块或工程的oh-package.json5文件中增加钩子设置,以管理install、uninstall命令的生命周期,配置示例如下:
- "hooks": {
- "preInstall": "echo 00 preInstall", // install命令执行之前
- "postInstall": "echo 00 postInstall", // install命令执行之后
- "preUninstall": "echo 00 preUninstall", // uninstall命令执行之前
- "postUninstall": "echo 00 postUninstall" // uninstall命令执行之后
- }
说明
- 目前只支持执行当前模块或工程的oh-package.json5文件中hooks,不支持执行依赖中hooks。
- 在引用共享包时,请注意当前只支持在模块和工程下的oh-package.json5文件中声明dependencies依赖,才会被当做依赖使用,并在编译构建过程中进行相应的处理。
45.hap包静态库怎么用? Har包使用
46.应用沙箱存储文件存在编辑器本地的哪些地方?(路径)呢
47.Builder和BuildParams的区别
builder传结构
builderParams接收结构
回答:他们都是修饰符,Builder是将一个函数修饰为轻量UI复用的函数,在Builder修饰的函数中可以实现 ArkUI的应用,但是Builder的用法比较多,有全局builder还有局部builder,全局builder不适合做状态更新,全局builder在鸿蒙4.0中被导出使用,支持在Next版本中使用。
Builder的传值有基础类型传值和引用类型传值,引用类型传值才能具备响应式的特点
BuilderParam类似于前端领域中Vue中的插槽,可以传入UI的结构,支持自定义组件的传入结构,首选在子组件中定义BuilderParam,在父组件中传入BuilderParam对应的函数,该函数可以没有builder修饰,但是必须调用一个Builder修饰的函数
• BuilderParam中有一种尾随闭包的写法,就是组件()后面的大括号可以传入内容,有两个前提
• 1.必须只有一个BuilderParam
• 2. BuilderParam没有接收参数的需求才可以
48.多线程和单线程区别
在鸿蒙操作系统(HarmonyOS)中,多线程和单线程的区别与一般操作系统相似,但由于鸿蒙系统的特性和设计目标(如分布式能力、高效性和资源有限设备的优化),这些区别在具体实现和应用上可能会有所不同。以下是对鸿蒙系统中多线程和单线程区别的详细解释:
- 基本概念
- 单线程(Single-threading) :在单线程环境下,一个应用程序只有一个线程在执行,即所有任务都是顺序执行的。
- 多线程(Multi-threading) :在多线程环境下,一个应用程序可以同时包含多个线程,这些线程可以并发执行,多个任务可以同时进行。
- 资源利用
- 单线程:只能利用一个CPU核心,即使在多核处理器上也只能使用一个核心,无法充分利用系统的多核资源。
- 多线程:可以利用多个CPU核心,充分利用系统的多核资源,提升程序的并发性能和响应速度。
- 执行效率
-
单线程:
- 优势:设计和实现相对简单,调试和维护也较为容易。
- 劣势:如果某个任务阻塞(如I/O操作),整个应用程序都会被阻塞,导致性能下降。
-
多线程:
- 优势:线程可以并发执行,提高程序的运行效率,尤其是在多核处理器上可以实现真正的并行。
- 劣势:需要处理线程同步和资源共享问题,设计和调试难度较大,容易出现死锁、竞态条件等问题。
- 稳定性和安全性
- 单线程:由于没有多线程的同步和竞态条件问题,程序的稳定性和安全性相对较高。
- 多线程:由于线程之间共享资源,容易出现同步问题、死锁、竞态条件等,可能影响程序的稳定性和安全性。
- 鸿蒙系统的特性
鸿蒙系统特别关注物联网设备和分布式系统,因而在多线程和单线程的实现和应用上有一些独特的考虑:
- 轻量级内核(LiteOS-A) :针对资源有限的设备进行了优化,支持高效的任务调度和管理。多线程在这种环境下可以显著提高任务的并发性和响应速度。
- 分布式架构:鸿蒙系统支持设备间的分布式协作,多线程在这种架构下可以更有效地实现设备间的任务分配和协同工作。
- 高效调度:鸿蒙系统的调度机制能够根据任务的优先级和设备的资源情况进行动态调整,确保关键任务的高效执行。
49.鸿蒙系统能力用过哪些
AudioCapurer-录音控制
AvPlayer-播放音频
Picker-图片上传
三方登录
LocationButton-位置控件
Request. downloadFile-代码完成打包上传压缩代码
拍照能力
相册能力
震动能力
zlib.decompressFile-解压
fs文件上传
首选项
关系型数据库
router-路由跳转
50.图案密码锁用的是什么?
PatternLock
图案密码锁组件,以九宫格图案的方式输入密码,用于密码验证场景。手指在PatternLock组件区域按下时开始进入输入状态,手指离开屏幕时结束输入状态完成密码输入。
使用onPatternComplete方法 密码输入结束时触发该回调。input: 与选中宫格圆点顺序一致的数字数组,数字为选中宫格圆点的索引值
51.华为支付
developer.huawei.com/consumer/cn…
52.Flex布局怎么进行二次布局
- flex以弹性方式布局,存在二次布局,对性能有要求换成column/row
- 当子组件主轴尺寸之和大于容器主轴尺寸长度,会触发二次布局
- 如何优化 :
- 使用Column/Row代替Flex。
- 大小不需要变更的子组件主动设置flexShrink属性值为0。
- 优先使用layoutWeight属性替代flexGrow属性和flexShrink属性。
- 子组件主轴长度分配设置为最常用场景的布局结果,使子组件主轴长度总和等于Flex容器主轴长度
- 注: flexgrow:设置父容器在主轴方向上的剩余空间分配给此属性所在组件的比例。flexshrink:设置父容器压缩尺寸分配给此属性所在组件的比例。
53.页面栈管理了解吗--页面是如何跳转的
- 栈是一种先进后出的数据结构,UIAbility内的页面跳转关系被称为页面栈
- 页面栈最多容纳32个页面
- router.pushurl ( ) 将目标页面压入页面栈中,不会销毁当前页面,可以router.back()
- router.replaceurl() ,目标页面会替换当前页面,不可以router.back( )
- router.clear 会清除栈内除了当前页面的所有页面
- 两种实例模式:
- Standard : 每次页面跳转,增加新的页面压入栈顶
- single:将离栈顶最近的页面拉到栈顶,栈内没有目标页面会新建一个压入栈顶,等同Standard
54.forEach和 LazyForeach 的区别?
- foreach从列表数据源一次加载全量数据 , 为每一个元素创建响应组件, 并全部挂载在组件树上 , 可视区外的listitem组件入屏幕时已经完成了数据加载和组件创建加载,直接渲染即可
- LazyForEach会根据屏幕可视区能够容纳显示的组件数量按需加载数据 , 根据加载的数据量创建组件,挂载在组件树上,构建出一棵短小的组件树。即,屏幕可以展示多少列表项组件,就按需创建多少个ListItem组件节点挂载在List组件树根节点上 ,可视区外的组件需要在屏幕内显示时,需要从头完成数据加载、组件创建、挂载组件树这一过程,直至渲染到屏幕上。cachedCount属性
- lazy适用于长列表加载或无限瀑布流 ,按需加载虽然可以优化长列表性能, 避免滑动过快来不及加载出现白块, list Grid、Swiper以及WaterFlow有cachedCount属性 ,可设置预加载数量
forEach
是立即执行的迭代方法,遍历集合时会立即对每个元素执行操作,适用于需要立即处理所有元素的场景。
LazyForeach
是惰性求值的迭代方法,操作只有在终结操作调用时才会执行,适用于处理大型集合或需要链式操作的场景。
1. forEach
forEach
是一种立即执行的迭代方法,它遍历集合中的每个元素,并对每个元素执行指定的操作。无论集合有多大,forEach
都会立即遍历整个集合。
特点:
- 立即执行:对每个元素的操作会在遍历时立即执行。
- 无返回值:通常用于具有副作用的操作(如修改元素、打印日志),不返回新集合。
- 无法中断:遍历过程一旦开始,无法在中途停止。
2. LazyForeach
LazyForeach
是一种惰性求值(Lazy Evaluation)的迭代方法,它通常用于流(Stream)或类似的集合处理框架中。惰性求值意味着在实际需要结果之前,不会立即对集合进行操作。这种方法可以提高性能,尤其是处理大型集合时,只有在需要结果时才会进行计算。
特点:
- 惰性求值:只有在需要结果时(如调用终结操作时)才会执行对每个元素的操作。
- 返回新的集合:通常用于返回一个新的流或集合,可以进行进一步的操作。
- 可以中断:在某些框架中,可以通过条件中断迭代。
55.图片上传,如果上传过程中中断了,这个要怎么解决办法?
断点续传
哪里断了 哪里重新开始上传
难度 => 哪里断了
每次上传文件时 配合后端接口 一小段一小段上传(slice 截取一部分) 后端在接受到每一小段文件后 记录下位置信息(播放声音) 如果有一次突然断开 基于保存的位置信息 重新上传这个部分
服务端接收到所有文件数据后 需要按照顺序拼接起来
56.回调函数
回调函数 就是把一个函数当成一个参数传给另一个函数,将来我们在执行一个函数的时候,我们可能要传一个函数给它,那么这个被传进去的函数就称为回调函数。 他有很多的作用,
作用1:关于回调函数,它其实本身就是一种通知,就是如果你希望通知别人做这件事情,你就用回调函数,(例如 :发请求拿数据,你的通知回来了,它通知你拿结果,这时候就要传回调函数了。 还有一个比如说注册了紧急事件,它通知你点击了,也要提供回调函数)
作用2:能够解耦核心业务解耦真正的逻辑,就是一般在底层的框架,底层的三方库里面会经常用到这个逻辑,他们都会把数据处理好了之后然后调用你传入的回调函数,自己去处理后续业务上的逻辑
57.说说对Ability的理解
ability 提供了应用程序必备的组件和运行机制
UIAbility 开发app页面
UIAbility是一种包含用户界面的应用组件,主要用于和用户进行交互。UIAbility也是系统调度的单元,为应用提供窗口在其中绘制界面,鸿蒙中为应用提供的展示窗口,且有它对应的生命周期
FormExtensionAbility 开发服务卡片
InputMethodExtensionAbility 输入法
58.对want的理解
59.用过哪些组件
容器组件:
Column-列容器
ColumnSplit-子组件纵向布局且组件间插入一根横向的分割线
Row-行容器
RowSplit-子组件纵向布局且组件间插入一根横向的分割线
Flex-弹性布局
Grid-网格容器组件
GridItem-网格容器单个内容组件
GridRow-栅格布局
GridCol-栅格子组件
List-列表组件
ListItem-每一个列表项
ListItemGroup-用来展示列表分组(header,footer)
Badge-气泡组件
Navigator-路由容器组件-提供跳转能力
Scroll-滚动组件
Tabs-通过页签进行内容视图切换的容器组件
TabContent-仅在Tabs中使用,对应一个切换页签的内容视图
Swiper-轮播图
WaterFlow-瀑布流
FlowItem-展示瀑布流具体item
Stack-层叠
SideBarContainer-侧边栏组件
Refresh-可以进行页面下拉操作并显示刷新动效的
Counter-计数器组件(用于购物车数量+-,控制按钮禁用enableInc/Dec)
基础组件
AlphabetIndexer-可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件
Blank-空白填充
Button-按钮组件
Check-提供多选框组件,通常用于某选项的打开或关闭
CheckGroup-多选框群组,控制多选框全选或者不全选状态
Divider-分割器组件,分隔不同内容/元素
Image-图片组件
ImageAminmator-帧动画组件,逐帧播放图片的能力,可以配置需要播放的图片列表,每张图片可以配置时长
Text-文本组件
TextArea-多行文本输入框
TextInput-单行输入框
TextPicker-滑动选择文本内容
span-做为Text和RichEditor子组件显示文字,ImageSpan-做为Text子组件显示图片
LoadingProgress-显示加载动效的
Navigation-路由导航的根视图容器,绑定路由栈到Navigation组件
PatternLock-图案密码锁组件,以九宫格图案的方式输入密码
Progress-进度条组件
RichText-富文本组件,解析并显示HTML格式文本
ScrollBar-滚动条组件,配合滚动组件使用
Toggle-组件提供勾选框样式、状态按钮样式及开关样式
Web-提供具有网页显示能力的Web组件,@ohos.web.webview提供web控制能力
60.用过哪些装饰器
-
LocalStorage:页面级UI状态存储 @LocalStorageProp @LocalStorageLink
-
AppStorage:应用全局的UI状态存储. @storageProp @storageLink(AppStorage与自定义组件间的联系)
-
@BuilderParam装饰器:引用@Builder函数,用来装饰指向@Builder方法的变量
-
@Component表示自定义组件
-
@Entry表示该自定义组件为入口组件
-
@CustomDialog装饰器用于装饰自定义弹框,此装饰器内进行自定义内容(也就是弹框内容)
-
@preview 预览
-
@Watch装饰器:状态变量更改通知
61.鸿蒙中app怎么打包上架的流程
一、开发环境准备
-
安装DevEco Studio:
- DevEco Studio是华为官方提供的HarmonyOS应用开发IDE,支持应用的开发、调试、编译和签名等功能。
- 前往华为开发者官网下载并安装最新版本的DevEco Studio。
-
配置项目:
- 在DevEco Studio中创建或导入HarmonyOS项目。
- 配置项目的基本信息,包括包名、签名信息等。
二、应用编译
-
构建项目:
- 在DevEco Studio中,使用“Build”菜单下的“Build Hap(s)/APP(s)”选项来编译你的应用。
- 对于HarmonyOS应用,编译后生成的是HAP(Harmony Ability Package)文件。
-
检查编译结果:
- 编译完成后,检查编译输出目录(如
build/outputs/app/release
)中的HAP文件是否生成成功。
- 编译完成后,检查编译输出目录(如
三、应用签名
-
生成签名文件:
- 在DevEco Studio中,使用“Build”菜单下的“Generate Key and CSR”选项来生成密钥库文件(.p12)和证书请求文件(.csr)。
- 填写密钥库和密钥的相关信息,如密钥库文件存储路径、密码、密钥别名等。
-
申请证书:
- 登录AppGallery Connect,进入“证书管理”页面,上传CSR文件并申请证书。
- 证书申请成功后,下载生成的证书文件(.cer)和Profile文件(.p7b)。
-
配置签名信息:
- 在DevEco Studio中,打开项目的“Project Structure”对话框,选择“Signing Configs”页签。
- 配置签名信息,包括密钥库文件、密钥库密码、密钥别名、密钥密码、签名算法以及Profile文件和证书文件等。
四、生成最终的应用包
-
重新编译并签名应用:
- 配置好签名信息后,重新编译应用。DevEco Studio会自动使用配置的签名信息对应用进行签名。
-
检查签名后的应用包:
- 编译完成后,检查输出目录中的签名后的HAP文件或APP文件。
五、上传应用到AppGallery Connect
-
登录AppGallery Connect:
- 使用华为开发者账号登录AppGallery Connect。
-
创建并发布应用:
- 在AppGallery Connect中创建新项目,并添加应用。
- 填写应用的基本信息、版本信息、上传应用包等。
- 提交应用审核,等待华为审核通过后,用户即可在华为应用市场下载你的应用。
注意事项
- 在整个打包流程中,请确保使用的开发环境、SDK版本和工具链都是最新的,以避免兼容性问题。
- 签名是应用上架的必要步骤,务必按照华为的要求生成和配置签名信息。
- 在上传应用到AppGallery Connect之前,请仔细检查应用的包名、版本号、签名信息等是否正确无误。
以上信息基于当前的知识和常见的开发实践,具体流程可能会随着HarmonyOS版本和DevEco Studio的更新而有所变化。建议在实际操作中参考最新的官方文档和指南。
62.鸿蒙中怎么判断折叠屏是打开还是关闭
63.公共事件 common event
-
收消息 创建订阅者对象createSubscriber() 订阅公共事件subscribe()
-
发消息 通过publish()方法发布事件
64.module.json5配置文件里面的type
4、module.json5里的type
entry:应用的主模块。每个鸿蒙应用都必须有一个入口模块,它负责应用的初始化和启动。
feature:应用的动态特性模块。功能模块是应用中的一个独立单元,它可以被其他模块引用或调用,以实现特定的功能
har:静态共享包模块。
shared:动态共享包模块。
65.在鸿蒙中有些页面想让用户看到,有些页面不想让用户看到,是怎么实现的
- 应用内控制:
- 页面跳转逻辑:在应用内部,可以通过控制页面跳转的逻辑来决定用户能否访问特定页面。例如,可以在用户尝试访问某个页面时先进行权限检查,如果用户没有相应权限,则不执行跳转。
- 条件渲染:在UI层面,可以根据用户的权限或应用的状态来决定是否渲染某个页面或页面元素。
-
用户权限管理:
- 权限申请与检查:鸿蒙系统支持权限管理,应用可以请求用户授予特定权限。在尝试显示某个页面之前,应用可以检查用户是否已经授予了相应的权限。
- 角色与权限绑定:可以为不同的用户角色分配不同的权限,然后根据用户的角色来决定他们能否访问特定页面。
66.冷启动和热启动
冷启动
定义:
- 当应用启动时,如果后台没有该应用的进程,系统会重新创建一个新的进程分配给该应用。这种启动方式被称为冷启动。
过程:
-
冷启动过程大致可以分成以下四个阶段:应用进程创建&初始化、Application&Ability初始化、Ability/AbilityStage生命周期、加载绘制首页。
- 应用进程创建&初始化:包含了系统完成应用进程的创建以及初始化的过程,如启动页图标的解码等。
- Application&Ability初始化:主要是资源加载、虚拟机创建、Application&Ability相关对象的创建与初始化、依赖模块的加载等。
- Ability/AbilityStage生命周期:执行相应的生命周期回调。
- 加载绘制首页:加载页面、测量和布局、渲染等,是首帧绘制最重要的阶段。
性能指标:
- 冷启动响应时延推荐时间为85ms,其中包含了硬件显示耗时(约25ms~30ms),剩下的为软件耗时。
优化建议:
- 设置合适分辨率的启动页图标,避免解码耗时影响启动速度。
- 减少import的模块数量,使用系统提供的模块,按需加载,以缩短启动耗时。
- 避免在生命周期回调函数中执行耗时过长的操作,建议通过异步任务延迟处理或放到其他线程执行。
热启动
定义:
- 当应用程序已经在后台运行,此时用户再次打开应用程序时,应用程序仍然在内存中,可以直接从内存中加载并继续之前的状态,而不需要重新初始化和加载资源。这种启动方式被称为热启动。
特点:
- 热启动速度通常比冷启动快,因为不需要重新创建进程和加载资源。
- 用户体验更为流畅,因为应用能够迅速恢复到之前的状态。
总结
鸿蒙系统中的冷启动和热启动是两种不同的应用启动方式,分别对应着不同的场景和性能需求。冷启动需要经历更多的初始化和加载过程,因此耗时较长;而热启动则能够迅速恢复应用状态,提高用户体验。在实际开发中,开发者可以通过优化冷启动过程来提高应用的启动速度和性能表现。
67.UIAbility启动模式(单实例模式 多实例模式 指定实例模式)
- Singleton(单实例模式)
- 特点:系统中只存在唯一一个该UIAbility实例。在创建该模型时,如果应用进程中该类型的UIAbility实例已经存在,则会复用该实例,不会创建新的实例。
- 应用场景:适用于那些不需要多次创建实例的场景,比如应用的主页面或设置页面。
- 配置方法:在
module.json5
配置文件中的"launchType"
字段配置为"singleton"
。
- Multiton(多实例模式,但行为类似单实例)
- 特点:虽然被归类为多实例模式,但在实际行为上更接近于单实例模式。每次调用
startAbility()
方法时,如果UIAbility已存在,则会替换原来的实例,并重新走onCreate
和onWindowStateCreate
生命周期。 - 应用场景:这个模式在某些特定场景下可能使用,但需要注意的是,其行为与标准的多实例模式有所不同。
- 配置方法:在
module.json5
配置文件中的"launchType"
字段配置为"multiton"
。
- Specified(指定实例模式)
- 特点:在UIAbility实例创建之前,允许开发者为该实例创建一个唯一的字符串Key。创建的UIAbility实例绑定Key之后,后续每次调用
startAbility()
方法时,都会询问应用使用哪个Key对应的UIAbility实例来响应请求。如果匹配到已存在的实例,则复用该实例;否则,创建新的实例。 - 应用场景:适用于一些特殊场景,比如文档应用中每次新建文档希望都能新建一个文档实例,而重复打开一个已保存的文档则希望打开的是同一个文档实例。
- 配置方法:在
module.json5
配置文件中的"launchType"
字段配置为"specified"
,并在启动该UIAbility时,通过Want
对象的parameters
字段传递一个包含instanceKey
的自定义参数。
68.ARkTs和TS区别
1.先做一下自我介绍
2.介绍一下你最近的一个项目
3.团队的构成
4.应用上线了吗
5.存储用户聊天记录为什么用首选项不用其他的方法
6.鸿蒙中持久化存储方案有哪些
7.首选项的Api叫什么
8.鸿蒙有没有数据库支持
9.你了解安卓和ios吗
10.聊天记录为什么不用数据库存储
11.使用首选项存储聊天的方案是谁决定的
12.首选项存储的时候key是用的什么
14.存储以后下次再进入应用,怎么找到某两条聊天记录
15.鸿蒙的网络请求是自己有提供吗
16.三方库是个人开发的还是团队开发的
17.对鸿蒙系统的架构层次了解吗
18.一多开发是什么,三层架构是什么(面试官不知道,提出来他才问)
19.多端部署是需要额外适配吗
20.icon(图标)在手表上怎么正常显示
21.写代码的时候UI是动态等比缩放的还是有一个处理方案
22.ArkTS与TS的不同点
23.怎么申请权限
24.了解分布式吗
25.你是怎么理解不同设备数据共享大致的原理
26.什么方式学习的鸿蒙
27.聊天的服务器回复用的是什么
28.为什么从前端转鸿蒙
29.有哪个项目是正在跑的
30.你怎么看待鸿蒙在海外的发展
31.你有架构设计的思路实践吗
32.鸿蒙系统总共分多少层(知不知道安卓的内核层,硬件抽样层,框架层)
33.应用运行时底层干了些啥事了解吗
34.如果录音的时候有电话打过来,录音如何处理
35.有下载我们公司的App体验吗
36.让你用鸿蒙做一款社交软件你会怎么做
37.目前第三方有提供给鸿蒙相应的支持吗比如腾讯
38.M(某个东西的缩写,Instant message)知道是什么吗
39.Websocket是什么,是鸿蒙自身支持的吗
40.WebSocket的使用思路是什么
41.用户发出了一条语音消息,服务端怎么知道它是一条语音消息
42.鸿蒙5.0之前是不是还支持安卓安装包
43.骨架屏是怎么做的,其他开发者如何使用
44.网络请求怎么封装的
45.flutter 小程序 uniapp都有实际开发经验吗
Flutter相关
在 Flutter 中,“三棵树” 指的是 Widget 树、Element 树和 RenderObject 树,它们是 Flutter 渲染机制的核心概念,相互协作完成界面的构建、布局和绘制。以下为你详细介绍:
Widget 树
定义
Widget 是 Flutter 中用于描述 UI 元素配置的不可变对象,Widget 树就是由一系列 Widget 组成的树状结构。它是开发者编写代码时直接操作的对象,用于描述界面的外观和结构。
特点
- 不可变性:一旦创建,Widget 的属性就不能再改变。如果需要更新界面,需要创建一个新的 Widget 来替换旧的。
- 轻量级:Widget 本身不包含实际的渲染信息,只是一个配置信息的载体,因此创建和销毁的成本较低。
Element 树
定义
Element 是 Widget 的实例化对象,它是 Widget 和 RenderObject 之间的桥梁。Element 树是根据 Widget 树创建的,每个 Widget 都会对应一个或多个 Element。
特点
- 可变性:Element 可以在其生命周期内改变状态,它会根据 Widget 的配置信息来创建和管理 RenderObject。
- 缓存机制:Element 具有缓存机制,当 Widget 的配置信息没有发生变化时,Element 可以复用,避免不必要的创建和销毁操作。
RenderObject 树
定义
RenderObject 是负责实际渲染的对象,它包含了元素的布局、绘制等信息。RenderObject 树是根据 Element 树创建的,每个 Element 都会对应一个 RenderObject。
特点
- 重量级:RenderObject 包含了大量的渲染信息,创建和销毁的成本较高。
- 布局和绘制:RenderObject 负责计算元素的位置和大小(布局),并将元素绘制到屏幕上。
渲染流程
- 布局阶段:从根 RenderObject 开始,逐层向下传递约束信息,每个 RenderObject 根据约束信息计算自己的大小和位置。
- 绘制阶段:在布局完成后,从根 RenderObject 开始,逐层向下绘制元素。
三棵树之间的关系
-
Widget 树描述了界面的配置信息,是最上层的抽象。
-
Element 树根据 Widget 树创建,负责管理 Widget 和 RenderObject 之间的映射关系,是中间层的桥梁。
-
RenderObject 树根据 Element 树创建,负责实际的布局和绘制,是最底层的实现。
当 Widget 树发生变化时,Flutter 框架会通过对比新旧 Widget 树,更新 Element 树,然后根据 Element 树的变化更新 RenderObject 树,最后重新进行布局和绘制,从而实现界面的更新。
vue2和vue3的区别
vue是用于构建用户界面的渐进式框架,vue3是vue2的升级版
性能方面:
vue2采用Object.defineProperty () 来实现数据响应式。这种方式会遍历对象的所有属性,将其转换为getter和setter方法,对于嵌套较深的对象,需要进行递归处理
vue3采用proxy对象来实现响应式,可以代理整个对象,而不仅仅是对象的属性,并且可以拦截更多操作,如属性的读取,复制,函数调用等
渲染性能:
vue2在组件更新时,,会比较虚拟dom的每个节点,即使是静态节点,也会带来一定的性能开销
vue3引入静态提升和事件监听缓存等优化操作
Api
vue2采用选项式api,通过包含data,methods,computed等选项的对象来定义组件
vue3采用组合式api,通过setup语法糖来组合逻辑,使得代码更易复用和维护
深浅拷贝
- 浅拷贝:是指创建一个新的对象,这个新对象有着原始对象属性值的一份精确拷贝。如果属性是基本数据类型,拷贝的就是基本数据类型的值本身;如果属性是引用数据类型,拷贝的就是内存地址,所以新对象和原始对象的引用数据类型属性会指向同一块内存地址。
实现方式:
展开运算符
slice
****
concat
- 深拷贝:是指在拷贝一个对象时,对于对象中的引用数据类型的属性,会递归地拷贝其所有层级的属性,创建一个全新的、与原始对象完全独立的对象,新对象和原始对象的引用数据类型属性不会共享同一块内存地址。
实现方式:
JSON序列化(会造成undefine,函数数据丢失)
函数递归
跨域
跨域是指浏览器从一个域名的网页去请求另一个域名下的资源,也称不同源
同源策略:是浏览器一种请求网络资源的一种安全机制,主要防止其他请求来访问我们服务器数据
判断标准
协议:http和https就非同一个协议
域名:qq.com baidu.com域名不同
端口:http正常8080 https 443
其中一项不同都属于非同源
解决办法
前端配置代理服务器
后端使用cors—跨域资源共享
闭包
是一种代码优化技术,隐藏全局变量,避免变量污染
表现形式:外部函数返回内部函数,外部函数存放全局变量,内部函数实现处理全局变量(业务代码)
缺点:容易导致内存泄漏,内部函数一直保留全局变量的引用,浏览器不会释放变量空间
解决方法:主动设置函数为null