【Android开发】一文全面解析Framework层

2023-11-19

前言

上一篇文章从Native角度讲解了Android进程管理的相关概念,本文将继续从上层的Framework中的进程启动、销毁场景和优先级处理、以及它们与四大组件的种种关联,来逐步解析Android进程管理的其他关键要素。

进程的启动

Android Framework层是运行在ART虚拟机之上的,对于一般的Java进程是不具备主动创建进程的能力的。我们都知道Android中存在四大组件:Activity、Service、BroadcastReceiver和ContentProvider,那么为什么这样划分呢,一个主要的原因是在四大组件的启动阶段,如果宿主进程没有正在运行,那么系统服务进程system_server将会先拉起进程,然后才会继续启动组件。简而言之,正因为AMS将安卓中进程的创建、销毁及优先级进行了封装,应用侧开发者才能够在对进程无感知的情况的同时使用四大组件。
在这里插入图片描述

启动四大组件方法 startActivity, sendBroadcast, startService/bindService 和 getContentProviderImpl 之所以能够启动进程,是因为它们都会通过发送 binder 消息到 AMS 进行处理,当 AMS 发现对应组件没有在 xml 声明的进程启动时,会先拉起该进程,然后再启动对应组件。

也就是说,四大组件正是APP进程的入口。它们拉起进程的方式都是一致的,基本流程:

1、Zygote进程启动,初始化虚拟机及基本环境,同时进入一个无限循环,提供创建 java 进程服务
2、AMS 通过调用 Process.start() 方法,发送 socket 信息到 Zygote 进程
3、Zygote 进程处理消息,并通过fork创建应用进程
4、应用进程反射调用 ActivityThread.main 方法,进入自身逻辑 (初始化 Looper/Binder 等主要服务 )
5、AMS 创建进程成功后,会将进程以 ProcessRecord 的形式进行封装、管理。

彩蛋:实用的进程相关adb命令

Java进程的创建与销毁实时

adb logcat -b events | grep proc**

07-16 14:19:23.357  4774  4800 I am_proc_start: [0,30314,10063,com.koushikdutta.vysor,activity,com.koushikdutta.vysor/.StartActivity]
07-16 14:19:23.375  4774  6027 I am_proc_bound: [0,30314,com.koushikdutta.vysor]
07-16 14:19:31.621  4774  5281 I am_proc_died: [0,30314,com.koushikdutta.vysor,900,17]

获取应用进程中组件详细信息

adb shell dumpsys activity p <packageName>

*APP* UID 10115 ProcessRecord{4114d9d 18987:com.ss.android.ugc.aweme/u0a115}
  user #0 uid=10115 gids={50115, 20115, 9997, 3002, 3003, 3001}
  requiredAbi=armeabi-v7a instructionSet=arm
  class=com.ss.android.ugc.aweme.app.host.HostApplication
  ...
  // adj, procState优先级信息
  oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
  curSchedGroup=2 setSchedGroup=2 systemNoUi=false trimMemoryLevel=0
  curProcState=2 repProcState=2 pssProcState=5 setProcState=2
  ...
  **Activities**:
    - ActivityRecord{5e38d19 u0 com.ss.android.ugc.aweme/.main.MainActivity t948}
  **Services**:
    - ServiceRecord{13f23bc u0 com.ss.android.ugc.aweme/com.tt.miniapphost.process.base.HostCrossProcessCallService}
    ...
  **Connections**:
    - ConnectionRecord{77ae579 u0 CR com.ss.android.ugc.aweme/com.ss.android.socialbase.downloader.downloader.IndependentProcessDownloadService:@30b4240}
    ...
  **Published Providers**:
    - com.umeng.message.provider.MessageProvider
      -> ContentProviderRecord{76d000a u0 com.ss.android.ugc.aweme/com.umeng.message.provider.MessageProvider}
    ...
  **Connected Providers**:
    - c887614/com.android.providers.settings/.SettingsProvider->18987:com.ss.android.ugc.aweme/u0a115 s1/1 u0/0 +19h23m3s147ms
    - c4d7fba/com.android.providers.media/.MediaProvider->18987:com.ss.android.ugc.aweme/u0a115 s0/1 u1/2 +1m10s935ms
  **Receivers**:
    - ReceiverList{4afbc5 18987 com.ss.android.ugc.aweme/10115/u0 remote:687d23c}

获取应用优先级信息

adb shell dumpsys activity o

Proc 0: fore  T/A/TOP  trm: 0 18987:com.ss.android.ugc.aweme/u0a115 (top-activity)
    oom: max=1001 curRaw=0 setRaw=0 cur=0 set=0
    state: cur=TOP  set=TOP  lastPss=268MB lastSwapPss=0.00 lastCachedPss=0.00
    cached=false empty=false hasAboveClient=false

进程的优先级

Android Framework中进程优先级的衡量单位有两种,除了 adj (对应 lmk 的 oom_score_adj)之外,新添了 procState 变量。这两个优先级相辅相成,adj 更多的用在给 lmk 判断该进程是否应该被杀死,而procState 更多的给 Framework 层的服务给进程状态"定级",例如,AMS可以简单粗暴的通过判断 procState 是否小于等于 PROCESS_STATE_IMPORTANT_FOREGROUND 来判断该进程是否为"前台进程":

[ActivityManagerService.java]

@Override
public boolean isAppForeground(int uid) {
    synchronized (this) {
        UidRecord uidRec = mActiveUids.get(uid);
        if (uidRec == null || uidRec.idle) {
            return false;
        }
        return uidRec.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
    }
}

这里的 UidRecord 是 Android 常用的权限管理方案,如果没有做修改的话,普通应用创建的进程都是在同一个 uid 里的。在系统侧,Android 常常通过 uid 来划分对应的进程是否具有相应的优先级。

关于Android中adj与procState具体值的含义,可以查看文章最后的附录表格,具体场景仅供参考。如果需要细致判定当前进程优先级的状态,通过源码分析最为直观。

adj、schedGroup、procState和adjType的核心计算步骤是在AMS中的computeOomAdjLocked方法完成的,如图:
请添加图片描述

这个方法的核心执行无外乎以下几个功能:

1、首先检查该进程是否处于高优场景中,如前台Activity、正在运行RemoteAnimation,正在处理广播和Service等
2、该进程是否是某种特殊进程,如Home,height weight和backup
3、是否存在进程因为service和content provider互相绑定提升优先级的情况
4、如果都不是上述情况,处于的优先级都比较低,最低的是cache进程,AMS对系统整体cache进程个数有阈值限制,超过上限就会触发清理操作

彩蛋:使用framework中的"自相矛盾"进行保活

进程保活无外乎也是一种普通应用对操作系统的攻击手段,对于一般的攻击手段,我把它理解成使用开发者约定俗成的规则漏洞来突破普通用户原有权限的行为。

比如说 buffer overflow,是利用了旧版本C编译器在切换函数堆栈时,没有有效检查堆栈信息作用范围的漏洞。使得普通应用用户可以利用超长的栈内信息覆盖调原有的堆栈跳转信息,使得应用执行其它意想不到的程序。通过这种方式可以获取到root权限;又比如说 TCP 劫持,是利用了发送方与接收方没有很好的 SEQ和ACK 序列的校验手段,使得中间者可以通过控制 SEQ和ACK的数值来控制正常通信方的流程。

方式一:bindService 互相绑定以提升优先级

这种优先级方式分为两种

第一种,依赖高优先级 client:

if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
    switch (procState) {
        case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
        case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
            // Something else is keeping it at this level, just leave it.
            break;
        case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
        case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
        case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
        case ActivityManager.PROCESS_STATE_SERVICE:
            procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
            app.adjType = mayBeTopType;
            app.adjSource = mayBeTopSource;
            app.adjTarget = mayBeTopTarget;
            ...
    }
}

maybeTop 为 true 需要 ContentProvider 或者 Service 的 client 正在前台显示 Activity

同理,通过让用户选择该client来提供服务,例如WallPaperService,可以让system_server成为client,以此来提高进程优先级。

第二种,两个进程互相绑定:

if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
    clientProcState =
            ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else if (mWakefulness
                == PowerManagerInternal.WAKEFULNESS_AWAKE &&
        (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
                != 0) {
    clientProcState =
            ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
} else {
    clientProcState =
            ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
}
...
if (procState > clientProcState) {
    procState = clientProcState;
    if (adjType == null) {
        adjType = "service";
    }
}

通过设置 flag,两个普通的进程可以通过互相"攀升"来提升优先级

方式二、监听锁屏广播拉起Activity,监听解锁广播销毁Activity:

具体步骤:

1、启动一个Service,通过动态广播receiver接收锁屏、开屏广播
2、灭屏时,启动Activity到前台
3、开屏时,将Activity进行 finish,确保用户无感知

adb shell ps | grep <pacakgeName>
ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)
  All known processes:
  *APP* UID 10064 ProcessRecord{f59eb2f 30917:com.bytedance.gcsuppression.demo/u0a64}
    ...
    oom: max=1001 curRaw=100 setRaw=100 cur=100 set=100
    curSchedGroup=1 setSchedGroup=1 systemNoUi=false trimMemoryLevel=0
    curProcState=5 repProcState=5 pssProcState=5 setProcState=5 lastStateTime=-23s716ms

(该方法在 Android 8.0 上测试过仍有效,更高版本尚未测试;因为是利用了 service 与动态广播的特性,所以 framework 很难对这个逻辑做出应对,除非是在系统侧维护一个 service 黑名单)

进程的清理

虽然杀进程有这么多的花样,但常见的方式就三种:清理名称触发场景功能kill粒度最细,用于进程自杀、AMS 查杀单个缓存进程等杀死单个 java 进程killBackground普通应用也可以通过 AM 服务调用杀死系统中 background 进程force-stop以 package 为单位,强力杀死和该包有关联的进程并禁止大部分自启杀死该包下所有进程,关闭并销毁相关组件

普通的查杀方式如kill和killBackground都只是杀死单个或部分进程,当进程死亡后,会因为 binder fd 的释放通过死亡回调通知 AMS 进程已死,这期间 AMS 也有可能因为 Service 等组件重新将进程拉起。考虑到拉活方案花样多,应用中的其它 java 进程或 c 进程仍然可以通过启动组件的方式重新将进程拉起来。

在一般的原生机器上,force-stop虽然很少使用,一般只在设置里的"强制停止"按钮触发。除了杀死进程之外也会销毁组件等信息,同时也会更改 PMS 中对应包名的状态,避免再次拉活。

彩蛋:当我们上滑应用卡片时Android在做什么

systemui 进程中的 com.android.systemui/.recents.RecentsActivity 接收到卡片滑动事件
systemui 获取到 AMS 服务,再调用 am.removeTask 方法来移除 Task 和杀死显示该界面的进程;注意这个方法普通应用是没有调用权限的。
最后通过 AMS 的 cleanUpRemovedTaskLocked 方法来杀死进程,这个方法的具体实现如下:

void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
    // 1. 在最近任务中移除 Task
    if (removeFromRecents) {
        mRecentTasks.remove(tr);
    }
        // 2. 选择需要查杀的进程
    final String pkg = component.getPackageName();
    ArrayList<ProcessRecord> procsToKill = new ArrayList<>();
    ArrayMap<String, SparseArray<ProcessRecord>> pmap = mService.mProcessNames.getMap();
    for (int i = 0; i < pmap.size(); i++) {
        SparseArray<ProcessRecord> uids = pmap.valueAt(i);
        for (int j = 0; j < uids.size(); j++) {
            ProcessRecord proc = uids.valueAt(j);
            ...
            // 2.1 如果 Task 所在进程还有别的 Task 在最近任务中显示,那么该进程不会被杀
            for (int k = 0; k < proc.activities.size(); k++) {
                TaskRecord otherTask = proc.activities.get(k).getTask();
                if (tr.taskId != otherTask.taskId && otherTask.inRecents) {
                    return;
                }
            }
            // 2.2 进程有前台service的不会被杀,典型的有音乐播放软件
            if (proc.foregroundServices) {
                return;
            }
            // Add process to kill list.
            procsToKill.add(proc);
        }
    }
    // 3. 正式查杀
    for (int i = 0; i < procsToKill.size(); i++) {
        ProcessRecord pr = procsToKill.get(i);
        // 如果满足这两个条件才会立即被杀
        if (pr.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                && pr.curReceivers.isEmpty()) {
            pr.kill("remove task", true);
        } else {
            pr.waitingToKill = "remove task";
        }
    }
}

彩蛋:反保活的终结者

Android 中的进程查杀最强武器是force-stop,本质上它会通过遍历并杀死所有和应用有关的Java进程:

private final boolean killPackageProcessesLocked(String packageName, int appId,
        int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart, boolean doit, boolean evenPersistent, String reason) {
    ArrayList<ProcessRecord> procs = new ArrayList<>();
    final int NP = mProcessNames.getMap().size();
    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 app = apps.valueAt(ia);
            ...
            // continue if the process do not need to kill
            app.removed = true;
            procs.add(app);
        }
    }

    int N = procs.size();
    // force-stop 的本质是通过遍历所有与应用有关的进程,依次查杀来实现杀死所有进程的
    for (int i=0; i<N; i++) {
        removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason);
    }
    updateOomAdjLocked();
    return N > 0;
}

想要做反保活的话,我们只需要在 removeProcessLocked 的遍历之前,将 java 进程和在其同一cgroup的c进程一起通过发送信号hang住,再通过循环依次杀死所有进程,这种方法基本能杜绝所有拉活方案。

总结

进程管理与大家日常开发息息相关,也是在Android系统中是个举足轻重的模块。相信通过本系列的两篇文章,大家已经对进程启动期间涉及到fork、对进程的优先级管理涉及到adj、对进程的调度这些概念已经有了更加深入的理解了。

这里我就分享一份资料,希望可以帮助到大家提升进阶。

内容包含:Android学习PDF+架构视频+面试文档+源码笔记高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 这几块的内容。分享给大家,非常适合近期有面试和想在技术道路上继续精进的朋友。

如果你有需要的话,可以扫描下方二维码,免费获取Android学习PDF+架构视频+面试文档+源码笔记领取方式

​​

​​

喜欢本文的话,不妨给我点个小赞、评论区留言或者转发支持一下呗~

img

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Android开发】一文全面解析Framework层 的相关文章

随机推荐

  • C++14中binary literals的使用

    一个形如42的值被称作字面值常量 literal 这样的值一望而知 每个字面值常量都对应一种数据类型 字面值常量的形式和值决定了它的数据类型 我们可以将整型字面值写作十进制 基数为10 八进制 基数为8 或十六进制 基数为16 数的形式 以
  • 数据库连接不上的几种情况

    客户端不能连接服务器的情况 首先保证客户端和服务器能够ping通 如果ping不通 请检查以下几点 1 服务器和客户端电脑是否通电 2 交换机是否通电 3 网卡安装是否正常 或者网卡是否在设备管理器被禁用 4 本地连接是否被禁用 5 服务器
  • 公钥 私钥 的理解

    公钥和私钥是成对出现 私钥可以生成公钥 公钥无法生成私钥 私钥加密的内容 可以被公钥解密 公钥加密的内容 可以被私钥解密 举个例子 A 持有自己的私钥 A的私钥生成的公钥被其他人持有 其他人用A的公钥加密的内容 只有用A的私钥才可以解密 且
  • 分享一个java+python双版本源码之基于微信小程序的校园跑腿接单系统 校园快递代领小程序(源码、lw、调试)

    作者 计算机源码社 个人简介 本人七年开发经验 擅长Java Python PHP NET 微信小程序 爬虫 大数据等 大家有这一块的问题可以一起交流 学习资料 程序开发 技术解答 文档报告 如需要源码 可以扫取文章下方二维码联系咨询 Ja
  • target_include_directories([SYSTEM][BEFORE]

    target include directories
  • mybatis获取新增记录的主键

    最近项目中有个需求 需要在新增一条记录后返回该记录的主键 查了下资料 用mybatis可以做 我需要拿第一个方法执行返回的id作为变量传给下面 数据库中该id是记录主键 并且数据库用的是主键自增长 这是前提 有两种方式 第一种方式写法如下
  • 信息化战略规划-CRO-SCM-应用集成-电子商务

    信息化战略规划 CRO SCM 应用集成 电子商务 信息化战略体系 重点 信息系统战略规划 重点 客户关系管理 重点 供应链管理 企业应用集成 电子商务 信息化战略体系 重点 企业战略 目标 企业战略规划 实现目标的规划 企业信息化战略 使
  • 操作系统(四):磁盘调度算法,先来先服务,最短寻道时间优先,电梯算法

    文章目录 一 磁盘结构 二 先来先服务 三 最短寻道时间优先 四 电梯算法 SCAN 一 磁盘结构 盘面 Platter 一个磁盘有多个盘面 磁道 Track 盘面上的圆形带状区域 一个盘面可以有多个磁道 扇区 Track Sector 磁
  • 在echarts中自定义提示框内容

    以折线图为例 在鼠标滑过每个数据标签时 为了更友好地显示数据内容 需要对显示的数据内容作格式化处理 添加自定义内容 这就需要用到tooltip的formatter属性的回调函数 本文将从配置代码和效果两方面来展示它的使用 柱状图的原理类似
  • NCC低代码平台服务搭建指南

    NCC服务搭建 前言 本文档旨在帮助第一次接触NCC的开发人员 搭建NCC开发环境 1 NCC相关资料 开发者社区 https nccdev yonyou com 资料 https pan baidu com s 15V71U7vVOGr8
  • 【论文笔记】TNASP:A Transformer-based NAS Predictor with a Self-evolution Framework

    文章目录 0 摘要 摘要解读 1 Introduction 2 相关工作 3 方法 3 1 Training based network performance predictors 3 2 基于Transformer的预测器 3 3 自演
  • unity3d实现简单的打飞碟游戏

    游戏内容 游戏有n个round 每个round发射10次trial 每个trial的飞碟都可能不同 包括速度角度得分等 使用鼠标进行射击 点中即表示射击成功 游戏要求 使用带缓存的工厂模式来管理飞碟的生产与再利用 工厂使用单例模式 游戏的设
  • python中.argsort()的用法

    argsort 是对数组参数从小到大排序 然后取出排序后的索引 例如 a np array 0 2 0 5 0 3 0 4 a argsort 输出的就是对a数组排序过后的索引 0 2 3 1
  • 三、web端显示之hdfs基本操作

    三台的节点均启动成功之后 完全匹配的上配置的下图所示情况 若启动不成功 则先 root hadoop1 hadoop 3 1 3 stop all sh 再输入ll显示一下 删除每一个集群上的data和logs root hadoop1 h
  • Qt源代码中二进制兼容及d、q指针的理解

    1 二进制兼容的理解 首先按照本文对二进制兼容进行理解 此处是本人的总结 将类的私有属性 不需要暴露的部分 放到私有类中 在类中定义私类的指针进行交互 指针的大小是已知不变的 指针数据类型为Int 4个字节 软件发布后只需要改私类中的部分
  • 安装vmware tools时,kernel版本不匹配问题的解决方法

    安装vmware tools 的时候 提示找不到C header files 此种情况下 按以下步骤操作 1 内核安装完毕后 需要用这个命令确定内核 C header 的安装目录 ls d usr src kernels uname r i
  • 怎样知道自己适不适合做程序员

    编程是一门非常有技术含量的手艺活 待遇和福利相对来说较为丰厚 由于种种原因想要转行做程序员的人 总会有这样的困惑 我是否适合做程序员呢 其实做为一个开发者 有一个学习的氛围跟一个交流圈子特别重要这里我推荐一个C语言C 交流群58365041
  • 全文检索几种词向量模型

    1 倒排索引模型 2 布尔检索类型 3 TF IDF权重计算 下面是TF IDF的JAVA代码实现 public class TFIDF public double tf List
  • vue的循环遍历(v-for)

    1 循环遍历 1 循环遍历 vue的循环遍历用v for 语法类似于js中的for循环 当我们有一组数据需要进行渲染时 我们就可以使用v for来完成 2 v for使用格式 格式为 v for item in items 遍历items中
  • 【Android开发】一文全面解析Framework层

    前言 上一篇文章从Native角度讲解了Android进程管理的相关概念 本文将继续从上层的Framework中的进程启动 销毁场景和优先级处理 以及它们与四大组件的种种关联 来逐步解析Android进程管理的其他关键要素 进程的启动 An