一次对话框库重构的完整审计:AestheticDialogs 2.0 重写的真实原因与 1.x 的 8 个致命问题

📅 2026/8/25 17:25:46
一次对话框库重构的完整审计:AestheticDialogs 2.0 重写的真实原因与 1.x 的 8 个致命问题
一次对话框库重构的完整审计AestheticDialogs 2.0 重写的真实原因与 1.x 的 8 个致命问题【免费下载链接】AestheticDialogs An Android Library built with Jetpack Compose for fluid, beautiful, custom Dialogs.项目地址: https://gitcode.com/gh_mirrors/ae/AestheticDialogsAestheticDialogs 是一个基于 Jetpack Compose 构建的 Android 对话框设计系统dialog design system。2.0 版本没有对 1.x 打补丁而是一次彻底重写。这篇文章基于项目仓库里保留的官方架构审计文档docs/ARCHITECTURE_AUDIT.md带你逐条复盘重写背后的真实原因以及 1.x 被判定为必须重建的 8 个致命问题——每一个都来自审计报告原文而非事后归因。为什么重写1.x 的 717 行单文件真相1.x 的全部代码只有一个文件AestheticDialog.kt717 行一个Builder类一个show()方法里面是一个 8 分支的when (dialogStyle)。每个分支做的事几乎一样——inflate 一个 XML 布局、用 ViewBinding 绑定、ContextCompat.getColor手动上色、new 一个AlertDialog最后再伸手进alertDialog.window调 gravity、背景和尺寸。围绕这一个文件是这么一大票资源类别1.x 数量备注公开 Kotlin 类型6Builder、DialogStyle等XML 布局10每个风格一个Drawable4434 个 XML 形状 10 个 PNG 位图动画 XML3016 种动画的进出场配对窗口动画样式16每种动画一个单元测试2Android Studio 模板断言2 2 4依赖是appcompatcardviewcore-ktxminSdk 19JitPack 分发没有 CI、没有 lint 配置、没有版本目录、没有 ProGuard 规则。视觉识别度确实不错——但支撑它的一切都需要重建。1.x 的 8 个致命问题来自审计报告的清单1.show()和dismiss()返回一个全新的无关实例审计报告里最经典的一个 API 陷阱fun dismiss(): AestheticDialog AestheticDialog()——它 new 了一个空的对象返回把调用者手里的句柄身份直接丢弃了。show()同样如此。也就是说你拿到的句柄什么都关不掉。2.setDuration()会在主线程上崩溃setDuration把{ dismiss() }post 到一个Handler上但这时show()还没给alertDialog赋值。如果先调用.setDuration(n)却没调用.show()或者时长短于到达show()的时间主线程直接抛UninitializedPropertyAccessException。雪上加霜的是Builder持有Activity并把它交给点击监听器任何被保留的监听器都会让 Activity 泄漏并且 Activity 销毁时没有任何机制取消 Handler 回调。3. 横幅不是模态对话框却被渲染成模态对话框Toaster、Rainbow、Connectify、Emoji、Emotion 五种贴边横幅在 1.x 里被当作AlertDialog显示Gravity.TOP。后果是一条信息性 toast 会变暗屏幕、抢占焦点、挡住背后内容的触摸、吞掉返回手势。而且 Toaster 的标题字号写的是18dp而不是18sp——用户系统字体设置被完全忽略。2.0 的解法五种横幅全部改由AestheticNotificationHost承载不再开窗口——屏幕保持焦点、返回手势和触摸输入全程不受影响。4.DRAKE风格一个版权地雷DRAKEDrake 反应梗图对话框内置的两张 PNG是一段受版权保护的音乐视频的截帧被重新分发在一个 Apache-2.0 开源库里——仅此一条就足以判死刑。此外文字直接烧录在位图里无法本地化、读屏软件读不出来而这个风格还会静默忽略setTitle/setMessage。2.0 已将其移除docs/MIGRATION.md建议用FeedbackDialogUiModel.Flat替代。5. 视图代码里直接调用SimpleDateFormatEMOTION风格在视图代码内部调用SimpleDateFormat(HH:mm)Calendar.getInstance()。这意味着忽略用户的 12/24 小时制偏好、忽略 locale、使用默认时区、每次显示都新建一个 formatter而且组件的输出每分钟都在变——让它根本无法做截图测试。2.0 改为由调用方传入预格式化的timestamp格式化时间是产品决策不是组件决策。6. 固定 300×290dp手机时代的假设FLAT风格的尺寸写死为 300×290dp超过三行的内容被静默截断而且手机和平板上是同一个尺寸。同样的毛病还有FLASH的渐变只做了 success/error 两种传WARNING会静默渲染成错误渐变setLayout(WRAP_CONTENT, fixedDp)假设字体不会被缩放Gravity.TOP横幅假设没有挖孔屏。2.0 的回答是自适应宽度问有多少空间而不是这是不是平板。可用宽度对话框宽度 600dp铺满两侧各留 24dp600–840dp480dp≥ 840dp560dp7. 无障碍基本是空白没有paneTitle读屏软件根本不播报对话框的存在关闭图标没有 content description且点击目标只有 30dp低于 48dp 下限状态色直接当标题文字色用warning 在白底上对比度不过 4.5:1横幅没有 live region没有任何键盘或焦点处理16 种窗口动画里有SPIN、WINDMILL、SPLIT、DIAGONAL这些重旋转过渡且没有任何方式减弱它们——而移除动画辅助功能设置存在的意义正是防这个。8.Keep 44 个 DrawableR8 删不掉APK 只变大Keep加在整个类上意味着消费者即使用不到那 8 个风格里的 7 个R8 也一个都删不掉。44 个 drawable其中 10 个位图和 30 个动画文件每个使用方全量携带哪怕只用其中一种。再配上每次显示都新建 formatter、inflate 整棵视图树的运行时分配以及零 CI、零 lint、模板测试的工程质量底座——这就是审计报告给出的完整账单。2.0 重写四层架构与 7 个对话框家族2.0 是一次 Jetpack Compose 重写没有兼容层、没有弃用路径1.x 的 API 接收Activity、inflate XML、返回句柄这些东西在 composable 里都没有意义所以2.0.0是主版本号——每个调用点都要改。架构上它采用四层设计文件夹结构 1:1 镜像Component (public) AestheticConfirmationDialog — 按 UI model 分发 ↓ Variant (internal) ConfirmationDialogDestructive — 解析语义 ↓ Primitive (internal) DialogFramePrimitive — 窗口、遮罩、宽度、无障碍 ↓ Tokens (public) AestheticColors / AestheticSpacing / AestheticMotion …边界靠编译器规则而非约定强制执行explicitApi()让每个公开声明都必须是刻意的internal让消费者根本无法 import 任何 variant 或 primitive。所有模态对话框共用唯一的DialogFramePrimitive位于aestheticdialogs/src/main/java/com/thecode/aestheticdialogs/primitives/它统一拥有窗口、自适应宽度、进场过渡和无障碍 pane——这正是为了杜绝 1.x八个对话框八种关闭行为的老病。状态所有权也被重新划分库拥有长什么样调用方拥有是否显示、显示什么。没有show()没有dismiss()对话框在组合里就存在每个信号包括Dismissed都只是请求由你决定响应。除了重建 1.x 的 Flat/Flash 反馈对话框和五种横幅2.0 还新增了 1.x 从未有过的五种对话框模式确认confirmation、告警alert、选择selection、富内容content、输入input。升级到 2.0 后你具体得到什么自适应宽度横屏手机、半开的折叠屏、桌面自由窗口全部正确真正的深色模式从布尔值变成主题跟随系统设置一处包裹全局生效减弱动态效果系统动画比例为 0 时所有过渡自动退化为切而非更短的动画见tokens/AestheticMotion.kt的enabled开关48dp 触控目标 paneTitle live region读屏软件播报对话框横幅按 Assertive/Polite 级别播报全部sp字号布局在 200% 字体缩放下经过截图测试验证零位图资源每个标记都是AestheticGlyph的单位正方形几何图形任意尺寸缩放不变糊加载态与空/错误态isConfirming true直接在确认按钮上显示 spinner 并锁定整个对话框工程底座二进制兼容性校验apiDump跟踪公开 API、lint、截图测试基线在aestheticdialogs/src/test/screenshots/、Maven Central 发布minSdk提升到 24。而没有变的是视觉识别度状态色还是 1.x 的色相大圆角还是那个圆角Flat/Flash 的轮廓一眼可认。一个名叫 AestheticDialogs 的库靠外观吃饭重建的是它下面的一切。1.x 用户如何迁移三件大事1.x 继续可用还没上 Compose 的可以留在1.3.8它只是不再开发2.0 不会破坏它可见性归你.show()/.dismiss()消失对话框在组合里即存在深色模式是主题、横幅不是对话框setDarkMode(true)改为包裹AestheticDialogsTheme五种横幅改由AestheticNotificationHost承载。逐属性的对照表.setTitle→title、.setCancelable(false)→DialogDismissBehavior.Blocking、.setDuration(ms)→autoDismissMillis等完整收录在docs/MIGRATION.md。迁移后建议重点检查每个setCancelable(false)的地方是否有可见的退出动作阻塞且无出口等于陷阱、原来被静默截断的长文本现在会完整显示、大屏幕上对话框不再一律 300dp。审计源码在哪里看完整审计报告本文全部问题的出处docs/ARCHITECTURE_AUDIT.md2.0 架构详解四层职责、为什么模态对话框只进不出场docs/ARCHITECTURE.md逐属性迁移对照docs/MIGRATION.md共享原语目录DialogFramePrimitive、BannerPrimitive等aestheticdialogs/src/main/java/com/thecode/aestheticdialogs/primitives/设计令牌颜色、间距、圆角、动效aestheticdialogs/src/main/java/com/thecode/aestheticdialogs/tokens/这份审计之所以值得读是因为它回答了一个每个开源项目都会被问、却很少有人认真回答的问题为什么现在长这样而审计者把答案写进了仓库因为这个问题比在场的所有人都活得久。【免费下载链接】AestheticDialogs An Android Library built with Jetpack Compose for fluid, beautiful, custom Dialogs.项目地址: https://gitcode.com/gh_mirrors/ae/AestheticDialogs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考