Skip to content

架构设计 / Gradle / 工程化 — 面试题篇

更新: 5/15/2026 字数: 0 字 时长: 0 分钟


Q1: MVC、MVP、MVVM、MVI 的区别?你在项目中用哪个?

考察点:架构模式理解

完整回答

  • MVC:Activity 同时是 Controller 和 View,职责不清,容易臃肿
  • MVP:View 和 Presenter 通过接口通信,Presenter 可测试。但接口爆炸,生命周期管理复杂
  • MVVM:View 观察 ViewModel 的 LiveData/StateFlow,数据驱动 UI。ViewModel 不持有 View 引用,配置变更时存活。目前 Android 官方推荐
  • MVI:单向数据流,所有 UI 状态集中在一个不可变 State 中。可预测、可追溯,但复杂度较高

项目中通常用 MVVM + 部分 MVI 思想:ViewModel 暴露 StateFlow 作为 UI State,View 发送事件给 ViewModel 处理。简单页面用纯 MVVM,复杂交互页面用 MVI 的单一 State。

追问:Clean Architecture 了解吗?

三层架构:Presentation(UI + ViewModel)→ Domain(UseCase + Entity + Repository 接口)→ Data(Repository 实现 + DataSource)。依赖规则是外层依赖内层,Domain 层是纯 Kotlin 不依赖 Android。UseCase 封装单一业务逻辑,ViewModel 组合多个 UseCase。

好处是可测试性强、职责清晰、替换数据源不影响业务逻辑。


Q2: 组件化架构怎么设计?组件间怎么通信?

考察点:大型项目架构能力

完整回答

组件化将应用拆分为多个独立模块:

App Shell(壳工程)
├── 业务组件(首页、播放、我的、消息...)
├── 公共组件(网络、图片、日志、埋点)
└── 基础库(工具类、UI 组件、路由)

关键问题和解决方案:

  1. 页面跳转:路由框架(ARouter)。编译期 APT 生成路由表,运行时根据 path 查找目标 Activity。支持拦截器(登录检查)和降级。

  2. 组件间通信:接口下沉 + SPI。公共层定义接口,业务组件实现,通过 ServiceLoader 或 ARouter 获取实现。

  3. 独立运行:每个组件可以配置为 application(独立运行调试)或 library(集成到主工程)。

  4. 依赖注入:Hilt 管理跨组件的依赖。

追问:ARouter 的原理?

编译期:APT 扫描 @Route 注解,为每个模块生成路由表类(path → ActivityClass 映射)。

运行时:初始化时通过反射或 Gradle Transform 加载所有模块的路由表。跳转时根据 path 查找目标 Class,构建 Intent 并启动。

支持拦截器链(IInterceptor),可以在跳转前做登录检查、权限验证等。


Q3: 常见的设计模式在 Android 中的应用?

考察点:设计模式实践

完整回答

  • 观察者模式:LiveData、Flow、BroadcastReceiver、OnClickListener、Lifecycle Observer
  • 责任链模式:OkHttp 拦截器链、View 事件分发机制
  • 代理模式:Retrofit 动态代理、Binder 的 Stub/Proxy
  • 建造者模式:AlertDialog.Builder、OkHttpClient.Builder、Notification.Builder
  • 工厂模式:BitmapFactory、LayoutInflater、Fragment.newInstance
  • 策略模式:RecyclerView.LayoutManager、动画 Interpolator
  • 单例模式:Application、Room Database
  • 模板方法:Activity 生命周期、BaseAdapter.getView
  • 装饰器模式:ContextWrapper 装饰 ContextImpl
  • 适配器模式:RecyclerView.Adapter、Retrofit CallAdapter

追问:举一个你在项目中用设计模式解决问题的例子?

用策略模式处理不同类型的消息展示:定义 MessageRenderer 接口,TextRenderer、ImageRenderer、VideoRenderer 分别实现。根据消息类型选择对应的 Renderer,避免了大量 if-else。新增消息类型只需添加新的 Renderer 实现。


Q4: Gradle 的构建流程?怎么优化构建速度?

考察点:构建系统理解

完整回答

Gradle 构建三个阶段:

  1. 初始化:执行 settings.gradle,确定参与构建的项目
  2. 配置:执行所有 build.gradle,构建 Task 依赖图(DAG)
  3. 执行:按 DAG 拓扑排序执行 Task

优化手段:

  • Gradle Daemon:常驻进程,避免每次启动 JVM
  • 并行构建org.gradle.parallel=true,无依赖的模块并行编译
  • Build Cache:缓存 Task 输出,相同输入直接复用
  • Configuration Cache:缓存配置阶段结果(Gradle 7+)
  • 合理使用 implementation:减少依赖传递,修改一个模块不会触发依赖它的模块重编译
  • 避免配置阶段耗时操作:不要在 build.gradle 中执行网络请求或文件 IO
  • 增量编译:Kotlin 增量编译、kapt 增量处理

追问:implementation 和 api 的区别?

  • implementation:依赖不传递。模块 A implementation 模块 B,模块 C 依赖 A 时看不到 B。修改 B 不会触发 C 重编译。
  • api:依赖传递。模块 C 依赖 A 时也能看到 B。修改 B 会触发 C 重编译。

原则:默认用 implementation,只有需要暴露给消费者的依赖才用 api。


Q5: 怎么做代码质量管控?

考察点:工程化能力

完整回答

  1. 静态检查

    • Android Lint:检查潜在 bug、性能问题、安全漏洞
    • Detekt/ktlint:Kotlin 代码风格和质量检查
    • 自定义 Lint 规则:检查项目特定的规范
  2. 测试

    • 单元测试:JUnit + MockK,覆盖 ViewModel 和 Repository 逻辑
    • UI 测试:Espresso / Compose Testing
    • 覆盖率:JaCoCo,设置最低覆盖率门槛
  3. Code Review:PR 必须至少一人审核通过

  4. CI/CD

    • PR 触发自动构建 + Lint + 单元测试
    • 任何检查失败则阻止合并
    • 合并后自动打包发布到测试环境
  5. 监控

    • 线上 Crash 率、ANR 率监控
    • 性能指标(启动时间、帧率)监控
    • 包体积变化监控

加分点:提到 Danger(自动化 Code Review 机器人)、SonarQube(代码质量平台)、Baseline Profile(运行时性能优化)。


Q6: 依赖注入的原理?Hilt 和 Dagger 的关系?

考察点:DI 框架

完整回答

依赖注入(DI):对象不自己创建依赖,而是由外部注入。好处是解耦、可测试(注入 Mock 对象)。

Dagger 是编译期 DI 框架,通过 APT 生成依赖注入代码(无反射,性能好)。但配置复杂(Component、Module、Scope)。

Hilt 是 Dagger 的封装,简化了 Android 中的使用:

  • 预定义了 Component 层级(SingletonComponent → ActivityComponent → FragmentComponent)
  • 自动绑定 Android 组件的生命周期
  • @HiltAndroidApp@AndroidEntryPoint 注解简化配置
kotlin
// Hilt 使用
@HiltAndroidApp
class MyApp : Application()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject lateinit var repo: UserRepository // 自动注入
}

@Module
@InstallIn(SingletonComponent::class)
object AppModule {
    @Provides @Singleton
    fun provideRepo(api: ApiService): UserRepository = UserRepositoryImpl(api)
}

追问:Koin 和 Hilt 的区别?

Koin 是运行时 DI(基于 Kotlin DSL),配置简单但运行时解析有性能开销,且错误在运行时才发现。Hilt 是编译期 DI,编译时就能发现依赖缺失,性能更好。大型项目推荐 Hilt,小项目 Koin 也够用。


实习面试补充:Gradle 与工程协作基础题

实习岗位常问你能不能看懂项目结构、依赖配置和 Git 协作,不一定要求做过大型组件化。

Q7: implementationapi 的区别?

考察点:Gradle 依赖配置

完整回答

  • implementation:依赖只在当前模块内部可见,不会暴露给依赖当前模块的其他模块。
  • api:依赖会传递暴露给上层模块。

一般优先使用 implementation,可以减少不必要的依赖暴露,加快编译,也让模块边界更清晰。只有当当前模块的公开 API 中使用了某个依赖的类型时,才考虑使用 api


Q8: Debug 包和 Release 包有什么区别?

考察点:构建变体

完整回答

Debug 包通常用于开发调试:

  • 默认可调试。
  • 可能开启日志、调试菜单、测试环境接口。
  • 通常不做完整混淆和优化。

Release 包用于发布:

  • 关闭调试。
  • 使用正式环境配置。
  • 通常开启签名、混淆、资源压缩等优化。

追问:为什么 Release 包要混淆?

混淆可以缩短类名和方法名,增加逆向难度,同时配合压缩移除未使用代码,减小包体积。但需要为反射、序列化、JNI、第三方 SDK 等场景配置 keep 规则。


Q9: Git 中 merge 和 rebase 有什么区别?

考察点:团队协作基础

完整回答

  • merge 会生成一次合并提交,保留分支真实合并历史。
  • rebase 会把当前分支的提交“挪到”目标分支之后,历史更线性。

多人协作时,不要随意 rebase 已经推送并被别人基于开发的公共分支,容易改写历史造成冲突。个人 feature 分支在合并前可以适当 rebase,让提交历史更清晰。