Cirno 本体开发
本文档介绍 Cirno Android 应用的项目架构和开发最佳实践。
Android 应用源码托管在独立仓库:
https://github.com/Adkimsm/Cirno
项目架构
技术栈
| 技术 | 用途 |
|---|---|
| Kotlin | 主要开发语言 |
| Jetpack Compose | 声明式 UI 框架 |
| Miuix KMP | MIUI 风格跨平台组件库 |
| LSPosed | 模块化 Hook 框架 |
| cgroup v2 | 内核级进程冻结 |
| ReKernel | 内核级 Binder/网络监听(可选) |
架构模式
LSPosed 模块化架构
Cirno 以 LSPosed 模块形式运行,通过 Hook 系统框架(system framework)实现功能。模块需要授予系统框架作用域。
内核-用户态双层架构
- 核心冻结逻辑在内核态通过 cgroup v2 实现
- 有 ReKernel 时:通过 Netlink 套接字实现内核级 Binder/网络监听
- 无 ReKernel 时:自动回退到用户态 Hook
核心模块
cgroup v2 后台冻结模块
通过写入 Linux cgroup v2 的 cgroup.freeze 控制文件,完全挂起后台应用的 CPU 调度。内核级实现,无法被应用层绕过,零开销解冻。
智能冻结决策模块
根据应用状态自动判断冻结/豁免:
| 状态 | 处理方式 |
|---|---|
| 可见应用 | 豁免 |
| 音频播放 | 豁免 |
| GPS 定位 | 豁免 |
| 录音中 | 豁免 |
| VPN 连接 | 豁免 |
| 普通后台 | 冻结 |
网络管控模块
冻结应用时销毁其网络连接。支持为特定应用保留连接以接收推送。网络解冻功能需要 ReKernel 支持。
电源管理模块
静默拦截冻结应用的 WakeLock,自动移除冻结应用的闹钟,防止后台应用阻止设备深度休眠。
ReKernel 内核集成模块
通过 Netlink 套接字与 ReKernel 内核模块通信,实现内核级 Binder 事务和网络事件监听。无 ReKernel 时自动回退到用户态 Hook。
厂商兼容层
自动检测并适配主流 Android 厂商的进程管理系统:
- 小米(Millet)
- OPPO / OnePlus(Hans)
- 三星
- 华为
- Vivo
- 努比亚
厂商 Binder 拦截在 ReKernel 激活时自动退让。
UI 模块
基于 Jetpack Compose 和 Miuix KMP 构建,支持硬件加速纹理模糊、三种导航栏风格、宽屏/折叠屏适配、全面屏手势集成、触觉反馈、Navigation3 动画过渡。
配置系统
- 黑名单/白名单机制
- 后台播放豁免、定位豁免、网络消息豁免
- 进程级冻结排除
- 所有配置热重载即时生效
- 支持配置备份/恢复
最佳实践
开发环境
- Android Studio 最新版
- JDK 17+
- Kotlin 1.9+
- Android SDK 34+
代码规范
Kotlin 风格
- 遵循 Kotlin 官方编码规范
- 使用
interface定义回调和契约 - 优先使用
data class表示数据模型 - 使用
sealed class表示受限类型层次
Compose 规范
- 组件使用
@Composable注解 - 状态提升到最近的父组件
- 使用
remember和mutableStateOf管理本地状态 - 避免在 Composable 中执行副作用
模块化原则
- 冻结逻辑与 UI 分离
- 决策引擎独立于具体 Hook 实现
- 厂商兼容层可插拔
测试策略
- 单元测试覆盖核心决策逻辑
- 集成测试验证 cgroup v2 操作
- 手动测试各厂商兼容性
构建与发布
- 通过 GitHub Actions 自动构建
- APK 命名格式:
Cirno-{version}-{buildNumber}.apk - 版本号遵循语义化版本规范
新增冻结豁免
1. Hook 侧:数据层
configs/policy/Capability.java — 新增枚举值
ALLOW_XXX(true), // isExemption = true
configs/settings/ApplicationSettings.java — 新增存储字段
public Set<String> xxxApps = new HashSet<>();
并在 ensureInitialized() 中添加 null 检查。
configs/checkers/AppConfigs.java — 三处修改
getCapabilityApps()新增 case 分支- 新增
isXxxAllowed(pkg, userId)getter - 新增
setXxxAllowed(pkg, userId, allowed)setter
互斥逻辑已内置于 setCapability():白名单开启时自动清除所有 isExemption=true 的豁免。
2. Hook 侧:冻结决策
utils/FreezeExemptionChecker.java — 在能力豁免区块新增判断
if (AppConfigs.isXxxAllowed(pkg, userId) && appState.isXxx()) {
return FreezeExemption.XXX;
}
configs/policy/FreezeExemption.java — 新增枚举值(含 reason 字符串和显示文本)
3. Hook 侧:运行时触发(可选)
如果该豁免有独立的运行时事件(如录音的 RecordingHandler),需在事件处理中检查开关:
if (AppConfigs.isXxxAllowed(pkg, userId)) {
FreezerService.thaw(appRecord);
}
若需要新hook,需要在/hook/android内在相关文件夹内加java文件,并在 /master/AndroidHooks.java 内使用相关构造函数。
hook相关的java文件应尽可能使用内部实现的hook框架,详见任意已实现的hook的java文件。如无法满足需求,也可直接调用xposed相关hook方法。
4. UI 侧:配置展示
entity/AppItem.java — 新增字段
public boolean xxxAllowed;
utils/PackageUtils.java — 两处修改
filter()中填充字段:item.xxxAllowed = AppConfigs.isXxxAllowed(pkg, item.userId);getAppListPriority()中加入特殊配置排序条件
ui/viewModel/AppItemCompose.kt — 两处修改
configured条件加入app.xxxAllowedStatusTag的when分支加入对应显示
5. UI 侧:开关控制
ui/ApplicationHome.kt — 三处修改
- 新增状态变量:
val xxx = remember { mutableStateOf(AppConfigs.isXxxAllowed(...)) } - 白名单 toggle 的
onCheckedChange中加入互斥回滚(prev/save/rollback 三项) - 新增
SwitchPreference,遵循标准模式:enabled = !white.valueonCheckedChange中检查白名单阻止 + 回滚逻辑
6. 字符串资源
res/values/strings.xml 和 res/values-zh-rCN/strings.xml — 新增开关标题
文件清单
| 文件 | 必须? |
|---|---|
Capability.java | ✓ |
ApplicationSettings.java | ✓ |
AppConfigs.java | ✓ |
FreezeExemption.java | ✓ |
FreezeExemptionChecker.java | ✓ |
| 运行时 Handler(如 RecordingHandler) | 有独立事件时需要 |
AppItem.java | ✓ |
PackageUtils.java | ✓ |
AppItemCompose.kt | ✓ |
ApplicationHome.kt | ✓ |
strings.xml (en + zh-CN) | ✓ |
核心约束
- 白名单互斥:
setCapability()已自动处理数据层;UI 层需在白名单 toggle 中手动回滚 - Binder 兼容:配置通过 JSON 整体序列化,新增字段自动兼容(Gson 忽略未知字段)
- 默认行为:新豁免默认关闭(
isExemption=true的开关不在任何 Set 中,hasCapability返回 false)