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 生命周期
// SwiftUI 没有传统生命周期,用这些替代structContentView:View{@Stateprivatevardata=""// 视图出现时调用 (类似 onResume).onAppear{loadData()}// 视图消失时调用 (类似 onPause).onDisappear{saveData()}}
内存管理差异
ARC vs GC
| Android | iOS |
|---|
| 垃圾回收器 (GC) | 自动引用计数 (ARC) |
| 何时回收不确定 | 引用计数归零立即释放 |
| 内存峰值可以较高 | 内存更可控但可能过早释放 |
强引用 vs 弱引用
// Swift 内存管理classPerson{varname:String// initinit(name:String){self.name=name}// deinit (类似 onDestroy)deinit{print("\(name)被释放")}}// 强引用 - 对象不会被释放varstrongRef:Person?=Person(name:"Alice")// 弱引用 - 不增加引用计数,可为 nilweakvarweakRef:Person?=Person(name:"Bob")// 无主引用 - 不增加引用计数,不能为 nil(类似 Kotlin lateinit var)unownedvarunownedRef:Person
特别注意:
- 循环引用是 iOS 最常见的内存泄漏原因
- ViewController 和 Delegate 之间常用
weak避免循环引用 - 闭包中的 self需要使用
[weak self]捕获
// 闭包中的循环引用classMyViewController:UIViewController{vardata:[String]=[]funcloadData(){// ❌ 错误:闭包捕获 self,造成循环引用network.request{responseinself.data=response}// ✅ 正确:使用 weak selfnetwork.request{[weakself]responseinself?.data=response}// ✅ 更好:使用 weak self 并 guardnetwork.request{[weakself]responseinguardletself=selfelse{return}self.data=response}}}
Android 对比
// Android 内存管理更简单classMyActivity:AppCompatActivity(){// Kotlin 有 GC,不需要手动处理循环引用funloadData(){network.request{response->data=response// 直接用,不用担心泄漏}}}
UI 架构差异
命令式 vs 声明式
| Android | iOS |
|---|
| 命令式 UI (XML/Kotlin) | 声明式 UI (SwiftUI) |
| 手动更新视图 | 状态驱动自动更新 |
| findViewById | 数据绑定 |
| RecyclerView 手动刷新 | 列表自动响应数据变化 |
状态管理
// SwiftUI 状态管理structCounterView:View{// @State - 值类型,用于简单状态@Stateprivatevarcount=0// @StateObject - 引用类型,用于 ViewModel@StateObjectprivatevarviewModel=CounterViewModel()// @Published - 属性发布变化,类似 LiveData// @ObservedObject - 观察外部 ViewModel// @EnvironmentObject - 从环境获取共享对象}// ViewModelclassCounterViewModel:ObservableObject{@Publishedvarcount=0funcincrement(){count+=1}}
Android 对比
// Android LiveData + ViewBindingclassCounterActivity:AppCompatActivity(){privatelateinitvarbinding:ActivityCounterBindingprivatevalviewModel:CounterViewModelbyviewModels()overridefunonCreate(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 导航
// 方式 1: NavigationStack (iOS 16+)NavigationStack{List(items){iteminNavigationLink(value:item){Text(item.name)}}.navigationDestination(for:Item.self){iteminDetailView(item:item)}}// 方式 2: NavigationView + NavigationLink (旧版)NavigationView{List(items){iteminNavigationLink(destination:DetailView(item:item)){Text(item.name)}}}// 方式 3: 编程式导航@StateprivatevarshowDetail=falseButton("查看详情"){showDetail=true}.sheet(isPresented:$showDetail){DetailView()}
Android 对比
// Android Navigation ComponentvalnavController=findNavController(R.id.nav_host_fragment)navController.navigate(R.id.action_list_to_detail)navController.popBackStack()// 或传统方式valintent=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 权限请求
importCoreLocationimportPhotosimportAVFoundationclassPermissionManager{letlocationManager=CLLocationManager()funcrequestLocation(){letstatus=locationManager.authorizationStatusswitchstatus{case.notDetermined:locationManager.requestWhenInUseAuthorization()case.authorizedWhenInUse,.authorizedAlways:// 已授权locationManager.requestLocation()case.denied,.restricted:// 被拒绝,跳转设置ifleturl=URL(string:UIApplication.openSettingsURLString){UIApplication.shared.open(url)}@unknowndefault:break}}funcrequestPhotos(){PHPhotoLibrary.requestAuthorization(for:.readWrite){statusinDispatchQueue.main.async{switchstatus{case.authorized,.limited:// 访问照片breakcase.denied,.restricted:// 跳转设置breakdefault:break}}}}}
Android 对比
classPermissionActivity:AppCompatActivity(){privatevallocationPermission=arrayOf(Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION)funrequestLocation(){if(checkSelfPermission(this,Manifest.permission.ACCESS_FINE_LOCATION)==PackageManager.PERMISSION_GRANTED){// 已授权}else{requestPermissions(locationPermission,REQUEST_CODE)}}overridefunonRequestPermissionsResult(requestCode:Int,permissions:Array<outString>,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 5.5+ async/await (类似 Kotlin Coroutines)funcfetchUser()asyncthrows->User{leturl=URL(string:"https://api.example.com/user")!let(data,_)=tryawaitURLSession.shared.data(from:url)returntryJSONDecoder().decode(User.self,from:data)}// 调用Task{do{letuser=tryawaitfetchUser()awaitMainActor.run{// 更新 UI - 必须回到主线程self.nameLabel.text=user.name}}catch{print("Error:\(error)")}}// GCD 方式 (类似 Handler)DispatchQueue.global(qos:.background).async{// 后台执行letresult=self.doHeavyWork()DispatchQueue.main.async{// 回到主线程更新 UIself.label.text=result}}
Android 对比
// Kotlin CoroutinesGlobalScope.launch(Dispatchers.IO){valuser=api.fetchUser()withContext(Dispatchers.Main){// 更新 UInameText.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
// Package.swiftimportPackageDescriptionletpackage=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
# Podfileplatform:ios,'15.0'use_frameworks!target'MyApp'dopod'Alamofire','~> 5.0'pod'SnapKit','~> 5.0'pod'Kingfisher','~> 7.0'endpost_installdo|installer|installer.pods_project.targets.eachdo|target|target.build_configurations.eachdo|config|config.build_settings['IPHONEOS_DEPLOYMENT_TARGET']='15.0'endendend
特别注意:
- 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 自动签名 | 必须手动管理证书 | 证书管理 |
# iOS 签名相关命令security find-identity-v-pcodesigning# 列出证书
3. 应用审查
| Android | iOS | 说明 |
|---|
| 审核 1-7 天 | 审核 1-3 天 (首次 7-14 天) | 审核时间 |
| 分级制度 | 评分制度 | 内容分级 |
| 权限声明可选 | 权限说明必须完整 | 权限说明 |
4. 后台执行
| Android | iOS | 说明 |
|---|
| 多任务后台 | 严格限制 | 后台能力 |
| JobScheduler / WorkManager | Background Tasks (有限) | 后台任务 |
| 推送唤醒 | APNs 推送 | 推送机制 |
// iOS 后台刷新BGAppRefreshTask.shared.register(forTaskWithIdentifier:"com.example.refresh"){taskin// 执行后台刷新task.setTaskCompleted(success:true)}
5. 深链接 (Deep Link)
| Android | iOS |
|---|
| Intent Filter | URL Scheme |
| App Links | Universal Links |
| Deferred Deep Link | Universal Links |
// iOS Universal Links 配置// 需要在 Apple Developer 配置 associated domains// applinks:yourdomain.com// 处理funcapplication(_application:UIApplication,continueuserActivity:NSUserActivity,restorationHandler:@escaping([UIUserActivityRestoring]?)->Void)->Bool{guarduserActivity.activityType==NSUserActivityTypeBrowsingWeb,leturl=userActivity.webpageURLelse{returnfalse}// 处理 deep linkreturnhandleDeepLink(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 开发中的关键差异,避免常见陷阱。