第一次打开仓颉鸿蒙工程,九成新手都从错的文件开始读
新建一个工程,双击打开,结果屏幕上甩出几十个文件夹和配置文件,你是不是也愣住了?很多人第一反应是从上往下一个一个点开,`AppScope`、`entry`、`hvigor`、`oh_modules`,越翻越乱,半小时过去还没搞懂这个 App 到底是怎么跑起来的。
其实老手打开一个陌生工程,从来不会平铺着看。他们只盯 4 个文件,顺着看一遍,整个应用的启动脉络就清清楚楚了。这 4 个地方,用华为仓颉编程语言开发华为鸿蒙系统应用时几乎一模一样,看懂它们,比你背一百条语法都管用。这篇就带你按正确顺序走完这条路。
第一站:app.json5,先认清这个 App 是谁
打开工程后,第一个该看的不是源码,而是 `AppScope/app.json5`。它是整个应用的身份证,记录的是全局信息,跟具体某个模块无关。
{ "app": { "bundleName": "com.example.myapplication", "vendor": "example", "versionCode": 1000000, "versionName": "1.0.0", "icon": "$media:layered_image", "label": "$string:app_name" } }
这里最关键的是 `bundleName`,也就是包名,它是这个应用在整个系统里独一无二的标识,上架、签名、调试都认它。`versionName` 是给用户看的版本号,`versionCode` 是给系统比大小用的整数版本。`icon` 和 `label` 决定桌面上显示的图标和名字。看完这个文件,你就知道「我打开的这个工程,到底是哪个 App」。

第二站:module.json5,系统从这里知道先启动谁
知道了 App 是谁,接下来要回答一个问题:点开图标,系统先跑哪段代码?答案藏在 `entry/src/main/module.json5` 里。
{ "module": { "name": "entry", "type": "entry", "mainElement": "EntryAbility", "srcEntry": "ohos_app_cangjie_entry.MyAbilityStage", "abilities": [ { "name": "EntryAbility", "srcEntry": "ohos_app_cangjie_entry.MainAbility", "exported": true, "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ] } ] } }
重点看三个地方。`mainElement` 指明这个模块的主入口能力是谁,这里是 `EntryAbility`。`abilities` 数组里那条 `srcEntry`,指向了真正承载启动逻辑的仓颉类 `MainAbility`。最下面 `skills` 里的 `entity.system.home` 和 `action.system.home`,等于告诉系统「我就是那个能放在桌面、点一下能从主屏启动的入口」。读懂这个文件,你就接上了配置和代码之间的那根线。

第三站:main_ability.cj,代码真正的起点
顺着 `module.json5` 里 `srcEntry` 指的 `MainAbility`,我们就能找到第三站,也就是仓颉源码里的 `main_ability.cj`。这才是代码层面真正的起跑线。
package ohos_app_cangjie_entry import kit.AbilityKit.UIAbility import kit.ArkUI.WindowStage class MainAbility <: UIAbility { public init() { super() registerSelf() } public override func onCreate(want: Want, launchParam: LaunchParam): Unit { Hilog.info(1, "Cangjie", "MainAbility OnCreated.") } public override func onWindowStageCreate(windowStage: WindowStage): Unit { windowStage.loadContent("EntryView") } }
`MainAbility` 继承自 `UIAbility`,是一个标准的页面型能力。`onCreate` 在能力被创建时触发,适合做初始化。真正的关键是 `onWindowStageCreate`:当窗口准备好,这里调用 `loadContent` 把名为 `EntryView` 的页面加载进窗口。换句话说,这一行代码决定了「打开 App 后第一个加载的页面是谁」。

第四站:index.cj,你看到的第一个页面
`loadContent` 加载的 `EntryView`,就定义在第四站 `index.cj` 里。这是用户睁眼看到的第一个画面,也是仓颉声明式 UI 最直观的样子。
@Entry @Component class EntryView { @State var message: String = "Hello World" func build() { Row { Column { Text(this.message) .fontSize(50) .fontWeight(FontWeight.Bold) .onClick({ evt => this.message = "Hello Cangjie" }) }.width(100.percent) }.height(100.percent) } }
`@Entry` 表示它是页面入口,`@Component` 表示它是一个可渲染的自定义组件,`@State` 修饰的 `message` 是会驱动界面刷新的状态变量。`build` 函数里用 `Row`、`Column`、`Text` 这种嵌套写法把布局搭出来。这套语法看一眼就能猜到长什么样,这正是声明式 UI 的好处。

把四站串起来:这其实就是一条启动链
回头看这 4 个地方,它们不是孤立的文件,而是一条环环相扣的启动链:`app.json5` 说清楚「这是哪个 App」,`module.json5` 指明「先启动哪个 Ability」,`main_ability.cj` 在 `onWindowStageCreate` 里「加载哪个页面」,`index.cj` 则「把页面画出来」。
你会发现,阅读顺序恰好就是程序的启动顺序。先有身份,再有入口配置,然后是代码起点,最后才是画面。任何一个陌生的仓颉鸿蒙工程,按这条链走一遍,再多的文件也只是这条主干上的枝叶。
新手最容易出错的三个地方,提前说清楚
第一,别一上来就钻 `oh_modules` 和 `build` 目录,那是依赖和构建产物,不是你该读的源码。第二,`module.json5` 里的 `srcEntry` 写的是「包名加类名」,包名要和 `cjpm.toml` 里的 `name` 对得上,对不上就会启动失败,这是新手最常见的卡点。第三,`loadContent` 传入的页面名要和 `@Entry` 修饰的类对得上,写错了就白屏。
记住这个阅读顺序,再大的工程也不慌
说到底,读懂一个仓颉鸿蒙工程,不靠蛮力翻文件,靠的是抓住这条「身份到画面」的主线。下次打开任何工程,按 `app.json5`、`module.json5`、`main_ability.cj`、`index.cj` 这个顺序走一遍,十分钟就能摸清它的骨架,再往里填业务细节就轻松多了。把简单的东西讲透、把顺序理顺,本来就是高效学习的捷径。

夜雨聆风