关于、日志与辅助页面
本页介绍 JL_OTA 示例应用中的辅助页面体系:关于页面(AboutPage)、日志文件列表页面(LogFileListPage)、网页浏览页面(WebPage),以及支撑它们的日志基础设施(Log / LogWriter / FaultLogWriter)、偏好存储(PreferencesHelper)与页面路由协作机制。
Purpose and Scope
本页覆盖 demo 应用中与"信息展示、日志查看、辅助入口"相关的完整页面链路:
- AboutPage(关于页):应用版本展示、用户协议/隐私政策入口、ICP 备案信息、隐藏的"日志保存"开关。
- LogFileListPage(日志文件列表页):日志目录浏览、单文件删除、批量清空、系统分享、故障日志导出。
- WebPage(网页浏览页):承载用户协议、隐私政策、ICP 查询等内嵌 Web 内容。
- 日志基础设施:
tool/log目录下的Log、LogWriter、FaultLogWriter、LogMessage等组件。 - 协作机制:
PreferencesHelper持久化日志开关、eventHub事件广播、router页面导航。
以下内容不在本页范围,由其他目录页覆盖:蓝牙扫描/连接/OTA 升级流程(见蓝牙与 OTA 相关页面)、主页面 MainPage 的设备列表与升级操作(见主流程页面)、启动页 LaunchPage 的初始化逻辑(见应用启动页面)。
Overview
JL_OTA 示例应用是一个基于 HarmonyOS ArkTS 的 OTA 升级 Demo。除核心的蓝牙扫描、连接、升级能力外,应用还包含一组面向用户与开发者的辅助页面,用于:
- 展示应用元信息:通过
bundleManager.getBundleInfoForSelf获取应用自身的版本号(versionName / versionCode),并结合资源文件显示"关于应用"信息。 - 提供合规入口:用户协议(USER_AGREEMENT)与隐私政策(PRIVACY_POLICY)通过
WebPage内嵌展示,ICP 备案信息可点击跳转工信部查询页。 - 支持调试与问题反馈:默认隐藏的日志保存开关——用户连续点击应用 Logo 5 次后开启日志落盘,之后可进入日志文件列表页查看、删除、分享或清空日志。
- 故障日志导出:通过
Log.getFaultLog获取故障日志并触发系统分享面板。
整个辅助页面体系的设计意图是:把"诊断能力"藏在用户无感知的交互之后(连续点击 Logo),避免普通用户误操作;同时为开发/测试人员提供一条完整、可分享的日志回溯链路。
Architecture
flowchart TD
subgraph sg_Pages["页面层 pages/"]
AboutPage["AboutPage"]
LogFileListPage["LogFileListPage"]
WebPage["WebPage"]
MainPage["MainPage"]
LaunchPage["LaunchPage"]
end
subgraph sg_CommonView["通用视图 view/"]
TopBarView["TopBarView"]
end
subgraph sg_ViewModel["视图模型 viewmodel/"]
SettingsVM["SettingsVM"]
ItemViewVM["ItemView"]
end
subgraph sg_Common["公共层 common/"]
PreferencesHelper["PreferencesHelper"]
Constant["Constant (AgreementUrl/CompanyInfo)"]
end
subgraph sg_Tool["工具层 tool/log/"]
Log["Log"]
LogWriter["LogWriter"]
FaultLogWriter["FaultLogWriter"]
LogMessage["LogMessage"]
end
subgraph sg_System["系统能力"]
Router["@ohos.router"]
Bundle["bundleManager"]
Share["systemShare (ShareKit)"]
FileIO["fileIo (CoreFileKit)"]
EventHub["eventHub"]
PrefStore["Preferences (持久化)"]
end
AboutPage -->|"router.pushUrl"| WebPage
AboutPage -->|"router.pushUrl"| LogFileListPage
AboutPage --> TopBarView
AboutPage --> ItemViewVM
AboutPage --> PreferencesHelper
AboutPage --> Constant
AboutPage -->|"Log.startSaving/stopSaving"| Log
AboutPage -->|"getBundleInfoForSelf"| Bundle
AboutPage -->|"eventHub.on/off"| EventHub
LogFileListPage --> TopBarView
LogFileListPage -->|"LOG_DIR_NAME"| Log
LogFileListPage -->|"log目录操作"| FileIO
LogFileListPage -->|"ShareController"| Share
LogFileListPage --> PreferencesHelper
LogFileListPage -->|"eventHub.emit"| EventHub
Log --> LogWriter
Log --> FaultLogWriter
LogWriter --> LogMessage
FaultLogWriter --> LogMessage
PreferencesHelper -->|"getValue/putValue"| PrefStore
架构说明
- 页面层:
AboutPage是辅助页面的枢纽,负责展示信息并导航到WebPage与LogFileListPage;MainPage与LaunchPage是主流程页面,与辅助页面通过router互相跳转。 - 通用视图/视图模型:
TopBarView提供统一标题栏(返回键、右侧操作键),ItemView提供"名称 + 值 + 图标"的列表行模型,SettingsVM定义EVENT_ON_LOG_CLOSED事件常量用于页面间通信。 - 公共层:
PreferencesHelper封装 HarmonyOS Preferences 的读写,PreferenceKey.LOG_SAVING_SWITCH持久化日志保存开关;Constant集中管理用户协议/隐私政策 URL 与公司备案信息。 - 工具层:
Log是全局日志门面,LogWriter负责普通日志落盘,FaultLogWriter负责故障日志落盘,二者均继承BaseWriter,LogMessage<T>是写入队列的消息模型。 - 系统能力:页面通过
router导航、eventHub广播事件、systemShare分享日志、fileIo操作日志文件、bundleManager读取版本号。
该分层遵循 ArkTS 声明式开发的标准组织方式:UI 与逻辑分离、公共能力下沉到 common/tool、页面间通过 router + eventHub 解耦。日志目录固定为应用 cacheDir 下的 /appLog(LOG_DIR_NAME),因此日志生命周期随应用缓存管理,清除缓存即可释放日志空间。
关于页面(AboutPage)实现详解
AboutPage 是 @Entry @Component 声明的页面组件,通过 router 被 MainPage 或其他入口推入。其核心职责与实现要点如下。
页面状态与初始化
页面持有三个 @State 变量:version(版本号字符串)、openLogSaving(日志保存开关是否开启)、icpInfo(ICP 备案信息文本),以及两个私有成员 lastClickTimeMs 与 clicks(用于连续点击检测)。
aboutToAppear() 生命周期中依次完成三件事:
- 从资源中读取 ICP 备案信息字符串;
- 通过
PreferencesHelper.getInstance().getValue(PreferenceKey.LOG_SAVING_SWITCH)读取持久化的日志开关状态,若为 boolean 则同步到openLogSaving; - 调用
bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)获取应用自身 bundle 信息,拼装版本号字符串app_version: V${versionName}_${versionCode}。
同时注册 eventHub.on(SettingsVM.EVENT_ON_LOG_CLOSED, this.onCloseLog),用于监听"日志保存被关闭"事件;aboutToDisappear() 中调用 eventHub.off 反注册,避免泄漏。
列表项与页面跳转
页面使用 List + ForEach 渲染 items 数组(用户协议、隐私政策两个 ItemView<Resource, string> 项)。点击列表项时调用 browseWeb(title, url):
private browseWeb(title?: string, url?: string) {
router.pushUrl({
url: 'pages/WebPage',
params: { "url": url, 'title': title }
}, router.RouterMode.Standard, (err) => {
if (err) {
Log.e(TAG, `Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
return;
}
})
}
Source: AboutPage.ets
该方法的三个参数通过 router 的 params 透传给 WebPage,WebPage 据此渲染标题与 URL。RouterMode.Standard 允许同一页面多次入栈(例如先看协议再返回),而进入日志列表页则使用 RouterMode.Single(见下文),避免重复入栈。
隐藏的日志保存开关(连续点击 Logo)
这是本页最具设计意图的交互:连续 5 次(每次间隔 ≤ 1000ms)点击应用 Logo,才开启日志保存功能。实现如下:
private clickToOpenSavingLog() {
const currTime = systemDateTime.getTime()
if (currTime - this.lastClickTimeMs <= 1000) {
this.clicks += 1
} else {
this.clicks = 1
}
this.lastClickTimeMs = currTime
if (this.clicks >= 5) {
PreferencesHelper.getInstance().putValue(PreferenceKey.LOG_SAVING_SWITCH, true).then(() => {
this.openLogSaving = true
Log.startSaving(getContext(this))
})
}
}
Source: AboutPage.ets
设计要点:
- 防误触:日志落盘涉及隐私与存储开销,不应对普通用户默认开放;连点门槛(5 次、1 秒内)足以拦截误触,又足够简单,便于开发/测试人员开启。
- 状态持久化:开关写入
Preferences(key 为LOG_SAVING_SWITCH),App 重启后仍然生效,aboutToAppear会重新读取。 - 立即生效:
putValue成功后调用Log.startSaving(getContext(this))启动落盘,并同步更新 UI 状态。 - 联动显示:
openLogSaving为 true 时,"日志文件"入口行(visibility: Visibility.Visible)才显示,点击后router.pushUrl({ url: "pages/LogFileListPage" }, router.RouterMode.Single)进入日志列表页。
备案信息与版权行
页面底部通过 fancy('auto') 扩展样式显示 icpInfo + ": " + CompanyInfo.ICP_RECORD,点击后 browseWeb 跳转到 CompanyInfo.ICP_URL(工信部备案查询);下方固定显示 CompanyInfo.COPYRIGHT 版权文本。fancy 是 @Extend(Text) 声明的样式扩展函数,统一设置字号、颜色与对齐方式。
日志文件列表页面(LogFileListPage)实现详解
LogFileListPage 是日志管理页面,仅在日志保存功能开启后可达。它把日志目录(cacheDir + LOG_DIR_NAME)中的 .txt 文件列出,并提供 5 类操作:刷新、单文件删除(滑动)、清空目录、系统分享、获取故障日志。
日志目录与列表加载
页面初始化时计算日志目录 LOG_DIR = getContext(this).cacheDir + LOG_DIR_NAME,其中 LOG_DIR_NAME = '/appLog' 来自 Log.ts。aboutToAppear 调用 refreshFileLit() 异步加载文件列表:
private refreshFileLit() {
tryToGetFileList(this.LOG_DIR).then((list) => {
this.fileList = list
Log.w(TAG, 'list=' + list.length)
}).catch((e: BusinessError) => {
Log.e(TAG, 'e=' + JSON.stringify(e))
})
}
Source: LogFileListPage.ets
底层 tryToGetFileList 使用 fileIo.listFile(dirPath, { filter: { suffix: ['.txt'] } }) 过滤出日志文本文件,并逐文件用 fileIo.stat 组装 SharedInfo(实现 systemShare.SharedRecord,含 filename、path、utd 类型描述符)。
顶部操作栏
顶栏右侧依次提供 4 个操作:
| 操作 | 图标 | 行为 |
|---|---|---|
| 日志开关(Toggle) | 系统 Switch | 关闭即写回 LOG_SAVING_SWITCH=false,调用 Log.stopSaving(),eventHub.emit(SettingsVM.EVENT_ON_LOG_CLOSED) 通知其他页面,并 router.back() 返回 |
| 刷新 | ic_refresh | 重新执行 refreshFileLit() |
| 分享 | ohos_ic_public_share | 将勾选的日志文件交给系统分享面板 |
| 故障日志 | ohos_ic_public_fail | 调用 Log.getFaultLog(getContext(this)) |
分享逻辑(systemShare)
tryToShare() 将勾选列表封装为 systemShare.SharedData,多条时循环 addRecord 追加,然后创建 ShareController 并监听 dismiss 事件复位勾选状态,最后 controller.show(context, { previewMode: SharePreviewMode.DETAIL, selectionMode: SelectionMode.BATCH }) 弹出分享面板:
tryToShare() {
const list = this.selectedList
if (list.length <= 0) {
Log.e(TAG, 'no select')
return
}
let data: systemShare.SharedData = new systemShare.SharedData(list[0]);
Log.d(TAG, 'share list size=' + list.length)
if (list.length > 1) {
for (let index = 1; index < list.length; index++) {
data.addRecord(list[index])
}
}
let controller: systemShare.ShareController = new systemShare.ShareController(data);
let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
controller.on('dismiss', () => {
Log.e(TAG, 'Share panel closed')
this.isSelected = true
this.isSelected = false
});
controller.show(context, {
previewMode: systemShare.SharePreviewMode.DETAIL,
selectionMode: systemShare.SelectionMode.BATCH
})
}
Source: LogFileListPage.ets
分享文件类型通过 SharedInfo.utd 声明为 uniformTypeDescriptor.UniformDataType.PLAIN_TEXT,使日志以纯文本形式被系统识别。这一设计让"问题反馈"闭环:测试人员无需连接电脑,即可把设备日志直接分享给开发。
删除与清空
- 单文件删除:列表项注册
swipeAction,左滑出现红色"删除"按钮,点击后fileIo.unlink(path)删除文件,并从fileList中同步移除,避免 UI 与磁盘不一致。 - 清空目录:
tryToClear()先清空fileList数组,再fileIo.rmdir(this.LOG_DIR)删除整个日志目录,成功后执行Log.stopSaving()+Log.startSaving(context)重建目录并继续落盘。删除失败(目录非空等)通过BusinessError捕获并记录日志。
日志基础设施(tool/log)
辅助页面的日志能力由 entry/src/main/ets/tool/log/ 下的组件支撑,它们组成了一个典型的"门面 + 写入器"结构。
Log 门面类
Log.ts 定义默认导出类 Log,并导出常量 LOG_DIR_NAME = '/appLog':
export const LOG_DIR_NAME = '/appLog'
...
export default class Log {
static domain: number = 0xFFBB
Source: Log.ts
Log 提供静态方法,页面层直接调用:
Log.d/i/w/e(TAG, message):按 debug/info/warn/error 级别输出日志(内部对接hilog,domain为 0xFFBB)。Log.startSaving(context):启动日志落盘,创建LogWriter等写入器。Log.stopSaving():停止日志落盘。Log.getFaultLog(context):获取/导出故障日志。
日志目录约定为 context.cacheDir + '/appLog',即 LOG_DIR_NAME。把日志放在 cacheDir 下的好处是:日志被视为可清除的缓存数据,不占用用户可见的持久存储配额,也无需额外申请存储权限。
写入器与消息模型
LogWriter继承BaseWriter,负责把普通日志写入logDir下的文本文件(文件名形如日期/序号)。FaultLogWriter同样继承BaseWriter,负责故障日志的写入与导出,供Log.getFaultLog使用。LogMessage<T>是队列消息模型,含type: MessageType等字段,用于写入器间传递日志内容。
classDiagram
class BaseWriter {
#logDir: string
+write() void
+close() void
}
class LogWriter {
-logDir: string
}
class FaultLogWriter {
-logDir: string
}
class LogMessage {
+type: MessageType
}
class Log {
+domain: number
+startSaving(context) void
+stopSaving() void
+getFaultLog(context) void
+d/i/w/e(TAG, msg) void
}
BaseWriter <|-- LogWriter
BaseWriter <|-- FaultLogWriter
LogWriter --> LogMessage
FaultLogWriter --> LogMessage
Log --> LogWriter
Log --> FaultLogWriter
将普通日志与故障日志分离为两个写入器,使两者可以拥有不同的文件命名、保留策略与导出入口:普通日志面向日常调试(批量分享/删除),故障日志面向异常定位(一键导出)。BaseWriter 抽公共逻辑(目录管理、文件句柄、关闭等),各写入器只关注自己的格式与策略。
Core Flow
流程一:开启日志保存 → 浏览/分享日志
sequenceDiagram
participant U as 用户
participant AP as AboutPage
participant PH as PreferencesHelper
participant Log as Log (tool)
participant LFP as LogFileListPage
participant FS as fileIo / systemShare
U->>AP: 连续点击 Logo 5 次(间隔≤1s)
AP->>PH: putValue(LOG_SAVING_SWITCH, true)
PH-->>AP: 持久化完成
AP->>Log: startSaving(context)
Log->>Log: 初始化 LogWriter 并落盘
AP->>AP: openLogSaving = true,显示"日志文件"入口
U->>AP: 点击"日志文件"
AP->>LFP: router.pushUrl(pages/LogFileListPage, Single)
LFP->>LFP: LOG_DIR = cacheDir + /appLog
LFP->>FS: fileIo.listFile(dir, {suffix:['.txt']})
FS-->>LFP: 文件列表 → SharedInfo[]
U->>LFP: 勾选文件 → 点击分享
LFP->>FS: ShareController.show(SharedData, BATCH)
FS-->>U: 系统分享面板
流程二:关闭日志保存(事件联动)
flowchart TD
Start(["LogFileListPage 中关闭 Toggle"]) --> A["PreferencesHelper.putValue(LOG_SAVING_SWITCH, false)"]
A --> B["Log.stopSaving()"]
B --> C["eventHub.emit(EVENT_ON_LOG_CLOSED)"]
C --> D["AboutPage.onCloseLog: openLogSaving = false"]
D --> E["router.back() 返回上一页"]
E --> F["日志入口行隐藏"]
该流程展示了 eventHub 作为"事件总线"在页面间的解耦作用:LogFileListPage 无需持有 AboutPage 的引用,只需广播 SettingsVM.EVENT_ON_LOG_CLOSED,所有注册了该事件的页面(如 AboutPage)都会同步关闭日志入口,保证 UI 状态与持久化状态一致。
网页浏览页面(WebPage)
WebPage 是辅助页面的通用 Web 容器,由 AboutPage.browseWeb 通过 router.pushUrl 携带 params: { url, title } 进入。它接收参数后展示 TopBarView 标题栏(返回键 + 页面标题),并用 HarmonyOS Web 组件加载对应 URL。
用途场景:
- 用户协议:
AgreementUrl.USER_AGREEMENT - 隐私政策:
AgreementUrl.PRIVACY_POLICY - ICP 备案查询:
CompanyInfo.ICP_URL
URL 常量集中定义在 common/Constant.ets 中,页面代码不直接写死地址,便于按渠道/版本替换合规文档链接。
Usage Examples
示例 1:读取持久化开关并联动 UI(AboutPage)
aboutToAppear() {
this.icpInfo = getContext(this).resourceManager.getStringSync($r('app.string.icp_filing_info'))
PreferencesHelper.getInstance().getValue(PreferenceKey.LOG_SAVING_SWITCH).then((value) => {
Log.w(TAG, `Open log saving=${value}`)
if (typeof value === 'boolean') {
this.openLogSaving = value
}
})
bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION).then((bundleInfo) => {
this.version = getContext(this).resourceManager.getStringSync($r('app.string.app_version'))
+ `: V${bundleInfo.versionName}_${bundleInfo.versionCode}`
})
getContext(this).eventHub.on(SettingsVM.EVENT_ON_LOG_CLOSED, this.onCloseLog)
}
Source: AboutPage.ets
示例 2:带筛选的文件列表加载(LogFileListPage)
async function tryToGetFileList(dirPath: string): Promise<SharedInfo[]> {
const list = await fileIo.listFile(dirPath, {
filter: {
suffix: ['.txt']
}
});
return await new Promise<SharedInfo[]>((resolve: Function) => {
const fileList: Array<SharedInfo> = []
for (let filename of list) {
// 逐个 stat 组装 SharedInfo(filename/path/utd/uri)
}
resolve(fileList)
});
}
Source: LogFileListPage.ets
示例 3:滑动删除单个日志文件
@Builder
SwipeItemAction(index: number) {
Button($r('app.string.delete'), { type: ButtonType.Normal })
.backgroundColor(Color.Red)
.onClick(() => {
let path: string = this.fileList[index].path
Log.i(TAG, 'path=' + path)
this.fileList.splice(index, 1);
fileIo.unlink(path).then(() => {
Log.i(TAG, "remove file succeed");
}).catch((err: BusinessError) => {
Log.e(TAG, "remove file failed with error message: " + err.message + ", error code: " + err.code);
});
})
}
Source: LogFileListPage.ets
Configuration Options
辅助页面体系的可配置项集中在 common/Constant.ets 与 common/PreferencesHelper.ets:
| 配置项 | 类型 | 默认值/位置 | 说明 |
|---|---|---|---|
LOG_DIR_NAME | string | '/appLog' | 日志目录名,拼接在 cacheDir 之后(tool/log/Log.ts) |
PreferenceKey.LOG_SAVING_SWITCH | PreferenceKey | 持久化键 | 日志保存开关,boolean 值 |
SettingsVM.EVENT_ON_LOG_CLOSED | string | 事件名常量 | 日志关闭广播事件 |
AgreementUrl.USER_AGREEMENT | string | 常量 | 用户协议 URL |
AgreementUrl.PRIVACY_POLICY | string | 常量 | 隐私政策 URL |
CompanyInfo.ICP_RECORD | string | 常量 | ICP 备案号文本 |
CompanyInfo.ICP_URL | string | 常量 | 工信部备案查询 URL |
CompanyInfo.COPYRIGHT | string | 常量 | 版权声明文本 |
| 连点阈值 | number | 5 次 / 1000ms | AboutPage 隐藏开关的触发条件(代码内常量) |
| 分享模式 | enum | DETAIL / BATCH | SharePreviewMode / SelectionMode |
API Reference
Log(tool/log/Log.ts,静态门面)
| 方法 | 签名 | 说明 |
|---|---|---|
d/i/w/e | static d/i/w/e(tag: string, message: string): void | 按级别输出日志到 hilog |
startSaving | static startSaving(context: Context): void | 启动日志落盘(创建 LogWriter) |
stopSaving | static stopSaving(): void | 停止日志落盘 |
getFaultLog | static getFaultLog(context: Context): void | 获取/导出故障日志 |
常量 LOG_DIR_NAME | string = '/appLog' | 日志目录名 |
PreferencesHelper(common/PreferencesHelper.ets)
| 方法 | 签名 | 说明 |
|---|---|---|
getInstance | static getInstance(): PreferencesHelper | 单例获取 |
getValue | getValue(key: PreferenceKey): Promise<Object> | 异步读取键值 |
putValue | putValue(key: PreferenceKey, value: Object): Promise<void> | 异步写入键值 |
AboutPage(pages/AboutPage.ets)
| 方法 | 签名 | 说明 |
|---|---|---|
browseWeb | private browseWeb(title?: string, url?: string): void | 携带参数跳转 WebPage |
clickToOpenSavingLog | private clickToOpenSavingLog(): void | 连点检测,≥5 次开启日志保存 |
LogFileListPage(pages/LogFileListPage.ets)
| 方法 | 签名 | 说明 |
|---|---|---|
refreshFileLit | private refreshFileLit(): void | 重新加载日志文件列表(注意:方法名中的拼写 Lit 为源码原样) |
tryToShare | tryToShare(): void | 分享勾选日志 |
tryToClear | tryToClear(): void | 清空日志目录并重建 |
SwipeItemAction | @Builder SwipeItemAction(index: number) | 列表项左滑删除按钮 |
路由与事件
router.pushUrl({ url, params }, mode, callback):页面入栈;RouterMode.Single防重复入栈,RouterMode.Standard允许多实例。router.back():返回上一页。eventHub.on/off/emit(eventName, ...args):页面级事件总线,用于EVENT_ON_LOG_CLOSED联动。
Failure Modes, Edge Cases & Concurrency
以下故障与边界行为均可在源码中直接观察到:
- 日志目录不存在:
LogFileListPage首次进入或清空后,fileIo.listFile可能抛错。refreshFileLit通过.catch((e: BusinessError))捕获并仅记录日志,UI 停留在空列表而非崩溃;tryToClear删除目录后通过stopSaving+startSaving重建目录。 - 单文件删除失败:
fileIo.unlink失败时(文件被占用、权限异常),UI 已先splice移除列表项,磁盘文件仍在;下次刷新会重新出现。源码未做回滚,属于已知取舍——删除失败仅记 error 日志。 - 未选择文件直接点分享:
tryToShare检查list.length <= 0并提前 return,避免构造空SharedData。 - 分享面板关闭的并发勾选状态:
dismiss回调中连续两次赋值isSelected = true / false,强制触发 UI 刷新复位全选框状态,规避框架状态未刷新问题。 - 连点窗口的并发:
clickToOpenSavingLog依赖lastClickTimeMs与clicks,若两次点击间隔 > 1000ms 则重置计数,防止"长时间内凑满 5 次"误开;状态写入Preferences为异步,连续触发存在幂等风险,但openLogSaving为 true 后 Logo 的 onClick 不再调用该方法,天然防重复。 - 事件监听泄漏:
aboutToAppear注册eventHub.on,aboutToDisappear必须off,否则页面销毁后仍会收到事件回调。 - 日志文件无限增长:源码未显示日志滚动/大小上限策略,长跑设备可能产生大量
.txt文件;依赖用户手动清空或系统清理缓存。
Performance & Operational Considerations
- 日志落盘是高频路径:
Log门面将日志经LogWriter/FaultLogWriter异步写入(LogMessage队列模型),避免阻塞 UI 线程;但每行日志均落盘会放大 IO,调试完成后建议关闭LOG_SAVING_SWITCH。 - 文件列表加载:
fileIo.listFile+ 逐文件stat组装列表,当日志文件数量很大时耗时线性增长,页面提供手动刷新入口(ic_refresh)而非自动轮询。 - 目录重建:
tryToClear中rmdir→startSaving重建,属于写密集型操作,期间日志可能短暂丢失(源码未做缓冲),可接受。 - cacheDir 语义:日志放在
cacheDir,系统可在存储紧张时清理;正式发布版不应把用户数据放 cacheDir,但作为调试日志这是合理权衡。
Extension Points
- 新增辅助入口:在
AboutPage.items数组追加ItemView<Resource, string>即可新增一个跳转 WebPage 的列表项;如需跳转自定义页面,仿照"日志文件"行的router.pushUrl即可。 - 自定义写入器:继承
BaseWriter新增写入器(如按天滚动、加密日志),再接入Log门面即可扩展日志策略。 - 事件联动:复用
SettingsVM.EVENT_ON_LOG_CLOSED事件名,在任意页面注册eventHub.on,即可在日志保存关闭时同步业务状态。 - 分享目标扩展:
SharedInfo.utd目前为PLAIN_TEXT,可改为其他UniformDataType以适配不同接收方。
Tests
仓库在 entry/src/ohosTest/ets/testability/pages/Index.ets 中存在 ohosTest 测试工程页面,表明应用具备测试ability基础;但本页聚焦的辅助页面(AboutPage/LogFileListPage/WebPage)未发现独立的单元测试文件,交互行为(连点开关、分享、删除)主要依赖手工验证。
Related Links
- AboutPage.ets
- LogFileListPage.ets
- WebPage.ets
- Log.ts
- LogWriter.ets
- FaultLogWriter.ets
- LogMessage.ts
- 主流程页面:MainPage(设备列表/升级)、LaunchPage(启动初始化)
- 蓝牙与 OTA 能力:BluetoothManager / BluetoothOTAManager(见对应目录页)