简介这份资源面向鸿蒙应用开发初学者与转型开发者提供鸿蒙5.0HarmonyOS NEXT平台App开发的入门级完整工程示例帮助读者在真实项目结构中理解从初始化到部署的开发流程。压缩包共445个文件约1.84MB以js、json、ts、json5、ets等为主ets与js承载页面与逻辑代码json/json5负责包管理、构建配置与代码规范ts用于构建脚本另有少量png、md、xml等资源与说明文件。工程内包含代码格式化、版本忽略、依赖锁定、构建配置、代码检查、本地环境变量及构建脚本等模块并配有AppScope应用源码与资源目录便于对照理解鸿蒙工程的组织方式与配置作用。目前已有454人学习下载适合希望快速上手鸿蒙5.0应用开发、熟悉工程结构与构建配置的读者参考实践。1. 鸿蒙5.0系统app开发入门Demo从零跑通第一个可安装应用很多人第一次接触鸿蒙5.0系统app开发入门Demo卡住的地方不是代码写不出来而是环境装完不知道下一步点哪里。我见过一个开发者DevEco Studio装了三天模拟器起了五次最后发现是SDK路径里带了一个中文字符。这类问题在入门阶段特别常见跟技术难度无关纯粹是工具链的细节没对齐。鸿蒙5.0HarmonyOS NEXT和之前最大的区别是彻底去掉了AOSP兼容层应用只能跑在ArkTSArkUI这套原生体系上。这意味着你不能再拿Android那套Java/Kotlin经验直接套得重新理解它的声明式UI和状态管理。但好处也明显一套代码可以覆盖手机、平板、车机等设备分布式能力是系统级提供的不需要自己搭通信层。这篇笔记面向两类人一是完全没碰过鸿蒙、想用最短路径跑通一个可安装Demo的开发者二是做过Android/iOS、想快速判断鸿蒙5.0值不值得投入的熟手。我会从环境搭建讲到页面跳转、数据请求、打包签名每一步都给出可复制的代码和参数说明最后把我在真机调试上踩过的坑摊开讲。读完你应该能独立做出一个带列表页和详情页的完整Demo并且知道后续往哪个方向深入。2. 环境搭建与工程结构把第一个页面跑起来2.1 DevEco Studio安装与SDK配置的必调项DevEco Studio是鸿蒙开发的官方IDE基于IntelliJ IDEA社区版定制。下载时注意选对版本——鸿蒙5.0需要DevEco Studio 5.0及以上旧版本不支持NEXT的API。安装过程本身没什么坑但装完之后的SDK配置有几个地方必须手动确认。打开IDE后进入Settings → SDK重点看三个位置配置项推荐值说明SDK Location纯英文路径无空格中文路径会导致编译时资源索引失败API Version12对应5.0 Release低于12不支持NEXT特性Build Tools默认勾选包含hvigor构建工具链SDK下载完成后还需要在Settings → Tools → Device Manager里配置模拟器。鸿蒙5.0的模拟器分本地模拟器和远程模拟器两种本地模拟器需要开启Hyper-VWindows或安装HAXMMac Intel远程模拟器不需要本地虚拟化支持但需要登录账号。我一般建议新手先用远程模拟器跑通流程因为本地模拟器的虚拟化环境配置经常出玄学问题。等代码逻辑验证完了再折腾本地模拟器做性能测试。2.2 创建工程时Module类型怎么选新建工程时DevEco Studio会让你选模板。对于入门Demo选Empty Ability就够了。关键在后面的配置页Project Name英文不要带特殊字符Bundle Name反向域名格式比如com.example.myappSave Location纯英文路径Compile SDK选API 12Model选Stage模型Stage模型是鸿蒙5.0推荐的架构和旧的FA模型相比它把UI和Ability的生命周期解耦了每个Ability可以独立管理自己的窗口和上下文。入门阶段不用深究区别但记住一点网上很多旧教程用的是FA模型代码结构不一样别混着抄。创建完成后工程目录长这样MyApplication/ ├── AppScope/ │ ├── app.json5 # 应用级配置 │ └── resources/ # 应用级资源 ├── entry/ │ ├── src/main/ │ │ ├── ets/ │ │ │ ├── entryability/ │ │ │ │ └── EntryAbility.ets # 入口Ability │ │ │ └── pages/ │ │ │ └── Index.ets # 默认首页 │ │ ├── resources/ # 模块级资源 │ │ └── module.json5 # 模块配置 │ └── build-profile.json5 └── build-profile.json5 # 工程级配置重点看三个文件app.json5管应用图标、名称、版本号module.json5管模块的Ability注册、权限声明Index.ets是默认生成的首页代码。2.3 用ArkTS写第一个可交互页面打开Index.ets把默认代码替换成下面这个带计数器的页面// Index.ets Entry Component struct Index { // State装饰的变量变化时会触发UI刷新 State count: number 0 State message: string 鸿蒙5.0入门Demo build() { Column({ space: 20 }) { // 文本组件显示message变量 Text(this.message) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(#182431) // 显示当前计数 Text(点击次数${this.count}) .fontSize(18) .fontColor(#666666) // 按钮组件点击时修改count Button(点我1) .width(200) .height(48) .backgroundColor(#007DFF) .onClick(() { this.count 1 // 计数到5时改变提示文字 if (this.count 5) { this.message 你已经点了5次了 } }) // 条件渲染count大于0时显示重置按钮 if (this.count 0) { Button(重置) .width(200) .height(48) .backgroundColor(#FF6B6B) .onClick(() { this.count 0 this.message 鸿蒙5.0入门Demo }) } } .width(100%) .height(100%) .justifyContent(FlexAlign.Center) .backgroundColor(#F1F3F5) } }这段代码的逻辑说明Entry标记这个组件是页面入口Component表示它是一个自定义组件。State是ArkUI的状态装饰器被它修饰的变量发生变化时框架会自动重新执行build()方法刷新UI。Column是纵向布局容器space: 20表示子组件间距20vp。Button的onClick接收一个箭头函数作为回调在回调里修改State变量就会触发刷新。参数方面fontSize的单位默认是fp字体像素width和height默认是vp虚拟像素。鸿蒙的vp和Android的dp类似会根据屏幕密度自动缩放。颜色值用十六进制字符串支持#RRGGBB和#AARRGGBB两种格式。点击IDE顶部的运行按钮选一个模拟器等编译完成就能看到页面了。如果编译报错先检查module.json5里mainElement是否指向了EntryAbility再检查Index.ets是否在pages目录下。3. 页面路由与数据请求让Demo像个真应用3.1 用router实现列表页到详情页的跳转单页面Demo只能验证环境要体现鸿蒙5.0的能力至少得有两个页面加跳转。鸿蒙5.0的页面路由用ohos.router模块不需要像Android那样在Manifest里注册每个Activity。先建第二个页面。在pages目录下右键新建Detail.ets// Detail.ets import router from ohos.router Entry Component struct Detail { // 接收路由传过来的参数 State itemTitle: string State itemDesc: string aboutToAppear() { // 页面即将显示时获取路由参数 const params router.getParams() as Recordstring, string if (params) { this.itemTitle params.title || 默认标题 this.itemDesc params.desc || 默认描述 } } build() { Column({ space: 16 }) { Text(this.itemTitle) .fontSize(22) .fontWeight(FontWeight.Bold) .margin({ top: 40 }) Text(this.itemDesc) .fontSize(16) .fontColor(#666666) .padding({ left: 20, right: 20 }) Button(返回) .width(160) .height(44) .backgroundColor(#007DFF) .onClick(() { router.back() }) } .width(100%) .height(100%) .backgroundColor(#F1F3F5) } }然后在Index.ets里加一个列表点击列表项跳转到详情页// Index.ets 改造后的核心部分 import router from ohos.router interface ListItem { id: number title: string desc: string } Entry Component struct Index { State dataList: ListItem[] [ { id: 1, title: ArkTS语言, desc: 基于TypeScript的静态类型语言扩展了声明式UI能力 }, { id: 2, title: ArkUI框架, desc: 声明式UI开发框架支持状态管理和条件渲染 }, { id: 3, title: Stage模型, desc: 鸿蒙5.0推荐的应用架构UI与Ability解耦 }, { id: 4, title: 分布式能力, desc: 系统级提供跨设备通信和任务流转 } ] build() { Column() { Text(鸿蒙5.0知识点) .fontSize(24) .fontWeight(FontWeight.Bold) .margin({ top: 40, bottom: 20 }) List({ space: 12 }) { ForEach(this.dataList, (item: ListItem) { ListItem() { Column({ space: 6 }) { Text(item.title) .fontSize(18) .fontWeight(FontWeight.Medium) Text(item.desc) .fontSize(14) .fontColor(#999999) .maxLines(1) .textOverflow({ overflow: TextOverflow.Ellipsis }) } .width(90%) .padding(16) .backgroundColor(Color.White) .borderRadius(12) .alignItems(HorizontalAlign.Start) } .onClick(() { // 跳转到详情页并传参 router.pushUrl({ url: pages/Detail, params: { title: item.title, desc: item.desc } }) }) }, (item: ListItem) item.id.toString()) } .width(100%) .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#F1F3F5) } }逻辑说明ForEach的第一个参数是数据数组第二个参数是生成子组件的函数第三个参数是键生成函数——键必须唯一否则列表刷新会出问题。router.pushUrl接收一个对象url指定目标页面路径相对于pages目录不带.ets后缀params是任意对象在目标页用router.getParams()取回。参数说明maxLines(1)限制文本最多显示一行配合textOverflow实现省略号效果。layoutWeight(1)让List占据剩余空间这在Column布局里很常用。3.2 用http模块请求网络数据替换本地假数据本地数组只能演示UI真实应用得从网络拉数据。鸿蒙5.0的网络请求用ohos.net.http模块不需要额外申请权限访问互联网需要在module.json5里声明ohos.permission.INTERNET。在module.json5的requestPermissions数组里加上{ name: ohos.permission.INTERNET }然后改造Index.ets把本地数据换成网络请求import http from ohos.net.http Entry Component struct Index { State dataList: ListItem[] [] State isLoading: boolean false State errorMsg: string // 页面显示时发起请求 aboutToAppear() { this.fetchData() } async fetchData() { this.isLoading true this.errorMsg // 创建http请求对象 const httpRequest http.createHttp() try { const response await httpRequest.request( https://api.example.com/list, // 替换为实际接口 { method: http.RequestMethod.GET, header: { Content-Type: application/json }, connectTimeout: 10000, // 连接超时10秒 readTimeout: 10000 // 读取超时10秒 } ) if (response.responseCode 200) { // response.result是字符串需要解析 const result JSON.parse(response.result as string) this.dataList result.data || [] } else { this.errorMsg 请求失败状态码${response.responseCode} } } catch (err) { this.errorMsg 网络异常${JSON.stringify(err)} } finally { this.isLoading false // 用完销毁避免内存泄漏 httpRequest.destroy() } } build() { Column() { // 加载中显示进度条 if (this.isLoading) { LoadingProgress() .width(48) .height(48) .margin({ top: 100 }) } else if (this.errorMsg) { // 错误提示 Text(this.errorMsg) .fontSize(16) .fontColor(#FF6B6B) .margin({ top: 100 }) Button(重试) .margin({ top: 16 }) .onClick(() this.fetchData()) } else { List({ space: 12 }) { ForEach(this.dataList, (item: ListItem) { // ... 列表项代码同上 }, (item: ListItem) item.id.toString()) } .layoutWeight(1) } } .width(100%) .height(100%) .backgroundColor(#F1F3F5) } }关键参数说明connectTimeout是从发起请求到建立连接的超时时间readTimeout是连接建立后等待响应的超时时间。两个都建议设默认值在某些网络环境下偏短。httpRequest.destroy()必须调用否则每次请求都会占用一个底层连接资源请求次数多了会报错。response.result的类型是string | Object | ArrayBuffer取决于请求时expectDataType参数。默认返回字符串所以需要手动JSON.parse。如果接口返回的是二进制数据比如图片要设expectDataType: http.HttpDataType.ARRAY_BUFFER。3.3 用Preferences做本地数据持久化网络数据每次打开应用都要重新拉体验不好。鸿蒙5.0提供了ohos.data.preferences做轻量级键值存储适合存用户设置、缓存列表这类小数据。import preferences from ohos.data.preferences // 存数据 async function saveCache(context: Context, key: string, value: string) { const pref await preferences.getPreferences(context, my_cache) await pref.put(key, value) await pref.flush() // 必须flush才会写入磁盘 } // 读数据 async function loadCache(context: Context, key: string): Promisestring { const pref await preferences.getPreferences(context, my_cache) return await pref.get(key, ) as string }getPreferences的第二个参数是文件名同一个文件名对应同一个存储实例。put只是写入内存flush才落盘。读取时第二个参数是默认值类型要和存入时一致否则会抛类型错误。在页面里拿到Context的方式是在aboutToAppear里用getContext(this)aboutToAppear() { const context getContext(this) as Context loadCache(context, list_data).then(cached { if (cached) { this.dataList JSON.parse(cached) } else { this.fetchData() } }) }这样冷启动时先读缓存有缓存就直接渲染没有再去请求网络。请求成功后把新数据写回缓存。这个模式在入门Demo里加上就能体现你对真实场景的考虑。4. 避坑与排查新手最容易翻车的五个地方4.1 模拟器启动报错“虚拟化未开启”现象点击运行后模拟器一直卡在启动界面日志里出现HAXM is not installed或Hyper-V is disabled。原因鸿蒙本地模拟器依赖硬件虚拟化。Windows上需要开启Hyper-VMac Intel需要安装HAXMMac M系列芯片则要用ARM镜像的模拟器。解决Windows在“启用或关闭Windows功能”里勾选Hyper-V和Windows虚拟机监控程序平台重启后生效。如果和Docker Desktop冲突改用远程模拟器。Mac M系列在Device Manager里选Phone (ARM)类型的镜像不要选x86的。4.2 编译通过但真机安装失败“签名校验不通过”现象模拟器上跑得好好的连真机调试时安装报signature verification failed。原因鸿蒙5.0的真机安装需要签名证书模拟器用的是调试签名两者不通用。另外真机需要在开发者模式里开启USB调试并且登录的账号要加入调试设备列表。解决在DevEco Studio里进入File → Project Structure → Signing Configs勾选Automatically generate signature然后登录已注册的开发者账号。IDE会自动生成调试证书并写入build-profile.json5。如果还失败检查真机的系统时间是否准确——证书校验依赖时间戳时间偏差超过几分钟就会失败。4.3 State修饰的数组修改后UI不刷新现象用this.dataList.push(newItem)往数组里加数据数据确实变了但列表不刷新。原因State只能观测到数组本身的引用变化和第一层元素的增删对嵌套对象的属性修改观测不到。而且push这种原地修改在某些版本里不会触发刷新。解决用展开运算符创建新数组this.dataList [...this.dataList, newItem]。如果要修改数组里某个对象的属性也要整体替换this.dataList this.dataList.map(item item.id targetId ? {...item, title: 新标题} : item)。这个坑在ArkUI里非常典型记住一个原则让State变量指向一个新对象而不是改老对象。4.4 页面跳转后返回原页面数据丢失现象从列表页跳到详情页再返回列表页列表数据被重新加载了滚动位置也回到了顶部。原因router.pushUrl默认会把当前页面压入栈返回时触发的是onPageShow而不是aboutToAppear。但如果你在aboutToAppear里发请求返回时不会重新执行数据应该还在。数据丢失通常是因为用了router.replaceUrl而不是pushUrlreplace会销毁当前页面。解决确认跳转用的是pushUrl。如果确实需要保留状态把数据提升到AppStorage里// 存 AppStorage.setOrCreate(listData, this.dataList) // 取 const cached AppStorage.getListItem[](listData) if (cached) this.dataList cachedAppStorage是应用级单例所有页面都能访问适合存跨页面共享的状态。4.5 网络请求在模拟器上正常真机上超时现象同样的接口地址模拟器能拿到数据真机一直超时。原因模拟器走的是宿主机的网络真机走的是自己的网络。如果接口地址是localhost或内网IP真机访问不到。另外鸿蒙5.0默认禁止明文HTTP请求只允许HTTPS。解决接口地址换成公网可访问的域名并且用HTTPS。如果调试阶段必须用HTTP在module.json5里加{ name: ohos.permission.INTERNET, reason: $string:internet_reason, usedScene: { abilities: [EntryAbility], when: always } }同时在app.json5里加cleartextTraffic: true。但上线前一定要改回HTTPS明文传输在应用市场审核时会被拒。5. 进阶技巧用一次开发多端部署验证鸿蒙5.0的真实价值入门Demo跑通之后下一步该验证的是鸿蒙5.0最核心的卖点——一次开发多端部署。这不是口号ArkUI提供了响应式布局能力同一套代码可以根据屏幕尺寸自动调整。具体做法是在resources目录下建多个profile文件夹resources/ ├── base/profile/main_pages.json # 默认页面配置 ├── tablet/profile/main_pages.json # 平板配置 └── car/profile/main_pages.json # 车机配置然后在代码里用断点监听import mediaquery from ohos.mediaquery Entry Component struct Index { State currentBreakpoint: string sm private listener: mediaquery.MediaQueryListener mediaquery.matchMediaSync( (width 600vp) ) aboutToAppear() { this.listener.on(change, (result: mediaquery.MediaQueryResult) { this.currentBreakpoint result.matches ? lg : sm }) } build() { // 根据断点切换布局 if (this.currentBreakpoint lg) { Row() { // 平板/车机左右分栏 List().width(40%) DetailPanel().layoutWeight(1) } } else { // 手机单栏 Column() { List().width(100%) } } } }mediaquery.matchMediaSync接收一个媒体查询字符串width 600vp表示宽度大于等于600虚拟像素时匹配。on(change)注册回调在回调里更新断点状态。这个机制和CSS媒体查询思路一致但跑在原生渲染层没有WebView的性能损耗。验证方法在DevEco Studio的Previewer里切换设备类型观察布局是否按预期变化。Previewer支持手机、折叠屏、平板、车机四种预设切换时不需要重新编译。如果布局没变检查main_pages.json里是否注册了对应设备的页面路径。我自己的习惯是每加一个新页面先在Previewer里把四种设备都过一遍确认没有布局错乱再上真机。这个习惯帮我省了很多返工时间——真机调试一次至少五分钟Previewer切换只要两秒。还有一个值得试的技巧是用Builder抽离可复用UI。比如列表项在手机上是纵向排列在平板上要变成横向卡片可以把差异部分用Builder函数封装根据断点传不同参数Builder function ListItemCard(item: ListItem, isHorizontal: boolean) { if (isHorizontal) { Row({ space: 12 }) { Text(item.title).fontSize(18) Text(item.desc).fontSize(14).layoutWeight(1) } .padding(16) .backgroundColor(Color.White) .borderRadius(12) } else { Column({ space: 6 }) { Text(item.title).fontSize(18) Text(item.desc).fontSize(14).maxLines(1) } .padding(16) .backgroundColor(Color.White) .borderRadius(12) } }Builder函数用function关键字声明在组件外部调用时直接写ListItemCard(item, true)。它和普通函数的区别是内部可以使用ArkUI组件语法并且支持按引用传递参数——如果传入的是对象且用了$$语法对象属性变化时会触发Builder重新执行。最后说一个我踩过的坑鸿蒙5.0的Builder在组件内定义时如果用了this访问状态变量必须用箭头函数或者Builder修饰的方法形式否则this指向会丢。这个在官方文档里写得很隐蔽我调试了半个下午才定位到。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?