Skip to content

About

课堂笔记:课程笔记、待办、语音转写、AI 总结的 Android 原生应用。零第三方依赖,APK 约 187 KB。功能与交互参考 lkx478482771-star 的开源项目,原生 Java 重新实现。

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

53 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

课堂笔记 · Android 原生版

课程笔记、待办、语音转写、AI 总结。纯原生 Java 实现,零第三方依赖,APK 约 232 KB。 当前版本 1.8.1。

参考与致谢

功能构成与交互设计参考了 @lkx478482771-star 的开源项目 「课堂整理」:课程、笔记、待办三块的功能划分、数据字段与整体交互取自原作者的设计,在此致谢。

代码不是在该项目上修改,而是按同样的功能目标在 Android 原生环境下重新实现——本项目为原生 Java + Android SDK,不含 WebView 与前端框架,也不依赖任何第三方库。参考项目采用 Capacitor + WebView 方案,几项核心能力在 WebView 里无法工作(webkitSpeechRecognition 不存在,录音转写 整块失效;blob 下载静默失败,导出点了没反应),这也是改用原生实现的原因:

项目 参考项目(Capacitor + WebView) 本项目(原生 Java)
技术栈 Capacitor + WebView + 前端 JS 原生 Java + Android SDK,零依赖
数据存储 localStorage(JSON 整存整取) SQLite(增量读写 + 索引)
语音识别 / 录音 Web Speech API,WebView 不支持 系统 SpeechRecognizer + 原生 MediaRecorder
导入导出 a.download,WebView 里点不动 SAF 系统文件选择器
APK 体积 3.7 MB 约 159 KB

后续的功能迭代与问题修复(云转写上传的 MIME 修正、Windows 构建脚本 build-pc.sh、语音识别 兼容、设置页重构等)见下方「更新日志」。

图标与素材授权

应用图标里的鹿是 "Deer" by Caro Asercion,来自 game-icons.net, 以 CC BY 3.0 授权使用(该授权允许商用,要求署名)。 本项目在原图基础上做了两件事:按品牌色(蓝 #4F7DF7 → 紫 #9A6BEE → 粉 #FF6FA8)重新着色, 并归一化到 Android 自适应图标的安全区。图形本身未改动。

素材出处与授权逐条记在 tools/icon-source/SOURCES.md, 生成流程见 tools/icon-from-svg.py 与 tools/make-icons.py。

功能

  • 课程:增删改查、自定义标识色、归档(上完的课收进「已归档」,数据保留、随时恢复),笔记与待办计数实时同步
  • 笔记:标题 / 日期 / 正文 / 重点清单,支持置顶、展开阅读
  • 搜索:首页一次搜全部课程的标题、正文与重点清单;课程页只搜当前课程
  • 待办:优先级与截止日期,自动按「进行中 / 已完成」分组
  • 待办提醒:给待办设一个提醒时刻,到点弹通知、点通知直接跳到那门课的待办页
  • 全部待办:跨课程汇总未完成的事,按「已过期 / 今天 / 明天 / 本周内 / 以后」分段
  • 语音转写:系统语音识别实时出字;设备不支持时自动把录音上传云端转写(OpenAI 兼容接口)
  • AI 总结:接入任意 OpenAI 兼容服务或国内厂商原生接口(通义 / 文心 / 智谱等),生成摘要与重点,一键回写笔记
  • 备份:zip 完整导入导出(含配图;包内 data.json 仍是 classroom-v2 格式,与网页版互通),另可导出 Markdown;旧版 .json 也能导入(无配图,数据完整)

下载

从 1.0 起各版本签名一致,可直接覆盖升级,笔记数据不会丢。

语音识别在各家 ROM 上的差异(小米 / OPPO / vivo…)

系统语音识别能不能用,不只取决于麦克风权限。 国产 ROM 的识别服务普遍加了一道 「跨应用放行」判定,小米就是 AsrService 里的 CTA / 机型白名单(实测日志):

AsrService: handleCTAAndPerms: isCTAAllow=false isRecordPermGranted=true
                                               ↑ 麦克风权限是给足的
AsrService: onStartListening: isCTAAllow=false …
AsrService: onDestroy        ← 服务不放行,随即自毁,App 收到 ERROR_SERVER_DISCONNECTED

同为 HyperOS 4 / Android 17(SDK 37)的两台机器结果就相反,可见这是按设备、而不是按 ROM 版本的策略:

机型 OS 版本 结果
Redmi M2007J3SC OS4.0.0.7 实时识别正常出字
Redmi 22127RK46C OS4.0.0.26 isCTAAllow=false 直接拒绝并自毁

被拒的机器上,唯一的识别服务是 com.xiaomi.mibrain.speech/.asr.AsrService(isDefault=true, 且没装任何其它 RecognitionService),所以「默认服务优先、显式组件兜底」两条路落在同一个服务上, 它不放行就没有第二条可走。系统设置里没有能让第三方 App 通过的开关,只能去厂商那一侧 同意一次:

厂商 同意入口
小米 / 红米 小爱同学 → 我的 → 设置(隐私 / 跨应用语音识别)。MIUI 未实测,判定机制与澎湃 OS 相同
OPPO / 一加 小布助手 → 设置
vivo Jovi 语音 → 设置
华为 / 荣耀 小艺 → 设置
其他 系统设置 → 语音输入(android.settings.VOICE_INPUT_SETTINGS)

同意状态记在 Settings.Global 的几个键上(xiaoai_cta_change、 soundrecorder_cta_net_accepted、soundrecorder_cta_permission_accepted), 取值随机型 / OS 小版本变化,键名也可能随版本变动,所以 App 不去读它们猜状态, 只在识别真的被拒时把用户送到语音助手那边。

App 里的对应行为:

  • 识别被设备侧拦下(一次回调都没给过就被拒)时,录音页主动弹一个模态引导框(不是会消失 的提示条),主按钮「去同意」依次尝试:小米识别服务自带的授权弹窗 (com.xiaomi.mibrain.speech.cta,实测可被外部唤起)→ 各家厂商语音助手(小米 / OPPO / 一加 / vivo / 华为 / 荣耀 / 魅族 / 三星)→ 系统「语音输入」设置页。这些入口全部写进了 manifest 的 <queries>——targetSdk ≥ 30 的包可见性过滤会让没声明的包解析不到,点了等于没点。
  • 引导是主动的:授权是恢复实时识别的唯一出路,不能赌用户看得见一条小提示。除了录音时弹框, 「设置 → 语音转写设置」底部还有一个常驻的**「打开厂商授权页」**入口——用户当场点了「稍后」, 之后也能自己找到地方,而不是这条路只响一次。
  • 点「去同意」会清掉「被拦下」标记:授权很可能就在这一步完成,下次录音必须重新试系统识别, 不能被一次旧失败永久堵死。「拦下」标记本身也只记在进程内——重启 / 升级 / 换机后自动重试, 识别真的出过字也立刻解除;只记「零回调被拒」,偶发故障不记。
  • 无论哪种设备,录音都不会因为识别失败被丢掉:识别用不了时会话降级成纯录音继续跑, 结束后自动走云端转写。

云端转写(任何设备都能用的兜底)

设置 → 语音转写设置,三选一:

模式 说明
关闭 只用系统语音识别,不保留录音
服务端转写 把录音上传到自建服务转写,填 http://192.168.1.10:8080/v1 这类根地址
API 直连 把录音上传到云 API,填地址 + Key(OpenAI 兼容的 /audio/transcriptions)

两个设置页都有「测试连接(GET /models)」:向 {地址}/models 发一次 GET,当场告诉你地址 / Key 通不通,不用录完一节课才发现 404。注意要填根地址而不是完整接口路径——/audio/transcriptions 是转写接口,纯对话 API(如 DeepSeek)不提供它,填了也只会 404。

自建服务用 whisper.cpp、FunASR 等任意实现了 OpenAI 兼容转写接口的服务都可以。

代码结构

src/java/com/lamireuxp/classroom/,约 5500 行 / 21 个文件:

  • MainActivity:首页课程列表 / 搜索 / 统计 / 导入导出,课程菜单底部抽屉
  • CourseActivity:课程详情——笔记 / 待办 / 录音 / AI 总结
  • SpeechSession:语音会话——系统识别 + 原生录音,识别被拒时降级成纯录音
  • VoiceAuth:厂商授权页入口链(小米 CTA → 各厂商语音助手 → 系统语音输入设置)
  • Db / Backup:SQLite 数据层;JSON 备份导入导出、Markdown 导出(SAF)
  • Net / AiProto:网络层与接口协议——AI 总结 / 云转写 / 连接自检(HttpURLConnection);四种协议的差异与厂商预设都收在 AiProto
  • Prefs:设置存储——主题、转写模式与地址 Key、AI 配置(含转写 → AI 的单向同步)
  • SettingsActivity / TsSettingsActivity / AiSettingsActivity / BaseSettingsActivity:设置页与公共基类
  • Ui / Dialogs / Icons / Tip / Loading / ThemeSwitch:设计系统与控件(色 token、弹窗、图标、底部状态药丸、加载条、主题开关)
  • Dates / Id / Extract:日期、ID 生成、本地关键词提取(AI 不可用时的兜底)

更新日志

1.8.1

  • 图标换回第一版:应用内顶栏标志从设计稿的「鹿角 + 话筒 + 声波」换回第一版的 五角星(ic_brand 删除);启动图标换回 1.0 的未经量化版本(图形与 1.3 起发布的 完全一致,画质更准)。顺带恢复第一版有、1.2.1 被误删的 ic_stats 柱状图图标备用。

1.8

  • 界面整体重构(按 DESIGN.md 设计系统):学习 awesome-design-md 收录的 Notion / Apple / Linear 三家 DESIGN.md 后为本 App 定稿 docs/DESIGN.md, 并按它换血——浅色模式从冷白紫蓝改成 Notion 暖纸系(暖白底 #FAFAF8、 暖炭字 #37352F),深色改成中性深灰(不用纯黑);层级改用表面阶梯表达, 全面去卡片阴影(阴影只留给 FAB 与对话框);首页顶栏改大标题式 (动作行 + 28sp 大字),课程卡的色点改成 Notion 式左缘色条。
  • 笔记编辑页推倒重做(沉浸式):去掉「标签 + 输入框」的表单结构——标题 26sp 大字直接写、日期弱化小字点选、正文 17sp 宽行距铺满、重点/配图全靠留白 分隔;底部长驻工具栏(日期 / 重点 / 图片直达),保存统一在顶栏 ✓。
  • 待办功能详细化:待办支持编辑(原来建错只能删了重来,现在课程页与 「全部待办」页点任意一条进编辑页);新增备注字段(作业要求、考试范围, 列表行上直接露摘要);记录完成时刻(已完成分组显示「完成于 X」); 编辑保存后提醒自动重排。数据库 v5→v6,老库就地升级。
  • 新增 docs/DESIGN.md(本项目设计系统文档:七条原则、表面阶梯、字号阶梯、 组件约定),后续改 UI 照着它来。

1.7

  • 备份纳入配图:1.6 加了笔记配图,但 JSON 备份只覆盖文本——换机/重装后笔记内容 回来了,配图全丢。这一版把备份从纯 .json 改成 .zip:包内 data.json(仍是 classroom-v2 格式,与网页版互通)+ note-img/ 下被引用的配图文件。导入时按文件头 判断格式——新 zip 含配图,旧 .json 也能导(无配图,数据完整)。只打包被引用的 图片,孤立文件不带。
  • 设置页「导出 JSON 备份」/「导入 JSON 备份」改成「导出备份」/「导入备份」,导出 文件名 .json → .zip。
  • 课程归档:上完的课从课程菜单里「归档课程」,退出首页列表、搜索、统计、 「全部待办」,提醒跟着安静;数据一个不少,首页底部「已归档课程」入口随时进去翻、 一键恢复(回到原来的排序位置)。归档状态跟着备份走(导入导出都保留);在归档 列表里点开归档课时,标题带「(已归档)」标记。数据层 DB v4→v5(courses 加 archived 列,老库自动就地升级)。

1.6

  • 新建/编辑笔记、新建待办改成独立页面:原来是小弹窗,笔记的正文与重点清单在弹窗里 可写空间太小。现在和设置页同一套骨架(顶栏 + 滚动正文 + 底部保存按钮),长文本给足 书写空间;标题必填校验不过时页面不关、已填内容不丢。待办的优先级点选与提醒选择器 照旧,选了只重建表单那一块。
  • 拖动排序推倒重做:1.5 的拖动走的是框架的 startDragAndDrop,真机上三个问题叠在 一起——起拖即崩(拖动阴影的尺寸取不到,系统直接抛「Drag shadow dimensions must be positive」)、拖到列表末尾松手落不了位(被拖的行隐藏后列表缩短,指针已在容器之外, 系统按「取消」处理)、待办行完全拖不动(行本身没有点击行为,按下时触摸流被 ScrollView 截走,长按后影子冻在原地)。这一版改为容器自己拦截触摸流:长按起拖、 浮动卡片跟手、原行就地转 INVISIBLE 占位(列表总高不变,空档始终看得见)、其余行滑开 让位;落点按槽位判定,组内首尾都能到达;拖到边缘自动滚动;落库顺序在松手那一刻 从视图序列现读,不会与看到的不一致。
  • 首页课程列表也能长按拖动排序:数据层的 sort 字段当初就留好了,只差界面这一层。
  • 界面细节:首页课程列表改卡片式(圆角 + 微弱浮起 + 按压回弹);列表入场从「咣当 拍上来」变成淡入 + 上移的「浮入」(错峰,封顶 180ms);顶栏补一条极细分隔线;搜索框 带清除按钮;空状态图标垫一层 8% 主题色;首页课程数与笔记数统一成同一段弱化文字。
  • 修复回收站操作后页面空白:清空 / 彻底删除 / 恢复之后代码先 render() 再 buildUi(), 而 buildUi() 会重建整个内容视图——刚画好的内容被丢掉、它自己又不渲染,页面就空白 在那里,直到退出重进。这是 1.5 就存在的问题。
  • 换主题不再闪:课程页跟随系统切深浅色时从 recreate() 降为就地重建;窗口层 (状态栏 / 导航栏)的重着色统一收进基类——课程页原来漏了这一步,主题在别的页面被 改过时回来自动着色是失效的。
  • 搜索栏不再随列表一起重建:原来每增删一门课、每次回到首页,输入焦点和软键盘就 丢一次;现在它只在进页面时建一次。

1.5

  • 笔记与待办支持长按拖动排序:长按某一行拿起来——那一行变成一张带阴影的浮动卡片 跟着手指走,原行就地隐藏、列表实时收拢让位;拖过哪一行,其余行就滑开让出位置 (160ms 的过渡);松手即落位,不重绘(视觉顺序已经是最终顺序,所以没有跳变)。 拖到列表上下边缘会自动滚动,否则长列表里根本拖不到远处。排序落在数据库的 sort 字段上,退出重进、换设备都还在。 第一版是「被拖的行压暗待在原地、落点描个高亮」——能用但一点也不丝滑:手指带着一整行 系统影子、列表纹丝不动、松手整页重绘,三件事叠起来就是「拖着一个色块,松手咣当一下」。 这一版改成上面那样,三个要点: ① 起拖瞬间先截图再隐藏——GONE 的视图 draw 出来是空白,顺序反了影子就是空的; ② 只在拖动期间挂 LayoutTransition——它会让容器里所有子 View 的增删都带动画, 一直挂着的话正常重绘也会慢半拍; ③ 松手不重绘——隐藏的那一行就是空档,列表的视觉顺序已经是最终顺序。 几处其它的取舍: ① 只在同组内可拖——笔记的「置顶 / 未置顶」、待办的「进行中 / 已完成」各自成组。 列表排序是「分组字段 DESC, sort ASC」,跨组改 sort 不会改变实际位置,用户只会看到那一行 「弹回去」,比直接不许拖更让人困惑,所以跨组不给高亮、也不生效。 ② 搜索过滤时关闭拖动:列表只剩匹配项,重排会把没显示出来的条目的 sort 写乱。 ③ 新条目仍然出现在最上方(拿当前最小 sort 减一),保住加拖动之前的行为—— 否则长列表里新建完还得往下翻去找它。没拖过的老数据 sort 全是 0,回退到 created DESC, 所以升级后顺序完全不变。 实现上没有引入任何依赖:ItemTouchHelper 属于 androidx.recyclerview,引进来会同时破坏 「零第三方依赖」和当前「直接对着 android.jar 编译、不走 Gradle」的构建方式, 所以用框架自带的 startDragAndDrop + OnDragListener 自己实现(见 DragSort.java)。

1.4

  • 换用「侧视鹿头 + 声波」图标:原来的图标(鹿角 + 话筒 + 两对声波,正面构图)在 512 尺寸下好看,但启动器实际显示的是 48dp——那里最外一对声波只有 0.5px,会直接消失成灰雾, 话筒又是白填充描边、在白底上融进背景。根本原因是三组元素在 48dp 下只能读成 「一坨带角的紫色东西」。中途试过整只侧视鹿,问题变成元素太多:腿、蹄、尾在 48dp 上 全是噪点,立姿的直腿也显得生硬;而且整只鹿的高度被腿占掉,头反而小。 最终只留头 + 颈 + 一点肩,另加两道粗声波贴在嘴前表达「音」:两道都够粗 (48dp 下约 1.2~1.5px)不会断;也不另画话筒,多一圈实心色块等于把 48dp 的问题搬回来。 裁剪高度由脚本自动判定(找「宽度突增」的那一行 = 身体起点,再多留一点肩胸, 否则头会像悬空吊着)。鹿的素材取自 game-icons.net 的 “Deer”(作者 Caro Asercion, CC BY 3.0,见上方「图标与素材授权」),按品牌三色渐变重新着色;裁剪与声波是本项目做的。 顺带把图标资源从 98 KB 压到 45 KB:渐变图形产生的独特色数以万计,直接存真彩 PNG 很大, 量化到 256 色后肉眼无差(逐像素核过,且没有引入色带),APK 因此比 1.3 版还小。

  • 首页顶栏换回原标志:顶栏那枚五角星换成设计稿里那个「鹿角 + 话筒 + 声波」—— 应用图标换成侧视鹿是为了在 48dp 的启动器里立得住,而顶栏是应用内标识,旁边就是 「课堂整理」四个字,不承担「在三十个图标中被认出来」的压力,原设计在这里反而更有辨识度。 实现上转成了 Android 矢量(tools/svg-to-vectordrawable.py):原设计是描边渐变,而 VectorDrawable 不支持描边渐变,好在应用内图标本来就用 SRC_IN 整体套主题色、渐变没有意义, 改成纯色即可转矢量。外圈两道声波的 opacity=0.7 转成了 strokeAlpha, SRC_IN 只换颜色不换 alpha,那层「弱化」保住了。

  • 图标生成流程脚本化:tools/icon-from-svg.py(任意 SVG → 品牌渐变,含剥满画布底色) 、tools/icon-deer-sound.py(鹿 + 自绘声波合成品牌图形)与 tools/make-icons.py (→ 5 个密度的自适应前景 + 旧版方形 + 旧版圆形);共用逻辑在 tools/iconlib.py。 以前换一次图标要手算三种产物、五种密度的尺寸,现在两条命令。 脚本带尺寸自检:旧版图标必须按 48dp 生成,混用 108dp 会让像素多出 5 倍、密度桶对不上, 而且图形比例看着完全正常,只有比对尺寸才能发现。

  • 回收站:删除不再是真的删掉。课程、笔记、待办都先进回收站(设置 → 回收站), 可以逐条恢复,也可以彻底删除 / 清空。整门课删掉时,它的笔记与待办会跟着一起进回收站, 恢复课程能把它们原样凑回来——做法是级联删除时给整批写同一个时间戳,恢复时按它捞回一组, 这样既不会漏掉随课程删的笔记,也不会顺手复活用户此前单独删过的那些。 回收站里的内容不参与统计、搜索、待办汇总,也不会触发提醒。 两个连带问题也处理了:删除时撤掉闹钟(否则到点会弹一条已经不存在的任务); 先单独恢复一条笔记、而它的课程还在回收站里时,把课程一起恢复—— 否则这条笔记「活着但无处可去」:课程列表按删除标记把它藏了,从搜索结果点进去 又因为课程取不到而直接关页面。确认框与提示语里的「不可恢复」也一并改掉了, 现在会写明「删掉的内容会放进回收站」,并指路去哪恢复。

  • 待办提醒:新建待办时可以设一个提醒时刻(系统的日期 + 时间选择器,不用手输格式), 到点弹通知,点通知直接进那门课的待办页。几处容易漏的地方都处理了:勾掉待办、删待办、 删课程都会顺手撤掉对应的闹钟与已发出的通知(否则到点会弹一条已经不存在的任务); 开机后重排(AlarmManager 的闹钟不跨重启,且部分 ROM 在被「强制停止」时也会清掉它, 所以主页面每次启动再兜底重排一次);导入备份后重排(导入是整体替换,旧闹钟全作废)。 提醒时刻也写进待办行的副标题,设没设上一眼可见。 故意没用精确闹钟:setExactAndAllowWhileIdle 要 SCHEDULE_EXACT_ALARM,而 Android 14 之后 这个权限对新装应用默认是拒绝的,被拒之后提醒会彻底不响——比晚几分钟糟得多。 改用 setAndAllowWhileIdle,不需要任何权限且允许在 Doze 里触发,代价是系统可能挪后几分钟。

  • 全部待办页:跨课程汇总所有未完成的待办,按截止日期分成 已过期 / 今天 / 明天 / 本周内 / 以后 / 无截止日期 六段,段内高优先级在前—— 回答的是「我接下来要交什么」,而不是「我做了多少」。入口是首页那张统计卡(整卡可点, 右侧加了「全部 ›」);没有待办时这张卡也照样出现,否则这个页面就没有入口了。 每行带课程色点与课程名,点进去到那门课的待办页签;勾选框就地完成。

  • 待办排序按截止日期:原来课程内的待办只按创建时间倒序,最近记的排最前, 最紧急的反而沉在底下。现在「全部待办」页先把没有截止日期的排到最后,再按日期升序。

  • 修好首页那个一直没生效的搜索框:它画在那儿、能输入,但一个字都不搜——query 只有 三处出现(声明、回填、赋值),没有任何一行拿它去过滤,Db 里也压根没有跨课程的查询, 当初是界面先画了、数据层没跟上。现在补齐 Db.searchNotes()(一条 LEFT JOIN 扫全库, 标题 / 正文 / 重点清单任一命中),首页接上结果列表:每行给出所属课程名与课程色点 (跨课程搜出来的标题不标明出处,用户得逐个点进去确认,反而比翻列表更慢)、日期与正文摘要。 搜索态是「专注模式」——统计卡与课程列表同时隐去,免得分不清哪块是结果。

  • 搜索结果可以直接跳到位:点一条结果会打开所属课程,把那条笔记展开并滚到眼前, 而不是只跳到课程页让用户自己再找一遍。滚动用 onPreDraw 拿布局后的真实坐标异步做, 坐标按父链逐级累加到 ScrollView 的直接子节点(getTop() 是相对父容器的,只取行自己的 会漏掉 TabBar 与搜索框那一段高度)。

  • 课程页的搜索也覆盖重点清单:原来只查标题与正文,「中值定理」这类只写在重点里的词 搜不到;现在两处共用同一套匹配范围,同一个关键词在首页和课程页结果一致。

  • 构建脚本不再吞掉编译错误:build-pc.sh 的 javac 那一步原来写了 2>/dev/null, 本意是挡掉「使用了过时的 API」这类无害提示,代价却是编译错误也一起没了——脚本只剩一句 退出码 1,报错信息一个字都看不到,只能手工照着那行命令重跑一遍。现在提示原样输出。

1.3

  • AI 总结支持四种接口协议:原来只认 OpenAI 那一种形状(POST {地址}/chat/completions + Bearer + choices[0].message.content),等于只能连 DeepSeek 那类服务。现在可选 OpenAI 兼容 / 通义千问(原生)/ 百度文心 / 自定义,字段跟着协议变。各家的差异比想象中大: 通义的消息在 input.messages、正文在 output.text;文心要先拿 API Key + Secret Key 换 access_token(参数挂在查询串上)、模型名嵌在 URL 路径里、system 是顶层字段,而且 出错也返回 HTTP 200——只看状态码会把「Access token invalid」当成一次成功的总结。 自定义协议可以自己填请求路径、鉴权方式与取值字段,覆盖没有预设的厂商。
  • 13 条厂商预设:DeepSeek / 通义(兼容与原生)/ 智谱 / Kimi / 混元 / 豆包方舟 / 讯飞 / 百度(千帆与老接口)/ 硅基流动 / OpenAI / 自建,地址都实测过(无 Key 请求返回 401 说明 路径存在);拿不准的模型名不硬塞,只写进备注,配合下面的「读取可用模型」挑。
  • 读取可用模型:填好地址和 Key 后直接拉 GET /models 列出清单(带过滤框),点一个填进 模型框。厂商的模型名会下架——智谱的 glm-4-flash 现在就已经查不到,照旧文档填会一路 报「模型不存在」。同一个账号里不同档位的可用性也不同(有的会回「余额不足」)。
  • 还没有 API Key?:按厂商列出控制台地址,点一行用浏览器打开,省得去搜「XX 的 Key 在哪申请」。页面上写明 Key 只存在本机私有设置里、不会上传。
  • 待办进度按优先级分行:主页面统计卡每行一个优先级(色点 + 「高 0/1」+ 进度条), 颜色为高 = 红 / 中 = 主题色 / 低 = 绿;每门课程的待办页也有同一套,只统计当前课程。 顺带修掉一个存在已久的问题:原来的单色进度线无论完成多少都画满——它用一个「权重 = 完成比」的子 View 表示进度,而 LinearLayout 是按权重之和分配剩余空间的,独苗子 View 权重再小也会吃掉整段。
  • 「新建」统一成右下角加号:课程页原来靠页签下面两个文字按钮(新建笔记 / 新建待办), 现在和主页面一样是右下角加号,点一下建当前页签对应的东西;课程页的搜索框也移到页签下面 (搜索只作用于当前那一类内容)。主页面则把搜索框挪到待办进度前面。
  • 优先级改成点选:新建待办原来要在输入框里敲 high / medium / low,敲错一个字母会被 静默改写成「中优先级」,而且那套颜色也没法在输入框里表达;现在是横排三段加对应颜色的圆点。
  • 全局纵向收紧:纵向间距统一 ×0.78(左右留白不动,横向的呼吸感保留),可点区域高度 保底 44dp。同时修掉顶栏重复留状态栏高度的问题——系统已经按状态栏把内容让开了,代码里 再补一次就是两份留白,整页凭空低 30dp。
  • 构建链两处修复:d8 把每个 .class 都当参数传,class 一多就撞上 Windows 命令行长度 上限(报出来只有一句「命令行太长」),改成先打成 jar 再交给它;签名指纹核对那行 grep SHA256 一直是空操作——中文环境下 keytool 打印的是「证书指纹 (SHA-256)」(带连字符), 而且它的输出不是 UTF-8,grep 会当二进制只回一句 "Binary file matches"。

1.2.4

  • 两个 API 设置不再重复填:保存「语音转写设置 → 服务端 / API 直连」时,默认把服务地址和 Key 单向同步给「AI 总结」设置(模型名不动,各自保留)。同步带开关可关;AI 总结里的修改 不会反向影响转写。转写 Key 留空时同步也不覆盖 AI 总结已有的 Key。
  • 新增「测试连接」:两个设置页都有,向 {地址}/models 发一次 GET,当场告诉你 地址 / Key 通不通,不用录完一节课才发现 404。没填 Key 的 401 会明确说「服务在线,就差 Key」。
  • 转写 / AI 报错全部翻译成人话:UnknownHost、证书、超时、明文拦截等网络异常不再抛英文原文; 404 会提示「转写走 /audio/transcriptions,纯对话 API(如 DeepSeek)不提供这个接口」—— API 直连跑不通,多数就是这个原因。

1.2.3

  • 语音授权改为主动弹出:识别被厂商策略拦下(小米 CTA / 机型白名单这类,表现是给了麦克风权限 但识别服务零回调直接拒绝)时,不再只在一条会消失的提示条上挂「去授权」按钮——录音页主动弹模态 引导框,一键把授权页拉起来;「设置 → 语音转写设置」底部同时加了常驻的「打开厂商授权页」 入口,当场点了「稍后」的用户之后也能自己找到。授权入口新增小米识别服务自带弹窗 (com.xiaomi.mibrain.speech.cta)为第一优先,厂商语音助手兜底,并补进 manifest <queries>; 点「去同意」后清除「被拦下」标记,下次录音重新试系统识别。

1.2.2

  • 重做轻提示:原来每次操作反馈都是一条通栏色带(占满屏宽、8dp 直角、48dp 大方块关闭按钮), 一句「课程已创建」也要占近 80dp 高的紫色横幅,又重又丑。改成底部居中的状态药丸: 宽度跟内容走、16dp 圆角;成功 = 深色(深主题为浅灰)药丸 + 绿对勾,错误才整条用 error 色, 颜色只花在该注意的地方;同屏只留一条,新的顶掉旧的,不再叠罗汉。
  • 操作反馈统一成语义态:所有「写入完成」提示(创建 / 更新 / 删除 / 保存 / 导入导出)统一走 成功态绿勾,不再和中性信息混用一种样式。
  • 修复课程菜单抽屉选完不关:点「更多 → 删除课程」时底部抽屉不关,确认框关掉后会露出一层 内容对不上的 stale 抽屉。现在选中动作即收起抽屉。

1.2.1

这一版是代码清理与写入路径优化,没有改动任何界面与功能行为——你在 1.2 里看到的东西, 1.2.1 一模一样。

  • 修复「按下缩放」会挤掉触摸监听:Ui.pressScale() 原来用 OnTouchListener 实现按下缩放。 Android 没有 getOnTouchListener(),谁先 setOnTouchListener 谁就永久占住这个位置, 第二个想监听触摸的人只能把它顶掉——两个需求只能活一个。改成 StateListAnimator (挂在 state_pressed 上的系统正规机制),完全退出触摸链路,涟漪 / 点击 / 将来任何手势都不再冲突。 行为肉眼无差别。
  • 批量写入优化:
    • 单条写入 saveCourse/saveNote/saveTodo 不再「先 SELECT 判存在」,改用 update() 返回的 受影响行数判定(rows == 0 即不存在):每条语句少一次查询,同时消除了「查完到写之间 那行被删掉」的竞态。
    • 新增 Db.replaceAll():导入专用,清空 + 重建全程一个事务,用预编译语句批量写,且完全不查 存在性。顺带去掉了外层事务里嵌套内层事务:deleteCourse() 自带事务,嵌在外层里一旦失败 只回滚内层、外层照常提交,会留下半新半旧的库。
    • 实测(10 门课 / 400 条笔记 / 100 条待办):82.8 ms → 39.2 ms,约 2.1×。
  • 清理死代码:删掉 23 个无调用点的方法、未引用的资源与失效 import,净减约 124 行, APK 无功能变化。

1.2

  • 修复录音丢失:识别服务中途失效(被拒 / 连接断开 / 启动抛异常)时,会话降级成纯录音继续跑, 已经录下的音频不再被删。原来这类错误一律按致命处理,调用方 cancel() 会把录音文件一并删除—— 识别出的毛病,赔进去的是用户整段录音。真机对照:旧版在识别被拒后 93 毫秒就 MediaRecorder: stop+ 并删文件,新版继续录到用户手动结束。
  • 修复导入备份丢数据:导入改成「先全量解析、再单事务清空重建」。损坏的备份文件不再清空已有数据 (原来先删后写,文件里任意一条记录不合法,用户拿到的是「导入失败」加一个已经被清空的库), 并校验 format 字段、修掉空 id 在主键上互相覆盖的问题。真机对照:同一个损坏文件,旧版把课程 清空并替换成半个文件,新版提示「第 2 门课程不是有效记录」且数据一字未动。
  • 设备侧识别放行:识别被厂商策略拦下时(小米 CTA / 机型白名单,见上文),提示条给「去授权」 入口直达厂商语音助手,并把「拦下」记入进程,后续录音不再反复戳识别服务;文案不再引导用户去 系统设置里改默认识别服务(那条路在这些机型上走不通)。新增 VoiceAuth 与 manifest <queries> 声明。
  • 提示条只在真的有出路时才挂按钮:「还能继续录」的那支不再错挂「去设置」。

1.1

  • 修复语音识别无法启动:Android 11 起启用的包可见性过滤会让应用查不到系统识别服务, 导致识别直接不可用。补上 <queries> 声明,并改为「系统默认识别服务优先、显式组件兜底」, 兼容国内 ROM 未设置默认识别服务的情况。
  • 修复误导性的权限提示:原来把识别服务返回的错误码直接说成「缺少麦克风权限」, 而实际权限是给了的。现在会先自查权限,真缺才说缺;识别服务无响应时如实提示, 不再出现「界面显示正在录音、实际谁都没在录」的假面板。
  • 新增主题模式:跟随系统 / 浅色 / 深色,默认跟随系统。顶栏开关改为按下即生效。
  • 设置改为独立页面:不再使用底部抽屉,AI 设置与语音转写设置同样拆成独立页面。
  • 界面与动效:统一缓动曲线与时长档位、按钮圆角改为 8dp、分段控件选中态重做、 笔记行补展开箭头、录音状态改为脉冲指示、联网期间补加载条。

构建

  • Windows:./build-pc.sh(需 JDK 17 + Android SDK build-tools 34)
  • src/build.sh 是早期在手机上直接构建的旧配方(签名口令已失效),留作参考,不是发布路径。

签名信息

所有版本(1.0 起)均使用同一把签名密钥,用户可正常覆盖升级。

项目 值
文件名 keystore.jks
密码(storepass / keypass) classroom-v2
alias classroom
算法 RSA 2048 / SHA256withRSA
有效期 2026-09-20 → 2056-09-12(30 年)

证书指纹(SHA-256)—— 发布前请核对:

32:A8:95:D3:77:4D:CA:F7:83:AB:4B:F5:1A:09:7E:E0:6C:B3:63:24:64:5B:87:DA:82:C9:8C:35:09:7E:E8:B0

核对命令:

keytool -list -keystore keystore.jks -storepass classroom-v2 | grep -a -i 'SHA-256\|SHA256'
apksigner verify --print-certs build/课堂笔记-v*.apk | grep 'SHA-256 digest'

中文环境下 keytool 打印的是「证书指纹 (SHA-256)」——带连字符。只 grep SHA256 会一条都搜不到,检查看起来「跑过了」但其实什么都没核对。输出里的中文也不是 UTF-8,-a 让 grep 按文本处理,否则只会回一句 "Binary file matches"。

⚠️ 密钥必须长期存档(云盘 + 本地各一份)。 Android 只允许「签名一致」的 APK 覆盖升级;一旦密钥丢失,此后所有版本都变成 新签名,用户必须卸载重装并会丢失数据(除非先导出备份)。

⚠️ build-pc.sh 在找不到 keystore.jks 时会静默生成一把新的。 密钥若因故不在原目录,跑脚本会悄悄换掉签名,而 apksigner verify 不会报错 —— 它只验签名有效性,不比对历史指纹。发布前务必按上面的命令核对指纹。

About

课堂笔记:课程笔记、待办、语音转写、AI 总结的 Android 原生应用。零第三方依赖,APK 约 187 KB。功能与交互参考 lkx478482771-star 的开源项目,原生 Java 重新实现。

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages