Android killPackageProcessesLSP 源码分析

news2025/4/28 12:24:03

该方法用于终止指定包名/用户ID/应用ID下符合条件的应用进程,涉及多进程管理、资源冻结、进程清理及优先级更新等操作。核心流程分为进程筛选、资源冻结、进程终止与资源恢复三个阶段。

    /**
     * 从已排序的进程列表中,提取从指定起始索引 startIdx 开始的连续同一 UID 的进程子列表
     */
    private static List<Pair<ProcessRecord, Boolean>> getUIDSublist(
            List<Pair<ProcessRecord, Boolean>> procs, int startIdx) {
        final int uid = procs.get(startIdx).first.uid;
        int endIdx = startIdx + 1;
        while (endIdx < procs.size() && procs.get(endIdx).first.uid == uid) ++endIdx;
        return procs.subList(startIdx, endIdx);
    }
    // 方法声明:需持有mService和mProcLock锁保证线程安全
    @GuardedBy({"mService", "mProcLock"})
    boolean killPackageProcessesLSP(String packageName, int appId,
            int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
            boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling,
            int reasonCode, int subReason, String reason) {
        // 获取包管理服务接口
        final PackageManagerInternal pm = mService.getPackageManagerInternal();
        // 存储待终止的进程列表,Pair<进程记录, 是否允许重启>
        final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>();

        // Remove all processes this package may have touched: all with the
        // same UID (except for the system or root user), and all whose name
        // matches the package name.
        // 移除此软件包可能涉及的所有进程:
        // 所有具有相同 UID 的进程(除了 system 或 root 用户),以及名称与软件包名称匹配的所有进程。
        final int NP = mProcessNames.getMap().size();
        // 此循环的目的就是想找到所有符合条件的进程,将它们添加到procs
        for (int ip = 0; ip < NP; ip++) {
            SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
            final int NA = apps.size();
            for (int ia = 0; ia < NA; ia++) {
                // 获取所有的进程的ProcessRecord,把符合条件的加入到procs中准备查杀
                ProcessRecord app = apps.valueAt(ia);
                // 如果进程标记为持久化且未显式允许终止,跳过处理
                if (app.isPersistent() && !evenPersistent) {
                    // we don't kill persistent processes
                    continue;
                }
                // 已标记移除
                // 是否应终止此进程并将其从进程列表中删除。当程序包被强制停止或进程崩溃次数过多时,将设置此参数
                // mRemoved 标记为true
                if (app.isRemoved()) {
                    // 如果需执行终止操作
                    if (doit) {
                        // 检查是否允许重启
                        boolean shouldAllowRestart = false;
                        // 非卸载场景下,检查包依赖关系
                        if (!uninstalling && packageName != null) {
                            // 此包依赖于正在停止的给定包,当它未被冻结或卸载时,允许重新启动它。
                            // This package has a dependency on the given package being stopped,
                            // while it's not being frozen nor uninstalled, allow to restart it.
                            shouldAllowRestart = !app.getPkgList().containsKey(packageName) //该进程不在正在停止的给定包中
                                    && app.getPkgDeps() != null
                                    && app.getPkgDeps().contains(packageName) //该进程依赖正在停止的给定包
                                    && app.info != null
                                    && !pm.isPackageFrozen(app.info.packageName, app.uid, //该进程所在包未被冻结
                                            app.userId);
                        }
                        //将进程需要从进程列表中删除,并且需要执行终止操作,那么就记录该进程信息以及该进程是否应该重启
                        procs.add(new Pair<>(app, shouldAllowRestart));
                    }
                    // 如果不需要执行终止就跳过
                    continue;
                }

                // 如果它不符合我们的 oom adj 要求,请跳过进程。
                // Skip process if it doesn't meet our oom adj requirement.
                if (app.mState.getSetAdj() < minOomAdj) {
                    // 没理解,以后再说
                    // 请注意,在被终止的进程中仍有可能有一个 oom adj 0 的进程,但这并不意味着误判。
                    // 例如,绑定服务进程及其客户端活动进程都在后台,因此它们被收集以被终止。
                    // 如果客户端活动先被终止,则可能会安排服务取消绑定并成为正在执行的服务 (oom adj 0)。
                    // Note it is still possible to have a process with oom adj 0 in the killed
                    // processes, but it does not mean misjudgment. E.g. a bound service process
                    // and its client activity process are both in the background, so they are
                    // collected to be killed. If the client activity is killed first, the service
                    // may be scheduled to unbind and become an executing service (oom adj 0).
                    continue;
                }
                // 如果mRemoved标记为false,且adj大于最小adj,那么就检查是否允许重启
                boolean shouldAllowRestart = false;

                // 如果未指定 package,则调用给定用户 ID 下的所有进程。
                // If no package is specified, we call all processes under the
                // given user id.
                if (packageName == null) {
                    // 用户ID过滤(USER_ALL表示所有用户)不是指定用户ID,则跳过
                    if (userId != UserHandle.USER_ALL && app.userId != userId) {
                        continue;
                    }
                    // 应用ID过滤(-1表示不限制)不是指定appId,则跳过
                    if (appId >= 0 && UserHandle.getAppId(app.uid) != appId) {
                        continue;
                    }
                    // Package has been specified, we want to hit all processes
                    // that match it.  We need to qualify this by the processes
                    // that are running under the specified app and user ID.
                    // package,我们希望命中与它匹配的所有进程。
                    // 我们需要通过在指定应用程序和用户 ID 下运行的进程来限定此条件。
                    // 有指定包名时的过滤逻辑
                } else {
                    // 该进程是否依赖待终止的包
                    final boolean isDep = app.getPkgDeps() != null
                            && app.getPkgDeps().contains(packageName);
                    // 如果不依赖,且应用ID不匹配,则跳过
                    if (!isDep && UserHandle.getAppId(app.uid) != appId) {
                        continue;
                    }
                    // 该进程的userId与待终止的userId不匹配,则跳过
                    if (userId != UserHandle.USER_ALL && app.userId != userId) {
                        continue;
                    }
                    // 检查该进程的包名列表里面是否包含待终止的包名
                    final boolean isInPkgList = app.getPkgList().containsKey(packageName);
                    // 如果该进程的包名列表里面不包含待终止的包名,且该进程不依赖待终止的包,则跳过
                    if (!isInPkgList && !isDep) {
                        continue;
                    }
                    // 如果该进程的包名列表里面不包含待终止的包名,且该进程依赖待终止的包,且不是卸载场景,
                    // 且该进程的包信息不为空,且该进程所在包未被冻结,则允许重启
                    if (!isInPkgList && isDep && !uninstalling && app.info != null
                            && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) {
                        // This package has a dependency on the given package being stopped,
                        // while it's not being frozen nor uninstalled, allow to restart it.
                        // 此包依赖于正在停止的给定包,当它未被冻结或卸载时,允许重新启动它
                        shouldAllowRestart = true;
                    }
                }

                // Process has passed all conditions, kill it!
                // 如果不需要终止,则返回true
                if (!doit) {
                    return true;
                }
                // 如果需要终止,需要被移除,则设置相关的flag
                if (setRemoved) {
                    app.setRemoved(true);
                }
                // 记录待终止的进程
                procs.add(new Pair<>(app, shouldAllowRestart));
                // MIUI ADD: Performance_ProcessKillPolicy
                ProcessListStub.get().notifyProcessDied(app);
                // END Performance_ProcessKillPolicy
            }
        }
        // 判断是否属于用户应用(应用ID在应用UID范围内)
        final boolean killingUserApp = appId >= Process.FIRST_APPLICATION_UID
                                    && appId <= Process.LAST_APPLICATION_UID;
        // 用户应用按UID排序,保证同UID进程连续处理
        if (killingUserApp) {
            procs.sort((o1, o2) -> Integer.compare(o1.first.uid, o2.first.uid));
        }

        int idx = 0;
        while (idx < procs.size()) {
            // 获取当前UID对应的所有进程子列表
            final List<Pair<ProcessRecord, Boolean>> uidProcs = getUIDSublist(procs, idx);
            final int packageUID = uidProcs.get(0).first.uid;
            
            // Do not freeze for system apps or for dependencies of the targeted package, but
            // make sure to freeze the targeted package for all users if called with USER_ALL.
            // 不要冻结系统应用程序或目标包的依赖项,但请确保在使用 USER_ALL 调用时冻结所有用户的目标包。
            // 用户应用且当前UID匹配目标应用ID时冻结资源 系统应用或依赖进程不冻结 
            final boolean doFreeze = killingUserApp && UserHandle.getAppId(packageUID) == appId;
            // 冻结Binder通信和CPU调度(防止终止过程中产生新请求)
            if (doFreeze) freezeBinderAndPackageCgroup(uidProcs, packageUID);
            // 逐进程执行终止操作
            for (Pair<ProcessRecord, Boolean> proc : uidProcs) {
                removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
                        reasonCode, subReason, reason, !doFreeze /* async */);
            }
            // 终止关联的Zygote子进程(防止残留)
            killAppZygotesLocked(packageName, appId, userId, false /* force */);
            // 解冻资源限制
            if (doFreeze) unfreezePackageCgroup(packageUID);

            idx += uidProcs.size();
        }
        // 更新剩余进程的OOM优先级
        mService.updateOomAdjLocked(OOM_ADJ_REASON_PROCESS_END);
        // 返回是否终止了至少一个进程
        return procs.size() > 0;
    }

​​一、进程筛选条件​​

​​1. 进程类型过滤​​
​​持久化进程跳过​​:除非显式要求 (evenPersistent=true),否则不终止 isPersistent 标记的进程。
​​预移除进程处理​​:已标记为 isRemoved 的进程直接加入待终止列表,并根据依赖关系决定是否允许重启。
​​2. OOM优先级限制​​
仅终止 OOM_ADJ 值≥指定 minOomAdj 的低优先级进程(如后台进程)。
​​3. 包名与用户ID匹配规则​​
​​未指定包名​​:根据用户ID(userId)和应用ID(appId)过滤所有相关进程。
​​指定包名​​:
终止包名列表中包含该包名或依赖该包的进程(pkgDeps)。
若非卸载场景,依赖此包的进程允许重启(需包未被冻结)。

​​二、进程处理流程​​

  1. 列表排序优化​​
    若目标是用户应用(appId在应用UID范围内),按UID排序进程列表,确保同UID进程连续,便于批量处理。
  2. 资源冻结阶段​​
    冻结条件​​:仅针对普通用户应用且UID匹配应用ID的进程。
    ​​操作内容​​:
    Binder通信冻结​​:阻止进程接收新的Binder请求。
    CGroup限制​​:停止CPU调度,防止进程执行新任务。
    ​​目的​​:确保终止时不会因新请求导致残留问题。
  3. 进程终止与清理​​
    逐进程移除​​:调用 removeProcessLocked 终止进程,设置重启策略(允许依赖进程重启)。
    ​​关联Zygote清理​​:终止产卵该进程的Zygote子进程,避免僵尸进程。
  4. 资源解冻恢复​​
    同批次冻结的进程终止后,解除CGroup限制,恢复资源调度。

三、结束处理​​

  1. 系统状态更新​​
    调用 updateOomAdjLocked 更新剩余进程的OOM优先级,优化系统资源分配。
  2. ​​返回值​​
    返回 true 表示至少终止了一个进程(或无需实际终止时符合条件)。

四、​​关键设计逻辑​​

  1. ​​同UID批量处理​​
    按UID分组处理避免资源竞争,同时利用 subList 高效分割连续进程,减少遍历成本。
  2. 依赖进程特殊处理​​
    对依赖被终止包的进程,允许重启以维持系统稳定性,避免级联崩溃。
  3. 冻结-终止-解冻流程​​
    确保进程终止过程中无法接收新任务,提升终止可靠性,防止状态不一致。
  4. 系统进程保护机制​​
    跳过持久化进程及系统UID保护,避免影响系统核心功能。

五、术语解析​​

  • ​​OOM_ADJ​​:Android进程优先级指标,值越小优先级越高(如前台应用为0)。
  • ​​CGroup​​:Linux内核资源控制机制,限制进程组资源使用。
  • ​​Zygote​​:Android应用进程孵化器,清理残留子进程可释放资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2343658.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

驱动开发硬核特训 · Day 16:字符设备驱动模型与实战注册流程

&#x1f3a5; 视频教程请关注 B 站&#xff1a;“嵌入式 Jerry” 一、为什么要学习字符设备驱动&#xff1f; 在 Linux 驱动开发中&#xff0c;字符设备&#xff08;Character Device&#xff09;驱动 是最基础也是最常见的一类驱动类型。很多设备&#xff08;如 LED、按键、…

Virtuoso ADE采用Spectre仿真中出现MOS管最小长宽比满足要求依然报错的情况解决方法

在ADE仿真中错误问题如下&#xff1a; ERROR (CMI-2440): "xxx.scs" 46338: I2.M1: The length, width, or area of the instance does not fit the given lmax-lmin, wmax-wmin, or areamax-areamin range for any model in the I2.M3.nch_hvt group. The channel w…

大模型应用开发之LLM入门

一、大模型概述 1、大模型概念 LLM是指用有大量参数的大型预训练语言模型&#xff0c;在解决各种自然语言处理任务方面表现出强大的能力&#xff0c;甚至可以展现出一些小规模语言模型所不具备的特殊能力 2、语言模型language model 语言建模旨在对词序列的生成概率进行建模…

武汉昊衡科技OLI光纤微裂纹检测仪:高密度光器件的精准守护者

随着AI技术应用越来越广&#xff0c;算力需求激增&#xff0c;光通信系统正加速向小型化、高密度、多通道方向演进。硅光芯片、高速光模块等核心器件内部的光纤通道数量成倍增加&#xff0c;波导结构愈发精细&#xff0c;传统检测手段因分辨率不足、效率低下&#xff0c;难以精…

SQL 函数进行左边自动补位fnPadLeft和FORMAT

目录 1.问题 2.解决 方式1 方式2 3.结果 1.问题 例如在SQL存储过程中&#xff0c;将1 或10 或 100 长度不足的时候&#xff0c;自动补足长度。 例如 1 → 001 10→ 010 100→100 2.解决 方式1 SELECT FORMAT (1, 000) AS FormattedNum; SELECT FORMAT(12, 000) AS Form…

Tailwind CSS实战:快速构建定制化UI的新思路

引言 在当今快节奏的前端开发环境中&#xff0c;开发者不断寻找能够提高效率并保持灵活性的工具。Tailwind CSS作为一个功能型优先的CSS框架&#xff0c;正在改变开发者构建用户界面的方式。与Bootstrap和Material UI等传统组件库不同&#xff0c;Tailwind不提供预设组件&…

【数据可视化-25】时尚零售销售数据集的机器学习可视化分析

🧑 博主简介:曾任某智慧城市类企业算法总监,目前在美国市场的物流公司从事高级算法工程师一职,深耕人工智能领域,精通python数据挖掘、可视化、机器学习等,发表过AI相关的专利并多次在AI类比赛中获奖。CSDN人工智能领域的优质创作者,提供AI相关的技术咨询、项目开发和个…

UML 活动图深度解析:以在线购物系统为例

目录 一、UML 活动图的基本构成要素 二、题目原型 三、在线购物系统用户购物活动图详细剖析 &#xff08;一&#xff09;概述 &#xff08;二&#xff09;节点分析 三、注意事项 四、活动图绘画 五、UML 活动图在软件开发中的关键价值 六、总结 在软件开发与系统设计领…

【MFC】 VS2022打开低版本的MFC,双击.rc文件,DIalog加载失败,页面弹窗fatal error RC***:cannot open*****

打开以前的MFC示例报错&#xff0c;打开VS2019的实例以及更早VS版本的实例都一样,打不开&#xff0c;还报错&#xff1b; 错误 MSB8041 此项目需要 MFC 库。从 Visual Studio 安装程序(单个组件选项卡)为正在使用的任何工具集和体系结构安装它们。 GxCameraEvents_VS2015 C:\P…

Centos9 安装 nginx 及配置

1. 安装nginx 安装依赖软件&#xff0c;安装之前可以看一下是否已经安装过以下软件&#xff0c;dnf list installed | grep zlib dnf install gcc-c dnf install zlib dnf install pcre pcre-devel dnf install openssl openssl-devel下载nginx&#xff0c;这里是下载到opt文…

使用Handsontable实现动态表格和下载表格

1.效果 2.实现代码 首先要加载Handsontable&#xff0c;在示例中我是cdn的方式引入的&#xff0c;vue的话需要下载插件 let hot null;var exportPlugin null;function showHandsontable(param) {const container document.getElementById("hot-container");// 如果…

Action:Update your application‘s configuration

在使用Maven项目时&#xff0c;有一个报错信息是&#xff1a;Update your applications configuration 这类问题&#xff0c;就是我们的application.yml文件 或者 application.properties文件 内容哪里写错了 最有可能就是对齐方式有问题

【计算机网络】IP地址

IPv4 五类地址 1.0.0.0 ~ 126.255.255.255A类子网8位&#xff0c;主机24位128.0.0.0 ~ 191.255.255.255B类子网16位&#xff0c;主机16位192.0.0.0 ~ 223.255.255.255C类子网24位&#xff0c;主机8位224.0.0.0 ~ 239.255.255.255D类不分网络地址和主机地址&#xff0c;作为组播…

Rundeck 介绍及安装:自动化调度与执行工具

Rundeck介绍 概述&#xff1a;Rundeck 是什么&#xff1f; Rundeck 是一款开源的自动化调度和任务执行工具&#xff0c;专为运维场景设计&#xff0c;帮助工程师通过统一的平台管理和执行跨系统、跨节点的任务。它由 PagerDuty 维护&#xff08;2016 年收购&#xff09;&#…

vue element使用el-table时,切换tab,table表格列项发生错位问题

展示问题 问题描述&#xff1a;使用el-table的fixed"right"属性后&#xff0c;如果切换tab时&#xff0c;回出现最后一列错误的问题 官网提供解决方法&#xff1a;doLayout 需要注意的事项&#xff1a;我这里是通过组件使用的table组件&#xff0c;涉及多层组件封装…

第十二章 Python语言-大数据分析PySpark(终)

目录 一. PySpark前言介绍 二.基础准备 三.数据输入 四.数据计算 1.数据计算-map方法 2.数据计算-flatMap算子 3.数据计算-reduceByKey方法 4.数据计算-filter方法 5.数据计算-distinct方法 6.数据计算-sortBy方法 五.数据输出 1.输出Python对象 &#xff08;1&am…

AD相同网络的铜皮和导线连接不上

出现这样的情况是不是很烦恼&#xff0c;明明是相同的网络连接不上&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f; 直接修改铜皮属性&#xff08;选择所有相同这个选项&#xff09; 这样就可以连接上了

keil修改字体无效,修改字体为“微软雅黑”方法

在网上下载了微软雅黑字体&#xff0c;微软雅黑参考下载链接 结果在Edit->Configuration中找不到这个字体 这个时候可以在keil的安装目录中找到UV4/global.prop文件 用记事本打开它进行编辑&#xff0c;把字体名字改成微软雅黑 重新打开keil就发现字体成功修改了。 这个…

【网络编程】从零开始彻底了解网络编程(三)

本篇博客给大家带来的是网络编程的知识点. &#x1f40e;文章专栏: JavaEE初阶 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺便进步 TCP流…

NVIDIA --- 端到端自动驾驶

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、传统驾驶模型二、NVIDIA的端到端驾驶模型1.基本模型2.自查讯向量3.通用框架 总结 前言 端到端自动驾驶指的是系统接收来自摄像头雷达和激光雷达的原始传感…