一个基于luajvm开发的第三方知乎安卓客户端
累计 2 次下载请求 · 近 30 天 2 次
由 plwd 推荐
作者:zhihulite(作者地址,在新窗口打开)(推荐者填写,未验证)
# Hydrogen > ⚠️ **项目维护延缓** > > 由于[相关原因](https://github.com/zhihulite/Hydrogen/issues/159),Hydrogen 项目维护已延缓。 > > 推荐使用同类优秀项目:[Zhihu++](https://github.com/zly2006/zhihu-plus-plus) **注意**:请勿在 Gitee 反馈问题或提交 PR,请前往 GitHub 提交。Gitee 仓库仅用于代码同步。 **重要**:发布更新前,必须修改 `core/app_info.lua` 中的 `version` 变量,否则应用无法检测到新版本。 [](LICENSE) [](https://gitee.com/huajicloud/Hydrogen) [](https://github.com/zhihulite/Hydrogen) ## 目录 - [项目介绍](#项目介绍) - [更新与反馈说明](#更新与反馈说明) - [历史版本存档](#历史版本存档) - [特别致谢](#特别致谢) - [打包说明](#打包说明) - [Aide Lua 调试指南](#aide-lua-调试指南) - [贡献指南](#贡献指南) ## 项目介绍 Hydrogen 是一个基于 [LuaJVM](https://github.com/zhihulite/luajvm) 开发的项目。 > **Aide Lua 调试提示**: > 你可以使用 [Aide Lua](https://gitee.com/AideLua/AideLua) 实现免打包快速调试 Lua 代码。 > **但在开始调试前,请务必阅读下方的 [配置指南](#aide-lua-调试指南)**,否则因环境配置缺失将导致运行失败。 > **不建议使用 Aide Lua 进行打包**。请务必通过 Gradle 打包 APK,详见下方 **[打包说明](#打包说明)**。 ## 更新与反馈说明 - **更新策略**:默认情况下,仅在必要时才会推送版本更新。 - **反馈要求**:反馈问题时,请务必使用最新版本进行测试。 - **最新构建下载**:你可以从 [GitHub Actions](https://github.com/zhihulite/Hydrogen/actions) 获取最新的自动构建版本。 ## 历史版本存档 - **Hydrogen Final 7 (2022 最终版)**:[查看源码存档](https://github.com/zhihulite/Hydrogen/releases/tag/archive-final7-original) > 此为 2022 年发布的 Final 7 原始代码存档,已停止维护。仅用于代码回溯与历史参考。 ## 特别致谢 - [ZL114514](https://github.com/ZL114514):目前负责仓库的维护工作 - [People 11](https://GitHub.com/People-11):使用 Gemini 进行了不少修缮 - [orz12](https://gitee.com/orz12):早期重新设计了部分布局 - [1582421598](https://github.com/1582421598):提交 PR 修复 bug - [NullCola](https://t.me/NullCola):绘制矢量 Hydrogen 图标 ## 打包说明 你可以通过以下方式打包: - 使用 `app:assembleRelease` 打包(包含 lua 文件) > **重要提示**:请务必指定 `app` 模块(即使用 `app:assembleRelease`)。 > **不要**直接使用根项目的 `assembleRelease`,这可能导致打包失败。 ### 签名信息 > **安全提示**:为了方便开发者调试和二次打包,本项目公开了签名文件 (`hydrogen.jks`) 及密码。**请务必确认你下载的 APK 来自官方 GitHub Releases 页面、Actions 构建产物或本仓库可信来源**。任何第三方渠道分发的 APK 均可能被篡改,请注意甄别安全风险。 `hydrogen.jks` 是软件的签名文件,你可以使用 apksigner 或其他签名工具进行签名。签名信息如下: - 别名:`hydrogen` - 密钥库密码:`zhihu` - 私钥密码:`android` 使用 apksigner 签名的示例命令: ```shell apksigner sign --ks hydrogen.jks --ks-key-alias hydrogen --ks-pass pass:zhihu --key-pass pass:android --v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled true -v --out 签名后apk 签名前apk ``` ### Aide Lua 调试指南 > **调试前必读**:如果不按以下步骤操作,Aide Lua 将无法快速调试 Hydrogen。 如果你使用 **Aide Lua** 进行代码调试,请严格遵守以下操作流程: 1. **预安装正式版**: 首先必须通过 Gradle 打包并安装正式版的 Hydrogen APK 到设备上(确保授权存储权限)。 2. **修改 Aide Lua 设置**: 打开 Aide Lua,点击右上角的 **设置** 图标,找到并 **关闭** “比较完整的运行” 选项。 *(注:开启此选项会导致调试所需时间增加)* 3. **开始调试**: 完成上述两步后,即可在 Aide Lua 中加载项目进行调试。 ## 贡献指南 ### 架构概览 Hydrogen 采用分层架构,Lua 编写业务逻辑,桥接 Android 原生 API。 #### 技术栈 - 语言:Lua 5.5.1 + Java(仅桥接层) - UI:Material Design Components(原生控件) - 网络:HTTP + Cookie + ZSE96 加密 - 混合渲染:原生列表 + WebView(注入 JS) #### 分层说明 | 层级 | 职责 | | -------------- | ------------------------------------------------------------ | | **Pages** | Activity / Fragment 页面容器,路由跳转 | | **Components** | 可复用组件(适配器、弹窗、自定义 Span、WebViewHelper) | | **Model** | 数据 + 视图控制(加载、分页、刷新) | | **Layout** | Lua 表描述的布局,经 `Layouts.<目录>.<文件>` 惰性加载 | | **Services** | 网络(含 ZSE96 签名、请求头、图片上传)、缓存、下载、权限 | | **Extensions** | 配置管理、文件操作、加密、OOP 工具 | | **Helpers** | UI 工具、布局脚手架、图片加载、资源访问、Material 组件、静态资源、底部弹窗、WebView JS 桥、截图合成、知乎数据与链接解析 | | **基础库** | 引擎内置的全局与 Lua 库(`import`、`loadlayout`、`task`、`json`)与 `libs/` 下的 `md5.lua`、`base64.lua`,支撑整个框架的运行 | | **Core** | 应用核心(初始化、常量、主题、文字样式、路由、应用信息),在 Pages/Model 等之前加载,提供全局配置和基础能力 | `extensions/`、`helpers/`、`services/` 三个目录的 `init.lua` 里注册的模块,由 `core/init.lua` 挂到同名全局命名空间,键名 snake_case 转 PascalCase(`ui` 特例为 `UI`):文件操作走 `Extensions.File`,下载走 `Services.Download`,Material 组件走 `Helpers.MaterialWidgets`。 #### 核心基础:init_app 与 Core - **init_app.lua**:入口脚本(`main.lua` 与独立 Activity)执行前引入的环境初始化脚本,带幂等标记只生效一次。是整个应用的**启动入口和运行环境基石**。 - **Core**:应用核心模块集合,在 `init_app` 之后加载,提供: - `constants.lua`:全局常量定义(SharedPreferences 键名、默认配置、请求头 key 与白名单) - `app_theme.lua`:主题管理(日间/夜间/OLED 模式,Material Design 3 颜色系统) - `app_text_style.lua`:屏幕信息、字体、MD3 文字样式与卡片样式(`Screen`、`Fonts`、`AppTextStyle`、`AppCardStyle`) - `app_info.lua`:应用信息与版本更新检查 - `router.lua`:路由系统,统一管理 Activity/Fragment 跳转 - `init.lua`:装载 Extensions/Helpers/Services 并挂到全局命名空间,注册常用全局(`json`、`Constants`、`AppTheme`、`Layouts`、`Router`、`tip`/`dp2px` 等),按序驱动请求头、文字样式与路由的初始化 > **加载顺序**:`init_app` → `core/init`(Extensions / Helpers / Services → Constants / AppTheme / AppInfo → Layouts → 请求头与文字样式 → Router)→ Pages/Model/Components #### 核心设计 - **Model 即控制器**:`PageToolModel` 封装完整列表逻辑(下拉刷新、上拉加载、分页、多 Tab),内部直接管理 RecyclerView/ViewPager 和适配器。一个 Model 即可驱动一个列表页,Fragment 仅需调用 `setupSingle` 或 `setupTabs`。 - **路由系统**:统一管理 Activity/Fragment 跳转,支持共享元素动画和返回栈。 - **WebView 混合**:`WebViewHelper` 封装 WebView 设置与 JS 桥接,注入的 JS 实现暗色模式、图片查看、滚动恢复、截图等,与知乎 Hybrid 页面无缝配合。 - **布局定义**:使用 Lua 表描述布局(类似 XML),运行时通过 `loadlayout` 转换为原生 View,支持主题属性和数据绑定。 - **主题系统**:遵循 Material Design 3 颜色规范,支持日间/夜间/OLED 模式,动态切换。 #### 扩展模块补充文档 - **Helpers.MaterialWidgets**(`helpers/material_widgets.lua`):Material Design 组件库,支持以**原本只能在 XML 中设置的自定义属性**动态创建 Material 组件。 - **Helpers.Resources**(`helpers/resources.lua`):资源快速访问工具,提供颜色、字符串、尺寸、Drawable 等资源的便捷获取方法。 - **Helpers.Layout**(`helpers/layout.lua`):布局脚手架,把卡片外壳、图标计数行、空状态页、列表页骨架收敛成可组合的构造器。构造器清单与 id 契约见 [docs/layout-scaffold.md](docs/layout-scaffold.md)。 --- 项目模块较多,其余部分请自行阅读相关源码。 ### 生命周期安全机制(isAlive / runIfAlive) 为防止 Fragment/Activity 销毁后异步回调仍执行导致崩溃,项目实现了统一的生命周期安全机制。 #### 基类支持 **BasePage** 和 **BaseModel** 均实现了: ```lua -- 检测是否存活(未销毁) function isAlive() return not self.isDestroyed end -- 安全执行回调(存活时执行) function runIfAlive(callback) if not callback then return function() end end return function(...) if self:isAlive() then callback(...) end end end ``` #### 使用规范 | 场景 | 做法 | 示例 | |------|------|------| | **网络回调** | 使用 `runIfAlive` 包装 | `NetWork.get(url, headers, self:runIfAlive(function(code, data) ... end))` | | **post/runnable** | 使用 `runIfAlive` 包装 | `view.post(self:runIfAlive(function() ... end))` | | **task 后台任务** | 主线程回调使用 `runIfAlive` 包装 | `task(bgFn, self:runIfAlive(function(result) ... end))` | | **延迟执行** | 使用 `runIfAlive` 包装 | `Helpers.UI.runDelayed(500, self:runIfAlive(function() ... end))` | | **PageTool** | PageTool 已自动处理,无需额外包装 | `pageTool:setupLoadFunction()` 内部已包装 | | **PageToolModel** | PageToolModel 已自动处理,无需额外包装 | `PageToolModel:refresh(key)` 内部已包装 | | **Model 回调或网络回调** | BaseModel 已自动处理,无需额外包装 | `model:load(params, callback)` 内部已包装 | | **addListener** | BaseModel 已自动处理,销毁时清除监听器 | `model:addListener("event", handler)` | #### 销毁链 ```lua function SomeFragment:onDestroy() -- chainUp 确保父类 onDestroy 自动调用 if self.model then self.model:destroy() -- 设置 isDestroyed = true self.model = nil end if self.webViewHelper then self.webViewHelper:destroy() -- 子模块自行销毁 self.webViewHelper = nil end end ``` #### 重要说明 > **1. `isAlive` / `runIfAlive` 的使用范围** > > 子模块(如 `WebViewHelper`、自定义 Model 等)可以在**内部实现**自己的 `isAlive` 和 `runIfAlive` 方法,用于内部异步回调的安全包装。但**外部调用方**(如 Fragment)应统一使用 `BasePage` 提供的 `isAlive` 和 `runIfAlive` 方法,而非直接调用子模块的对应方法。 > > 原因:正确实现销毁链后,`BasePage` 会在 `onDestroy` 中统一销毁所有子模块,子模块的销毁状态与 `BasePage` 保持一致。外部通过 `BasePage` 的方法可以确保生命周期判断的统一性,避免因直接依赖子模块状态而导致的不一致问题。 > > **推荐做法**: > - 子模块内部:实现私有 `isAlive` / `runIfAlive` 供内部回调使用 > - 外部调用:使用 `self:isAlive()` 或 `self:runIfAlive()`(来自 `BasePage`) > - Fragment/Activity:始终使用继承自 `BasePage` 的生命周期方法 > > > **2. BaseModel 已自动处理** > > `BaseModel` 已自动对网络回调和监听器进行 `runIfAlive` 包装,子类无需关心。所有继承 `BaseModel` 的子类自动获得生命周期安全保护。 > > **3. 新增子模块的销毁检测** > > 如果需要增加子模块(如 `WebViewHelper`),你需要正确实现销毁检测: > - 在 `ctor` 中初始化 `self.isDestroyed = false` > - 在 `destroy()` 方法中将 `self.isDestroyed = true` > - 所有异步回调在调用前和 callback 设置做相关判断 > - 在当前被使用方的 `onDestroy` 中调用子模块的 `destroy()` 方法 > > 子模块(如 `WebViewHelper`、自定义组件等)可以在内部实现 `isAlive` 和 `runIfAlive` 方法,作为快捷方式供内部异步回调使用: > > ```lua > -- 子模块内部实现 > function WebViewHelper:isAlive() > return not self.isDestroyed > end > > function WebViewHelper:runIfAlive(callback) > if not callback then return function() end end > return function(...) > if self:isAlive() then > callback(...) > end > end > end > > -- 子模块内部使用 > function WebViewHelper:loadData() > NetWork.get(url, headers, self:runIfAlive(function(code, data) > self:processData(data) > end)) > end > ``` > 在子模块实现完成后,建议使用 `final` 关键字锁定 `isAlive` 和 `runIfAlive` 方法,防止外部继承时意外覆盖,例如: > ```lua > -- 锁定方法,禁止子类覆盖 > WebViewHelper:final("isAlive", "runIfAlive") > ``` > > 不要为子模块再抽统一的生命周期基类或共享库:组件的销毁条件与清理时机各不相同 > (如 `WebViewHelper` 的 `isAlive` 还要求 `webView` 非空、`PageTool` 与持有它的 Model 销毁同步), > 统一基类收不了这些差异,只会多一层间接。每个子模块按上述三件套手写即可,重复的十几行是可接受的。 > > **4. 网络请求必须包装** > > 所有 `NetWork.get/post/put/delete` 等网络请求,应在调用前和回调中正确使用 `runIfAlive`。 > 注:部分未使用 BaseModel 的组件,如果涉及网络请求,请在调用设置 callback 自行添加 `runIfAlive` 包装。例如 `CollectionMoveSheet.show({..., onSuccess = self.runIfAlive(function() ... end), onError = self.runIfAlive(function() ... end)})` #### 正确示例 ```lua -- Fragment 中发起网络请求 function MyFragment:loadData() NetWork.get("https://api.example.com/data", headers, self:runIfAlive(function(code, data) if code == 200 then self:updateUI(data) -- 仅在 Fragment 存活时执行 end end)) end -- 子模块实现示例(如 WebViewHelper) function WebViewHelper:new(webView) local self = { webView = webView, isDestroyed = false, } return self end function WebViewHelper:runIfAlive(callback) return function(...) if not self.isDestroyed then callback(...) end end end function WebViewHelper:destroy() self.isDestroyed = true if self.webView then self.webView.destroy() self.webView = nil end end ``` ### 快速上手 1. 入口:`main.lua` → `init_app` → `core/init` → 欢迎页或主页。 2. 路由跳转:`Router.go("answer", { id = "123" })` 3. 新列表页:继承 `PageToolModel` → 实现 `getInitialUrl`、`parseItem`、`createAdapter` → Fragment 中调用 `setupSingle` 4. 新详情页:继承 `BaseModel` → 实现 `load` → Fragment 中手动调用并更新 UI 掌握 `PageToolModel`、`WebViewHelper` 以及生命周期安全机制(`runIfAlive`)是快速开发大部分页面的关键。
⚠️ 项目维护延缓
由于相关原因(在新窗口打开),Hydrogen 项目维护已延缓。
推荐使用同类优秀项目:Zhihu++(在新窗口打开)
注意:请勿在 Gitee 反馈问题或提交 PR,请前往 GitHub 提交。Gitee 仓库仅用于代码同步。
重要:发布更新前,必须修改 core/app_info.lua 中的 version 变量,否则应用无法检测到新版本。
[图片:License](在新窗口打开) [图片:Gitee 仓库](在新窗口打开) [图片:Github 仓库](在新窗口打开)
Hydrogen 是一个基于 LuaJVM(在新窗口打开) 开发的项目。
Aide Lua 调试提示: 你可以使用 Aide Lua(在新窗口打开) 实现免打包快速调试 Lua 代码。 但在开始调试前,请务必阅读下方的 配置指南(在新窗口打开),否则因环境配置缺失将导致运行失败。 不建议使用 Aide Lua 进行打包。请务必通过 Gradle 打包 APK,详见下方 打包说明(在新窗口打开)。
Hydrogen Final 7 (2022 最终版):查看源码存档(在新窗口打开)
此为 2022 年发布的 Final 7 原始代码存档,已停止维护。仅用于代码回溯与历史参考。
你可以通过以下方式打包:
使用 app:assembleRelease 打包(包含 lua 文件)
重要提示:请务必指定
app模块(即使用app:assembleRelease)。 不要直接使用根项目的assembleRelease,这可能导致打包失败。
安全提示:为了方便开发者调试和二次打包,本项目公开了签名文件 (
hydrogen.jks) 及密码。请务必确认你下载的 APK 来自官方 GitHub Releases 页面、Actions 构建产物或本仓库可信来源。任何第三方渠道分发的 APK 均可能被篡改,请注意甄别安全风险。
hydrogen.jks 是软件的签名文件,你可以使用 apksigner 或其他签名工具进行签名。签名信息如下:
hydrogenzhihuandroid使用 apksigner 签名的示例命令:
apksigner sign --ks hydrogen.jks --ks-key-alias hydrogen --ks-pass pass:zhihu --key-pass pass:android --v1-signing-enabled true --v2-signing-enabled true --v3-signing-enabled true --v4-signing-enabled true -v --out 签名后apk 签名前apk
调试前必读:如果不按以下步骤操作,Aide Lua 将无法快速调试 Hydrogen。
如果你使用 Aide Lua 进行代码调试,请严格遵守以下操作流程:
预安装正式版: 首先必须通过 Gradle 打包并安装正式版的 Hydrogen APK 到设备上(确保授权存储权限)。
修改 Aide Lua 设置: 打开 Aide Lua,点击右上角的 设置 图标,找到并 关闭 “比较完整的运行” 选项。 (注:开启此选项会导致调试所需时间增加)
开始调试: 完成上述两步后,即可在 Aide Lua 中加载项目进行调试。
Hydrogen 采用分层架构,Lua 编写业务逻辑,桥接 Android 原生 API。
| 层级 | 职责 |
|---|---|
| Pages | Activity / Fragment 页面容器,路由跳转 |
| Components | 可复用组件(适配器、弹窗、自定义 Span、WebViewHelper) |
| Model | 数据 + 视图控制(加载、分页、刷新) |
| Layout | Lua 表描述的布局,经 Layouts.<目录>.<文件> 惰性加载 |
| Services | 网络(含 ZSE96 签名、请求头、图片上传)、缓存、下载、权限 |
| Extensions | 配置管理、文件操作、加密、OOP 工具 |
| Helpers | UI 工具、布局脚手架、图片加载、资源访问、Material 组件、静态资源、底部弹窗、WebView JS 桥、截图合成、知乎数据与链接解析 |
| 基础库 | 引擎内置的全局与 Lua 库(import、loadlayout、task、json)与 libs/ 下的 md5.lua、base64.lua,支撑整个框架的运行 |
| Core | 应用核心(初始化、常量、主题、文字样式、路由、应用信息),在 Pages/Model 等之前加载,提供全局配置和基础能力 |
extensions/、helpers/、services/ 三个目录的 init.lua 里注册的模块,由 core/init.lua 挂到同名全局命名空间,键名 snake_case 转 PascalCase(ui 特例为 UI):文件操作走 Extensions.File,下载走 Services.Download,Material 组件走 Helpers.MaterialWidgets。
main.lua 与独立 Activity)执行前引入的环境初始化脚本,带幂等标记只生效一次。是整个应用的启动入口和运行环境基石。init_app 之后加载,提供:
constants.lua:全局常量定义(SharedPreferences 键名、默认配置、请求头 key 与白名单)app_theme.lua:主题管理(日间/夜间/OLED 模式,Material Design 3 颜色系统)app_text_style.lua:屏幕信息、字体、MD3 文字样式与卡片样式(Screen、Fonts、AppTextStyle、AppCardStyle)app_info.lua:应用信息与版本更新检查router.lua:路由系统,统一管理 Activity/Fragment 跳转init.lua:装载 Extensions/Helpers/Services 并挂到全局命名空间,注册常用全局(json、Constants、AppTheme、Layouts、Router、tip/dp2px 等),按序驱动请求头、文字样式与路由的初始化加载顺序:
init_app→core/init(Extensions / Helpers / Services → Constants / AppTheme / AppInfo → Layouts → 请求头与文字样式 → Router)→ Pages/Model/Components
PageToolModel 封装完整列表逻辑(下拉刷新、上拉加载、分页、多 Tab),内部直接管理 RecyclerView/ViewPager 和适配器。一个 Model 即可驱动一个列表页,Fragment 仅需调用 setupSingle 或 setupTabs。WebViewHelper 封装 WebView 设置与 JS 桥接,注入的 JS 实现暗色模式、图片查看、滚动恢复、截图等,与知乎 Hybrid 页面无缝配合。loadlayout 转换为原生 View,支持主题属性和数据绑定。helpers/material_widgets.lua):Material Design 组件库,支持以原本只能在 XML 中设置的自定义属性动态创建 Material 组件。helpers/resources.lua):资源快速访问工具,提供颜色、字符串、尺寸、Drawable 等资源的便捷获取方法。helpers/layout.lua):布局脚手架,把卡片外壳、图标计数行、空状态页、列表页骨架收敛成可组合的构造器。构造器清单与 id 契约见 docs/layout-scaffold.md(在新窗口打开)。项目模块较多,其余部分请自行阅读相关源码。
为防止 Fragment/Activity 销毁后异步回调仍执行导致崩溃,项目实现了统一的生命周期安全机制。
BasePage 和 BaseModel 均实现了:
-- 检测是否存活(未销毁)
function isAlive()
return not self.isDestroyed
end
-- 安全执行回调(存活时执行)
function runIfAlive(callback)
if not callback then return function() end end
return function(...)
if self:isAlive() then
callback(...)
end
end
end
| 场景 | 做法 | 示例 |
|---|---|---|
| 网络回调 | 使用 runIfAlive 包装 | NetWork.get(url, headers, self:runIfAlive(function(code, data) ... end)) |
| post/runnable | 使用 runIfAlive 包装 | view.post(self:runIfAlive(function() ... end)) |
| task 后台任务 | 主线程回调使用 runIfAlive 包装 | task(bgFn, self:runIfAlive(function(result) ... end)) |
| 延迟执行 | 使用 runIfAlive 包装 | Helpers.UI.runDelayed(500, self:runIfAlive(function() ... end)) |
| PageTool | PageTool 已自动处理,无需额外包装 | pageTool:setupLoadFunction() 内部已包装 |
| PageToolModel | PageToolModel 已自动处理,无需额外包装 | PageToolModel:refresh(key) 内部已包装 |
| Model 回调或网络回调 | BaseModel 已自动处理,无需额外包装 | model:load(params, callback) 内部已包装 |
| addListener | BaseModel 已自动处理,销毁时清除监听器 | model:addListener("event", handler) |
function SomeFragment:onDestroy()
-- chainUp 确保父类 onDestroy 自动调用
if self.model then
self.model:destroy() -- 设置 isDestroyed = true
self.model = nil
end
if self.webViewHelper then
self.webViewHelper:destroy() -- 子模块自行销毁
self.webViewHelper = nil
end
end
1.
isAlive/runIfAlive的使用范围子模块(如
WebViewHelper、自定义 Model 等)可以在内部实现自己的isAlive和runIfAlive方法,用于内部异步回调的安全包装。但外部调用方(如 Fragment)应统一使用BasePage提供的isAlive和runIfAlive方法,而非直接调用子模块的对应方法。原因:正确实现销毁链后,
BasePage会在onDestroy中统一销毁所有子模块,子模块的销毁状态与BasePage保持一致。外部通过BasePage的方法可以确保生命周期判断的统一性,避免因直接依赖子模块状态而导致的不一致问题。推荐做法:
子模块内部:实现私有
isAlive/runIfAlive供内部回调使用外部调用:使用
self:isAlive()或self:runIfAlive()(来自BasePage)Fragment/Activity:始终使用继承自
BasePage的生命周期方法2. BaseModel 已自动处理
BaseModel已自动对网络回调和监听器进行runIfAlive包装,子类无需关心。所有继承BaseModel的子类自动获得生命周期安全保护。3. 新增子模块的销毁检测
如果需要增加子模块(如
WebViewHelper),你需要正确实现销毁检测:
- 在
ctor中初始化self.isDestroyed = false- 在
destroy()方法中将self.isDestroyed = true- 所有异步回调在调用前和 callback 设置做相关判断
- 在当前被使用方的
onDestroy中调用子模块的destroy()方法子模块(如
WebViewHelper、自定义组件等)可以在内部实现isAlive和runIfAlive方法,作为快捷方式供内部异步回调使用:-- 子模块内部实现 function WebViewHelper:isAlive() return not self.isDestroyed end function WebViewHelper:runIfAlive(callback) if not callback then return function() end end return function(...) if self:isAlive() then callback(...) end end end -- 子模块内部使用 function WebViewHelper:loadData() NetWork.get(url, headers, self:runIfAlive(function(code, data) self:processData(data) end)) end在子模块实现完成后,建议使用
final关键字锁定isAlive和runIfAlive方法,防止外部继承时意外覆盖,例如:-- 锁定方法,禁止子类覆盖 WebViewHelper:final("isAlive", "runIfAlive")不要为子模块再抽统一的生命周期基类或共享库:组件的销毁条件与清理时机各不相同 (如
WebViewHelper的isAlive还要求webView非空、PageTool与持有它的 Model 销毁同步), 统一基类收不了这些差异,只会多一层间接。每个子模块按上述三件套手写即可,重复的十几行是可接受的。4. 网络请求必须包装
所有
NetWork.get/post/put/delete等网络请求,应在调用前和回调中正确使用runIfAlive。 注:部分未使用 BaseModel 的组件,如果涉及网络请求,请在调用设置 callback 自行添加runIfAlive包装。例如CollectionMoveSheet.show({..., onSuccess = self.runIfAlive(function() ... end), onError = self.runIfAlive(function() ... end)})
-- Fragment 中发起网络请求
function MyFragment:loadData()
NetWork.get("https://api.example.com/data", headers, self:runIfAlive(function(code, data)
if code == 200 then
self:updateUI(data) -- 仅在 Fragment 存活时执行
end
end))
end
-- 子模块实现示例(如 WebViewHelper)
function WebViewHelper:new(webView)
local self = {
webView = webView,
isDestroyed = false,
}
return self
end
function WebViewHelper:runIfAlive(callback)
return function(...)
if not self.isDestroyed then
callback(...)
end
end
end
function WebViewHelper:destroy()
self.isDestroyed = true
if self.webView then
self.webView.destroy()
self.webView = nil
end
end
main.lua → init_app → core/init → 欢迎页或主页。Router.go("answer", { id = "123" })PageToolModel → 实现 getInitialUrl、parseItem、createAdapter → Fragment 中调用 setupSingleBaseModel → 实现 load → Fragment 中手动调用并更新 UI掌握 PageToolModel、WebViewHelper 以及生命周期安全机制(runIfAlive)是快速开发大部分页面的关键。
社区评论
登录后即可分享使用体验,首条评论会先经过审核。前往登录