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 Navigation vs iOS Navigation
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 开发中的关键差异,避免常见陷阱。