美文网首页
安卓中的进程模型

安卓中的进程模型

作者: 邱穆 | 来源:发表于2020-12-14 14:04 被阅读0次

    一、安卓进程概述

    进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元。

    通俗地讲一个进程代表一个应用程序,该应用程序运行在自己的进程当中,使用系统为其分配的堆内存,不受其他应用程序或者是其他进程的影响,是独立运行的。当然一个进程中可以同时运行多个应用程序,这时堆内存是共享的。

    在Android 系统中一个进程会对应一个虚拟机实例。虚拟机实例的运存在 /system/build.prop 文件里面中配置,该文件在
    一般情况下是不可修改的,所以 Android 在应用程序配置文件AndroidManifest.xml 中的 Application 节点下可设置属性 largeheap="true" 来使用最大的内存。

    二、安卓进程模型

    2.1 每个应用一个id

    在 Linux 中,一个用户ID 识别一个给定用户;在 Android 上,一个用户ID 识别一个应用程序。

    应用程序在安装时被分配用户 ID,应用程序在设备上的存续期间内,用户ID 保持不变。并设置相应的权限,这样其它应用程序就不能访问此应用程序所拥有的数据和资源了

    默认情况下,每个apk运行在它自己的Linux进程中。当需要执行应用程序中的代码时,Android会启动一个虚拟机,即一个新的进程来执行,因此不同的apk运行在相互隔离的环境中。

    下图显示了两个 Android 应用程序,各自在其自己的基本沙箱或进程上。他们是不同的Linux user ID。

    进程模型

    2.1 共享id

    开发者也可以给两个应用程序分配相同的linux用户id,这样他们就能访问对方所拥有的资源。

    假如两个应用程序的用户ID是一样的,那么这两个应用程序将运行在同一个进程中,这就是一个进程中存在多个应用程序的情况,此时这两个应用程序使用同一份堆内存,使用同一个虚拟机。要实现这个功能,首先必须使用相同的私钥签署这些应用程序,然后必须使用AndroidManifest.xml文件给它们分配相同的Linux用户ID,这通过在manifest节点下得属性android:shared UserId来实现。

    如下图,显示了两个 Android 应用程序,运行在同一进程上。

    共享id

    三、安卓进程的管理

    3.1 生命周期

    Android系统为每个应用程序分配了一个进程,应用程序中组件( Activity Service BroadCast )的状态决定的一个进程的“重要性层次”,层次最低的属于旧进程。这个“重要性层次”有五个等级,也就是进程的生命周期,按最高层次到最低层次排列如下:

    1. 前台进程:正在与用户进行交互的进程
    2. 可见进程:可在屏幕上显示但不在前台运行,比如一个前台进程以对话框的形式显示在该进程前面。典型的如输入法。
    3. 服务进程:正在运行已使用 startService() 方法启动的服务且不属于上述两个更高类别进程的进程。尽管服务进程与用户所见内容没有直接关联,但是它们通常在执行一些用户关心的操作(例如,在后台播放音乐或从网络下载数据)
    4. 后台进程:包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 onStop() 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。,一旦按home键(注意不是back键)返回到桌面,程序就停留在后台,成为后台进程。
    5. 空进程:不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。一般来说,当应用按back按键退出后应用后,就变成了一个空进程。

    Home键、Back键和多任务键

    Home键、Back键和多任务键,在手机屏幕的下方,这三个按键一般称为导航栏,中间的按钮为Home键,多任务键和Back键分别在其左右,一般根据手机品牌不同,左右位置也有所差异。

    • Home键。按Home键的时候,App如果没有Service开启,会从一个前台进程转变为一个后台进程;如果有前台service运行,就仍然是前台进程,比如音乐播放器等;如果是只有普通service运行,那么就转变为服务进程(参照前文中讲的Android进程的5个级别)。
    • Back键。按Back键的时候,App如果没有Service开启,会从一个前台进程转变为一个空进程;对于有Service运行的情况,和按Home键一样。

    后台进程和空进程的区别就是,在剩余内存不足时,会优先移除空进程,再不足,才会移除空进程。所以,如果确实要退出某个应用一段时间内不大使用了,如果这款应用有退出按钮,就用应用自带的退出功能;如果没有,则最好按系统的Back键,这样可以变成空进程,当系统要回收内存时,就会优先被回收,从而释放的所占的资源。如果只是暂时退出去做点别的,过一会还要切换回来,或者对这款应用使用比较频繁,那就使用Home键,因为相比于按Back键,这样可以尽可能保住后台进程,方便下次使用的时候快速启动。

    3.2 进程优先级

    Android系统的设计理念是希望应用进程能尽量长时间地存活,以提升用户体验。应用首次打开比较慢,这个过程有进程创建以及Application等信息的初始化,所以应用在启动之后,即便退到后台并非立刻杀死,而是存活一段时间,这样下次再使用则会非常快。物极必反,系统处于低内存的状态下,手机性能会有所下降;系统继续放任所有进程一直存活,系统内存很快就会枯竭而亡,那么需要合理地进程回收机制。

    到底该回收哪个进程呢?系统根据进程的组件状态来决定每个进程的优先级值ADJ,系统根据一定策略先杀优先级最低的进程,然后逐步杀优先级更低的进程,依此类推,以回收预期的可用系统资源,从而保证系统正常运转。

    进程管理主要涉及到两个值:

    • adj:通过调整oom_score_adj来影响进程寿命(Lowmemorykiller杀进程策略);
    • schedGroup:影响进程的CPU资源调度与分配;

    进程的优先级取决于进程四大组件的运行状态。例如Activity是否在前台,用户是否可见;Service正在被哪些客户端使用;ContentProvider正在被哪些客户端使用;BroadcastReceiver是否正在接受广播:

    ProcessRecord

    ProcessRecord中有以下成员变量:

    // 进程中的所有Activity
    final ArrayList<ActivityRecord> activities = new ArrayList<>();
    // 此进程中运行的所有根Activity的任务
    final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
    // 在此进程中运行的所有ServiceRecord
    final ArraySet<ServiceRecord> services = new ArraySet<>();
    // 当前正在执行代码的服务(需要保持在前台).
    final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
    // 此过程保存的所有ConnectionRecord
    final ArraySet<ConnectionRecord> connections = new ArraySet<>();
    //从该过程注册的所有IIntentReceivers。
    final ArraySet<ReceiverList> receivers = new ArraySet<>();
    // class (String) -> ContentProviderRecord
    final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();
    // 进程正在使用的所有ContentProviderRecord
    final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();
    

    即:

    • activities 记录了进程中运行的Activity
    • services,executingServices 记录了进程中运行的Service
    • receivers 记录了进程中运行的BroadcastReceiver
    • pubProviders 记录了进程中运行的ContentProvider

    而:

    • connections 记录了对于Service连接
    • conProviders 记录了对于ContentProvider的连接

    连接的意义在于:连接的客户端的进程优先级会影响被使用的Service和ContentProvider所在进程的优先级。 例如:当一个后台的Service正在被一个前台的Activity使用,那么这个后台的Service就需要设置一个较高的优先级以便不会被回收。(否则后台Service进程一旦被回收,便会对前台的Activity造成影响。)

    Android会依据进程中当前活跃组件的重要程度来尽可能高的估量一个进程的级别,比如说,如一个进程中同时有一个服务和一个可视的 Activity ,则进程会被判定为可视进程,而不是服务进程。

    此外,一个进程的级别可能会由于其他进程依赖于它而升高。一个为其他进程提供服务的进程级别永远高于使用它服务的进程。比如说,如果 A 进程中的内容提供者为进程 B 中的客户端提供服务,或进程 A 中的服务为进程 B 中的组件所绑定,则 A 进程高于或者等于进程 B 的等级。

    ADJ的具体数值:

    级别 常量名称 简述
    -1000 NATIVE_ADJ 并不被系统管理的native进程的优先级
    -900 SYSTEM_ADJ 系统进程
    -800 PERSISTENT_PROC_ADJ 系统级常驻进程,比如telephony
    -700 PERSISTENT_SERVICE_ADJ 关联着系统进程或者常驻进程的进程
    0 FOREGROUND_APP_ADJ 前台APP
    100 VISIBLE_APP_ADJ 可见APP
    200 PERCEPTIBLE_APP_ADJ 可感知的APP
    300 BACKUP_APP_ADJ 正在备份的进程
    400 HEAVY_WEIGHT_APP_ADJ 后台重量级进程,在system/rootdir.init.rc中配置
    500 SERVICE_ADJ 正在运行这service的进程
    600 HOME_APP_ADJ 桌面进程
    700 PREVIOUS_APP_ADJ 上一个APP的进程(通过back返回)
    800 SERVICE_B_ADJ List B中的service进程(区分于上一个 service list,更老、使用到的可能性更小)
    900 CACHED_APP_MIN_ADJ 缓存进程的最小值
    906 CACHED_APP_MAX_ADJ 缓存进程的最大值

    以上各值数值越小,优先级越高

    从Android P(7.0)开始,进一步细化ADJ级别,增加了VISIBLE_APP_LAYER_ MAX(99),是指VISIBLE_APP_ADJ(100)跟PERCEPTIBLE_APP_ADJ(200)之间有99个槽,则可见级别ADJ的取值范围为[100,199]。 算法会根据其所在task的mLayerRank来调整其ADJ,100加上mLayerRank就等于目标ADJ,layer越大,则ADJ越小。

    3.3 进程的回收

    内存的回收包括进程内的回收和进程级的回收,前者是通过进程虚拟机的GC机制实现的,后者则是Linux内核或者安卓系统实现的,如图:

    内存回收

    在安卓中,我们主要关注LowMemoryKiller,也就是所谓的LMK。

    3.3.1 LMK原理

    所有应用进程都是从zygote孵化出来的,记录在AMS中mLruProcesses列表中,由AMS进行统一管理,AMS中会根据进程的状态更新进程对应的oom_adj值,这个值会通过文件传递到kernel中去,kernel有个低内存回收机制,在内存达到一定阀值时会触发清理oom_adj值高的进程腾出更多的内存空间,这就是LowMemoryKiller工作原理。

    3.3.2 基本实现方案

    根据不同手机的配置,就有对应的杀进程标准,这个标准用minfree和adj两个文件来定义:
    /sys/module/lowmemorykiller/parameters/minfree:里面是以","分割的一组数,每个数字代表一个内存级别。
    /sys/module/lowmemorykiller/parameters/adj:对应上面的一组数,每个数组代表一个进程优先级级别

    $ adb shell cat /sys/module/lowmemorykiller/parameters/minfree
    18432,23040,27648,32256,85296,120640
    $ adb shell cat /sys/module/lowmemorykiller/parameters/adj
    0,100,200,300,900,906
    

    进程刚启动时ADJ等于INVALID_ADJ,当执行完attachApplication(),该该进程的curAdj和setAdj不相等,则会触发执行setOomAdj()将该进程的节点/proc/pid/oom_score_adj写入oomadj值。下图参数为Android原生阈值,当系统剩余空闲内存低于某阈值(比如147MB),则从ADJ大于或等于相应阈值(比如900)的进程中,选择ADJ值最大的进程,如果存在多个ADJ相同的进程,则选择内存最大的进程。 如下是64位机器,LMK默认阈值图:

    ADJ和阈值

    Minfree是一个阈值,它跟内存大小和分辨率有关系,通过ProcessList中updateOomLevels计算设置的,这里的adj并不是直接对应ProcessList中定义的。
    而是adj*17/1000=ProcessList文件里面的值

    对于应用进程来说,也需要有自身的adj,由AMS负责更新。定义在oom_adj和oom_score_adj文件中:
    /proc/pid/oom_adj:代表当前进程的优先级,这个优先级是kernel中的优先级。
    /proc/pid/oom_score_adj:这个是AMS上层的优先级,与ProcessList中的优先级对应。

    3.3.3 LMK 回收过程

    ​用户在启动一个进程之后,通常伴随着启动一个Activity页面或者一个Service等等,这个时候此进程的adj被AMS提高,LMK就不会杀死这个进程,当这个进程要做的事情做完了,退出后台了,此进程的adj很快又被AMS降低。

    当需要杀死一个进程释放内存时,一般先根据当前手机剩余内存的状态,在minfree节点中找到当前等级,再根据这个等级去adj节点中找到这个等级应该杀掉的进程的优先级, 之后遍历所有进程并比较进程优先级adj与优先级阈值,并杀死优先级低于阈值的进程,达到释放内存的目的。

    LMK过程

    AMS负责更新各应用的进程优先级与阈值数组
    lmkd负责接收AMS传输过来的数据然后写入到sys与proc节点
    lowmemorykiller则在内存低于阈值时才会被触发并负责杀死低优先级的进程。

    LMK协作

    ​>参考资料

    Android 进程管理概述 - 简书 (jianshu.com)

    (1条消息) Android 系统(248)---解读Android进程优先级ADJ算法_zhangbijun1230的专栏-CSDN博客

    【朝花夕拾】Android性能篇之(六)Android进程管理机制 - 宋者为王 - 博客园 (cnblogs.com)

    相关文章

      网友评论

          本文标题:安卓中的进程模型

          本文链接:https://www.haomeiwen.com/subject/mxqvgktx.html