本文转载自小米开放平台(ID:xiaomideveloper)
作者
袁辉辉:MIUI系统工程师(网络ID: Gityuan)
从事Android framework相关工作
1.1 引言
话说Android开源系统拥有着App不计其数,百家争鸣,都想在这“大争之世”寻得系统存活的一席之地。然则系统资源有限,如若都割据为王,再强劲的CPU也会忙不过来,再庞大的内存终会消耗殆尽,再大容量的电池续航终会昙花一现。
面对芸芸众生,无尽变数,系统以不变应万变,一招绝杀神技forceStop腾空出世,此处以adb指令的方式为例来说说其内部机理:
force-stop命令杀掉所有用户空间下的包名pkgName相关的信息,也可以通过--user来指定用户Id。 当执行上述am指令时,则会触发调用Am.java的main()方法,接下来从main方法开始说起。
1.2 Am.main
[-> Am.java]
1.3 Am.run
[-> Am.java]
1.4 Am.onRun
[-> Am.java]
1.5 Am.runForceStop
[-> Am.java]
当不指定userId时,则默认为UserHandle.USER_ALL。
1.6 AMP.forceStopPackage
[-> ActivityManagerNative.java ::AMP]
1.7 AMN.onTransact
[-> ActivityManagerNative.java]
MP.forceStopPackage来运行在执行adb时所创建的进程,经过Binder Driver后,进入system_server进程的一个binder线程来执行AMN.forceStopPackage,从这开始的操作(包括当前操作)便都运行在system_server系统进程。
1.8 小节
进程绝杀技force-stop,并非任意app可直接调用, 否则App间可以相互停止对方,则岂非天下大乱。该方法的存在便是供系统差遣。一般地,点击home弹出的清理用户最近使用app采取的策略便是force-stop.
至于force-stop的触发方式,除了adb的方式,还可通过获取ActivityManager再调用其方法forceStopPackage(),不过这是@hide隐藏方法,同样是需要具有FORCE_STOP_PACKAGES权限。虽然第三方普通app不能直接调用,但对于深入理解Android,还是很有必要知道系统是如何彻底清理进程的过程。接下来,进入AMS来深入探查force-stop的内部机理。
2.1 AMS.forceStopPackage
[-> ActivityManagerService.java]
这里有一个过程非常重要,那就是setPackageStoppedState()将包的状态设置为stopped,那么所有广播都无法接收,除非带有标记FLAG_INCLUDE_STOPPED_PACKAGES的广播,系统默认的广播几乎都是不带有该标志,也就意味着被force-stop的应用是无法通过建立手机网络状态或者亮灭的广播来拉起进程。
当使用force stop方式来结束进程时, reason一般都是”from pid “ + callingPid. 当然也有另外,那就是AMS.clearApplicationUserData方法调用forceStopPackageLocked的reason为”clear data”.
2.2 AMS.forceStopPackageLocked
清理跟该包名相关的进程和四大组件之外,还会发送广播ACTION_PACKAGE_RESTARTED,用于清理已注册的alarm,notification信息。
2.3 AMS.forceStopPackageLocked
对于didSomething只指当方法中所有行为,则返回true.比如killPackageProcessesLocked(),只要杀过一个进程则代表didSomething为true.
该方法的主要功能:
Process: 调用AMS.killPackageProcessesLocked()清理该package所涉及的进程;
Activity: 调用ASS.finishDisabledPackageActivitiesLocked()清理该package所涉及的Activity;
Service: 调用AS.bringDownDisabledPackageServicesLocked()清理该package所涉及的Service;
Provider: 调用AMS.removeDyingProviderLocked()清理该package所涉及的Provider;
BroadcastRecevier: 调用BQ.cleanupDisabledPackageReceiversLocked()清理该package所涉及的广播
接下来,从这5个角度来分别说说force-stop的执行过程。
3.1 AMS.killPackageProcessesLocked
一般地force-stop会指定包名,该方法会遍历当前所有运行中的进程mProcessNames,以下条件同时都不满足的进程,则会成为被杀的目标进程:(也就是说满足以下任一条件都可以免死)
persistent进程:
进程setAdj
非UserHandle.USER_ALL同时, 且进程的userId不相等:多用户模型下,不同用户下不能相互杀;
进程没有依赖该packageName, 且进程的AppId不相等;
进程没有依赖该packageName, 且该packageName没有运行在该进程.
通俗地来说就是:
除此之外,以下情况则必然会成为被杀进程:
进程已标记remove=true的进程,则会被杀;
进程的pkgDeps中包含该packageName,则会被杀;
进程的pkgList中包含该packageName,且该进程与包名所指定的AppId相等则会被杀;
进程的pkgList是在启动组件或者创建进程的过程向该队列添加的,代表的是该应用下有组件运行在该进程。那么pkgDeps是指该进程所依赖的包名,调用ClassLoader的过程添加。
3.2 AMS.removeProcessLocked
该方法的主要功能:
从mProcessNames, mPidsSelfLocked队列移除该进程;
移除进程启动超时的消息PROC_START_TIMEOUT_MSG;
调用app.kill()来杀进程会同时调用Process.kill和Process.killProcessGroup, 该过程详见理解杀进程的实现原理
调用handleAppDiedLocked()来清理进程相关的信息, 该过程详见binderDied()过程分析
4.1 ASS.finishDisabledPackageActivitiesLocked
[-> ActivityStackSupervisor.java]
4.2 AS.finishDisabledPackageActivitiesLocked
[-> ActivityStack.java]
4.3 AS.finishActivityLocked
[-> ActivityStack.java]
4.3.1 AR.makeFinishingLocked
[-> ActivityRecord.java]
4.3.2 ASS.requestVisibleBehindLocked
4.3.3 TaskRecord.setFrontOfTask
[-> TaskRecord.java]
将该Task中从底部往上查询, 第一个处于非finishing状态的ActivityRecord,则设置为根Activity(即r.frontOfTask = true),其他都为false;
当所有的activity都处于finishing状态,则把最底部的activity设置成跟Activity.
4.3.4 AS.adjustFocusedActivityLocked
[-> ActivityStack.java]
4.3.5 AS.finishCurrentActivityLocked
满足下面其中之一的条件,则会执行finish以及destroy Activity.
模式为FINISH_IMMEDIATELY
模式为FINISH_AFTER_PAUSE, 且Activity状态已处于PAUSED;
Activity的状态为STOPPED或INITIALIZING.
4.3.6 AS.destroyActivityLocked
5.1 bringDownDisabledPackageServicesLocked
[-> ActiveServices.java]
5.2 collectPackageServicesLocked
[-> ActiveServices.java]
该方法的主要功能就是收集该满足条件service放入mTmpCollectionResults.
5.3 bringDownServiceLocked
[-> ActiveServices.java]
5.4 unscheduleServiceRestartLocked
6.1 PM.collectPackageProvidersLocked
[-> ProviderMap.java]
当userId = UserHandle.USER_ALL时, 则会mSingletonByClass
和mProvidersByClassPerUser
结构中查询所有属于该package的providers.
当userId = UserHandle.USER_OWNER时,则会从mSingletonByClass
和mProvidersByClassPerUser
中userId相等的 数据结构中查询所有属于该package的providers.
当userId不属于上述两者之一时,则会从mProvidersByClassPerUser
中userId相等的查询所有属于该package的providers.
6.2 PM.collectPackageProvidersLocked
[-> ProviderMap.java]
6.3 AMS.removeDyingProviderLocked
当其他app使用该provider, 且建立stable的连接, 那么对于非persistent进程,则会由于依赖该provider的缘故而被杀.
7.1 BQ.cleanupDisabledPackageReceiversLocked
[-> BroadcastQueue.java]
该方法主要功能:
7.2 BR.cleanupDisabledPackageReceiversLocked
[-> BroadcastRecord.java]
在前面[小节2.2]介绍到处理完forceStopPackageLocked(),紧接着便是发送广播ACTION_PACKAGE_RESTARTED,经过Broadcast广播分发,最终调用到注册过该广播的接收者。
8.1 Alarm清理
[-> AlarmManagerService.java]
调用AlarmManagerService中的removeLocked()方法,从mAlarmBatches和mPendingWhileIdleAlarms队列中移除包所相关的alarm.
8.2 Notification清理
[-> NotificationManagerService.java]
mNotificationList队列中移除包所相关的Notification.
这里就跟大家分享一段经历吧,记得之前有BAT的某浏览器大厂(具体名称就匿了),浏览器会因为另一个app被杀而导致自己无辜被牵连所杀,并怀疑是ROM定制化导致的bug,于是发邮件向我厂请教缘由。
遇到这个问题,首先将两个app安装到Google原生系统,结果是依然会被级联诛杀,很显然可以排除厂商ROM定制的缘故,按常理说bug应该可以让app自行解决。出于好奇,帮他们进一步调查了下这个问题,发现并非无辜被杀,而是force-stop的级联诛杀所导致的。
简单来说就是App1调用了getClassLoader()来加载App2,那么App1所运行的进程便会在其pkgDeps队列中增加App2的包名,在前面[小节3.2]已经提到pkgDeps,杀进程的过程中会遍历该队列,当App2被forceStop所杀时,便是级联诛杀App1。App1既然会调用App2的ClassLoader来加载其方法,那么就建立了一定的联系,这是Google有意赋予forceStop这个强力杀的功能。
这个故事是想告诉大家在插件化或者反射的过程中要注意这种情况,防止不必要的误伤。接下来具体说说这个过程是如何建立依赖的。
9.1 CI.getClassLoader
[-> ContextImpl.java]
9.2 LA.getClassLoader
[-> LoadedApk.java]
9.3 AMS.addPackageDependency
调用ClassLoader来加载启动包名时,则会将该包名加入到进程的pkgDeps。
forceStop的功能如下:
Process: 调用AMS.killPackageProcessesLocked()清理该package所涉及的进程;
Activity: 调用ASS.finishDisabledPackageActivitiesLocked()清理该package所涉及的Activity;
Service: 调用AS.bringDownDisabledPackageServicesLocked()清理该package所涉及的Service;
Provider: 调用AMS.removeDyingProviderLocked()清理该package所涉及的Provider;
BroadcastRecevier: 调用BQ.cleanupDisabledPackageReceiversLocked()清理该package所涉及的广播
发送广播ACTION_PACKAGE_RESTARTED,用于停止已注册的alarm,notification.
功能点归纳:
force-stop并不会杀persistent进程;
当app被force-stop后,无法接收到任何普通广播,那么也就常见的监听手机网络状态的变化或者屏幕亮灭的广播来拉起进程肯定是不可行;
当app被force-stop后,那么alarm闹钟一并被清理,无法实现定时响起的功能;
app被force-stop后,四大组件以及相关进程都被一一剪除清理,即便多进程架构的app也无法拉起自己;
级联诛杀:当app通过ClassLoader加载另一个app,则会在force-stop的过程中会被级联诛杀;
生死与共:当app与另个app使用了share uid,则会在force-stop的过程,任意一方被杀则另一方也被杀,建立起生死与共的强关系。
既然force-stop多次提到杀进程,那最后简单说两句关于保活:正确的保活姿态,应该是在用户需要时保证千万别被杀,用户不需要时别强保活,一切以用户为出发点。
进程是否需要存活,系统上层有AMS来管理缓存进程和空进程,底层有LowMemoryKiller来根据系统可用内存的情况来管理进程是否存活,这样的策略是从系统整体性角度考虑,为了是给用户提供更好更流畅的用户体验。
用户需要的时候千万别被杀:谨慎使用插件化和共享uid,除非愿意接受级联诛杀和生死与共的场景;还有就是提高自身app的稳定性,减少crash和anr的发生频率,这才是正道。
用户不需要的时候别强保活:为了保活,多进程架构,利用各种小技巧来提升优先级等都是不可取的,一招force-stop足以干掉90%以上的保活策略,当然还有一些其他手段及漏洞来保活,系统层面往往还会采取一些特别的方法来禁止保活。博主曾经干过手机底层的性能与功耗优化工作,深知不少app的流氓行径,严重系统的流畅度与手机续航能力。
为了Android有更好的用户体验,为了不影响手机系统性能,为了不降低手机续航能力,建议大家花更多时间精力在如何提高app的稳健性,如何优化app性能,共同打造Android的良好生态圈。
想了解更多?
那就赶紧来关注我们