
1. 前台、后台不是用户看到的那么简单系统视角下的三六九等你有没有过这种经历手机切到微信回个消息再切回刚才的购物App发现页面还在原地音乐App锁屏了照样放歌导航App切后台了照样播报。但反过来有些App切走后再回来画面直接白屏重加载甚至刚切出去就被系统“掐死”。同样是App为什么差别这么大这里面的关键就是App的“前台”和“后台”到底处于什么状态以及它在这两种状态下有没有继续拿到系统资源。很多人听到“前台”和“后台”第一反应是“看得见”和“看不见”。这个理解没错但不够准确。系统对App的调度是按“进程优先级”来分的而进程优先级又由“是否被用户看见”“是否正在交互”“是否被系统判定为重要”三件事共同决定。换句话说一个App切到后台后还能不能跑、能跑多久、能跑些什么不是它自己说了算而是系统在统一调度。1.1 前台状态也分“活跃”和“可见但没焦点”以Android为例一个App刚被打开、用户正在上面点击滑动它处在最顶层并且拥有输入焦点这叫“活跃状态”Resumed。此时系统会给它最高优先级CPU、内存、网络基本完全放行代码里new出来的线程、启动的动画、正在收发的数据都能正常执行。但还有一种情况App仍然占据整个屏幕可它已经没有焦点。比如你下拉了通知栏或者弹出了一个权限对话框这时候顶层虽然还看得到界面但你已经不能在App上做操作了。系统会给App发送onPause回调App虽然还可见但已经进入“暂停”状态。很多开发者容易忽略这个细节点onPause不一定是切后台才触发任何临时失去焦点的情况都会触发它。如果把“前台”理解成“用户正在操作”和“用户看得到但不能操作”两个级别就更容易理解后面系统的调度逻辑了。前一种情况下App只要不崩系统基本不会限制它后一种情况下系统已经开始提醒App“你马上要让位了赶紧把手里的事收一收”。1.2 后台状态下的“挂起”和“终止”不是一回事切回桌面或切到另一个App之后原先的App就完全不可见了进入“停止”状态。这里有两个经常被搞混的概念后台和挂起。后台严格来说指进程还在、状态还在但界面不可见挂起是指在后台状态下系统把App的线程全部暂停不让它执行任何代码同时保留它内存里的数据。iOS系统做得比较极端App进入后台后大约只有5秒左右的时间继续执行之后就会被挂起。这也是为什么很多iOS App切后台后再回来界面还在但里面一些实时数据已经“冻住”了等回到前台才重新刷新。Android相对宽容一些允许后台进程短暂运行但也会根据内存压力、耗电情况、后台任务类型在某个时间点把进程“终止”。进程终止意味着什么呢内存被回收App的Java/Kotlin对象全被销毁。如果你的App没有把数据正确保存用户再点开时看到的可能就是启动页或白屏。这里有一个很反直觉的事实从用户角度看他只是“切走了几分钟”但系统可能已经把进程杀了从开发者角度看你以为用户回来后会触发某个“恢复”回调实际上整个App是从头重新启动的。我把这三种状态放在一起对比一下看的时候更直观状态用户感知CPU网络内存前台活跃正常操作页面实时更新完全放行正常请求正常占用后台短暂运行切走后仍可能产生通知、播放声音受限但允许短时执行受限会被集中限制进程保留挂起切回来时页面“停在走之前那一下”几乎等于0暂停进程保留但可能被回收终止切回来时App重新加载无无已释放所以“App在后台到底在忙啥”这个问题真正的答案是大多数情况下后台App其实什么都没忙它只是“躺着不动”是系统在替它保管数据、压缩内存、记录状态。真正能让App继续忙的要么是它申请了特殊资格比如前台服务要么是系统给了它短暂的任务窗口。2. 切换瞬间系统给App的“命令”生命周期回调是怎么排队执行的App从后台切回前台或者从前台切走系统并不会莫名其妙地暂停和恢复它而是会通过一套成熟的生命周期回调机制明确告诉App“该收尾了”或者“该醒来了”。开发者如果遵循这套规则App就会表现得很省电、很稳定如果无视规则硬要搞自己的逻辑就会出现各种问题后台耗电、状态丢失、切回后白屏。2.1 AndroidActivity和整个App的生命周期不是一回事做Android开发的人都知道Activity有一串回调onCreate、onStart、onResume、onPause、onStop、onDestroy。但很多人并不清楚这串回调并不完全对应“前后台切换”它主要描述的是Activity自己在屏幕上怎么变化。比如用户按下Home键当前Activity会依次走onPause和onStop用户再点回AppActivity会走onRestart、onStart、onResume。但如果你在这个Activity上又打开了另一个Activity原本那个Activity也会走onPause和onStop可它显然还在“前台App的栈里”并没有真正退到后台。所以单看Activity回调无法准确判断整个App有没有进入后台。判断整个App前后台更可靠的方式是使用AndroidX提供的ProcessLifecycleOwner。它可以监听整个进程的生命周期当最后一个Activity走onStop时它判定App真正退到了后台当第一个Activity走onStart时判定App回到了前台。这个机制在业务上非常有用比如做用户埋点、判断是否需要暂停视频播放、或者统计App在前台停留时长。我见过不少刚接触Android的开发者把onDestroy当成“用户退出App”的唯一信号把所有数据保存逻辑都写在那里。结果遇到系统为了省电直接把进程杀掉时onDestroy根本不会被调用数据就丢了。这是一个很痛的教训。正确的习惯是把关键数据保存在onPause或onStop里甚至每次变更都实时写入本地存储而不是指望某个“最后时刻”帮你兜底。2.2 iOS的切入后台时间窗口很严格iOS的生命周期相比Android更直接。App进入后台时系统会调用applicationDidEnterBackground然后给你一个非常短暂的收尾时间。以前有开发者误以为可以在这个方法里做长时间的数据上传结果发现App很快就挂起任务根本没执行完。苹果的设计思路很清晰用户在界面上看不到你的App了你就可以停了。但如果你的App确实有特殊需求比如要向服务器发送一个紧急请求iOS允许你通过beginBackgroundTask(expirationHandler:)申请一段额外时间通常是几分钟。在这段时间里App还能继续执行代码等到时间结束前必须主动结束任务否则系统会强制终止。回复前台时对应的回调是applicationWillEnterForeground和applicationDidBecomeActive。这里有个很容易被忽视的点如果用户是通过通知栏点击进入App的回调顺序会和普通启动不完全一样。有些App在didFinishLaunchingWithOptions里判断启动来源结果漏掉了从后台被通知点亮的情况导致跳转逻辑出错。2.3 为什么这些回调一定得“及时收尾”系统设计这些回调本质上是在告诉开发者一句话你想干活可以但必须按我给的窗口来。窗口一关你再强行干活就是偷跑。偷跑有什么后果最直接的是耗电。想象一下用户以为你已经关了App结果你的线程还在后台轮询服务器、刷新定位、解析数据。手机的电量开始哗哗掉系统会把这些耗电记录算到你的App头上。如果一天之内反复出现这种情况Android会弹出“后台高耗电”通知严重时还会在电池优化里直接限制你的后台活动。iOS虽然没有明说“记小本本”但用户的感知更直接App切后台没几分钟再打开时发现重新加载了一遍。因为系统判定你这个App后台行为太重宁可把它早点挂起也不想让它后台折腾。这里给一个我自己的经验刚做开发时总想着“既然能后台执行那就多干点”在onPause里上传日志、刷新Token、清理缓存结果把用户切出App的1秒搞得特别卡。后来才明白能放在前台做的事情不要拖到后台做必须后台做的要做“任务最小化”比如把数据先存在本地等系统给了安静窗口再批量上传。3. 后台里真正值得“忙”的事情前台服务、后台任务和定位的正规玩法既然系统限制这么严那有没有光明正大让App在后台继续跑的方案有。关键是你要向系统证明这个后台行为是用户感知得到的或者是有明确业务价值的而不是单纯想偷偷驻留。正规的开发路线大概有三条前台服务、后台任务调度、后台定位。3.1 前台服务音乐、导航、通话这类场景的“合法后台身份”音乐App锁屏后还能播放导航App切到后台还能继续语音播报靠的都是“前台服务”Foreground Service。名字有点容易误解它运行在“后台”但会通过常驻通知栏的方式让用户明确知道“这个App还在干活”。以Android为例创建一个前台服务必须同时发起一个通知通知里通常要写明“正在播放音乐”或“正在使用位置信息”这类内容。用户看到通知栏有一条去不掉的消息可能会觉得烦但这正是系统要求必须让用户知情。Android 13之后通知不再是一刀切而是把前台服务按类型区分比如mediaPlayback媒体播放、location定位、phoneCall通话等。启动不同类型服务需要声明对应的权限。在代码里用startForeground()传一个通知实例即可很多新人漏掉这个调用导致App在后台时服务直接被系统判为“非法后台启动”。iOS对应的方案是AVAudioSession配合后台音频模式。如果你只是想让App在后台播放音乐需要在Info.plist里声明UIBackgroundModes添加audio。声明之后系统允许App锁屏播放音频但前提是确实有音频输出如果播放器没有真正播放声音系统照样会挂起你的App。这方面苹果比Android更严格不是声明了就能一直跑而是检测到你在干实事才放行。3.2 后台任务与消息推送用系统给的窗口干定时活很多App都有“每天定时同步数据”的需求。常规做法是每隔几分钟请求一次接口但这在现在的系统环境下行不通了因为系统会限制后台网络而且连续多次唤醒会消耗大量电量。Android推荐的做法是WorkManager。它是一个延迟任务调度器适合执行一次性或周期性的后台任务。关键理解是WorkManager不保证“到点就执行”它会在满足条件的窗口内执行比如设备空闲、电量充足、处于网络环境时。如果你想实现“每天早上8点准时报到”WorkManager不够精确可能需要配合AlarmManager但Android 12开始对精确闹钟增加了开关限制用户可以在设置里关掉所以“精确”本身也是有代价的。推送则是另一套完全不同的逻辑。Android用FCMFirebase Cloud MessagingiOS用APNs。推送消息其实是服务器直接发到系统推送服务再由系统显示在通知栏里整个过程根本不需要App常驻后台。很多产品经理会问为什么我关了微信的后台刷新微信还能收到消息答案就是这个消息不是微信App在后台收的是系统收的微信只是收到了系统转交的通知并展示。设计新App时把“实时通信”做成推送而不是自己后台轮询是现代移动开发的必选项。3.3 后台定位需求真实但权限门槛越来越高运动记录、出行助手这类App确实需要在后台持续获取位置。这个领域的坑几乎是所有做过定位类功能的人都能写出一篇血泪史。Android方面即使你已经申请了ACCESS_FINE_LOCATION和ACCESS_COARSE_LOCATION后台定位还需要单独声明ACCESS_BACKGROUND_LOCATION。而且系统规定后台定位权限必须在设置里单独开启不能在运行时直接弹窗申请。用户必须先去应用详情页在“权限”里找到“后台位置信息”手动打开。这么高的门槛直接导致了很多App的后台定位功能形同虚设。iOS方面后台定位模式需要在Info.plist里声明location并且会在用户授权定位时弹出三个选项“使用App期间”“使用App期间或后台”和“精确位置”。注意如果用户选了“使用App期间”后台定位就完全不可用你只能在App切后台的那几分钟内接收最后一次位置更新。实际开发中很多用户会因为隐私担忧选择限制定位权限产品不能把后台定位当成默认能力来设计。我接过一个运动类项目需求是“关闭屏幕后也能记录跑步轨迹”。听起来合理但上线后不断被用户反馈“轨迹缺失一段”。查了半天发现是用户在系统设置里关闭了“后台位置信息”或者手机自带的省电策略在我们App进入后台一段时间后暂停了定位更新。最后方案是在进入运动状态时开启前台服务并展示“正在记录运动轨迹”通知同时检查用户是否开启了后台定位没开启就弹窗引导。上线后轨迹缺失率才明显下降。3.4 实际案例怎么应对“让App永远在线”的产品需求几乎每一个做App的开发者都被产品经理问过同一句话“为什么别人的App可以后台一直运行我们的不行”说实话现在真正能长期后台运行的App要么有系统级别的特殊协议要么就是通过前台服务让用户在通知栏看到了明确的运行状态。以我自己做即时通讯工具的观察为例一些主流的聊天工具Android端经常用“推送服务”加“网络连接保持”的方案iOS端则主要靠推送。Android端如果强制结束App消息就会延迟原因是推送通道被系统砍掉了等下次打开App或者系统允许服务重启时才会补收消息。这和“永久后台保活”是两码事。如果产品经理提“必须后台一直驻留”我的建议是先拆解真实需求。是要“收到服务器消息”那就用推送。是要“定时上报位置”那就评估是否值得用前台服务。是要“监测用户每天走了多少步”那就用系统自带的运动传感器不需要App常驻。把需求拆成系统允许的方式比研究任何“黑科技”都省心。4. 为什么你的App被杀、别人的App却活着后台限制和厂商策略的博弈后台限制是现代移动系统最让开发者头疼的部分没有之一。明明代码写得没问题测试也过了上线后用户手机上却总是收不到推送、定位断掉、后台服务被杀。这不见得是代码的问题更像是系统对后台资源的分配策略越来越像一个“精打细算的管家”而不同手机厂商的管家脾气还不一样。4.1 Android后台限制演进从“随便跑”到“必须申请”Android 7及之前后台App要启动服务、执行网络请求其实没有太严格的限制。真正开始收紧是在Android 8。从8.0开始系统禁止了大多数隐式广播比如你不能再通过清单文件隐式监听网络变化来拉起后台服务同时系统会限制后台应用创建后台服务除非使用前台服务或者被系统放行。Android 10引入了“后台Activity启动限制”后台App不能直接跳起一个Activity盖在用户当前正在用的App上面防止应用“强行弹窗”。Android 12收紧的是精确闹钟和通知权限Android 13要求前台服务必须声明类型Android 14更是进一步要求每个前台服务都要有对应的权限类型。这套演进方向非常明确不是不让你后台干活而是每个后台动作都要有明确的“用途”和“title”。普通开发者如果不关注这些系统版本变化就很容易在用户升级系统后突然莫名其妙地崩溃。比如以前startForegroundService()之后忘了在几秒钟内调用startForeground()在旧版本可能不会立刻出问题在高版本会直接抛ForegroundServiceDidNotStartInTimeException。4.2 国产ROM的“自启动管理”为什么经常误伤正常App国内手机和国外Android手机有个很大的差异国产ROM普遍在系统层加了一套更激进的“清扫”策略比如MIUI的“自启动管理”、EMUI的“应用启动管理”、ColorOS的“省电模式”。这些策略会在App“退到后台一段时间”后主动冻结进程或者拦截应用自启动。这带来了一个非常典型的开发现象同一套代码在Pixel或原生Android上测试一切正常在国产手机上却会收到大量“App被杀”的反馈。并不是你的服务没有优先级而是厂商的优化策略把“杀后台”当成了卖点——续航更长用户就觉得手机更好。我遇到过最夸张的一次是测试机上一款App退到后台五分钟再看日志发现进程被系统用“update app”策略强制停止了而且没有任何系统级恢复机制。最后是引导用户去电池管理里把App设为“不限制”同时把核心后台功能改成通过推送系统实现才彻底解决问题。这里有个底线不要引导用户做太复杂的“加白名单”操作一方面体验差另一方面应用商店审核也会判定为干扰系统控制。4.3 用户日常会踩到的“假象”杀后台、推送、页面恢复对普通用户来说“前台后台”的感受更多是“切回去页面还在不在”。这里有几个经常被误解的现象第一为什么我划掉聊天App消息还是能收到因为消息走的是系统推送通道App进程虽然被杀了但系统服务可以重新唤醒它来展示通知。划掉App不等于永久离线更像是一个“请勿打扰”开关。第二为什么有的App切后台几小时回来原封不动这可能是系统保留了它的进程也可能是App做了“界面状态保存”。Android里用onSaveInstanceState保存界面临时数据功能很强大但需要开发者主动写逻辑。很多旅游类、购物类App切回来还是原页面就是做了这一步如果是白屏重载说明没有做好状态保存或者进程刚被杀了。第三为什么有些App在后台“杀不死”通常是因为它们有前台服务或者系统级的推送通道。比如正在播放音乐时音乐App的前台服务会顶着你划掉应用的任务卡片它依然会播放直到你专门在通知栏里停止。这其实不算Bug是产品有意为之用户也可以用“强制停止”来真正结束它。5. 怎么看到App前后台到底在忙什么排查工具与个人经验了解了一堆理论最实际的问题是我作为一个普通用户或开发者怎么直观看到某个App在前后台干了什么我觉得有几个低成本又好用的办法不需要太复杂的工具。5.1 安卓开发者选项正在运行的服务看一眼就懂在Android手机上打开“设置→开发者选项→正在运行的服务”能看到当前所有在跑的服务列表。每一栏会显示服务名称、所在App、进程名、占用内存大小。比如你会发现微信在后台挂着一个类似WMPFService的服务音乐App在后台挂着一个带“前台服务”标识的媒体服务。如果你发现某个你认为早就退出的App后台居然还挂着服务那基本可以判断它的后台行为是什么类型。有些系统还会显示“缓存正在停止的进程”这类进程通常已被系统冻结但状态还在方便你快速回到原界面。iOS用户可以参考“设置→通用→后台App刷新”切换列表里各个App的开关。如果你看到某个App后台刷新是开着的而它其实不太需要可以直接关掉。需要注意的是关掉后台刷新不会影响推送通知很多人以为关了就不收消息了其实推送依然能到达。5.2 用adb看进程状态开发者的另一个维度如果你会使用adb还能看得更细。连接手机后执行adb shell dumpsys activity processes里面每一个进程后面都有一段adj值这个值就是“进程优先级”。数值越小优先级越高越不容易被杀。比如前台App的adj一般接近0后台不可见进程通常在几百左右。想看某个App有没有后台服务在跑可以执行adb shell dumpsys activity services 包名这会列出该包名下的所有服务及状态。比如isForegroundtrue表示该服务是前台服务isProcessStartedtrue表示进程已启动。我在定位MapView问题的时候常常用这个命令确认系统到底有没有把服务拉起来而不是只看代码日志。5.3 开发时验证“进程被杀后能否恢复”的小实验最实用的开发建议是在开发阶段就测试“切后台后进程被杀”的场景。Android开发者选项里有一个“不保留活动”不保留后台活动开关打开之后用户一离开ActivityActivity立刻被销毁并释放资源。打开这个开关再进App就能模拟出最糟糕的状态。如果App打开后白屏、数据丢失、页面位置不对说明状态管理没有做好。再配合一个操作在开发者选项里把“后台进程限制”改为“不允许保留活动”或“最多两个进程”就能测试系统在内存压力下会优先杀掉哪些App。很多后台崩溃只有在内存紧张时才会出现如果这部分没有测过线上就很容易出现“特定机型问题”。我在发布前都会用不同版本系统过一遍这套测试能发现大量潜在问题。5.4 给普通用户一个最直观的小试验如果你不想碰adb也可以做一个简单的试验打开一个App进到某个页面记录一下页面内容然后回桌面等几分钟再点回去。如果页面还在说明系统保留了进程或App保存了状态如果页面重载说明进程已经被回收了。再用“最近任务”划掉这个App立刻重新点开大多数情况都是从启动页开始这就是“进程被终止后重启”的状态。做这个试验的时候会形成一个直观印象后台App不是“一个人还在偷偷干活”而是像被保安保管起来的一件行李。保安在给你保管如果你不回来取他就可能把行李放去仓库甚至扔掉但如果你身上有特殊的凭证比如前台服务保安就得替你一直看好它。我个人在这条路上踩过最多的坑就是“以为后台能多做一点是一点”。后来慢慢意识到App设计里最好的后台策略不是“多做”而是“少做”和“按系统窗口做”。把需要用户感知的事情用前台服务做把可以延后的事情交给后台任务调度把无法保证的事情做成推送让系统替你传话。这样用户的手机不卡、不耗电你的代码也不容易在升级后突然失灵。最后再分享一个细节切后台前的状态保存永远比“杀进程后的恢复”更可靠。不要赌系统会给你完整的销毁回调把重要数据写在每一次变更的边上比写在一堆生命周期结束时踏实得多。