Android Architecture Templates架构解析:对标大厂的高效模块化架构模块实现
从零搭建一套可对标大厂的 Android 基础框架我踩过的坑和最终的解决方案“如果这个项目对你有帮助请在 GitHub 上给我一个 ⭐ Star —— 你的支持是我持续维护的最大动力”一、我为什么要“重复造轮子”去年年底我接手了一个“历史悠久”的 Android 项目冷启动 3.2 秒APK 体积 47MB代码里充斥着GlobalScope.launch、Log.d硬编码、以及让人头皮发麻的“万能工具类”。每次加一个新功能我都感觉在雷区里跳舞。更重要的是这个项目使用了ARouter 的 kapt 方案每次编译都在 3 分钟以上。团队里 6 个人每天都在抱怨“build 太慢了”但没有人敢动——因为“能跑就行”。所以我决定做一件事从零搭建一套全新的 Android 基础架构目标是“开箱即用、性能极致、代码优雅”。最终的结果是指标结果冷启动耗时380msRelease APK 体积9.59MB编译速度KSP 替代 kapt提升60%模块数量14 个核心层/服务层/中间件/业务层代码行数~5000 行纯框架这篇文章我会把其中的关键技术点、踩过的坑、以及最终的解决方案完整分享出来。框架适用性这套框架经过精心设计和实战验证可直接应用于日活千万级别的 App 生产环境。快速上手具体使用方式请查阅 GitHub 仓库中的 README 文档。框架已封装了所有底层复杂性开发者无需关注基础设施细节可以完全专注于业务逻辑的开发。持续维护我会持续维护并升级此框架未来计划增加对高频业务组件的二次封装进一步提升开发效率。开发规范业务开发应遵循模块化原则。请在项目中创建独立的feature模块并在其下按功能划分子模块。开发时只需引入所需的基础框架模块即可。请务必注意所有业务代码都应置于feature模块中严禁在壳工程app module内直接添加代码。。二、项目整体架构MVVM 模块化分层先说一下整体架构设计。我采用了四层分层结构严格遵循“上层依赖下层”的原则┌─────────────────────────────────────────────────────────────┐ │ features (业务功能层) │ │ 登录/首页/个人中心等业务模块可独立编译运行 │ ├─────────────────────────────────────────────────────────────┤ │ middleware (中间件层) │ │ 路由 (ARouter KSP) 权限申请 (Permission) │ ├─────────────────────────────────────────────────────────────┤ │ service (基础服务层) │ │ 启动调度 (DAG) 崩溃捕获 (CrashHandler) 日志/埋点 │ ├─────────────────────────────────────────────────────────────┤ │ core (核心基建层) │ │ base/network/storage/cache/common/ui - 纯基础设施 │ └─────────────────────────────────────────────────────────────┘依赖方向铁律不可逆features→middleware→service→corecore:common绝对不能依赖core:storage纯工具层禁飞区三、第一大亮点KSP 路由 ViewBinding 兼容性突破真正的行业痛点3.1 背景ARouter 的 kapt 方案太慢了在传统方案中ARouter 使用kapt生成路由表。随着模块数量增加kapt的编译时间呈指数级增长。在我之前的项目中仅路由表的生成就占用了 1.5 分钟的编译时间。解决方案将kapt替换为KSPKotlin Symbol Processing。// build.gradle.ktsplugins{id(com.google.devtools.ksp)}ksp{arg(router.moduleName,project.name)}dependencies{implementation(project(:middleware:router-annotations))ksp(project(:middleware:router-compiler))}切换后编译时间从 3 分钟降到了 1.2 分钟提升约 60%。3.2 翻车现场KSP 找不到我的 Route 注解编译速度确实快了但我遇到了一个更隐蔽的问题KSP 的getSymbolsWithAnnotation找不到某些类的Route注解。具体现象是普通类如SimpleClass的Route能被正常扫描到。继承BaseActivity的类如LoginActivity的Route却被静默跳过。经过 3 天的排查我终于定位到了根因classLoginActivity:BaseActivityActivityLoginBinding,LoginUiState,LoginEvent,LoginViewModel()BaseActivity的泛型参数中包含了ActivityLoginBinding这是一个由 Android Gradle Plugin 在编译期生成的类。而 KSP 的运行时机早于 AGP 的 ViewBinding 生成导致 KSP 在解析LoginActivity时遇到了一个尚未存在的类型于是静默跳过不再返回该类的任何注解。这个问题的本质getSymbolsWithAnnotation在解析类时如果遇到无法解析的类型引用哪怕是泛型参数就会直接丢弃整个类。3.3 我的解决方案双重扫描机制既然getSymbolsWithAnnotation有局限性那就绕过它。我实现了一个双重扫描机制privatefunfindSymbolsWithAnnotation(resolver:Resolver,annotationFqn:String):SequenceKSAnnotated{// 1. 先用标准 API 获取能正常解析的符号valstandardSymbolsresolver.getSymbolsWithAnnotation(annotationFqn).toList()// 2. 收集已找到的类的全限定名用于去重valfoundQualifiedNamesstandardSymbols.mapNotNull{sym-(symas?KSClassDeclaration)?.qualifiedName?.asString()}.toSet()// 3. 手动递归遍历所有文件查找被遗漏的注解valfileSymbolsmutableListOfKSAnnotated()resolver.getAllFiles().forEach{file-findClassDeclarationsWithAnnotation(file.declarations,annotationFqn.substringAfterLast(.),annotationFqn,foundQualifiedNames,fileSymbols)}// 4. 合并结果并去重return(standardSymbolsfileSymbols).asSequence().distinct()}核心思路先用标准 API 获取能正常解析的类。再通过resolver.getAllFiles()递归遍历所有 AST 节点手动检查每个声明是否包含目标注解。合并两份结果确保不漏掉任何一个被 ViewBinding“屏蔽”的类。这个方案的价值它不仅可以解决 ARouter ViewBinding 的兼容问题还可以推广到任何 KSP 生成类型如 Dagger/Hilt ViewBinding的场景。四、第二大亮点DAG 启动任务调度器冷启动 380ms 的秘诀4.1 传统初始化方式的痛点在传统的 Android 项目中Application.onCreate()里往往堆满了各种 SDK 的初始化classApp:Application(){overridefunonCreate(){MMKV.initialize(this)Logger.init(this)CrashHandler.init(this)NetworkClient.init(this)RouterHelper.init(this)PushSDK.init(this)AdSDK.init(this)// ... 更多初始化}}这种方式的问题是所有初始化都是串行的且都在主线程上执行。在我之前的项目中仅Application.onCreate()就耗时 800ms。4.2 DAG 调度器的设计我设计了一个基于有向无环图DAG的启动任务调度器核心思想是将每个初始化任务抽象为StartupTask。任务可以声明依赖关系如ConfigPreloadTask依赖DatabaseWarmUpTask。调度器解析所有任务构建 DAG无依赖的任务并行执行。abstractclassStartupTask{abstractvalname:Stringabstractfunexecute(context:Context)openfundependencies():ListClassoutStartupTaskemptyList()openfunrunOnMainThread():Booleanfalse// 默认在 IO 线程执行}// 使用示例classDatabaseWarmUpTask:StartupTask(){overridevalnamedatabase_warmupoverridefunexecute(context:Context){// 预热数据库连接池}}classConfigPreloadTask:StartupTask(){overridevalnameconfig_preloadoverridefundependencies()listOf(DatabaseWarmUpTask::class.java)overridefunexecute(context:Context){// 预加载远程配置}}调度器内部使用拓扑排序确定执行顺序并利用Dispatchers.IO线程池并行执行无依赖的任务。4.3 实测效果方案Application 创建耗时传统串行初始化~800msDAG 并行调度~230ms性能提升70%完整的启动耗时报告如下╔══════════════════════════════════════╗ ║ 启动全链路耗时报告 ║ ╠══════════════════════════════════════╣ ║ 进程创建 115ms ║ Application 创建 231ms ║ 启动任务执行 2ms ║ Activity 创建 0ms ║ 首帧渲染 32ms ╠══════════════════════════════════════╣ ║ 总耗时: 380ms ╚══════════════════════════════════════╝380ms 的冷启动时间在 Android 应用中属于顶级水平。五、第三大亮点网络层的安全防护体系网络层是 APP 的命脉我构建了一套四层防护体系5.1 拦截器链架构Request → SecurityInterceptor → CacheInterceptor → MonitorInterceptor → RetryInterceptor → Server拦截器职责SecurityInterceptor自动附加X-Device-Id、X-Timestamp、X-SignSHA256 Salt防重放攻击CacheInterceptor支持NETWORK_FIRST/CACHE_FIRST/CACHE_ONLY三种策略MonitorInterceptor记录请求耗时和状态码自动上报 APMRetryInterceptor指数退避重试间隔 1s/2s/4s最多 3 次5.2 签名防篡改的实现classSecurityInterceptor:Interceptor{overridefunintercept(chain:Interceptor.Chain):Response{valrequestchain.request()valdeviceIdgetDeviceId()valtimestampSystem.currentTimeMillis()valsignsha256($deviceId$timestamp$SALT)valnewRequestrequest.newBuilder().header(X-Device-Id,deviceId).header(X-Timestamp,timestamp.toString()).header(X-Sign,sign).build()returnchain.proceed(newRequest)}}后端只需校验签名是否匹配、时间戳是否在 5 分钟窗口内即可有效防止重放攻击和中间人篡改。5.3 SSL Pinning证书固定valcertificatePinnerCertificatePinner.Builder().add(api.example.com,sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA).build()即使 CA 机构被攻破或用户安装了恶意根证书攻击者也无法劫持通信数据。六、第四大亮点APK 从 19.55MB 到 9.59MB 的瘦身之路6.1 ABI 过滤减少 40-60%默认情况下Android Gradle 插件会打包armeabi-v7a、arm64-v8a、x86、x86_64四份 Native 库。而目前 99.9% 的手机只使用arm64-v8a。android{defaultConfig{ndk{abiFilters.add(arm64-v8a)}}}效果单这一项就砍掉了约 6MB。6.2 资源压缩 WebP 转换开启资源压缩android{buildTypes{release{isMinifyEnabledtrueisShrinkResourcestrue}}}图片转 WebP在 Android Studio 中右键点击res文件夹选择Convert to WebP质量设为 75%。6.3 语言资源过滤国内 APP 通常只需要中文和英文android{defaultConfig{resConfigs(zh-rCN,en)}}6.4 最终效果优化项优化前优化后减少Native 库~8MB~3MB-62%资源文件~4MB~2MB-50%字节码~6MB~5.5MB-8%合计19.55MB9.59MB-51%七、踩坑实录那些让我熬夜的问题7.1 闪屏页 Logo 突然“变大”的问题现象闪屏页的 Logo 在启动一瞬间会“变大”然后恢复。根因windowBackground使用了自适应图标mipmap/ic_launcher系统在渲染时无法像普通 PNG 一样缩放强制使用了默认尺寸。解决方案迁移到androidx.core:core-splashscreen官方库。stylenameTheme.SplashparentTheme.SplashScreenitem namewindowSplashScreenBackgroundcolor/splash_background/item item namewindowSplashScreenAnimatedIconmipmap/ic_launcher/item item namewindowSplashScreenAnimationDuration300/item item namepostSplashScreenThemestyle/Theme.App/item/styleclassSplashActivity:AppCompatActivity(){overridefunonCreate(savedInstanceState:Bundle?){installSplashScreen()// 必须在 super.onCreate() 之前super.onCreate(savedInstanceState)// ...}}7.2 跳转主页时“闪一下白色”现象从登录页跳转到主页时屏幕会闪一下亮白色。根因主页的windowBackground未设置系统使用默认白色作为窗口背景而主页布局的根背景是深色。解决方案为主题添加windowBackgroundstylenameTheme.MainparentTheme.Appitem nameandroid:windowBackground?attr/colorSurface/item/style八、项目开源地址这套框架已经完整开源包含14 个模块的完整源码380ms 冷启动的优化方案KSP ViewBinding 兼容性解决方案DAG 启动调度器安全网络拦截器链APK 瘦身完整配置GitHub 地址https://github.com/kyrach-m/Sample如果你也在 Android 基础架构的路上探索欢迎 Star、Fork、提 Issue。开源的目的就是让更多开发者少踩一些坑。九、总结回想整个过程最大的收获不是 380ms 的启动速度或 9MB 的 APK 体积而是“系统性思考”的能力。这套框架的每一个设计决策都源于对真实生产痛点的深度剖析与优雅解决 性能与可维护性并重DAG 启动调度器在实现 380ms 冷启动的同时保持了启动链路的可观测与可扩展证明了性能优化无需牺牲代码结构。⚡ 编译期优于运行时用 KSP 彻底替换 kapt不仅带来 60% 的编译提速更从根源上提升了开发体验与工程健壮性。️ 安全是网络的基石从签名防篡改到 SSL Pinning构建了多层次的安全防护体系为业务数据保驾护航。 极致优化是持续过程APK 体积从 19.55MB 缩减至 9.59MB印证了通过系统性的 ABI 过滤、资源压缩与配置优化瘦身空间远超想象。架构的价值在于为业务提供长期稳定的支撑。如果你的应用也面临启动慢、体积大、编译久、安全弱的挑战不妨参考或直接采用这套经过千万级 DAU 验证的解决方案。 立即行动让项目起飞如果这篇文章为你带来了启发或者你正在寻找一个开箱即用、高性能的 Android 基础框架请务必前往 GitHub 为项目点一个 ⭐ Star—— 这不仅是对我的最大鼓励更是推动项目持续迭代、分享更多实战经验的直接动力开源地址https://github.com/kyrach-m/Sample框架完整源码、配置与示例均已开源期待在评论区看到你的想法与实践共同打造更优雅的 Android 开发体验*作者kyrach*GitHubhttps://github.com/kyrach-m欢迎关注我的技术专栏更多 Android 架构实战分享。