资讯详情

鸿蒙记事本开发:ArkTS+Stage模型实战指南

📅 2026/9/12 22:33:33 | 华诺云谱 👁 阅读
鸿蒙记事本开发:ArkTS+Stage模型实战指南
简介这是一份基于鸿蒙OSHarmonyOS开发的记事本应用完整源码项目面向计算机类专业学生、初学者及嵌入式/移动开发入门者适用于毕业设计、课程设计、项目演示或二次开发参考。资源包含127个文件主体为37个Java业务逻辑代码、46个XML布局与配置文件、26个PNG图标资源辅以Gradle构建脚本、JSON数据模板及基础工具脚本如gradlew.bat总大小11.63MB结构清晰核心功能模块位于entry/src/main/java/com/example/todolistapplication下含Item数据类等关键组件。已有2979人学习下载项目经实机测试可稳定运行配套说明涵盖IDE环境搭建DevEco Studio、关键目录定位与扩展修改路径便于快速上手与功能迭代。读者可直接部署运行深入理解HarmonyOS应用生命周期、AbilitySlice页面跳转、本地数据存储等核心开发范式并基于现有结构拓展任务管理、云同步等进阶功能。1. 鸿蒙记事本不是“移植Windows记事本”而是用ArkTS重构的轻量级本地文本编辑器很多同学拿到“基于鸿蒙系统开发记事本设计与实现”这个毕设题目时第一反应是不就是把Windows记事本抄一遍结果在DevEco Studio里新建项目、跑通第一个页面就卡住——UI组件不识别、文件权限报错、保存路径找不到。真相是鸿蒙的记事本不是图形界面复刻而是围绕分布式能力、沙箱化存储、声明式UIArkTS和轻量级文件操作重新设计的终端级应用。它解决的不是“能打字”而是“在HarmonyOS设备上安全、可靠、可扩展地管理用户私有文本数据”。适合计算机/软件工程专业大四学生要求掌握ArkTS基础语法、Ability生命周期、ohos.file.fs API调用及HAP包构建流程不适合零基础直接套模板但也不需要深入内核或驱动层。项目价值不在功能多炫而在完整走通从UI声明→状态管理→文件读写→权限申请→打包签名的鸿蒙原生开发闭环。2. 用ArkTSStage模型搭建记事本最小可运行结构鸿蒙记事本必须基于Stage模型开发而非旧版FA模型。Stage模型强制分离UI、逻辑与数据天然适配鸿蒙的Ability分层机制且DevEco Studio 4.1对Stage支持更稳定。选择ArkTS而非JavaScript是因为其类型安全能提前捕获file.writeText()参数类型错误避免运行时报undefined is not a function这类调试黑洞。2.1 创建Stage模型项目并配置module.json5权限新建项目时在DevEco Studio中选择“Empty Ability”模板务必勾选“Stage Model”语言选ArkTS。项目生成后首件事是修改module.json5——这是鸿蒙应用的“身份证”决定能否访问文件系统{ module: { reqPermissions: [ { name: ohos.permission.READ_USER_STORAGE, reason: 读取用户文档用于打开已有笔记 }, { name: ohos.permission.WRITE_USER_STORAGE, reason: 保存编辑内容到用户文档目录 } ], abilities: [ { name: MainAbility, srcEntry: ./ets/entryability/EntryAbility.ts, exported: true, skills: [ { entities: [entity.system.home], actions: [action.system.home] } ] } ] } }提示READ_USER_STORAGE和WRITE_USER_STORAGE是访问用户文档目录如/data/storage/el2/000/下的Documents/的必要权限。仅声明不够还需在代码中动态申请——鸿蒙要求敏感权限必须运行时弹窗授权否则file.openSync()会直接抛出SecurityException。2.2 声明式UI用TextInputButton构建核心编辑区鸿蒙UI不写XML全部用ArkTS声明。记事本主界面只需一个可滚动文本输入框和两个按钮保存/新建避免过度设计// pages/Index.ets Entry Component struct Index { State textContent: string ; State fileName: string untitled.txt; build() { Column({ space: 8 }) { // 标题栏 Text(鸿蒙记事本) .fontSize(20) .fontWeight(FontWeight.Bold) .width(100%) .textAlign(TextAlign.Center) .backgroundColor(Color.LightGray) // 编辑区 - 使用Scrollable包裹TextInput实现滚动 Scrollable({ scrollDirection: ScrollDirection.Vertical }) { TextInput({ placeholder: 请输入内容..., text: this.textContent, textAlign: TextAlign.Start }) .onChange((value: string) { this.textContent value; }) .height(70%) .backgroundColor(Color.White) } // 操作按钮组 Row({ space: 12 }) { Button(保存, { type: ButtonType.Normal }) .onClick(() { this.saveFile(); }) .width(45%) .height(50) Button(新建, { type: ButtonType.Normal }) .onClick(() { this.clearContent(); }) .width(45%) .height(50) } } .padding(16) .backgroundColor(Color.Gray) } // 清空内容并重置文件名 clearContent() { this.textContent ; this.fileName untitled.txt; } // 保存逻辑将在2.3节展开 saveFile() { // 留空后续实现 } }2.2.1 关键细节说明TextInput的onChange回调必须绑定this.textContentArkTS的响应式更新依赖State装饰器直接赋值textContent xxx无效Scrollable容器是必需的——鸿蒙TextInput默认不支持内部滚动超出高度会截断文字Button使用ButtonType.Normal避免默认圆角和阴影符合记事本简洁定位width用百分比而非固定像素适配不同屏幕密度backgroundColor层级要清晰Column设背景灰TextInput设白底形成视觉层次避免纯白屏刺眼。2.3 文件读写用ohos.file.fs API操作用户文档目录鸿蒙应用无法直接访问绝对路径必须通过ohos.file.fs模块操作沙箱内路径。关键路径是getExternalStorageDir()返回的用户文档目录这是唯一允许应用自由读写的公共区域。// utils/FileUtils.ets import fs from ohos.file.fs; // 获取用户文档目录路径 function getDocumentsPath(): string { const context getContext(this) as common.UIAbilityContext; return context.resourceManager.getApplicationInfo().codePath /Documents/; } // 保存文本到指定文件 export async function saveTextToFile(content: string, fileName: string): Promiseboolean { try { // 1. 获取用户文档目录 const docPath await fs.getRealPath(/data/storage/el2/000/Documents/); // 2. 构建完整文件路径 const filePath ${docPath}/${fileName}; // 3. 打开文件不存在则创建 const file await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); // 4. 写入UTF-8编码文本 await fs.writeText(file.fd, content, { encoding: UTF-8 }); // 5. 关闭文件句柄 await fs.close(file.fd); console.info(文件保存成功: ${filePath}); return true; } catch (err) { console.error(保存失败:, err); return false; } } // 读取文本文件内容 export async function readTextFromFile(fileName: string): Promisestring { try { const docPath await fs.getRealPath(/data/storage/el2/000/Documents/); const filePath ${docPath}/${fileName}; // 检查文件是否存在 const stat await fs.stat(filePath); if (!stat.isDirectory stat.size 0) { const file await fs.open(filePath, fs.OpenMode.READ_ONLY); const content await fs.readText(file.fd); await fs.close(file.fd); return content; } return ; } catch (err) { console.error(读取失败:, err); return ; } }2.3.1 权限申请与错误处理要点fs.getRealPath()必须传入/data/storage/el2/000/Documents/这是鸿蒙为每个应用分配的独立用户文档路径硬编码不可改fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE组合确保文件不存在时自动创建存在时覆盖写入encoding: UTF-8显式指定编码避免中文保存为乱码鸿蒙默认编码非UTF-8fs.stat()检查文件存在性再读取防止fs.open()因文件不存在而抛异常所有fsAPI均为异步必须用await否则file.fd为undefined导致fs.writeText崩溃。3. 实现文件列表浏览与多文档切换功能纯文本编辑器若只能单文件操作实用性大打折扣。鸿蒙记事本需支持查看Documents/目录下所有.txt文件并点击切换编辑——这要求理解鸿蒙的文件系统遍历和页面状态同步机制。3.1 动态加载Documents目录下的.txt文件列表在Index.ets中增加文件列表状态并在aboutToAppear生命周期中加载// pages/Index.ets续 Entry Component struct Index { State textContent: string ; State fileName: string untitled.txt; State fileList: string[] []; // 存储.txt文件名数组 State currentFileIndex: number -1; // 当前选中文件索引 aboutToAppear() { this.loadFileList(); } // 加载Documents目录下所有.txt文件 async loadFileList() { try { const docPath await fs.getRealPath(/data/storage/el2/000/Documents/); const dir await fs.opendir(docPath); let files: string[] []; // 遍历目录条目 for await (const entry of dir) { if (entry.name.endsWith(.txt)) { files.push(entry.name); } } await fs.closedir(dir); this.fileList files; } catch (err) { console.error(加载文件列表失败:, err); this.fileList []; } } // 切换到指定文件 async switchToFile(index: number) { if (index 0 || index this.fileList.length) return; const fileName this.fileList[index]; this.fileName fileName; this.currentFileIndex index; // 异步读取内容并更新UI const content await readTextFromFile(fileName); this.textContent content; } }3.1.1 关键参数与性能优化点fs.opendir()返回异步迭代器for await语法是鸿蒙推荐的目录遍历方式比fs.readdir()更内存友好entry.name.endsWith(.txt)过滤非文本文件避免误加载.log或隐藏文件fs.closedir(dir)必须显式调用否则文件句柄泄漏多次刷新后触发EMFILE错误aboutToAppear是页面即将显示时的钩子比onPageShow更早执行确保列表在UI渲染前已加载。3.2 构建可滚动文件列表侧边栏将文件列表以垂直列表形式嵌入主界面右侧使用Flex布局实现响应式分栏// pages/Index.ets续build方法 build() { Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) { // 主编辑区占70%宽度 Column({ space: 8 }) { // ...原有标题、TextInput、按钮代码... } .width(70%) // 文件列表侧边栏占30%宽度 Column({ space: 4 }) { Text(文件列表) .fontSize(16) .fontWeight(FontWeight.Medium) .width(100%) .textAlign(TextAlign.Center) .backgroundColor(Color.LightGray) List({ space: 2 }) { ForEach(this.fileList, (fileName: string, index: number) { ListItem() { Row({ space: 8 }) { Text(${index 1}. ${fileName}) .fontSize(14) .flexGrow(1) Button(打开, { type: ButtonType.Text }) .onClick(() { this.switchToFile(index); }) .width(80) .height(30) } .backgroundColor(index this.currentFileIndex ? Color.Blue : Color.White) .borderRadius(4) .padding(8) } }, (fileName: string) fileName) } .listHeight(100%) .width(100%) } .width(30%) .backgroundColor(Color.Gray) } .padding(16) .backgroundColor(Color.Gray) }3.2.1 UI交互细节说明FlexDirection.Row实现左右分栏width用百分比保证在手机/平板不同尺寸下自适应List组件比Column更高效处理长列表ForEach绑定fileList数组keyGenerator用文件名避免重复key警告ListItem内Row布局让文件名左对齐、按钮右对齐backgroundColor高亮当前选中项listHeight(100%)确保列表填满侧边栏高度避免内容溢出。4. 处理鸿蒙特有文件编码与权限问题鸿蒙设备上打开记事本文件出现乱码或保存后文件在PC端无法正常读取90%源于编码不一致和路径权限配置错误。这不是Bug而是鸿蒙沙箱机制的必然约束。4.1 统一使用UTF-8 BOM编码避免中文乱码鸿蒙fs.writeText()默认不添加BOMByte Order Mark而Windows记事本依赖BOM识别UTF-8。解决方案是在写入前手动添加BOM头// utils/FileUtils.ets修改saveTextToFile export async function saveTextToFile(content: string, fileName: string): Promiseboolean { try { const docPath await fs.getRealPath(/data/storage/el2/000/Documents/); const filePath ${docPath}/${fileName}; // 添加UTF-8 BOM头EF BB BF const bomContent \uFEFF content; const file await fs.open(filePath, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE); await fs.writeText(file.fd, bomContent, { encoding: UTF-8 }); await fs.close(file.fd); return true; } catch (err) { console.error(保存失败:, err); return false; } }注意BOM头\uFEFF必须加在content最前面且encoding仍设为UTF-8。若设为UTF-8-BOM鸿蒙API不识别该编码名会回退到默认编码导致乱码。4.2 解决“Permission denied”错误的三步检查法当fs.open()抛出Permission denied按顺序排查检查项操作命令/位置正确状态1. module.json5权限声明查看reqPermissions数组必须包含READ_USER_STORAGE和WRITE_USER_STORAGE2. 运行时动态申请在Ability启动后调用requestPermissionsFromUser()用户需在弹窗中点击“允许”3. 路径是否沙箱内console.log(await fs.getRealPath(/data/storage/el2/000/Documents/))输出路径必须以/data/storage/el2/000/开头动态申请权限的代码示例// 在EntryAbility.ts的onCreate中 import abilityAccessCtrl from ohos.abilityAccessCtrl; async onCreate(want: Want) { super.onCreate(want); // 请求存储权限 const atManager abilityAccessCtrl.createAtManager(); const permissions [ ohos.permission.READ_USER_STORAGE, ohos.permission.WRITE_USER_STORAGE ]; try { const result await atManager.requestPermissionsFromUser(this.context, permissions); if (result.authResults.every(r r 0)) { console.info(权限申请成功); } else { console.warn(权限申请被拒绝); } } catch (err) { console.error(权限申请异常:, err); } }4.3 验证文件是否被正确保存到用户目录最可靠的验证方式不是看日志而是用hdc工具连接真机或模拟器直接查看文件系统# 连接设备 hdc shell # 进入应用沙箱文档目录 cd /data/storage/el2/000/Documents/ # 列出文件并查看大小 ls -la # 输出示例-rw------- 1 system system 128 Jan 1 00:00 mynote.txt # 查看文件内容确认中文正常 cat mynote.txt # 输出应为你好鸿蒙提示hdc shell是鸿蒙官方调试工具比ADB更适配。若cat显示乱码说明BOM未生效或编码参数错误若ls无文件说明路径写错或权限未授予。5. 打包HAP包并部署到真机的完整流程毕设答辩时演示必须在真机运行不能只在模拟器。鸿蒙HAP包HarmonyOS Ability Package的签名和安装流程与Android APK不同需严格遵循官方规范。5.1 配置签名证书与Profile文件DevEco Studio默认使用调试证书但真机安装必须用发布证书。步骤如下生成密钥库Build Generate Signed Hap... Create new keystore密钥库密码HarmonyOS2024!建议复杂密码别名notebook-release别名密码同密钥库密码申请发布Profile访问 AppGallery Connect → “我的项目” → “应用服务” → “配置文件”创建新Profile包名填com.example.notebook与module.json5中bundleName一致上传刚生成的.p12证书文件下载生成的.hapProfile文件关联Profile到项目Project Structure Signing Configs→ 勾选“Enable signing”Profile path指向下载的.p7b文件Keystore path指向生成的.p12文件5.2 构建Release HAP包并安装配置完成后执行构建# 在项目根目录执行 ./gradlew buildRelease生成的HAP包路径build/outputs/default/entry-default-unsigned.hap签名后路径build/outputs/default/entry-default-signed.hap安装到真机# 连接真机开启USB调试 hdc install build/outputs/default/entry-default-signed.hap # 输出install hap success # 启动应用 hdc shell aa start -a EntryAbility -b com.example.notebook5.2.1 常见安装失败原因与修复错误信息原因修复方案Failed to install hap: ERROR_INSTALL_FAILED_INVALID_APKProfile未关联或包名不匹配检查module.json5的bundleName与Profile申请时填写的包名完全一致ERROR_INSTALL_FAILED_CONFLICTING_PROVIDER真机已安装同包名调试版先hdc shell bm uninstall com.example.notebook卸载旧版ERROR_INSTALL_FAILED_SIGNATURE_ERROR签名证书与Profile不匹配重新生成密钥库确保Profile上传的是同一.p12文件5.3 毕设答辩演示技巧3分钟展示核心亮点答辩时评委关注“是否真懂鸿蒙特性”而非功能多全。聚焦三个可验证点展示分布式能力在手机上编辑文件用startAbility()拉起平板上的同应用验证ohos.distributedschedule跨设备同步需在module.json5中配置distributedCapabilities演示权限控制进入设置→应用管理→鸿蒙记事本→权限关闭“文件和媒体”权限再点击保存按钮观察是否弹出“权限被拒绝”Toast提示验证沙箱隔离用hdc shell进入/data/storage/el2/000/Documents/创建一个test.txt然后在应用内打开——证明应用只能访问自己沙箱内的文件无法越界读取其他应用数据。提示答辩PPT中放一张hdc shell ls -la截图比10页架构图更有说服力。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。