写点什么

【HarmonyOS Next】鸿蒙应用故障处理思路详解

作者:GeorgeGcs
  • 2025-03-24
    上海
  • 本文字数:3382 字

    阅读完需:约 11 分钟

【HarmonyOS Next】鸿蒙应用故障处理思路详解

【HarmonyOS Next】鸿蒙应用崩溃处理思路详解

一、崩溃问题发现后定位

1. 崩溃现象:常见的崩溃问题表现为,应用操作后白屏闪退,或者应用显示无响应卡死。


2.定位问题:发现崩溃后,我们首先需要了解复现步骤,精确定位复现步骤。因为提供复现步骤得人,可能是用户和测试,非开发人员,其中的步骤并非最短路径。


3.排查问题点根据复现步骤,我们需要查看日志表现,鸿蒙的 DevEco IDE 提供了日志看板,根据 HiLog 和 FaultLog,我们可以初步区分崩溃问题的类型。



根据日志看板的 FaultLog,可以查看崩溃输出的


  1. JS Crash,一般是 ArkTS 原生逻辑的崩溃

  2. CppCrash,一般是 NDK,C 层的崩溃


JS Crash 点击进入 JS Crash 中,可以查看到崩溃信息,以下几种类型的错误:



可以根据 JS Crash 提示的错误行数,直接点击跳转到错误代码处,根据提示检查和修复问题。


Device info:xxxBuild info:xxx-xxx x.x.x.xxx(xxxx)Fingerprint:a370fceb59011d96e41e97bda139b1851c911012ab8c386d1a2d63986d6d226dModule name:com.xxx.xxXVersion:1.0.0Versioncode:1000000PreInstalled:NoForeground:YesPid:39185Uid:20020145Reason:TypeErrorError name:TypeErrorError message:Cannot read property needRenderTranslate of undefinedStacktrace:Cannot get SourceMap info, dump raw stack:at anonymous (entry/src/main/ets/pages/Index.ts:49:49)
复制代码



this.translationUpY = (this,multiCardsNum >= 1)? sceneContainerSessionListlthis.multiCardsNum -、1].needRenderTranslate.translateY :0this.translationDownY = (this.multiCardsNum >= 2)? sceneContainerSessionList[this.multiCardsNum -2].needRenderTranslate.translateY :0;
复制代码


CppCrash 根据日志提示,检查是否有以下错误情况:





AppFreeze 一般是由于耗时操作,导致堵塞主线程。此时用户操作时,会触发无响应。一般分为以下三种情况,超时时间一般在 6s 左右。



内存泄漏这种问题排查起来是最麻烦的,所以保持良好的代码编程规范,不需要的对象该释放释放,不用的句柄也需要释放。


一般碰到内存泄漏,根据提供的复现步骤,很多情况下是非必现。(如果是必现,那最好了,修复该问题会很快。)我们需要操作复现步骤,使用鸿蒙 DevEco IDE 的 ProFiler 工具:



检测应用操作时的内存使用情况。


二、问题解决,检查类似错误情况一起修复

1.根据章节一问题定位清楚后,根据错误具体情况,思考修复方案


2.根据问题情况,检查其他代码是否有同类问题,一起修复后验证。


3.根据官网提供的材料进行学习,编码过程中进行规范和排查:



性能优化举例:业务场景是:点击跳转下一页,直接加载 Web 页面。这是大多数三方应用的实现方式,其实应该在后台创建一个 ArkWeb 组件来预先启动用于渲染的 Web 渲染进程。这样跳转到 web 页面的时间就不会那么长:


// 创建NodeController// common.etsimport { UIContext } from '@kit.ArkUI';import { webview } from '@kit.ArkWeb';import { NodeController, BuilderNode, Size, FrameNode } from '@kit.ArkUI';
// @Builder中为动态组件的具体组件内容// Data为入参封装类class Data { url: string = 'https://www.example.com'; controller: WebviewController = new webview.WebviewController();}
@Builderfunction webBuilder(data: Data) { Column() { Web({ src: data.url, controller: data.controller }) .domStorageAccess(true) .zoomAccess(true) .fileAccess(true) .mixedMode(MixedMode.All) .width('100%') .height('100%') .onPageEnd((event) => { // 输出Web页面加载完成时间 console.info(`load page end time: ${Date.now()}`); }) }}
let wrap = wrapBuilder<Data[]>(webBuilder);
// 用于控制和反馈对应的NodeContainer上的节点的行为,需要与NodeContainer一起使用export class MyNodeController extends NodeController { private rootnode: BuilderNode<Data[]> | null = null; private root: FrameNode | null = null; private rootWebviewController: webview.WebviewController | null = null;
// 必须要重写的方法,用于构建节点数、返回节点挂载在对应NodeContainer中 // 在对应NodeContainer创建的时候调用、或者通过rebuild方法调用刷新 makeNode(uiContext: UIContext): FrameNode | null { console.info(' uicontext is undefined : ' + (uiContext === undefined)); if (this.rootnode != null) { const parent = this.rootnode.getFrameNode()?.getParent(); if (parent) { console.info(JSON.stringify(parent.getInspectorInfo())); parent.removeChild(this.rootnode.getFrameNode()); this.root = null; } this.root = new FrameNode(uiContext); this.root.appendChild(this.rootnode.getFrameNode()); // 返回FrameNode节点 return this.root; } // 返回null控制动态组件脱离绑定节点 return null; }
// 当布局大小发生变化时进行回调 aboutToResize(size: Size) { console.info('aboutToResize width : ' + size.width + ' height : ' + size.height); }
// 当controller对应的NodeContainer在Appear的时候进行回调 aboutToAppear() { console.info('aboutToAppear'); }
// 当controller对应的NodeContainer在Disappear的时候进行回调 aboutToDisappear() { console.info('aboutToDisappear'); }
// 此函数为自定义函数,可作为初始化函数使用 // 通过UIContext初始化BuilderNode,再通过BuilderNode中的build接口初始化@Builder中的内容 initWeb(url: string, uiContext: UIContext, control: WebviewController) { if (this.rootnode != null) { return; } // 绑定预创建的WebviewController this.rootWebviewController = control; // 创建节点,需要uiContext this.rootnode = new BuilderNode(uiContext); // 创建动态Web组件 this.rootnode.build(wrap, { url: url, controller: control }); }
// 此函数为自定义函数,可作为初始化函数使用 loadUrl(url: string) { if (this.rootWebviewController !== null) { // 复用预创建组件,重新加载url this.rootWebviewController.loadUrl(url); } }}
// 创建Map保存所需要的NodeControllerlet NodeMap: Map<string, MyNodeController | undefined> = new Map();// 创建Map保存所需要的WebViewControllerlet controllerMap: Map<string, WebviewController | undefined> = new Map();
// 初始化需要UIContext 需在Ability获取export const createNWeb = (url: string, uiContext: UIContext) => { // 创建NodeController let baseNode = new MyNodeController(); let controller = new webview.WebviewController(); // 初始化自定义web组件 baseNode.initWeb(url, uiContext, controller); controllerMap.set(url, controller); NodeMap.set(url, baseNode);};
// 自定义获取NodeController接口export const getNWeb = (url: string): MyNodeController | undefined => { // 加载新的Url时,建议复用预创建的Web组件 if (!NodeMap.get(url) && NodeMap.get('about://blank')) { // 获取预创建的Web组件 let webNode = NodeMap.get('about://blank') as MyNodeController; // 重新加载url webNode.loadUrl(url); return webNode; } return NodeMap.get(url);};
复制代码

三、思考如何避免问题再次发生,复盘

1.保持好的开发习惯创建踩坑文档,避免自己第二次再发生该问题


2.根据错误情况,分析是否为自己代码逻辑问题,逻辑 bug 不可避免,只能从开发经验和 code review 中尽量避免


3.代码容错分支问题,只保证了主流程,未考虑代码错误分支的覆盖情况。这种情况,需要自己吸取教训,避免再次发生。


4.使用检测工具对代码进行扫描,提前规避一些问题:



5.使用 IDE 提供的 AppAnglyzer 生成应用检测报告。根据报告提示,进行修改:



6.使用鸿蒙提供的 DevEco Testing 工具,进行稳定性和功耗等测试:



发布于: 2025-03-24阅读数: 2
用户头像

GeorgeGcs

关注

路漫漫其修远兮,吾将上下而求索。 2024-12-24 加入

历经腾讯,宝马,研究所,金融。 待过私企,外企,央企。 深耕大应用开发领域十年。 OpenHarmony,HarmonyOS,Flutter,H5,Android,IOS。 目前任职鸿蒙应用架构师。 HarmonyOS官方认证创作先锋

评论

发布
暂无评论
【HarmonyOS Next】鸿蒙应用故障处理思路详解_故障_GeorgeGcs_InfoQ写作社区