仓颉鸿蒙 App 的入口在哪里?很多人第一步就找错了
写过 C、Java、Go 的人,打开一个新工程,第一件事几乎都是找 `main` 函数,因为程序从那里开始跑,这是刻进肌肉里的习惯。可当你用华为仓颉编程语言开发华为鸿蒙系统应用时,翻遍源码却找不到一个像样的 `main`,于是开始怀疑人生:这 App 到底是从哪儿开始执行的?
问题不在你,而在于「App 的入口」和「命令行程序的入口」根本是两回事。今天我们把 `main` 函数、包、模块、MainAbility、页面入口这五个概念一次性理清楚,看完你就再也不会在第一步找错地方了。
先搞懂:main 函数到底是谁的入口
`main` 函数确实是仓颉程序的入口,但它服务的是命令行程序。它有几条硬规矩:每个根包最多一个,不能加访问修饰符,定义时不写 `func` 关键字,参数要么没有、要么是 `Array
main(): Int64 { return 0 }
注意第一行,是 `main()` 而不是 `func main()`,这是仓颉的规定。如果你写的是一个跑在终端里的工具,那么程序确实从这个 `main` 开始执行。但鸿蒙 App 不是终端工具,它是由系统托管、有界面、能被点击启动的应用,规则就完全不同了。

为什么你的鸿蒙 App 里找不到 main
鸿蒙 App 的启动权不在你手里,而在系统手里。用户在桌面点一下图标,是操作系统按照配置去把应用「叫醒」,而不是去执行某个 `main` 函数。所以一个标准的仓颉鸿蒙 App 工程里,你压根不需要写命令行式的 `main`。
这就是第一步最容易找错的根源:拿命令行程序的思维去找 App 入口,方向从一开始就偏了。App 的入口不是一个函数,而是一套「系统该启动谁」的约定。这套约定写在配置文件里,下面就来找它。
包和模块:先认清入口的地理位置
在找具体入口之前,得先理解仓颉里两个容易混的概念。包是最小编译单元,每个包有自己的命名空间,源文件第一行的 `package` 声明就决定了它属于哪个包。模块是包的集合,是最小分发单元。
src |-- network ||-- util.cj// package default.network |-- main.cj// 默认为 default 包
记住一句话:命令行程序的 `main` 入口必须放在模块的根目录里。而鸿蒙 App 走的是另一条路,它的入口不靠目录位置决定,而靠配置文件点名。搞清楚包和模块,是为了让你明白「入口的坐标系」长什么样,接下来就看 App 真正的入口写在哪。
真正的入口:MainAbility 和它的生命周期
鸿蒙 App 的入口,是一个继承自 `UIAbility` 的类,通常叫 `MainAbility`。系统启动应用时,会创建这个能力,并按顺序回调它的几个生命周期函数,这些函数才是 App 真正「开始执行」的地方。
class MainAbility <: UIAbility { 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") } }
`onCreate` 在能力创建时触发,适合做初始化。`onWindowStageCreate` 在窗口准备好后触发,这里用 `loadContent` 把首页加载进窗口。这一整套,才相当于命令行程序里那个 `main` 的角色,只不过它被拆成了多个由系统调用的回调函数。

系统怎么知道该启动 MainAbility
那系统又是怎么知道要启动 `MainAbility` 而不是别的类的?答案在 `entry/src/main/module.json5` 这个配置文件里。
{ "module": { "mainElement": "EntryAbility", "abilities": [ { "name": "EntryAbility", "srcEntry": "ohos_app_cangjie_entry.MainAbility", "skills": [ { "entities": ["entity.system.home"], "actions": ["action.system.home"] } ] } ] } }
`srcEntry` 指向了承载启动逻辑的仓颉类 `MainAbility`,`skills` 里的 `entity.system.home` 则告诉系统「我就是那个能从主屏点击启动的入口」。所以真正的「入口声明」在这个配置文件里,代码里的 `MainAbility` 只是被它点名的执行者。

页面入口又是另一回事:@Entry
还有一个常被混为一谈的概念:页面入口。`MainAbility` 在 `onWindowStageCreate` 里 `loadContent` 加载的那个页面,是用 `@Entry` 修饰的组件,比如 `EntryView`。
@Entry @Component class EntryView { func build() { Column { Text("Hello Cangjie").fontSize(50) } } }
`@Entry` 表示这是一个页面级入口组件,`@Component` 表示它可被渲染。注意它和 App 入口是两个层级:`MainAbility` 是「应用启动入口」,`@Entry` 是「页面显示入口」。一个管应用怎么起来,一个管页面怎么显示,别再把它俩当一回事。
五个概念一次理清,入口再也不会找错
现在把五层入口串成一条线:命令行程序靠 `main` 函数,但 App 不用它;包和模块决定了代码的组织坐标;`module.json5` 配置点名了启动哪个能力;`MainAbility` 的生命周期函数是代码真正的执行起点;`@Entry` 组件则是用户看到的页面入口。

层次分清了,你就明白「入口在哪」这个问题其实有好几个答案,关键看你问的是哪一层。下次再打开仓颉鸿蒙工程,先问自己一句:我要找的是应用入口、还是页面入口?方向对了,第一步就不会再走偏。把概念辨析清楚,本来就是学得快的关键。

夜雨聆风