Cirno 本体开发

本文档介绍 Cirno Android 应用的项目架构和开发最佳实践。

Android 应用源码托管在独立仓库:https://github.com/Adkimsm/Cirno

项目架构

技术栈

技术用途
Kotlin主要开发语言
Jetpack Compose声明式 UI 框架
Miuix KMPMIUI 风格跨平台组件库
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 注解
  • 状态提升到最近的父组件
  • 使用 remembermutableStateOf 管理本地状态
  • 避免在 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 — 三处修改

  1. getCapabilityApps() 新增 case 分支
  2. 新增 isXxxAllowed(pkg, userId) getter
  3. 新增 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 — 两处修改

  1. filter() 中填充字段:item.xxxAllowed = AppConfigs.isXxxAllowed(pkg, item.userId);
  2. getAppListPriority() 中加入特殊配置排序条件

ui/viewModel/AppItemCompose.kt — 两处修改

  1. configured 条件加入 app.xxxAllowed
  2. StatusTagwhen 分支加入对应显示

5. UI 侧:开关控制

ui/ApplicationHome.kt — 三处修改

  1. 新增状态变量:val xxx = remember { mutableStateOf(AppConfigs.isXxxAllowed(...)) }
  2. 白名单 toggle 的 onCheckedChange 中加入互斥回滚(prev/save/rollback 三项)
  3. 新增 SwitchPreference,遵循标准模式:
    • enabled = !white.value
    • onCheckedChange 中检查白名单阻止 + 回滚逻辑

6. 字符串资源

res/values/strings.xmlres/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)