笔记 / 2026-05-03 10:40:00 CST

移动端全栈工程师面试题与详解

覆盖 iOS、Android、Flutter 三端的面试核心考点,包括语言基础、内存管理、并发编程和架构设计。


目录

第一部分:iOS 开发

  1. Swift 语言基础
  2. 内存管理
  3. UIKit 与 SwiftUI
  4. 并发编程
  5. 架构与设计模式
  6. 性能优化与工具
  7. 系统机制与框架

第二部分:Android 开发

  1. Kotlin 语言基础
  2. 四大组件与生命周期
  3. Jetpack 组件
  4. 并发编程(Coroutines)
  5. 架构与设计模式
  6. 性能优化与工具
  7. 系统机制

第三部分:Flutter 开发

  1. Dart 语言与核心机制
  2. 渲染与三棵树
  3. 状态管理
  4. 平台通信与混合开发
  5. 性能优化

第四部分:跨平台通用

  1. 网络与安全
  2. CI/CD 与发布
  3. 架构设计与系统设计

第一部分:iOS 开发

1. Swift 语言基础

Q1.1: structclass 的区别?什么时候用哪个?

答:

特性Struct(值类型)Class(引用类型)
存储位置通常在栈上(小对象)堆上
赋值行为拷贝(Copy-on-Write)共享引用
继承不支持支持
引用计数ARC
线程安全天然安全(值拷贝)需要同步
mutating需要标记修改方法不需要
deinit有析构器
// Struct:值语义
struct Point {
    var x: Double
    var y: Double
}

var a = Point(x: 1, y: 2)
var b = a       // 拷贝
b.x = 10
print(a.x)     // 1,a 不受影响

// Class:引用语义
class Person {
    var name: String
    init(name: String) { self.name = name }
}

let p1 = Person(name: "Alice")
let p2 = p1     // 共享引用
p2.name = "Bob"
print(p1.name)  // "Bob",p1 也被修改了

选择指南(Apple 官方建议):

  • 默认用 Struct:数据模型、坐标、配置等
  • 用 Class:需要继承、需要引用语义(身份标识)、需要 deinit、需要与 Objective-C 互操作

Copy-on-Write (COW): Swift 标准库的集合类型(Array、Dictionary、Set)使用 COW 优化:只在真正修改且有多个引用时才执行拷贝。

var arr1 = [1, 2, 3]
var arr2 = arr1      // 此时共享底层存储,未拷贝
arr2.append(4)       // 修改时才触发拷贝

Q1.2: Swift 中的 ProtocolProtocol-Oriented Programming 是什么?

答:

Protocol 定义行为契约,是 Swift 实现多态和代码复用的核心机制:

protocol Drawable {
    func draw()
}

// Protocol Extension:提供默认实现
extension Drawable {
    func draw() {
        print("Default drawing")
    }

    // 非协议要求的方法:静态派发
    func description() -> String {
        return "A drawable object"
    }
}

struct Circle: Drawable {
    func draw() { print("Drawing circle") }         // 动态派发
    func description() -> String { return "Circle" } // ⚠️ 静态派发!
}

let shape: Drawable = Circle()
shape.draw()          // "Drawing circle" ✅ 动态派发
shape.description()   // "A drawable object" ⚠️ 调用的是 extension 的默认实现!

关键陷阱: Protocol Extension 中定义的方法如果不在协议声明中,则使用静态派发,根据编译时类型决定调用哪个实现。

Protocol 与泛型约束:

// 关联类型(Associated Type)
protocol Container {
    associatedtype Item
    var count: Int { get }
    mutating func append(_ item: Item)
    subscript(i: Int) -> Item { get }
}

// 泛型约束
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
    return array.firstIndex(of: value)
}

// any vs some
func draw(shape: any Drawable) { }  // 存在类型(运行时多态,有开销)
func draw(shape: some Drawable) { } // 不透明类型(编译时确定,零开销)

POP vs OOP:

// OOP:通过继承复用(单继承限制、紧耦合)
class Animal { func eat() { } }
class Dog: Animal { func bark() { } }

// POP:通过组合复用(灵活、可用于值类型)
protocol Eating { func eat() }
protocol Barking { func bark() }

extension Eating { func eat() { print("Eating") } }
extension Barking { func bark() { print("Barking") } }

struct Dog: Eating, Barking { }  // 组合多个能力

Q1.3: Swift 中的 enum 有哪些高级用法?

答:

Swift 的 enum 是一等类型,远比 C/Java 的枚举强大:

// 1. 关联值(Associated Values)
enum NetworkResult {
    case success(data: Data, statusCode: Int)
    case failure(error: Error)
}

let result: NetworkResult = .success(data: data, statusCode: 200)

switch result {
case .success(let data, let statusCode) where statusCode == 200:
    process(data)
case .success(_, let statusCode):
    print("Unexpected status: \(statusCode)")
case .failure(let error):
    print("Error: \(error)")
}

// 2. 递归枚举
indirect enum ArithExpr {
    case number(Int)
    case add(ArithExpr, ArithExpr)
    case multiply(ArithExpr, ArithExpr)
}

func evaluate(_ expr: ArithExpr) -> Int {
    switch expr {
    case .number(let n): return n
    case .add(let a, let b): return evaluate(a) + evaluate(b)
    case .multiply(let a, let b): return evaluate(a) * evaluate(b)
    }
}

// 3. 遵循协议 + 计算属性
enum Planet: CaseIterable, Comparable {
    case mercury, venus, earth, mars

    var distanceFromSun: Double {
        switch self {
        case .mercury: return 57.9
        case .venus: return 108.2
        case .earth: return 149.6
        case .mars: return 227.9
        }
    }
}

// 4. 用 enum 做命名空间(无 case 的 enum 不能被实例化)
enum Constants {
    static let apiBaseURL = "https://api.example.com"
    static let maxRetries = 3
}

// 5. Result 类型(标准库)
enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}

Q1.4: 解释 Swift 的 @propertyWrapper

答:

Property Wrapper 封装属性的存取逻辑,避免重复的样板代码:

// 定义:限制值在指定范围内
@propertyWrapper
struct Clamped<Value: Comparable> {
    var wrappedValue: Value {
        didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
    }
    let range: ClosedRange<Value>

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

// 使用
struct Player {
    @Clamped(0...100) var health: Int = 100
    @Clamped(0...999) var score: Int = 0
}

var player = Player()
player.health = 150  // 被钳制为 100
player.health = -10  // 被钳制为 0

SwiftUI 中的常见 Property Wrapper:

Wrapper用途
@StateView 私有状态,值类型
@Binding父子 View 之间的双向绑定
@ObservedObject外部传入的可观察对象
@StateObjectView 拥有的可观察对象(生命周期绑定)
@EnvironmentObject通过环境注入的共享对象
@Environment读取系统环境值(如 colorScheme)
@Published属性变更时自动通知订阅者
@AppStorageUserDefaults 的声明式封装

Q1.5: someany 关键字的区别是什么?

答:

// some:不透明类型(Opaque Type)
// 编译时确定具体类型,但对调用者隐藏
func makeShape() -> some Shape {
    return Circle(radius: 10)
    // 每次调用返回同一具体类型(编译器保证)
}

// any:存在类型(Existential Type)
// 运行时可以是任意遵循协议的类型
func draw(shapes: [any Shape]) {
    for shape in shapes {
        shape.draw() // 运行时动态派发
    }
}
someany
派发方式静态派发(零开销)动态派发(有开销)
类型一致性必须始终是同一具体类型可以是不同类型
放在集合中不能(类型不统一)可以 [any Protocol]
性能更好有装箱开销(Existential Container)
使用场景函数返回值、SwiftUI body异构集合、需要运行时多态
// SwiftUI 中 some 是必须的
struct ContentView: View {
    var body: some View {  // 编译器知道具体类型
        VStack {
            Text("Hello")
            Image(systemName: "star")
        }
    }
}

// 需要 any 的场景
protocol Animal { func sound() -> String }
struct Dog: Animal { func sound() -> String { "Woof" } }
struct Cat: Animal { func sound() -> String { "Meow" } }

let pets: [any Animal] = [Dog(), Cat()] // 异构集合必须用 any

2. 内存管理

Q2.1: ARC(Automatic Reference Counting)的工作原理?如何解决循环引用?

答:

ARC 在编译时自动插入 retain(引用计数 +1)和 release(引用计数 -1)调用。当引用计数归零时,对象被销毁。

循环引用(Retain Cycle):

// ❌ 循环引用:两个对象互相强引用
class Person {
    var apartment: Apartment?
    deinit { print("Person deinit") } // 永远不会调用
}

class Apartment {
    var tenant: Person?   // 强引用
    deinit { print("Apartment deinit") } // 永远不会调用
}

var john: Person? = Person()
var unit: Apartment? = Apartment()
john?.apartment = unit
unit?.tenant = john
john = nil  // 引用计数 1(Apartment 还持有)
unit = nil  // 引用计数 1(Person 还持有)
// 内存泄漏!

解决方案:weakunowned

class Apartment {
    weak var tenant: Person?  // 弱引用,不增加引用计数
    // tenant 被销毁后自动置为 nil
}

class CreditCard {
    unowned let owner: Person  // 无主引用,不增加引用计数
    // 假设 owner 生命周期 >= CreditCard
    // owner 被销毁后访问会崩溃(类似野指针)
}
weakunowned
类型Optional(可选)非可选
对象销毁后自动设为 nil悬垂引用(访问崩溃)
性能略有开销(side table)更轻量
适用场景引用对象可能先销毁确信引用对象生命周期更长

闭包中的循环引用:

class ViewController: UIViewController {
    var name = "VC"

    func setupTimer() {
        // ❌ 闭包隐式捕获 self(强引用)
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
            self.updateUI()  // self 引用计数 +1
        }

        // ✅ 使用 capture list 打破循环
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
            guard let self else { return }
            self.updateUI()
        }
    }
}

Q2.2: 什么是 Autorelease Pool?在 Swift 中还需要关心它吗?

答:

Autorelease Pool 是 Objective-C MRC 时代的遗留机制,用于延迟释放对象:

// Swift 中仍然需要关心的场景:
// 循环中大量创建临时对象时
for i in 0..<1_000_000 {
    // ❌ 临时对象积压到 RunLoop 结束才释放
    let image = processImage(data[i])
}

// ✅ 用 autoreleasepool 及时释放
for i in 0..<1_000_000 {
    autoreleasepool {
        let image = processImage(data[i])
        // 每次循环结束时释放 pool 中的对象
    }
}

什么时候需要 autoreleasepool:

  • 循环中大量创建临时对象(特别是与 Objective-C 桥接的对象)
  • 后台线程(没有自动的 RunLoop autorelease pool)
  • 命令行工具(没有 UIApplication 管理的 pool)

主线程 RunLoop 每次迭代结束时会自动 drain autorelease pool,所以日常开发中大多不需要手动管理。


3. UIKit 与 SwiftUI

Q3.1: SwiftUI 的视图更新机制是怎样的?与 UIKit 有什么本质区别?

答:

UIKit(命令式):

// 手动管理状态同步
class CounterVC: UIViewController {
    var count = 0
    let label = UILabel()

    func increment() {
        count += 1
        label.text = "\(count)"       // 手动更新 UI
        button.isEnabled = count < 10  // 手动更新 UI
        // 每个状态变化都要手动同步所有关联的 UI
    }
}

SwiftUI(声明式):

struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("\(count)")
            Button("Increment") { count += 1 }
                .disabled(count >= 10)
        }
        // 状态变化 → 自动重新计算 body → 高效 diff 更新
    }
}

SwiftUI 更新机制:

@State / @ObservedObject 变化
          ↓
标记 View 为 invalid
          ↓
调用 body 属性(重新计算声明)
          ↓
与旧的 View 值进行结构对比(Diff)
          ↓
只更新变化的部分到底层 UIKit/AppKit 视图

关键设计差异:

UIKitSwiftUI
View 本质类(引用类型,长寿命对象)结构体(值类型,轻量描述)
更新方式命令式(手动 set)声明式(状态驱动)
布局Auto Layout(约束求解)自带布局系统(Stack/Alignment)
数据流Delegate/KVO/NotificationCenter@State/@Binding/Environment
动画UIView.animate / Core AnimationwithAnimation / .animation modifier
生命周期viewDidLoad/viewWillAppear...onAppear/onDisappear/task

Q3.2: SwiftUI 中 @State@StateObject@ObservedObject@EnvironmentObject 的区别?

答:

// @State:View 私有的简单值类型状态
struct ToggleView: View {
    @State private var isOn = false  // View 拥有此状态
    var body: some View {
        Toggle("Switch", isOn: $isOn)
    }
}

// @StateObject:View 拥有的引用类型状态(整个生命周期只创建一次)
struct ParentView: View {
    @StateObject private var viewModel = MyViewModel()  // 只创建一次
    var body: some View {
        ChildView(viewModel: viewModel)
    }
}

// @ObservedObject:外部传入的引用类型状态(不拥有,可能重建)
struct ChildView: View {
    @ObservedObject var viewModel: MyViewModel  // 由父传入
    var body: some View {
        Text(viewModel.title)
    }
}

// @EnvironmentObject:通过环境注入的共享状态
struct DeepChildView: View {
    @EnvironmentObject var settings: AppSettings  // 从祖先注入
    var body: some View {
        Text(settings.theme)
    }
}

// 注入方式
ContentView()
    .environmentObject(AppSettings())

核心区别总结:

Wrapper所有权类型创建时机适用场景
@StateView 拥有值类型View 首次创建时简单的局部状态
@StateObjectView 拥有ObservableObjectView 首次创建时ViewModel、复杂对象
@ObservedObject外部传入ObservableObject外部创建父传子的数据
@EnvironmentObject环境注入ObservableObject祖先提供全局共享(主题/认证)

⚠️ 常见陷阱:

// ❌ 用 @ObservedObject 创建对象 → 每次 body 重算都会重建
struct BuggyView: View {
    @ObservedObject var vm = MyViewModel()  // 每次父 View 重建都创建新实例!
    var body: some View { Text(vm.data) }
}

// ✅ 用 @StateObject → 只创建一次
struct CorrectView: View {
    @StateObject var vm = MyViewModel()
    var body: some View { Text(vm.data) }
}

Q3.3: UIKit 中 UITableView / UICollectionView 的性能优化要点?

答:

// 1. Cell 复用(最基础)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
    // 配置 cell...
    return cell
}

// 2. 预估高度 + 自动计算
tableView.estimatedRowHeight = 80
tableView.rowHeight = UITableView.automaticDimension

// 3. 异步图片加载 + 取消
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ImageCell
    cell.task?.cancel()  // 取消上一次请求
    cell.task = Task {
        let image = try await ImageLoader.load(url: items[indexPath.row].imageURL)
        if !Task.isCancelled {
            cell.photoView.image = image
        }
    }
    return cell
}

// 4. 预加载(Prefetching)
extension VC: UITableViewDataSourcePrefetching {
    func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            ImageLoader.prefetch(url: items[indexPath.row].imageURL)
        }
    }

    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        for indexPath in indexPaths {
            ImageLoader.cancelPrefetch(url: items[indexPath.row].imageURL)
        }
    }
}

// 5. Diffable Data Source(iOS 13+,避免 reloadData)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
snapshot.appendSections([.main])
snapshot.appendItems(items, toSection: .main)
dataSource.apply(snapshot, animatingDifferences: true)

// 6. Compositional Layout(iOS 13+,高性能复杂布局)
let layout = UICollectionViewCompositionalLayout { sectionIndex, env in
    let item = NSCollectionLayoutItem(layoutSize: ...)
    let group = NSCollectionLayoutGroup.horizontal(layoutSize: ..., subitems: [item])
    return NSCollectionLayoutSection(group: group)
}

关键优化清单:

  • 避免在 cellForRow 中做耗时操作
  • Cell 中避免离屏渲染(圆角用 cornerRadius + masksToBounds 会触发,可用贝塞尔路径裁剪)
  • 图层扁平化(减少层级嵌套)
  • 固定高度优于动态计算

4. 并发编程

Q4.1: Swift Concurrency(async/await、Actor)如何工作?

答:

async/await(结构化并发):

// 定义异步函数
func fetchUser(id: String) async throws -> User {
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(User.self, from: data)
}

// 调用
Task {
    do {
        let user = try await fetchUser(id: "123")
        // 自动在合适的线程更新(如果是 @MainActor)
    } catch {
        print(error)
    }
}

并行执行:

// async let:并行启动多个任务
func loadDashboard() async throws -> Dashboard {
    async let user = fetchUser()
    async let posts = fetchPosts()
    async let notifications = fetchNotifications()

    // 三个请求并行执行,在这里等待全部完成
    return try await Dashboard(
        user: user,
        posts: posts,
        notifications: notifications
    )
}

// TaskGroup:动态数量的并行任务
func fetchAllImages(urls: [URL]) async throws -> [UIImage] {
    try await withThrowingTaskGroup(of: UIImage.self) { group in
        for url in urls {
            group.addTask { try await downloadImage(url) }
        }
        var images: [UIImage] = []
        for try await image in group {
            images.append(image)
        }
        return images
    }
}

Actor(数据竞争保护):

// Actor:保证内部状态串行访问,消除数据竞争
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }

    func withdraw(_ amount: Double) throws -> Double {
        guard balance >= amount else { throw InsufficientFundsError() }
        balance -= amount
        return amount
    }
}

// 外部访问 Actor 属性/方法必须 await
let account = BankAccount()
await account.deposit(100)
let money = try await account.withdraw(50)

// @MainActor:保证在主线程执行
@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []

    func load() async {
        let data = await fetchFromNetwork()  // 可能在其他线程
        items = data  // 自动回到主线程,因为类标记了 @MainActor
    }
}

Sendable:

// Sendable 标记可以安全跨并发域传递的类型
struct UserData: Sendable {  // 值类型通常自动 Sendable
    let name: String
    let age: Int
}

// ⚠️ 类需要满足条件才能 Sendable
final class Config: Sendable {  // final + 所有属性不可变
    let apiKey: String
    init(apiKey: String) { self.apiKey = apiKey }
}

Q4.2: GCD(Grand Central Dispatch)的核心概念?与 Swift Concurrency 的对比?

答:

// GCD:基于队列的并发模型
// 串行队列
let serial = DispatchQueue(label: "com.app.serial")
serial.async { /* 任务按顺序执行 */ }

// 并发队列
let concurrent = DispatchQueue(label: "com.app.concurrent", attributes: .concurrent)
concurrent.async { /* 任务并行执行 */ }

// 主队列
DispatchQueue.main.async { /* UI 更新 */ }

// 全局队列(按优先级)
DispatchQueue.global(qos: .userInitiated).async { /* 高优先级后台任务 */ }
DispatchQueue.global(qos: .utility).async { /* 低优先级任务 */ }

// DispatchGroup:等待一组任务完成
let group = DispatchGroup()
group.enter()
fetchA { group.leave() }
group.enter()
fetchB { group.leave() }
group.notify(queue: .main) { /* 全部完成 */ }

// Semaphore:限制并发数
let semaphore = DispatchSemaphore(value: 3) // 最多 3 个并发
for url in urls {
    semaphore.wait()
    queue.async {
        download(url)
        semaphore.signal()
    }
}

GCD vs Swift Concurrency:

GCDSwift Concurrency
语法闭包回调async/await
线程管理开发者控制队列运行时自动管理
数据安全需手动加锁/串行队列Actor 自动保护
取消支持需手动实现Task.cancel() 内建
编译检查Sendable 编译检查
回调地狱容易出现线性代码流
优先级反转可能发生运行时自动处理

5. 架构与设计模式(iOS)

Q5.1: 对比 MVC、MVVM、VIPER、TCA 架构

答:

MVC(Apple 传统):

View ← Controller → Model
       (Massive)

问题:Controller 承担过多职责(Massive View Controller)

MVVM:

// Model
struct User { let name: String; let email: String }

// ViewModel:不依赖 UIKit
class UserViewModel: ObservableObject {
    @Published var displayName = ""
    @Published var isLoading = false
    private let repository: UserRepository

    init(repository: UserRepository) {
        self.repository = repository
    }

    func load() async {
        isLoading = true
        let user = try? await repository.getUser()
        displayName = user?.name ?? "Unknown"
        isLoading = false
    }
}

// View:只负责展示
struct UserView: View {
    @StateObject var viewModel: UserViewModel

    var body: some View {
        if viewModel.isLoading {
            ProgressView()
        } else {
            Text(viewModel.displayName)
        }
    }
}

TCA(The Composable Architecture):

// 单向数据流,强调可测试性和可组合性
@Reducer
struct Counter {
    @ObservableState
    struct State: Equatable {
        var count = 0
    }

    enum Action {
        case increment
        case decrement
        case fetchCompleted(Int)
    }

    var body: some ReducerOf<Self> {
        Reduce { state, action in
            switch action {
            case .increment:
                state.count += 1
                return .none
            case .decrement:
                state.count -= 1
                return .none
            case .fetchCompleted(let value):
                state.count = value
                return .none
            }
        }
    }
}

// View
struct CounterView: View {
    let store: StoreOf<Counter>

    var body: some View {
        HStack {
            Button("-") { store.send(.decrement) }
            Text("\(store.count)")
            Button("+") { store.send(.increment) }
        }
    }
}
架构复杂度可测试性适用规模学习成本
MVC小型/原型
MVVM中型
VIPER很好大型团队
TCA中-高极好中大型中-高

6. 性能优化与工具(iOS)

Q6.1: 如何使用 Instruments 诊断 iOS 性能问题?

答:

Instrument用途关注指标
Time ProfilerCPU 耗时分析主线程占用、热点函数
Allocations内存分配跟踪总内存、分配频率、泄漏
Leaks内存泄漏检测泄漏对象、循环引用
Core Animation渲染性能FPS、离屏渲染、混合图层
Network网络请求分析请求数、响应时间、数据量
Energy Log电池消耗分析CPU/GPU/网络唤醒、后台活动

常见性能问题与解决方案:

// 1. 主线程阻塞
// ❌ 主线程做 IO/计算
let data = try! Data(contentsOf: largeFileURL) // 主线程阻塞

// ✅ 移到后台
Task.detached {
    let data = try await loadData(from: largeFileURL)
    await MainActor.run { self.process(data) }
}

// 2. 离屏渲染
// ❌ 触发离屏渲染
view.layer.cornerRadius = 10
view.layer.masksToBounds = true  // 配合圆角 → 离屏渲染
view.layer.shadowOffset = CGSize(width: 0, height: 2)  // 阴影 → 离屏渲染

// ✅ 优化
view.layer.cornerRadius = 10
view.layer.cornerCurve = .continuous
// 阴影加 path 避免离屏渲染
view.layer.shadowPath = UIBezierPath(roundedRect: view.bounds, cornerRadius: 10).cgPath

// 3. 图片解码
// ❌ 加载原图(4000x3000 解码消耗大量内存)
imageView.image = UIImage(named: "huge_photo")

// ✅ 降采样(Downsampling)
func downsample(imageAt url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
    let options = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let source = CGImageSourceCreateWithURL(url as CFURL, options) else { return nil }

    let maxDimension = max(pointSize.width, pointSize.height) * scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceShouldCacheImmediately: true,
        kCGImageSourceThumbnailMaxPixelSize: maxDimension
    ] as CFDictionary

    guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
    return UIImage(cgImage: cgImage)
}

7. 系统机制与框架

Q7.1: iOS App 的启动流程是怎样的?如何优化启动时间?

答:

┌─────────────────────────────────────────────┐
│  Pre-main(系统阶段)                        │
│  1. 加载 dyld(动态链接器)                   │
│  2. 加载动态库(系统 + 第三方)                │
│  3. Rebase & Bind(指针修正)                 │
│  4. ObjC Runtime Setup(类注册、category 加载)│
│  5. +load 方法                               │
│  6. C++ 静态初始化器                          │
├─────────────────────────────────────────────┤
│  Post-main(应用阶段)                       │
│  7. main() → UIApplicationMain()            │
│  8. application:didFinishLaunchingWithOptions│
│  9. 首帧渲染完成                              │
└─────────────────────────────────────────────┘

优化策略:

阶段优化方法
Pre-main减少动态库数量(合并或改用静态库)、移除不用的 ObjC 类、避免 +load(改用 +initialize)、减少 C++ 静态初始化
Post-maindidFinishLaunching 中延迟非必要初始化、首屏只加载必要数据、懒加载 SDK、使用 Launch Storyboard 提升感知速度
// 测量启动时间
// Scheme → Environment Variables → 添加 DYLD_PRINT_STATISTICS = 1

// 延迟初始化示例
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 只做必要初始化
    setupCrashReporting()
    setupCoreData()

    // 延迟非关键初始化
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        self.setupAnalytics()
        self.setupPushNotifications()
    }

    return true
}

Q7.2: 什么是 RunLoop?它和 iOS 事件处理的关系?

答:

RunLoop 是线程上的事件处理循环,让线程在没有事件时休眠,有事件时唤醒处理:

┌──────────────────────────────────────┐
│              RunLoop 循环             │
│                                      │
│   1. 处理 Source0(非端口事件)         │
│      ↓                               │
│   2. 处理 Source1(端口/系统事件)      │
│      ↓                               │
│   3. 处理 Timer                       │
│      ↓                               │
│   4. 处理 Observer 通知               │
│      ↓                               │
│   5. 处理 GCD dispatch 到主队列的 block│
│      ↓                               │
│   6. 没有事件 → 线程休眠              │
│      ↓                               │
│   7. 有事件 → 唤醒,回到 1            │
└──────────────────────────────────────┘

RunLoop Mode:

Mode用途
default默认模式,大部分事件
trackingScrollView 滚动时的模式
commondefault + tracking 的集合
// 常见问题:Timer 在滚动时停止
// ❌ 默认 mode,滚动时 RunLoop 切换到 tracking mode,Timer 不触发
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
    updateUI()
}

// ✅ 添加到 common mode
RunLoop.current.add(timer, forMode: .common)

RunLoop 的应用场景:

  • AutoreleasePool 的 drain 时机(每次 RunLoop 迭代结束)
  • 手势识别
  • 屏幕刷新(CADisplayLink)
  • 延迟加载(在 RunLoop 空闲时加载图片)

Q7.3: 解释 iOS 中的 App Lifecycle(前后台切换)和 Scene 生命周期

答:

Not Running → Inactive → Active → (用户使用中)
                 ↕
              Background → Suspended → (系统可能终止)
// iOS 13+ Scene-based lifecycle
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    func sceneDidBecomeActive(_ scene: UIScene) {
        // 前台活跃:恢复暂停的任务、刷新 UI
    }

    func sceneWillResignActive(_ scene: UIScene) {
        // 即将失去焦点:暂停游戏、保存草稿
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        // 进入后台:保存数据、释放共享资源
        // 约 5 秒执行时间
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        // 即将回到前台:撤销后台的更改
    }
}

// SwiftUI
@main
struct MyApp: App {
    @Environment(\.scenePhase) var scenePhase

    var body: some Scene {
        WindowGroup { ContentView() }
            .onChange(of: scenePhase) { _, phase in
                switch phase {
                case .active: break      // 前台活跃
                case .inactive: break    // 临时不活跃
                case .background: break  // 后台
                @unknown default: break
                }
            }
    }
}

后台任务:

// Background Task:申请额外执行时间
func sceneDidEnterBackground(_ scene: UIScene) {
    let taskID = UIApplication.shared.beginBackgroundTask {
        // 时间到了的清理回调
        UIApplication.shared.endBackgroundTask(taskID)
    }

    Task {
        await saveData()
        UIApplication.shared.endBackgroundTask(taskID)
    }
}

// BGTaskScheduler(iOS 13+):后台定时任务
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.refresh", using: nil) { task in
    handleBackgroundRefresh(task: task as! BGAppRefreshTask)
}

第二部分:Android 开发

8. Kotlin 语言基础

Q8.1: Kotlin 中的 data classsealed classobjectcompanion object 各有什么用?

答:

// 1. data class:自动生成 equals/hashCode/toString/copy/componentN
data class User(val name: String, val age: Int)

val user1 = User("Alice", 25)
val user2 = user1.copy(age = 26)       // 浅拷贝并修改
val (name, age) = user1                 // 解构
println(user1)                          // User(name=Alice, age=25)
println(user1 == User("Alice", 25))     // true(结构相等)

// 2. sealed class:受限的继承层次,编译器可穷举
sealed class Result<out T> {
    data class Success<T>(val data: T) : Result<T>()
    data class Error(val message: String) : Result<Nothing>()
    data object Loading : Result<Nothing>()
}

fun handle(result: Result<String>) = when (result) {
    is Result.Success -> println(result.data)
    is Result.Error -> println(result.message)
    Result.Loading -> println("Loading...")
    // 不需要 else,编译器知道已穷举
}

// 3. object:单例
object Database {
    fun connect() { /* ... */ }
}
Database.connect()

// 4. companion object:类的静态成员(类似 Java static)
class MyFragment : Fragment() {
    companion object {
        private const val ARG_ID = "id"

        fun newInstance(id: String) = MyFragment().apply {
            arguments = bundleOf(ARG_ID to id)
        }
    }
}

Q8.2: Kotlin 的空安全和作用域函数(let/run/with/apply/also)

答:

空安全:

var name: String = "Hello"   // 非空
var name: String? = null     // 可空

name?.length                 // 安全调用
name ?: "default"            // Elvis 操作符
name!!.length                // 强制解包(null 时抛 NPE)

// 智能转换(Smart Cast)
fun process(value: Any) {
    if (value is String) {
        println(value.length)  // 自动转换为 String,无需强转
    }
}

作用域函数:

函数对象引用返回值典型场景
letitLambda 结果空安全调用、变量转换
runthisLambda 结果对象配置 + 计算结果
withthisLambda 结果对已有对象调用多个方法
applythis对象本身对象初始化/配置
alsoit对象本身额外的副作用(日志等)
// let:空安全 + 转换
val length = name?.let {
    println("Name is $it")
    it.length  // 返回长度
}

// apply:对象初始化
val textView = TextView(context).apply {
    text = "Hello"
    textSize = 16f
    setTextColor(Color.BLACK)
}

// also:链式操作中的副作用
fun getUser() = repository.findUser(id)
    .also { log("Found user: $it") }  // 日志,不影响返回值

// run:配置 + 计算
val result = service.run {
    port = 8080
    query("SELECT * FROM users")  // 返回查询结果
}

// with:已有对象的多个操作
with(binding) {
    titleText.text = item.title
    subtitleText.text = item.subtitle
    imageView.load(item.imageUrl)
}

Q8.3: Kotlin 中的委托(Delegation)机制

答:

// 1. 类委托(by):用组合替代继承
interface Printer {
    fun print(message: String)
}

class ConsolePrinter : Printer {
    override fun print(message: String) = println(message)
}

// LoggingPrinter 自动委托 Printer 接口的实现给 printer
class LoggingPrinter(private val printer: Printer) : Printer by printer {
    override fun print(message: String) {
        println("[LOG] About to print")
        printer.print(message)  // 委托给真正的实现
    }
}

// 2. 属性委托
// lazy:首次访问时初始化
val heavyObject: HeavyObject by lazy {
    HeavyObject()  // 线程安全的懒初始化
}

// observable:属性变化监听
var name: String by Delegates.observable("initial") { prop, old, new ->
    println("$old → $new")
}

// 自定义委托
class SharedPreferencesDelegate(
    private val prefs: SharedPreferences,
    private val key: String,
    private val default: String
) : ReadWriteProperty<Any, String> {

    override fun getValue(thisRef: Any, property: KProperty<*>): String {
        return prefs.getString(key, default) ?: default
    }

    override fun setValue(thisRef: Any, property: KProperty<*>, value: String) {
        prefs.edit().putString(key, value).apply()
    }
}

// 使用
class Settings(prefs: SharedPreferences) {
    var username: String by SharedPreferencesDelegate(prefs, "username", "")
    var theme: String by SharedPreferencesDelegate(prefs, "theme", "light")
}

Q8.4: Kotlin 的内联函数(inline)、reified 泛型和高阶函数

答:

// inline:编译时将函数体内联到调用处,消除 Lambda 对象开销
inline fun measureTime(block: () -> Unit): Long {
    val start = System.currentTimeMillis()
    block()  // 不会创建 Function 对象
    return System.currentTimeMillis() - start
}

// reified:在 inline 函数中获取泛型的实际类型(运行时可用)
// 普通泛型在运行时被擦除,reified 保留类型信息
inline fun <reified T> Gson.fromJson(json: String): T {
    return fromJson(json, T::class.java)  // 可以直接使用 T::class
}

// 使用
val user = gson.fromJson<User>(jsonString) // 不需要传 User::class.java

// crossinline / noinline
inline fun execute(
    crossinline setup: () -> Unit,   // 不允许非局部返回
    noinline callback: () -> Unit    // 不内联此参数(可以存储引用)
) {
    setup()
    post(callback)  // noinline 的 lambda 可以作为对象传递
}

// 非局部返回
inline fun List<Int>.forEachInlined(action: (Int) -> Unit) {
    for (item in this) action(item)
}

fun test() {
    listOf(1, 2, 3).forEachInlined {
        if (it == 2) return  // 直接从 test() 返回(非局部返回)
    }
}

9. 四大组件与生命周期

Q9.1: Activity 的生命周期和配置变更处理

答:

                  onCreate()
                      ↓
                  onStart()    ←── onRestart()
                      ↓               ↑
                  onResume()          │
                      ↓               │
               [Activity Running]     │
                      ↓               │
                  onPause()           │
                      ↓               │
                  onStop()  ──────────┘
                      ↓
                  onDestroy()

配置变更(如旋转屏幕)默认行为: 销毁 Activity → 重建 Activity

// 方案 1:ViewModel(推荐,数据在配置变更中存活)
class MyViewModel : ViewModel() {
    private val _data = MutableLiveData<List<Item>>()
    val data: LiveData<List<Item>> = _data

    fun loadData() {
        viewModelScope.launch {
            _data.value = repository.fetch()
        }
    }
}

// 方案 2:onSaveInstanceState(少量简单数据)
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.putInt("scroll_position", scrollPosition)
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    savedInstanceState?.getInt("scroll_position")?.let { scrollTo(it) }
}

// 方案 3:自行处理配置变更(不推荐,除非有特殊需求)
// AndroidManifest.xml
// android:configChanges="orientation|screenSize|keyboardHidden"
override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    // 手动调整布局
}

Q9.2: Fragment 的生命周期和常见问题

答:

onAttach() → onCreate() → onCreateView() → onViewCreated()
    → onStart() → onResume()

onPause() → onStop() → onDestroyView() → onDestroy() → onDetach()

常见问题与最佳实践:

class MyFragment : Fragment(R.layout.fragment_my) {

    // ✅ 使用 viewLifecycleOwner(而非 this)观察 LiveData
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        // ❌ 使用 this 可能导致多次观察(Fragment 可能多次 onCreateView)
        // viewModel.data.observe(this) { ... }

        // ✅ viewLifecycleOwner 跟随 View 生命周期
        viewModel.data.observe(viewLifecycleOwner) { data ->
            adapter.submitList(data)
        }
    }

    // ViewBinding 的正确用法
    private var _binding: FragmentMyBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentMyBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null  // 避免内存泄漏
    }

    // Fragment 之间通信:使用 ViewModel 或 FragmentResult API
    // ❌ 直接引用其他 Fragment
    // ✅ 共享 ViewModel
    private val sharedViewModel: SharedViewModel by activityViewModels()

    // ✅ FragmentResult API
    setFragmentResult("requestKey", bundleOf("key" to value))
    // 接收方
    setFragmentResultListener("requestKey") { _, bundle ->
        val value = bundle.getString("key")
    }
}

Q9.3: Service、BroadcastReceiver、ContentProvider 的使用场景

答:

Service:

// Foreground Service(长时间后台任务,如音乐播放、GPS 导航)
class MusicService : Service() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val notification = createNotification()
        startForeground(NOTIFICATION_ID, notification) // 必须显示通知
        playMusic()
        return START_STICKY // 被杀后自动重启
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

// WorkManager(推荐替代后台 Service)
val request = OneTimeWorkRequestBuilder<UploadWorker>()
    .setConstraints(Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .setRequiresBatteryNotLow(true)
        .build())
    .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS)
    .build()

WorkManager.getInstance(context).enqueue(request)

BroadcastReceiver:

// 静态注册(AndroidManifest.xml,受限)
// 动态注册(推荐)
val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        when (intent.action) {
            Intent.ACTION_BATTERY_LOW -> showWarning()
            ConnectivityManager.CONNECTIVITY_ACTION -> checkNetwork()
        }
    }
}

// 注册
registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_LOW))
// 注销(避免泄漏)
unregisterReceiver(receiver)

ContentProvider:

// 跨应用数据共享
class MyProvider : ContentProvider() {
    override fun query(uri: Uri, ...): Cursor? {
        return when (uriMatcher.match(uri)) {
            ITEMS -> database.query("items", ...)
            ITEM_ID -> database.query("items", ..., "id=${uri.lastPathSegment}")
            else -> null
        }
    }
}

// 使用
val cursor = contentResolver.query(
    ContactsContract.Contacts.CONTENT_URI, null, null, null, null
)

10. Jetpack 组件

Q10.1: Jetpack Compose 的重组(Recomposition)机制

答:

// Compose 核心:声明式 UI,状态变化触发重组
@Composable
fun Counter() {
    var count by remember { mutableStateOf(0) }

    Column {
        Text("Count: $count")  // count 变化时重组
        Button(onClick = { count++ }) {
            Text("Increment")
        }
    }
}

重组优化原则:

// 1. Compose 是智能的:只重组读取了变化状态的 Composable
@Composable
fun Parent() {
    var name by remember { mutableStateOf("") }
    var age by remember { mutableStateOf(0) }

    NameDisplay(name)  // 只有 name 变化时重组
    AgeDisplay(age)    // 只有 age 变化时重组
}

// 2. remember:在重组中保持值
@Composable
fun ExpensiveCalculation(input: List<Int>) {
    // ❌ 每次重组都计算
    val result = input.sorted().take(10)

    // ✅ 只有 input 变化时重新计算
    val result = remember(input) { input.sorted().take(10) }
}

// 3. derivedStateOf:减少不必要的重组
@Composable
fun ItemList(items: List<Item>) {
    val listState = rememberLazyListState()

    // ❌ 每次滚动都重组
    val showButton = listState.firstVisibleItemIndex > 0

    // ✅ 只在条件变化时重组
    val showButton by remember {
        derivedStateOf { listState.firstVisibleItemIndex > 0 }
    }

    if (showButton) {
        ScrollToTopButton()
    }
}

// 4. 稳定性(Stability)
// Compose 跳过重组的条件:参数是 Stable 且 equals 返回 true
// data class 默认 Stable(所有属性都是 val 且类型 Stable)
data class User(val name: String, val age: Int)  // ✅ Stable

// List/Map 不是 Stable(可能被外部修改)
data class State(val items: List<Item>)  // ⚠️ 不 Stable

// 解决方案:使用 @Immutable 或 kotlinx.collections.immutable
@Immutable
data class State(val items: ImmutableList<Item>)  // ✅ Stable

Q10.2: ViewModel + LiveData/StateFlow + Repository 模式

答:

// Repository
class UserRepository(
    private val api: UserApi,
    private val dao: UserDao
) {
    fun getUsers(): Flow<List<User>> = dao.observeAll()  // Room 返回 Flow

    suspend fun refresh() {
        val users = api.fetchUsers()
        dao.insertAll(users)
    }
}

// ViewModel(使用 StateFlow)
class UserViewModel(private val repository: UserRepository) : ViewModel() {

    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    init {
        viewModelScope.launch {
            repository.getUsers()
                .catch { _uiState.value = UiState.Error(it.message ?: "Unknown") }
                .collect { users -> _uiState.value = UiState.Success(users) }
        }
    }

    fun refresh() {
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            try {
                repository.refresh()
            } catch (e: Exception) {
                _uiState.value = UiState.Error(e.message ?: "Unknown")
            }
        }
    }
}

sealed class UiState {
    data object Loading : UiState()
    data class Success(val users: List<User>) : UiState()
    data class Error(val message: String) : UiState()
}

// Compose UI
@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when (val state = uiState) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> UserList(state.users)
        is UiState.Error -> ErrorView(state.message, onRetry = { viewModel.refresh() })
    }
}

LiveData vs StateFlow:

LiveDataStateFlow
生命周期感知自动需要 collectAsStateWithLifecycle()
初始值可选必须有初始值
纯 Kotlin需要 Android 依赖纯 Kotlin,跨平台
操作符有限(map/switchMap)完整 Flow 操作符
推荐旧项目维护新项目首选

Q10.3: Room 数据库的使用和优化

答:

// Entity
@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey val id: String,
    @ColumnInfo(name = "display_name") val name: String,
    val email: String,
    @ColumnInfo(index = true) val createdAt: Long  // 添加索引
)

// DAO
@Dao
interface UserDao {
    @Query("SELECT * FROM users ORDER BY created_at DESC")
    fun observeAll(): Flow<List<UserEntity>>  // 响应式查询

    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getById(userId: String): UserEntity?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(users: List<UserEntity>)

    @Transaction  // 事务
    suspend fun replaceAll(users: List<UserEntity>) {
        deleteAll()
        insertAll(users)
    }

    @Query("DELETE FROM users")
    suspend fun deleteAll()
}

// Database
@Database(entities = [UserEntity::class], version = 2)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

// 类型转换器
class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? = value?.let { Date(it) }

    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? = date?.time
}

// 数据库迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE users ADD COLUMN email TEXT NOT NULL DEFAULT ''")
    }
}

// 构建
Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
    .addMigrations(MIGRATION_1_2)
    .build()

11. 并发编程(Coroutines)

Q11.1: Kotlin Coroutines 的核心概念

答:

// CoroutineScope:定义协程的生命周期
class MyViewModel : ViewModel() {
    // viewModelScope:ViewModel 销毁时自动取消
    fun load() = viewModelScope.launch {
        val data = fetchData()  // 挂起,不阻塞线程
        _state.value = data
    }
}

// Dispatcher:决定协程在哪个线程执行
viewModelScope.launch(Dispatchers.Main) {      // 主线程
    val data = withContext(Dispatchers.IO) {    // 切到 IO 线程
        api.fetchData()                        // 网络请求
    }
    updateUI(data)                             // 回到主线程
}

// Dispatchers 对比
// Main:主线程,UI 操作
// IO:IO 密集型(网络、文件),线程池较大
// Default:CPU 密集型(排序、解析),线程数 = CPU 核数
// Unconfined:不切换线程(不推荐常规使用)

结构化并发:

// 并行请求
suspend fun loadDashboard(): Dashboard = coroutineScope {
    val user = async { fetchUser() }
    val posts = async { fetchPosts() }
    val stats = async { fetchStats() }

    Dashboard(
        user = user.await(),
        posts = posts.await(),
        stats = stats.await()
    )
    // 任何一个失败,其他自动取消
}

// 异常处理
viewModelScope.launch {
    try {
        val result = repository.fetchData()
        _state.value = UiState.Success(result)
    } catch (e: CancellationException) {
        throw e  // 不要吞掉 CancellationException!
    } catch (e: Exception) {
        _state.value = UiState.Error(e.message)
    }
}

// SupervisorJob:子协程失败不影响其他子协程
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch { task1() }  // task1 失败不会取消 task2
scope.launch { task2() }

Q11.2: Flow 的冷流与热流,以及 StateFlow / SharedFlow

答:

// Cold Flow(冷流):每个收集者独立执行
fun fetchItems(): Flow<Item> = flow {
    for (item in api.getItems()) {
        emit(item)
        delay(100)
    }
}

// 每次 collect 都会重新执行 flow { } 块
fetchItems().collect { item -> println(item) } // 执行一次
fetchItems().collect { item -> println(item) } // 再执行一次

// Hot Flow(热流):多个收集者共享
// StateFlow:始终持有当前值,新收集者立即获得最新值
private val _state = MutableStateFlow(UiState.Loading)
val state: StateFlow<UiState> = _state.asStateFlow()

// SharedFlow:不持有值,可配置重放和缓冲
private val _events = MutableSharedFlow<Event>(
    replay = 0,              // 新订阅者不接收历史事件
    extraBufferCapacity = 1, // 缓冲区
    onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val events: SharedFlow<Event> = _events.asSharedFlow()

Flow 操作符:

repository.getUsers()
    .map { users -> users.filter { it.isActive } }          // 转换
    .distinctUntilChanged()                                  // 去重
    .debounce(300)                                           // 防抖
    .catch { e -> emit(emptyList()) }                        // 异常处理
    .flowOn(Dispatchers.IO)                                  // 上游在 IO 线程
    .onEach { users -> analytics.log("users: ${users.size}") }
    .stateIn(                                                 // 转换为 StateFlow
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),      // 无订阅者 5s 后停止
        initialValue = emptyList()
    )

在 UI 中安全收集:

// Compose
@Composable
fun MyScreen(viewModel: MyViewModel) {
    val state by viewModel.state.collectAsStateWithLifecycle()
    // 自动跟随 Lifecycle 暂停/恢复
}

// View 系统
lifecycleScope.launch {
    repeatOnLifecycle(Lifecycle.State.STARTED) {
        viewModel.state.collect { state ->
            updateUI(state)
        }
    }
}

12. 架构与设计模式(Android)

Q12.1: Android 推荐架构(Google 官方)

答:

┌─────────────────────────────────────────┐
│              UI Layer                    │
│   Activity/Fragment/Compose ← ViewModel │
│         (观察 UiState)                   │
├─────────────────────────────────────────┤
│            Domain Layer (可选)           │
│            UseCases                      │
├─────────────────────────────────────────┤
│             Data Layer                   │
│   Repository → Remote DataSource (API)   │
│              → Local DataSource (Room)   │
└─────────────────────────────────────────┘

单向数据流(UDF):

// 事件从 UI → ViewModel → Repository(向下)
// 状态从 Repository → ViewModel → UI(向上)

// UiState
data class HomeUiState(
    val items: List<Item> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

// ViewModel
class HomeViewModel(private val repository: ItemRepository) : ViewModel() {
    private val _uiState = MutableStateFlow(HomeUiState())
    val uiState: StateFlow<HomeUiState> = _uiState.asStateFlow()

    fun onEvent(event: HomeEvent) {
        when (event) {
            is HomeEvent.Refresh -> refresh()
            is HomeEvent.Delete -> delete(event.itemId)
        }
    }

    private fun refresh() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            try {
                val items = repository.getItems()
                _uiState.update { it.copy(items = items, isLoading = false) }
            } catch (e: Exception) {
                _uiState.update { it.copy(error = e.message, isLoading = false) }
            }
        }
    }
}

sealed class HomeEvent {
    data object Refresh : HomeEvent()
    data class Delete(val itemId: String) : HomeEvent()
}

Q12.2: Hilt 依赖注入

答:

// 1. Module 定义
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideRetrofit(): Retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com")
        .addConverterFactory(GsonConverterFactory.create())
        .build()

    @Provides
    @Singleton
    fun provideUserApi(retrofit: Retrofit): UserApi =
        retrofit.create(UserApi::class.java)
}

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Binds
    @Singleton
    abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository
}

// 2. 注入
@HiltViewModel
class UserViewModel @Inject constructor(
    private val repository: UserRepository
) : ViewModel() { ... }

@AndroidEntryPoint
class UserActivity : AppCompatActivity() {
    private val viewModel: UserViewModel by viewModels()
}

// 3. Scope 对比
// @Singleton → SingletonComponent(应用级)
// @ActivityScoped → ActivityComponent(Activity 级)
// @ViewModelScoped → ViewModelComponent(ViewModel 级)
// @FragmentScoped → FragmentComponent(Fragment 级)

13. 性能优化与工具(Android)

Q13.1: Android 性能优化的关键领域

答:

1. 启动优化:

// App Startup 库:控制初始化顺序和延迟
class AnalyticsInitializer : Initializer<Analytics> {
    override fun create(context: Context): Analytics {
        return Analytics.init(context)
    }

    override fun dependencies(): List<Class<out Initializer<*>>> {
        return listOf(CrashReportingInitializer::class.java) // 依赖关系
    }
}

// Baseline Profile(提升启动和运行时性能)
// 提前编译热路径代码为机器码
@get:Rule
val rule = BaselineProfileRule()

@Test
fun generateBaselineProfile() {
    rule.collect(packageName = "com.example.app") {
        startActivityAndWait()
        // 模拟用户操作...
    }
}

2. 内存优化:

// 避免内存泄漏
// ❌ 静态引用 Activity
companion object {
    var activity: Activity? = null  // 永远不要这样做
}

// ❌ 内部类持有外部引用
class MyActivity : AppCompatActivity() {
    inner class MyHandler : Handler() { ... }  // 隐式持有 Activity
}

// ✅ 使用 WeakReference 或静态内部类
class MyHandler(activity: Activity) : Handler(Looper.getMainLooper()) {
    private val activityRef = WeakReference(activity)
    override fun handleMessage(msg: Message) {
        activityRef.get()?.let { /* ... */ }
    }
}

3. 布局优化:

// Compose:避免不必要的重组(前面已详述)
// View 系统:减少层级、使用 ConstraintLayout
// 工具:Layout Inspector

// R8/ProGuard:代码缩减 + 混淆
android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

Q13.2: Android 常用性能分析工具

答:

工具用途
Android Studio ProfilerCPU、内存、网络、电量实时监控
Layout InspectorCompose/View 层级、重组次数
LeakCanary自动检测内存泄漏
Systrace / Perfetto系统级帧分析
Macrobenchmark启动时间、帧率等自动化基准测试
Baseline Profile预编译热路径提升性能
R8代码缩减、混淆、优化
// LeakCanary(Debug 版自动检测泄漏)
debugImplementation("com.squareup.leakcanary:leakcanary-android:2.14")
// 无需额外代码,泄漏时自动弹出通知

// Macrobenchmark
@LargeTest
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun startupCompilation() = benchmarkRule.measureRepeated(
        packageName = "com.example.app",
        metrics = listOf(StartupTimingMetric()),
        iterations = 5,
        startupMode = StartupMode.COLD
    ) {
        pressHome()
        startActivityAndWait()
    }
}

14. 系统机制

Q14.1: Android 的进程优先级和后台限制

答:

进程优先级(从高到低):
1. 前台进程(Foreground)     - 用户正在交互的 Activity
2. 可见进程(Visible)        - 可见但不在前台(如对话框后面的 Activity)
3. 服务进程(Service)        - 正在运行 startService
4. 缓存进程(Cached/Background) - 不可见,可能被系统随时杀死
5. 空进程(Empty)            - 无活跃组件,最先被杀

Android 后台限制演进:

版本限制
Android 8 (O)后台服务限制、广播限制
Android 9 (P)Standby Buckets
Android 12 (S)前台服务启动限制、精确闹钟权限
Android 13 (T)通知权限、前台服务类型声明
Android 14 (U)前台服务类型强制声明
// 正确的后台工作方式选择
// 立即执行 + 长时间 → Foreground Service
// 可延迟 + 需要保证执行 → WorkManager
// 精确定时 → AlarmManager(需要权限)
// 短暂后台操作 → Coroutine(在 ViewModel/lifecycle scope 中)

Q14.2: Intent 和 Intent Filter 的工作机制

答:

// 显式 Intent:指定目标组件
val intent = Intent(this, DetailActivity::class.java).apply {
    putExtra("item_id", "123")
}
startActivity(intent)

// 隐式 Intent:通过 Action/Category/Data 匹配
val intent = Intent(Intent.ACTION_VIEW, Uri.parse("https://example.com"))
startActivity(intent)

// Intent Filter(在 Manifest 中声明)
// <intent-filter>
//     <action android:name="android.intent.action.VIEW" />
//     <category android:name="android.intent.category.DEFAULT" />
//     <data android:scheme="https" android:host="example.com" />
// </intent-filter>

// Deep Link 处理
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    intent?.data?.let { uri ->
        when {
            uri.pathSegments.firstOrNull() == "product" -> {
                val productId = uri.lastPathSegment
                navigateToProduct(productId)
            }
        }
    }
}

// PendingIntent(跨进程传递 Intent)
val pendingIntent = PendingIntent.getActivity(
    context, 0, intent,
    PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)

第三部分:Flutter 开发

15. Dart 语言与核心机制

Q15.1: Dart 的事件循环和 Isolate

答:

// 事件循环(单线程模型)
// 执行顺序:同步代码 → Microtask Queue → Event Queue

void main() {
  print('1');                                    // 同步
  Future(() => print('2'));                       // Event Queue
  Future.microtask(() => print('3'));             // Microtask Queue
  scheduleMicrotask(() => print('4'));            // Microtask Queue
  Future(() => print('5'));                       // Event Queue
  print('6');                                    // 同步
}
// 输出:1, 6, 3, 4, 2, 5

// Isolate:真正的并行(独立内存,消息传递通信)
// 适用于 CPU 密集型任务:JSON 解析、图片处理、加密
final result = await Isolate.run(() {
  return jsonDecode(hugeJsonString); // 在独立 Isolate 中执行
});

// compute() 是 Flutter 封装的便捷方法
final parsed = await compute(parseJson, rawData);

// 复杂场景:长时间运行的 Isolate + 双向通信
final receivePort = ReceivePort();
await Isolate.spawn(heavyWork, receivePort.sendPort);
receivePort.listen((message) {
  if (message is SendPort) {
    message.send('start');
  } else {
    print('Result: $message');
  }
});

Q15.2: Dart 3 的新特性:Records、Patterns、sealed class

答:

// Records(匿名复合类型)
(String, int) getUserInfo() => ('Alice', 25);

final info = getUserInfo();
print(info.$1); // 'Alice'
print(info.$2); // 25

// 命名字段
({String name, int age}) getUser() => (name: 'Alice', age: 25);
final user = getUser();
print(user.name);

// Patterns(模式匹配)
// switch 表达式
String describe(Object obj) => switch (obj) {
  int n when n < 0 => 'negative',
  int n => 'int: $n',
  String s => 'string: $s',
  (int x, int y) => 'point($x, $y)',  // 解构 Record
  [int first, ...rest] => 'list starting with $first',
  {'name': String name} => 'map with name: $name',
  _ => 'unknown',
};

// if-case
if (json case {'users': [{'name': String name}, ...]}) {
  print('First user: $name');
}

// sealed class + 穷举
sealed class Shape {}
class Circle extends Shape { final double radius; Circle(this.radius); }
class Rect extends Shape { final double w, h; Rect(this.w, this.h); }

double area(Shape shape) => switch (shape) {
  Circle(radius: var r) => 3.14 * r * r,
  Rect(w: var w, h: var h) => w * h,
};

// class modifiers(Dart 3)
// base class → 只能继承,不能实现
// interface class → 只能实现,不能继承
// final class → 不能继承也不能实现(同文件除外)
// sealed class → 同文件内穷举子类

16. 渲染与三棵树

Q16.1: Widget / Element / RenderObject 三棵树详解

答:

Widget Tree          Element Tree            RenderObject Tree
(不可变配置)          (可变实例)               (布局+绘制)

Scaffold ──────► StatefulElement
  │                    │
  ├─ AppBar ────► ComponentElement ──────► RenderFlex
  │                    │
  └─ ListView ──► SliverMultiBoxAdaptorElement ──► RenderSliverList
       │
       └─ ListTile ──► StatelessElement ──► RenderPadding
                                               └─ RenderFlex

核心流程:

  1. Widget:不可变的配置描述,每次 build() 可能创建新实例
  2. Element:Widget 的实例化,管理生命周期,决定是否复用
    • canUpdate(oldWidget, newWidget)runtimeType + key 都相同则复用
  3. RenderObject:真正执行布局(performLayout)和绘制(paint

约束传递规则:

Constraints go down(父传子约束)
Sizes go up(子回传尺寸)
Parent sets position(父设子位置)
// 理解约束的典型问题
SizedBox(
  width: 100,
  height: 100,
  child: Container(
    width: 200,  // 无效!父约束 tight=100,子不能大于 100
    height: 200,
    color: Colors.red,
  ),
)
// 实际渲染为 100x100

// ConstrainedBox 传递约束
ConstrainedBox(
  constraints: BoxConstraints(maxWidth: 200),
  child: Container(width: 300),  // 实际宽度被限制为 200
)

17. 状态管理

Q17.1: BLoC vs Riverpod 深度对比

答:

BLoC(Business Logic Component):

// 事件驱动、流式响应
// Event → BLoC → State

sealed class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email, password;
  LoginRequested(this.email, this.password);
}
class LogoutRequested extends AuthEvent {}

sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState { final User user; AuthSuccess(this.user); }
class AuthFailure extends AuthState { final String message; AuthFailure(this.message); }

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _repo;

  AuthBloc(this._repo) : super(AuthInitial()) {
    on<LoginRequested>(_onLogin);
    on<LogoutRequested>(_onLogout);
  }

  Future<void> _onLogin(LoginRequested event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    try {
      final user = await _repo.login(event.email, event.password);
      emit(AuthSuccess(user));
    } catch (e) {
      emit(AuthFailure(e.toString()));
    }
  }

  Future<void> _onLogout(LogoutRequested event, Emitter<AuthState> emit) async {
    await _repo.logout();
    emit(AuthInitial());
  }
}

// UI
BlocBuilder<AuthBloc, AuthState>(
  builder: (context, state) => switch (state) {
    AuthInitial() => LoginForm(),
    AuthLoading() => CircularProgressIndicator(),
    AuthSuccess(user: final u) => HomePage(user: u),
    AuthFailure(message: final m) => ErrorView(message: m),
  },
)

Riverpod:

// 编译安全、无需 context、自动依赖管理
final authRepositoryProvider = Provider((ref) => AuthRepository());

final authStateProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
  return AuthNotifier(ref.read(authRepositoryProvider));
});

class AuthNotifier extends StateNotifier<AuthState> {
  final AuthRepository _repo;
  AuthNotifier(this._repo) : super(AuthInitial());

  Future<void> login(String email, String password) async {
    state = AuthLoading();
    try {
      final user = await _repo.login(email, password);
      state = AuthSuccess(user);
    } catch (e) {
      state = AuthFailure(e.toString());
    }
  }
}

// UI
class LoginPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authState = ref.watch(authStateProvider);
    return switch (authState) {
      AuthInitial() => LoginForm(),
      AuthLoading() => CircularProgressIndicator(),
      AuthSuccess(user: final u) => HomePage(user: u),
      AuthFailure(message: final m) => ErrorView(message: m),
    };
  }
}
BLoCRiverpod
数据流事件 → BLoC → 状态(严格单向)函数式、Provider 依赖图
学习曲线中-高
模板代码较多(Event + State + BLoC)较少
测试blocTest(),非常规范Provider override,灵活
context 依赖需要(BlocProvider)不需要(编译安全)
适用团队大型团队(规范强制)中小型团队(灵活高效)

18. 平台通信与混合开发

Q18.1: Platform Channel 和 Add-to-App 方案

答:

// MethodChannel(最常用)
class NativeService {
  static const _channel = MethodChannel('com.app/native');

  Future<String> getPlatformVersion() async {
    return await _channel.invokeMethod('getPlatformVersion');
  }

  // 原生调用 Dart
  void setupCallHandler() {
    _channel.setMethodCallHandler((call) async {
      switch (call.method) {
        case 'onNativeEvent':
          handleNativeEvent(call.arguments);
          return 'handled';
        default:
          throw MissingPluginException();
      }
    });
  }
}
// iOS 端(Swift)
let channel = FlutterMethodChannel(name: "com.app/native",
                                    binaryMessenger: controller.binaryMessenger)
channel.setMethodCallHandler { (call, result) in
    switch call.method {
    case "getPlatformVersion":
        result("iOS \(UIDevice.current.systemVersion)")
    default:
        result(FlutterMethodNotImplemented)
    }
}
// Android 端(Kotlin)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "com.app/native")
    .setMethodCallHandler { call, result ->
        when (call.method) {
            "getPlatformVersion" -> result.success("Android ${Build.VERSION.RELEASE}")
            else -> result.notImplemented()
        }
    }

Pigeon(类型安全的代码生成替代方案):

// 定义接口(pigeon 文件)
@HostApi()
abstract class NativeApi {
  String getPlatformVersion();
  @async
  UserInfo getUserInfo(String userId);
}

// 自动生成 Dart/Swift/Kotlin 代码,类型安全无需手动序列化

Add-to-App(在现有原生应用中嵌入 Flutter):

// iOS:将 Flutter 模块作为 Framework 引入
let flutterEngine = FlutterEngine(name: "my_engine")
flutterEngine.run()

let flutterVC = FlutterViewController(engine: flutterEngine, nibName: nil, bundle: nil)
present(flutterVC, animated: true)
// Android:FlutterFragment 或 FlutterActivity
val flutterFragment = FlutterFragment
    .withCachedEngine("my_engine")
    .build<FlutterFragment>()

supportFragmentManager.beginTransaction()
    .replace(R.id.container, flutterFragment)
    .commit()

19. 性能优化(Flutter)

Q19.1: Flutter 性能优化全面指南

答:

// 1. 减少 rebuild 范围
// ❌ 整个页面因一个计数器重建
class _PageState extends State<Page> {
  int count = 0;
  Widget build(context) {
    return Column(children: [
      ExpensiveHeader(),    // 不需要重建
      Text('$count'),       // 需要重建
      ExpensiveFooter(),    // 不需要重建
    ]);
  }
}

// ✅ 拆分为独立 Widget
class CounterText extends StatefulWidget { ... }
// 或使用 ValueListenableBuilder
ValueListenableBuilder<int>(
  valueListenable: counter,
  builder: (context, value, child) => Text('$value'),
)

// 2. const 构造函数
const SizedBox(height: 16)  // 编译期创建,可复用
const EdgeInsets.all(8)
const TextStyle(fontSize: 14)

// 3. ListView 优化
ListView.builder(
  itemCount: 10000,
  itemExtent: 72,          // 固定高度,跳过布局计算
  itemBuilder: (ctx, i) => ItemTile(items[i]),
)

// 4. 图片优化
Image.asset('photo.png', cacheWidth: 200)  // 降采样
CachedNetworkImage(imageUrl: url)          // 缓存网络图片

// 5. 动画性能
AnimatedBuilder(
  animation: controller,
  child: const ExpensiveChild(),  // 缓存不变的子树
  builder: (ctx, child) => Transform.scale(
    scale: controller.value,
    child: child,  // 复用
  ),
)

// 6. Shader 预热(Impeller 已大幅缓解)
// 收集 SkSL → 编译时打包
// flutter run --profile --cache-sksl --purge-persistent-cache
// flutter build apk --bundle-sksl-path flutter_01.sksl.json

// 7. 使用 DevTools 分析
// flutter run --profile
// 打开 DevTools → Performance → 分析帧耗时

第四部分:跨平台通用

20. 网络与安全

Q20.1: HTTPS、证书固定(Certificate Pinning)和网络安全最佳实践

答:

TLS 握手过程:
Client ──→ ClientHello(支持的密码套件)──→ Server
Client ←── ServerHello + 证书 ←── Server
Client 验证证书链 → 信任 CA → 建立加密连接

证书固定(Certificate Pinning):

// iOS:使用 URLSession 实现
class PinningDelegate: NSObject, URLSessionDelegate {
    let pinnedHashes: Set<String> = ["sha256/BBBBBBBBB..."]

    func urlSession(_ session: URLSession,
                    didReceive challenge: URLAuthenticationChallenge,
                    completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let serverTrust = challenge.protectionSpace.serverTrust,
              let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0) else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }

        let serverHash = sha256(SecCertificateCopyData(certificate) as Data)
        if pinnedHashes.contains(serverHash) {
            completionHandler(.useCredential, URLCredential(trust: serverTrust))
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}
// Android:OkHttp 证书固定
val client = OkHttpClient.Builder()
    .certificatePinner(CertificatePinner.Builder()
        .add("api.example.com", "sha256/BBBBBBBBB...")
        .add("api.example.com", "sha256/backup-pin...")  // 备用 pin
        .build())
    .build()
<!-- Android:Network Security Config(推荐) -->
<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">api.example.com</domain>
        <pin-set expiration="2025-12-31">
            <pin digest="SHA-256">base64-encoded-hash</pin>
            <pin digest="SHA-256">backup-pin-hash</pin>
        </pin-set>
    </domain-config>
</network-security-config>

安全存储:

平台安全存储方案用途
iOSKeychainToken、密码、证书
AndroidEncryptedSharedPreferences / KeystoreToken、敏感配置
Flutterflutter_secure_storage(底层用 Keychain/Keystore)跨平台安全存储
// Android EncryptedSharedPreferences
val masterKey = MasterKey.Builder(context)
    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
    .build()

val prefs = EncryptedSharedPreferences.create(
    context, "secret_prefs", masterKey,
    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
prefs.edit().putString("auth_token", token).apply()

Q20.2: 移动端常见安全威胁与防护

答:

威胁防护措施
中间人攻击(MITM)证书固定、TLS 1.3
逆向工程代码混淆(ProGuard/R8)、防调试检测
数据泄露加密存储、不在日志中打印敏感信息
越狱/Root 检测Jailbreak/Root 检测 + 降级功能
不安全的 WebView禁用 JavaScript(除非必要)、验证 URL scheme
剪贴板泄露敏感字段禁止复制、清除剪贴板
截屏泄露FLAG_SECURE(Android)、applicationDidEnterBackground 遮挡(iOS)
// iOS 防截屏
func applicationWillResignActive(_ application: UIApplication) {
    let blurView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
    blurView.tag = 999
    window?.addSubview(blurView)
}
// Android 防截屏
window.setFlags(
    WindowManager.LayoutParams.FLAG_SECURE,
    WindowManager.LayoutParams.FLAG_SECURE
)

21. CI/CD 与发布

Q21.1: 移动端 CI/CD 流水线设计

答:

# 典型流水线(以 GitHub Actions 为例)
# .github/workflows/mobile.yml

name: Mobile CI/CD

on:
  pull_request:
    branches: [main]
  push:
    tags: ['v*']

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4

      # Flutter
      - uses: subosito/flutter-action@v2
        with:
          flutter-version: '3.24.0'
      - run: flutter pub get
      - run: flutter analyze
      - run: flutter test --coverage

      # iOS
      - run: xcodebuild test -workspace App.xcworkspace -scheme App -destination 'platform=iOS Simulator,name=iPhone 15'

      # Android
      - run: ./gradlew test
      - run: ./gradlew lint

  build-ios:
    needs: test
    runs-on: macos-latest
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - run: flutter build ipa --release --export-options-plist=ExportOptions.plist
      - uses: apple-actions/upload-testflight-build@v1

  build-android:
    needs: test
    runs-on: ubuntu-latest
    if: startsWith(github.ref, 'refs/tags/')
    steps:
      - run: flutter build appbundle --release
      - uses: r0adkll/upload-google-play@v1
        with:
          track: internal

代码签名管理:

平台方案说明
iOSfastlane match集中管理证书和 Provisioning Profile
AndroidPlay App SigningGoogle 管理签名密钥
通用CI 环境变量 + Secrets不在代码中存储密钥

22. 架构设计与系统设计

Q22.1: 如何设计一个离线优先(Offline-First)的移动应用?

答:

┌────────────────────────────────────────────────┐
│                   UI Layer                      │
│    展示数据(始终从本地数据库读取)                │
├────────────────────────────────────────────────┤
│                Repository Layer                 │
│  ┌──────────┐    ┌─────────────┐               │
│  │ Local DB  │←──│ Sync Engine │               │
│  │ (Room/    │   │             │               │
│  │  SQLite/  │──→│ Conflict    │               │
│  │  Hive)    │   │ Resolution  │               │
│  └──────────┘    └──────┬──────┘               │
│                         │                       │
│                  ┌──────▼──────┐               │
│                  │  Remote API  │               │
│                  └─────────────┘               │
└────────────────────────────────────────────────┘
// Repository:先写本地,后台同步
class TodoRepository(
    private val dao: TodoDao,
    private val api: TodoApi,
    private val syncManager: SyncManager
) {
    // 读取:始终从本地
    fun observeTodos(): Flow<List<Todo>> = dao.observeAll()

    // 写入:先本地,然后排队同步
    suspend fun addTodo(todo: Todo) {
        val localTodo = todo.copy(syncStatus = SyncStatus.PENDING)
        dao.insert(localTodo)
        syncManager.enqueueSync(localTodo.id)
    }

    // 同步引擎
    suspend fun sync() {
        // 1. 上传本地未同步的变更
        val pending = dao.getPendingSyncItems()
        for (item in pending) {
            try {
                api.upsert(item)
                dao.updateSyncStatus(item.id, SyncStatus.SYNCED)
            } catch (e: ConflictException) {
                resolveConflict(item, e.serverVersion)
            }
        }

        // 2. 拉取远程变更
        val lastSync = prefs.lastSyncTimestamp
        val remoteChanges = api.getChangesSince(lastSync)
        dao.upsertAll(remoteChanges)
        prefs.lastSyncTimestamp = System.currentTimeMillis()
    }
}

冲突解决策略:

  • Last Write Wins:最后修改时间戳大的胜出(简单但可能丢数据)
  • Server Wins:服务器版本始终优先
  • Client Wins:客户端版本始终优先
  • Manual Merge:提示用户手动解决冲突
  • CRDT:无冲突复制数据类型(适合协作编辑)

Q22.2: 移动端如何实现推送通知系统?

答:

┌─────────┐    ┌──────────┐    ┌──────────────┐    ┌────────────┐
│ Backend  │───→│ Push     │───→│ APNs / FCM   │───→│ Mobile App │
│ Server   │    │ Service  │    │              │    │            │
└─────────┘    └──────────┘    └──────────────┘    └────────────┘
// iOS:APNs 注册
func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { granted, _ in
        guard granted else { return }
        DispatchQueue.main.async { application.registerForRemoteNotifications() }
    }
    return true
}

func application(_ application: UIApplication,
                 didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    let token = deviceToken.map { String(format: "%02x", $0) }.joined()
    api.registerPushToken(token)
}

// 处理推送
extension AppDelegate: UNUserNotificationCenterDelegate {
    // 前台收到通知
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.banner, .sound])
    }

    // 用户点击通知
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        let userInfo = response.notification.request.content.userInfo
        handleDeepLink(from: userInfo)
        completionHandler()
    }
}
// Android:FCM
class MyFirebaseService : FirebaseMessagingService() {
    override fun onNewToken(token: String) {
        api.registerPushToken(token)
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // 数据消息(自定义处理)
        remoteMessage.data["type"]?.let { type ->
            when (type) {
                "chat" -> showChatNotification(remoteMessage.data)
                "order" -> showOrderNotification(remoteMessage.data)
            }
        }

        // 通知消息(系统自动显示)
        remoteMessage.notification?.let { notification ->
            showNotification(notification.title, notification.body)
        }
    }

    private fun showNotification(title: String?, body: String?) {
        val intent = Intent(this, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent = PendingIntent.getActivity(this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE)

        val notification = NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(body)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)
            .build()

        NotificationManagerCompat.from(this).notify(NOTIFICATION_ID, notification)
    }
}
// Flutter:使用 firebase_messaging
FirebaseMessaging.instance.requestPermission();

FirebaseMessaging.instance.getToken().then((token) {
  api.registerPushToken(token!);
});

// 前台消息
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
  showLocalNotification(message);
});

// 后台/终止态 点击通知
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
  navigateToScreen(message.data);
});

Q22.3: 如何设计一个高效的图片加载框架?

答:

请求 URL
   ↓
┌──────────────┐
│ 内存缓存      │ → 命中 → 返回
│ (LRU Cache)  │
└──────┬───────┘
       ↓ 未命中
┌──────────────┐
│ 磁盘缓存      │ → 命中 → 解码 → 存入内存缓存 → 返回
│ (文件系统)    │
└──────┬───────┘
       ↓ 未命中
┌──────────────┐
│ 网络请求      │ → 下载 → 存入磁盘缓存 → 解码 → 存入内存缓存 → 返回
│ (HTTP)       │
└──────────────┘

各平台常用方案:

平台框架特点
iOSKingfisher / SDWebImage / Nuke自动缓存、降采样、动画
AndroidCoil / Glide生命周期感知、协程支持
Fluttercached_network_image内存+磁盘缓存
// Android Coil 示例
AsyncImage(
    model = ImageRequest.Builder(LocalContext.current)
        .data("https://example.com/photo.jpg")
        .crossfade(true)
        .size(200, 200)           // 降采样到指定尺寸
        .memoryCachePolicy(CachePolicy.ENABLED)
        .diskCachePolicy(CachePolicy.ENABLED)
        .build(),
    contentDescription = null,
    modifier = Modifier.size(200.dp),
    placeholder = painterResource(R.drawable.placeholder),
    error = painterResource(R.drawable.error),
)

关键设计考量:

  • 内存缓存大小:通常为可用内存的 1/8
  • 磁盘缓存大小:通常 50-250MB
  • 图片降采样:根据 ImageView 实际尺寸解码,避免全尺寸解码浪费内存
  • 请求合并:相同 URL 的并发请求合并为一个
  • 优先级管理:可见区域的图片优先加载
  • 生命周期绑定:页面销毁时取消请求

Q22.4: 设计一个聊天应用的消息系统(WebSocket + 本地存储)

答:

┌────────────────────────────────────────────────────────────┐
│                     Chat Architecture                       │
│                                                            │
│   UI Layer                                                 │
│   ├── MessageList (observe local DB)                       │
│   └── MessageInput → send()                                │
│                                                            │
│   Domain Layer                                             │
│   └── ChatRepository                                       │
│       ├── observeMessages() → Flow<List<Message>>          │
│       ├── sendMessage(text) → optimistic insert            │
│       └── syncMessages() → pull missing                    │
│                                                            │
│   Data Layer                                               │
│   ├── WebSocketManager (实时消息收发)                       │
│   ├── MessageDao (本地持久化)                               │
│   └── ChatApi (HTTP 补充:历史消息、文件上传)               │
└────────────────────────────────────────────────────────────┘
// 消息模型
@Entity
data class Message(
    @PrimaryKey val id: String = UUID.randomUUID().toString(),
    val chatId: String,
    val senderId: String,
    val text: String,
    val timestamp: Long,
    val status: MessageStatus = MessageStatus.SENDING
)

enum class MessageStatus { SENDING, SENT, DELIVERED, READ, FAILED }

// WebSocket 管理
class WebSocketManager(private val dao: MessageDao) {
    private var socket: WebSocket? = null
    private val _connectionState = MutableStateFlow(ConnectionState.DISCONNECTED)

    fun connect() {
        val request = Request.Builder().url("wss://chat.example.com/ws").build()
        socket = okHttpClient.newWebSocket(request, object : WebSocketListener() {
            override fun onMessage(webSocket: WebSocket, text: String) {
                val message = json.decodeFromString<Message>(text)
                scope.launch { dao.insert(message) }  // 收到消息存入本地
            }

            override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
                _connectionState.value = ConnectionState.DISCONNECTED
                reconnectWithBackoff()  // 指数退避重连
            }
        })
    }

    // 指数退避重连
    private fun reconnectWithBackoff() {
        scope.launch {
            var delay = 1000L
            repeat(10) {
                delay(delay)
                try { connect(); return@launch }
                catch (e: Exception) { delay = minOf(delay * 2, 30_000L) }
            }
        }
    }

    fun send(message: Message) {
        socket?.send(json.encodeToString(message))
    }
}

// Repository:乐观更新
class ChatRepository(
    private val dao: MessageDao,
    private val wsManager: WebSocketManager
) {
    fun observeMessages(chatId: String): Flow<List<Message>> =
        dao.observeMessages(chatId)

    suspend fun sendMessage(chatId: String, text: String) {
        val message = Message(
            chatId = chatId,
            senderId = currentUserId,
            text = text,
            timestamp = System.currentTimeMillis(),
            status = MessageStatus.SENDING
        )

        // 乐观插入:先显示在 UI 上
        dao.insert(message)

        try {
            wsManager.send(message)
            dao.updateStatus(message.id, MessageStatus.SENT)
        } catch (e: Exception) {
            dao.updateStatus(message.id, MessageStatus.FAILED)
        }
    }
}

附录:高频面试知识点速查

iOS 核心

主题关键词
Swift 语言struct vs class、POP、enum 关联值、some/any、@propertyWrapper
内存管理ARC、weak/unowned、闭包 capture list、autoreleasepool
SwiftUI@State/@StateObject/@ObservedObject、声明式 UI、body 重算
并发async/await、Actor/@MainActor、Sendable、TaskGroup
性能Instruments、离屏渲染、图片降采样、启动优化
系统RunLoop、App Lifecycle、Scene、Background Task

Android 核心

主题关键词
Kotlin 语言data/sealed/object class、作用域函数、委托、inline/reified
组件Activity/Fragment 生命周期、ViewModel、配置变更
ComposeRecomposition、remember、derivedStateOf、Stability
Coroutines结构化并发、Flow 冷热流、StateFlow/SharedFlow
JetpackRoom、Hilt、WorkManager、Navigation、Paging
性能Baseline Profile、LeakCanary、R8、Macrobenchmark

Flutter 核心

主题关键词
Dart 语言Null Safety、Isolate、事件循环、Records/Patterns
渲染三棵树、Constraints、RepaintBoundary、Impeller
状态管理BLoC vs Riverpod、InheritedWidget 原理
平台通信MethodChannel/EventChannel/FFI、Pigeon
性能const Widget、ListView.builder、AnimatedBuilder child

跨平台通用

主题关键词
网络HTTPS/TLS、证书固定、安全存储
架构Clean Architecture、单向数据流、离线优先
系统设计推送通知、图片缓存、WebSocket 聊天
CI/CD自动化测试、代码签名、分发