Android 与 iOS 核心差异

Android 与 iOS 核心差异

专为 Android 开发者整理,需要特别注意的设计理念和实现差异


核心理念差异

Android:组件化 vs iOS:单入口

方面 Android iOS
应用入口 多个 Activity/Service/BroadcastReceiver 单一 App 入口
页面跳转 Activity 栈、Broadcast、Intent View 切换、Navigation
后台运行 多组件可独立运行 依赖 App 生命周期
应用模型 四大组件自由组合 单一 ViewController 树

特别注意

  • iOS 没有 Intent 机制
  • iOS 没有 BroadcastReceiver
  • iOS 的后台能力非常受限
  • 页面跳转通过 Navigation 或 NavigationLink,不是新建实例

生命周期差异

Activity/Fragment vs UIViewController

复制代码
Android Activity 生命周期:
onCreate → onStart → onResume → [运行] → onPause → onStop → onDestroy

iOS ViewController 生命周期:
init → loadView → viewDidLoad → viewWillAppear → viewDidAppear
    → [运行] → viewWillDisappear → viewDidDisappear → deinit

关键差异

Android iOS 说明
onCreate viewDidLoad 视图加载完成
onStart viewWillAppear 即将显示
onResume viewDidAppear 已显示
onPause viewWillDisappear 即将消失
onStop viewDidDisappear 已消失
onDestroy deinit 释放资源

特别注意

  • iOS 没有 onDestroy 的精确对应,viewDidDisappear 不代表页面被销毁
  • ViewController 可能被系统回收(内存不足时)
  • SwiftUI 使用 @StateObject@ObservedObject 替代生命周期管理

SwiftUI 生命周期

swift 复制代码
// SwiftUI 没有传统生命周期,用这些替代
struct ContentView: View {
    @State private var data = ""

    // 视图出现时调用 (类似 onResume)
    .onAppear {
        loadData()
    }

    // 视图消失时调用 (类似 onPause)
    .onDisappear {
        saveData()
    }
}

内存管理差异

ARC vs GC

Android iOS
垃圾回收器 (GC) 自动引用计数 (ARC)
何时回收不确定 引用计数归零立即释放
内存峰值可以较高 内存更可控但可能过早释放

强引用 vs 弱引用

swift 复制代码
// Swift 内存管理
class Person {
    var name: String

    // init
    init(name: String) {
        self.name = name
    }

    // deinit (类似 onDestroy)
    deinit {
        print("\(name) 被释放")
    }
}

// 强引用 - 对象不会被释放
var strongRef: Person? = Person(name: "Alice")

// 弱引用 - 不增加引用计数,可为 nil
weak var weakRef: Person? = Person(name: "Bob")

// 无主引用 - 不增加引用计数,不能为 nil(类似 Kotlin lateinit var)
unowned var unownedRef: Person

特别注意

  • 循环引用是 iOS 最常见的内存泄漏原因
  • ViewController 和 Delegate 之间常用 weak 避免循环引用
  • 闭包中的 self 需要使用 [weak self] 捕获
swift 复制代码
// 闭包中的循环引用
class MyViewController: UIViewController {
    var data: [String] = []

    func loadData() {
        // ❌ 错误:闭包捕获 self,造成循环引用
        network.request { response in
            self.data = response
        }

        // ✅ 正确:使用 weak self
        network.request { [weak self] response in
            self?.data = response
        }

        // ✅ 更好:使用 weak self 并 guard
        network.request { [weak self] response in
            guard let self = self else { return }
            self.data = response
        }
    }
}

Android 对比

kotlin 复制代码
// Android 内存管理更简单
class MyActivity : AppCompatActivity() {
    // Kotlin 有 GC,不需要手动处理循环引用
    fun loadData() {
        network.request { response ->
            data = response  // 直接用,不用担心泄漏
        }
    }
}

UI 架构差异

命令式 vs 声明式

Android iOS
命令式 UI (XML/Kotlin) 声明式 UI (SwiftUI)
手动更新视图 状态驱动自动更新
findViewById 数据绑定
RecyclerView 手动刷新 列表自动响应数据变化

状态管理

swift 复制代码
// SwiftUI 状态管理
struct CounterView: View {
    // @State - 值类型,用于简单状态
    @State private var count = 0

    // @StateObject - 引用类型,用于 ViewModel
    @StateObject private var viewModel = CounterViewModel()

    // @Published - 属性发布变化,类似 LiveData
    // @ObservedObject - 观察外部 ViewModel
    // @EnvironmentObject - 从环境获取共享对象
}

// ViewModel
class CounterViewModel: ObservableObject {
    @Published var count = 0

    func increment() {
        count += 1
    }
}

Android 对比

kotlin 复制代码
// Android LiveData + ViewBinding
class CounterActivity : AppCompatActivity() {
    private lateinit var binding: ActivityCounterBinding
    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityCounterBinding.inflate(layoutInflater)
        setContentView(binding.root)

        viewModel.count.observe(this) { count ->
            binding.textView.text = count.toString()
        }

        binding.button.setOnClickListener {
            viewModel.increment()
        }
    }
}

特别注意

  • SwiftUI 的 @Published 类似 LiveData,但自动在主线程发布
  • SwiftUI 数据流是单向的:View → Action → ViewModel → State → View
  • 不能直接修改 View 中的 @State,必须通过 ViewModel

导航系统差异

Android iOS 说明
Activity 栈 UINavigationController 栈 前进/后退
Intent 跳转 NavigationLink / pushViewController 页面跳转
startActivity NavigationLink 启动新页面
finish() popViewController 返回
FragmentTransaction sheet/fullScreenCover 弹窗/模态页面

SwiftUI 导航

swift 复制代码
// 方式 1: NavigationStack (iOS 16+)
NavigationStack {
    List(items) { item in
        NavigationLink(value: item) {
            Text(item.name)
        }
    }
    .navigationDestination(for: Item.self) { item in
        DetailView(item: item)
    }
}

// 方式 2: NavigationView + NavigationLink (旧版)
NavigationView {
    List(items) { item in
        NavigationLink(destination: DetailView(item: item)) {
            Text(item.name)
        }
    }
}

// 方式 3: 编程式导航
@State private var showDetail = false

Button("查看详情") {
    showDetail = true
}
.sheet(isPresented: $showDetail) {
    DetailView()
}

Android 对比

kotlin 复制代码
// Android Navigation Component
val navController = findNavController(R.id.nav_host_fragment)

navController.navigate(R.id.action_list_to_detail)
navController.popBackStack()

// 或传统方式
val intent = Intent(this, DetailActivity::class.java)
intent.putExtra("item", item)
startActivity(intent)
finish()

特别注意

  • iOS 没有 Intent 的 extras 机制,传值方式不同
  • SwiftUI 传值:初始化时传入、Environment 传递
  • NavigationLink destination 是视图,不是字符串路径
  • 返回手势 iOS 系统自动处理

权限管理差异

Android 6.0+ vs iOS

Android iOS 说明
运行时请求 运行时请求 大部分权限
ActivityCompat.requestPermissions present(UNAuthorizationAlert) 请求方式
onRequestPermissionsResult CLLocationManagerDelegate 等 结果回调
拒绝后可再请求 拒绝后需跳转设置 用户拒绝后
分组权限 单一权限 权限粒度

iOS 权限请求

swift 复制代码
import CoreLocation
import Photos
import AVFoundation

class PermissionManager {
    let locationManager = CLLocationManager()

    func requestLocation() {
        let status = locationManager.authorizationStatus

        switch status {
        case .notDetermined:
            locationManager.requestWhenInUseAuthorization()
        case .authorizedWhenInUse, .authorizedAlways:
            // 已授权
            locationManager.requestLocation()
        case .denied, .restricted:
            // 被拒绝,跳转设置
            if let url = URL(string: UIApplication.openSettingsURLString) {
                UIApplication.shared.open(url)
            }
        @unknown default:
            break
        }
    }

    func requestPhotos() {
        PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in
            DispatchQueue.main.async {
                switch status {
                case .authorized, .limited:
                    // 访问照片
                    break
                case .denied, .restricted:
                    // 跳转设置
                    break
                default:
                    break
                }
            }
        }
    }
}

Android 对比

kotlin 复制代码
class PermissionActivity : AppCompatActivity() {
    private val locationPermission = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )

    fun requestLocation() {
        if (checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED) {
            // 已授权
        } else {
            requestPermissions(locationPermission, REQUEST_CODE)
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == REQUEST_CODE) {
            if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 授权成功
            }
        }
    }
}

特别注意

  • iOS 权限说明必须写在 Info.plist 的对应 key 下
  • 用户拒绝后只能跳转设置,无法再次弹出请求
  • iOS 蓝牙权限需要 NSBluetoothAlwaysUsageDescription

线程模型差异

Main Thread vs UI Thread

Android iOS
主线程 = UI 线程 主线程 = UI 线程
所有 UI 操作在主线程 所有 UI 操作在主线程
Handler / runOnUiThread DispatchQueue.main
Coroutines (多线程) async/await / GCD
后台线程无限制 后台执行有限制

Swift 并发

swift 复制代码
// Swift 5.5+ async/await (类似 Kotlin Coroutines)
func fetchUser() async throws -> User {
    let url = URL(string: "https://api.example.com/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()
        await MainActor.run {
            // 更新 UI - 必须回到主线程
            self.nameLabel.text = user.name
        }
    } catch {
        print("Error: \(error)")
    }
}

// GCD 方式 (类似 Handler)
DispatchQueue.global(qos: .background).async {
    // 后台执行
    let result = self.doHeavyWork()

    DispatchQueue.main.async {
        // 回到主线程更新 UI
        self.label.text = result
    }
}

Android 对比

kotlin 复制代码
// Kotlin Coroutines
GlobalScope.launch(Dispatchers.IO) {
    val user = api.fetchUser()

    withContext(Dispatchers.Main) {
        // 更新 UI
        nameText.text = user.name
    }
}

// 或
runOnUiThread {
    nameText.text = user.name
}

特别注意

  • SwiftUI 的 View 更新自动在主线程
  • @Published 属性发布默认在主线程
  • 长时间后台任务 iOS 会被系统挂起
  • Background Tasks API 有限制

包管理差异

Gradle vs CocoaPods / SPM

Android iOS 说明
build.gradle Podfile / Package.swift 依赖声明
Gradle CocoaPods / SPM 包管理器
Maven/Gradle Repository CocoaPods trunk / Git 依赖源
implementation 'com.android...' pod 'Alamofire', '~> 5.0' 声明方式
sync project pod install 安装依赖
aar framework 产物格式

Swift Package Manager

swift 复制代码
// Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    platforms: [.iOS(.v15)],
    products: [
        .library(
            name: "MyApp",
            targets: ["MyApp"]),
    ],
    dependencies: [
        .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.0.0")
    ],
    targets: [
        .target(
            name: "MyApp",
            dependencies: [
                .product(name: "Alamofire", package: "Alamofire")
            ])
    ]
)

CocoaPods

ruby 复制代码
# Podfile
platform :ios, '15.0'
use_frameworks!

target 'MyApp' do
  pod 'Alamofire', '~> 5.0'
  pod 'SnapKit', '~> 5.0'
  pod 'Kingfisher', '~> 7.0'
end

post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.0'
    end
  end
end

特别注意

  • Xcode 需要打开 .xcworkspace 而不是 .xcodeproj(使用 CocoaPods 时)
  • SPM 和 CocoaPods 不能同时使用做主依赖管理
  • 版本号写法不同:~> 5.0 等于 >= 5.0 and < 6.0

其他重要差异

1. 文件系统

Android iOS 说明
/data/data/package/ App Sandbox 应用私有目录
getExternalFilesDir() Documents/ 外部存储
SAF (Storage Access Framework) UIDocument / FileManager 文件访问

2. 应用签名

Android iOS 说明
debug.keystore / release.jks Certificate + Provisioning 签名文件
v1 / v2 / v3 签名 代码签名 (Code Sign) 签名方式
Play Store 自动签名 必须手动管理证书 证书管理
bash 复制代码
# iOS 签名相关命令
security find-identity -v -p codesigning  # 列出证书

3. 应用审查

Android iOS 说明
审核 1-7 天 审核 1-3 天 (首次 7-14 天) 审核时间
分级制度 评分制度 内容分级
权限声明可选 权限说明必须完整 权限说明

4. 后台执行

Android iOS 说明
多任务后台 严格限制 后台能力
JobScheduler / WorkManager Background Tasks (有限) 后台任务
推送唤醒 APNs 推送 推送机制
swift 复制代码
// iOS 后台刷新
BGAppRefreshTask.shared.register(
    forTaskWithIdentifier: "com.example.refresh"
) { task in
    // 执行后台刷新
    task.setTaskCompleted(success: true)
}

5. 深链接 (Deep Link)

Android iOS
Intent Filter URL Scheme
App Links Universal Links
Deferred Deep Link Universal Links
swift 复制代码
// iOS Universal Links 配置
// 需要在 Apple Developer 配置 associated domains
// applinks:yourdomain.com

// 处理
func application(_ application: UIApplication,
                continue userActivity: NSUserActivity,
                restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let url = userActivity.webpageURL else {
        return false
    }

    // 处理 deep link
    return handleDeepLink(url)
}

常见踩坑清单

必踩坑及解决方案

# 踩坑点 问题描述 解决方案
1 闭包循环引用 内存泄漏 始终使用 [weak self]
2 UI 在后台线程更新 崩溃 DispatchQueue.main
3 权限 Info.plist 缺失 崩溃 添加对应 UsageDescription
4 忘记处理 Optional 编译错误 guard let / if let
5 Navigation 返回手势 意外返回 navigationBarBackButtonHidden(true)
6 @Published 默认主线程 行为不符预期 注意线程切换
7 async/await 主线程假象 死锁 await MainActor.run {}
8 xcworkspace vs xcodeproj 找不到文件 用 workspace
9 CocoaPods 冲突 编译失败 pod deintegrate && pod install
10 真机签名失效 无法运行 更新证书和描述文件

快速对比速查表

方面 Android iOS
语言 Kotlin / Java Swift
UI 框架 Jetpack Compose / XML SwiftUI / UIKit
架构组件 ViewModel, LiveData, Room ObservableObject, @Published, SQLite
依赖管理 Gradle CocoaPods / SPM
包格式 APK / AAB IPA
应用商店 Play Store App Store
导航 NavController / Intent NavigationStack / NavigationLink
权限请求 requestPermissions 框架特定 API
后台任务 WorkManager BackgroundTasks (受限)
生命周期 onCreate/onDestroy viewDidLoad/deinit
内存管理 GC ARC
线程 Coroutines async/await / GCD
网络 Retrofit / OkHttp Alamofire / URLSession
图片加载 Coil / Glide Kingfisher / AsyncImage
布局 ConstraintLayout SwiftUI Stacks / Auto Layout
列表 RecyclerView List / LazyVStack
调试 Logcat / Android Studio Xcode / Console

这份文档帮助 Android 开发者快速识别 iOS 开发中的关键差异,避免常见陷阱。

相关推荐
UXbot1 小时前
Vibecoding 工具如何一次性生成 Web + iOS + Android 三端 APP?功能架构深度解读
android·前端·ui·ios·交互·软件构建·ai编程
Digitally1 小时前
6 种简单快速导出 iPhone 照片的方法
ios·iphone
鹏晨互联1 小时前
Jetpack Compose vs XML:fillMaxSize、fillMaxHeight、fillMaxWidth 全面对比
android·xml
Android小码家1 小时前
ptrace 内存追踪
android
三少爷的鞋1 小时前
为什么 Android 不用接口做 Activity 通信?
android
恋猫de小郭1 小时前
2026 Android I/O ,全新 AI 手机、 Android PC 和车载驾驶
android·前端·flutter
MonkeyKing71551 小时前
iOS音频编解码基础:PCM、WAV、MP3、AAC、FLAC 格式差异与移动端适配
ios·objective-c·音视频
2501_916008893 小时前
Xcode功能、下载、反馈与版本支持详细解析
ide·vscode·macos·ios·个人开发·xcode·敏捷流程
90后的晨仔13 小时前
SwiftUI高级特性之高级主题系统设计与实现
ios