Android Freezer

news2024/12/13 16:40:52

Freezer原理

Android按照优先级将一般的APP从高到低分为: 前台进程 --> 可感知进程–> 服务进程 --> Cached进程

Freezer通过冻住cached进程,来迫使这些进程让出CPU,以达到优化系统资源使用的目的。

Cached进程是怎么判定的呢?

由于android的进程的adj是会根据不同的运行状态,会动态计算的。当adj大于等于CACHED_APP_MIN_ADJ(900)

整个流程是比较清晰的,在fwk的系统服务中,会计算ADJ的变化,当满足条件后就会调用cgroup hal提供的freeze的接口,设置crashed进程的状态为freeze 

Freeze实现

下面列出下整个流程最主要的几部分代码

1)Framework层

当进程发生了以下变化后,AMS就会触发OomAdjuster.java 中的 updateOomAdjLocked 函数。

    static final String OOM_ADJ_REASON_METHOD = "updateOomAdj";   //触发ADJ重新计算的条件
    static final String OOM_ADJ_REASON_NONE = OOM_ADJ_REASON_METHOD + "_meh";
    static final String OOM_ADJ_REASON_ACTIVITY = OOM_ADJ_REASON_METHOD + "_activityChange";
    static final String OOM_ADJ_REASON_FINISH_RECEIVER = OOM_ADJ_REASON_METHOD + "_finishReceiver";
    static final String OOM_ADJ_REASON_START_RECEIVER = OOM_ADJ_REASON_METHOD + "_startReceiver";
    static final String OOM_ADJ_REASON_BIND_SERVICE = OOM_ADJ_REASON_METHOD + "_bindService";
    static final String OOM_ADJ_REASON_UNBIND_SERVICE = OOM_ADJ_REASON_METHOD + "_unbindService";
    static final String OOM_ADJ_REASON_START_SERVICE = OOM_ADJ_REASON_METHOD + "_startService";
    static final String OOM_ADJ_REASON_GET_PROVIDER = OOM_ADJ_REASON_METHOD + "_getProvider";
    static final String OOM_ADJ_REASON_REMOVE_PROVIDER = OOM_ADJ_REASON_METHOD + "_removeProvider";
    static final String OOM_ADJ_REASON_UI_VISIBILITY = OOM_ADJ_REASON_METHOD + "_uiVisibility";
    static final String OOM_ADJ_REASON_ALLOWLIST = OOM_ADJ_REASON_METHOD + "_allowlistChange";
    static final String OOM_ADJ_REASON_PROCESS_BEGIN = OOM_ADJ_REASON_METHOD + "_processBegin";
    static final String OOM_ADJ_REASON_PROCESS_END = OOM_ADJ_REASON_METHOD + "_processEnd";

updateOomAdjLocked -》 updateOomAdjLSP -》 performUpdateOomAdjLSP 基本的传参和判断,不展开了。

    @GuardedBy({"mService", "mProcLock"})
    private void updateOomAdjInnerLSP(String oomAdjReason, final ProcessRecord topApp,
            ArrayList<ProcessRecord> processes, ActiveUids uids, boolean potentialCycles,
            boolean startProfiling) {
	......
      if (size > 0) {
            mAdjSeq--;
            // Update these reachable processes
            updateOomAdjInnerLSP(oomAdjReason, topApp, processes, uids, containsCycle, false);	//更新新的ADJ
        } else if (state.getCurRawAdj() == ProcessList.UNKNOWN_ADJ) {
            // In case the app goes from non-cached to cached but it doesn't have other reachable
            // processes, its adj could be still unknown as of now, assign one.
            processes.add(app);
            assignCachedAdjIfNecessary(processes);
            applyOomAdjLSP(app, false, SystemClock.uptimeMillis(),		//如果
                    SystemClock.elapsedRealtime());
        }
        mTmpProcessList.clear();
        mService.mOomAdjProfiler.oomAdjEnded();
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        return true;

        ......
    }

    @GuardedBy({"mService", "mProcLock"})
    private void updateAppFreezeStateLSP(ProcessRecord app) {
        if (!mCachedAppOptimizer.useFreezer()) {
            return;
        }

        if (app.mOptRecord.isFreezeExempt()) {
            return;
        }

        final ProcessCachedOptimizerRecord opt = app.mOptRecord;
        // if an app is already frozen and shouldNotFreeze becomes true, immediately unfreeze
        if (opt.isFrozen() && opt.shouldNotFreeze()) {
            mCachedAppOptimizer.unfreezeAppLSP(app);
            return;
        }

        final ProcessStateRecord state = app.mState;
        // Use current adjustment when freezing, set adjustment when unfreezing.
        if (state.getCurAdj() >= ProcessList.CACHED_APP_MIN_ADJ && !opt.isFrozen()	//根据当前的ADJ,frozen的状态等,设置进入还是退出frozen状态
                && !opt.shouldNotFreeze()) {
            mCachedAppOptimizer.freezeAppAsyncLSP(app);
        } else if (state.getSetAdj() < ProcessList.CACHED_APP_MIN_ADJ) {
            mCachedAppOptimizer.unfreezeAppLSP(app);
        }
    }

2)Native层

void android_os_Process_setProcessFrozen(
        JNIEnv *env, jobject clazz, jint pid, jint uid, jboolean freeze)
{
    bool success = true;

    if (freeze) {
        success = SetProcessProfiles(uid, pid, {"Frozen"});		//根据Frozen的字符串,去 task_profiles.json 查找对应的设备节点,最终设置 /sys/fs/cgroup/<UID_xxx>/<PID_xxx>/cgroup.freeze 
    } else {
        success = SetProcessProfiles(uid, pid, {"Unfrozen"});
    }

    if (!success) {
        signalExceptionForGroupError(env, EINVAL, pid);
    }
}

3)Kernel层

Kernel的设备节点会通过定义write函数,回调到相关的定义函数,这样就能看到具体的实现代码

{
	.name = "cgroup.freeze",		 //cgroup_freeze_show文件 触发write函数,执行cgroup_freeze_write函数。
	.flags = CFTYPE_NOT_ON_ROOT,
	.seq_show = cgroup_freeze_show,   
	.write = cgroup_freeze_write,    
},

freezer.c 最终会调用到 freezer中的 cgroup_do_freeze 函数
/*
 * Freeze or unfreeze all tasks in the given cgroup.
 */
static void cgroup_do_freeze(struct cgroup *cgrp, bool freeze)
{
	struct css_task_iter it;
	struct task_struct *task;

	lockdep_assert_held(&cgroup_mutex);

	spin_lock_irq(&css_set_lock);
	if (freeze)
		set_bit(CGRP_FREEZE, &cgrp->flags);   //往设备节点写入值
	else
		clear_bit(CGRP_FREEZE, &cgrp->flags);
	spin_unlock_irq(&css_set_lock);

	if (freeze)
		TRACE_CGROUP_PATH(freeze, cgrp);   
	else
		TRACE_CGROUP_PATH(unfreeze, cgrp);

	css_task_iter_start(&cgrp->self, 0, &it);
	while ((task = css_task_iter_next(&it))) {
		/*
		 * Ignore kernel threads here. Freezing cgroups containing
		 * kthreads isn't supported.
		 */
		if (task->flags & PF_KTHREAD)  //忽略 kernel进程
			continue;
		cgroup_freeze_task(task, freeze);  //把状态加入task中
	}
	css_task_iter_end(&it);

	/*
	 * Cgroup state should be revisited here to cover empty leaf cgroups
	 * and cgroups which descendants are already in the desired state.
	 */
	spin_lock_irq(&css_set_lock);
	if (cgrp->nr_descendants == cgrp->freezer.nr_frozen_descendants)
		cgroup_update_frozen(cgrp);
	spin_unlock_irq(&css_set_lock);
}

Freeze 项目中遇到问题:

1)进程在unfreeze的时候被system 主动kill了

被kill的原因根据根据错误提示很容易找到,相关代码也比较好理解

int freezeInfo = getBinderFreezeInfo(pid);   //从binder驱动读取在freezed的状态下的状态
//下面的判断会在unfreeze之前是否有同步的binder通信请求,如果有就会杀死进程,其因为无法清除binder驱动中的堆积信息,会引起内存泄露。
//异步的binder通信也是可以被接受,并被保存在binder驱动的buffer中,但是也会存在buffer满的问题,同样也要注意此类问题。
if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
	Slog.d(TAG_AM, "pid " + pid + " " + app.processName
			+ " received sync transactions while frozen, killing");
 	app.killLocked("Sync transaction while in frozen state",
 		 ApplicationExitInfo.REASON_OTHER,
 		 ApplicationExitInfo.SUBREASON_FREEZER_BINDER_TRANSACTION, true);
  	processKilled = true;
}

此问题在那些非persisit的进程上遇到过几次,比如多媒体中心。引起的问题也都是因为注册过capservice的回调,在后台的情况下有回调过来。

解决方案:把服务变成前台服务,或者有UI界面退出后,取消注册可能被回调的接口。

2)STR后第一次打开应用会闪退,再次打开恢复

APP退出后台并进入了freezon在状态,是可以接受oneway的消息,但是会堆积在kernel的buffer中,但是在超过50%后,就会分配不出内存。

当再发起oneway消息后binder驱动就会直接返回 BR_DEAD_REPLY。不同的client会有不同的处理方式,systemservice的处理方式大部分均为kill相关进程。

主要代码如下

	t->buffer = binder_alloc_new_buf(&target_proc->alloc, tr->data_size,  //每次有binder通信后会分配对应内存快
		tr->offsets_size, extra_buffers_size,
		!reply && (t->flags & TF_ONE_WAY), current->tgid);
	if (IS_ERR(t->buffer)) {  //在已经分配512KB后就不能正常分配,会走进此判断
		/*
		 * -ESRCH indicates VMA cleared. The target is dying.
		 */
		return_error_param = PTR_ERR(t->buffer);
		return_error = return_error_param == -ESRCH ?
			BR_DEAD_REPLY : BR_FAILED_REPLY;   //会返回 BR_DEAD_REPLY 给到client端。
		return_error_line = __LINE__;
		t->buffer = NULL;
		goto err_binder_alloc_buf_failed;
	}

此问题并不会让进程直接crash,而是在后续发起其他binder通信的情况下才会出现,比如startactivty或者sendboardcast之类的消息。

解决方案:解决方案如问题1的方式,退出后需要自行unregiest相关注册,如果一定要监听某些状态,则可以启动一个前台服务来监听相关进程。此种用法也符合google的用法规则

Freezer问题调试命令

1)修改进frezon的时间,改为1秒,方便测试frezon后的状态是否Ok

adb shell device_config put activity_manager_native_boot freeze_debounce_timeout 100
2)关闭打开freezer功能,需要重启才能生效

adb shell settings put global cached_apps_freezer <enabled|disabled|default>

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

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

相关文章

websocker的java集成过程

第一步&#xff1a;引入依赖包 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency> 第二步设置配置类&#xff1a; // 需要注入Bean的话必须声明为配置类 Co…

设计模式:24、访问者模式

目录 0、定义 1、访问者模式的五种角色 2、访问者模式的UML类图 3、示例代码 0、定义 表示一个作用于某对象结构中的各个元素的操作。它可以在不改变各个元素的类的前提下&#xff0c;定义作用于这些元素的新操作。 1、访问者模式的五种角色 抽象元素&#xff08;Element…

umi实现动态获取菜单权限

文章目录 前景登录组件编写登录逻辑菜单的时机动态路由页面刷新手动修改地址 前景 不同用户拥有不同的菜单权限&#xff0c;现在我们实现登录动态获取权限菜单。 登录组件编写 //当我们需要使用dva的dispatch函数时&#xff0c;除了通过connect函数包裹组件还可以使用这种方…

Color-Light-Control-and-Four-Way-Responder based on STM32F103C8T6

Light Control and Responder 若要实现同样效果请看源码: gitee.com/apollo_666/Color-Light-Control-and-Four-Way-Responder # Abstract The design project for a decorative lighting controller enhanced our practical skills and engineering capabilities. During our…

数据库中的运算符

1.算术运算符 算术运算符主要用于数学运算&#xff0c;其可以连接运算符前后的两个数值或表达式&#xff0c;对数值或表达式进行加&#xff08;&#xff09;、减&#xff08;-&#xff09;、乘&#xff08;*&#xff09;、除&#xff08;/&#xff09;和取模&#xff08;%&…

python爬虫--小白篇【爬取B站视频】

目录 一、任务分析 二、网页分析 三、任务实现 一、任务分析 将B站视频爬取并保存到本地&#xff0c;经过分析可知可以分为四个步骤&#xff0c;分别是&#xff1a; 爬取视频页的网页源代码&#xff1b;提取视频和音频的播放地址&#xff1b;下载并保存视频和音频&#x…

【计算机网络】实验18:动态主机配置协议DHCP的作用

实验18 动态主机配置协议DHCP的作用 一、实验目的 验证动态主机协议DHCP的作用 二、实验环境 Cisco Packet Tracer模拟器 三、实验过程 1.构建网络拓扑&#xff0c;不给局域网中的各主机手动配置IP地址、子网掩码、默认网关、DNS服务器等信息&#xff0c;而是开启动态主机…

MFC案例:基于对话框的简易阅读器

一、功能目标&#xff1a; 1.阅读txt文件 2.阅读时可以调整字体及字的大小 3.打开曾经阅读过的文件时&#xff0c;能够自动从上次阅读结束的位置开始显示&#xff0c;也就是能够保存和再次使用阅读信息。 4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。 5.显示…

【开源】基于SpringBoot框架的个性化的旅游网站 (计算机毕业设计)+万字毕业论文 T025

系统合集跳转 源码获取链接 一、系统环境 运行环境: 最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 IDE环境&#xff1a; Eclipse,Myeclipse,IDEA或者Spring Tool Suite都可以 tomcat环境&#xff1a; Tomcat 7.x,8.x,9.x版本均可 操作系统…

谷粒商城—分布式基础

1. 整体介绍 1)安装vagrant 2)安装Centos7 $ vagrant init centos/7 A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on…

MySQL概述以及下载安装

MySQL5.7概述以及下载安装&#xff08;centOS7&#xff09; 一、MySQL简介 MySQL是一个典型的关系数据库&#xff0c;目前是Oracle公司产品之一&#xff0c;也是目前主流使用的关系型数据库之一。使用MySQL可以进行最基本的数据存储、管理、查询等操作&#xff0c;也可以方便的…

中粮凤凰里共有产权看房记

中粮凤凰里看房是希望而来&#xff0c;失望而归。主要是对如下失望&#xff0c;下述仅个人看房感受&#xff1a; 1. 户型不喜欢&#xff1a;三房的厨房和餐厅位置很奇葩 2. 样板间在25楼&#xff1a;湖景一言难尽和有工厂噪声 3. 精装修的交房质量:阳台的推拉门用料很草率 …

轮播(css+js)

目录 1.实现效果 2.基础代码演示 2.1js代码 2.1css样式 2.3实现效果 3.实现点击切换 3.1给button添加点击事件 3.2效果图如下 3.3发现问题 3.3.1不循环 3.3.2循环 1.实现效果 2.基础代码演示 2.1js代码 <div class"out-box"><div class"tes…

深度学习(2)前向传播与反向传播

这一次我们重点讲解前向传播与反向传播&#xff0c;对这里还是有点糊涂 前向传播&#xff08;Forward Propagation&#xff09;和反向传播&#xff08;Backward Propagation&#xff09;是深度学习中神经网络训练的核心过程。它们分别负责计算神经网络的输出以及更新神经网络的…

HTML5 拖拽 API 深度解析

一、HTML5 拖拽 API 深度解析 1.1 背景与发展 HTML5 的拖拽 API 是为了解决传统拖拽操作复杂而设计的。传统方法依赖鼠标事件和复杂的逻辑计算&#xff0c;而 HTML5 提供了标准化的拖拽事件和数据传递机制&#xff0c;使得开发者能够快速实现从一个元素拖拽到另一个元素的交互…

前端自己也能开启HTTPS

目录 前言 使用mkcert 安装 创建证书 利用 mkcert 创建 ca 根据 ca 创建 cert 安装证书 项目开启HTTPS 安装插件 配置 vitecofnig.js 最终效果 前言 今天我发现了一个宝藏&#xff0c;兄弟们&#xff01;就是前端开发阶段是可以使用https来开发的。对不懂前端的后端兄…

精通 Python 网络安全

与 FTP、SSH 和 SNMP 服务器交互 本章将帮助您了解允许我们与 FTP、SSH 和 SNMP 服务器交互的模块。在本章中&#xff0c;我们将探讨网络中的计算机如何相互交互。一些允许我们连接 FTP、SSH 和 SNMP 服务器的工具可以在 Python 中找到&#xff0c;其中我们可以突出显示 FTPLi…

【C++跬步积累】 —— 二叉搜索树(模拟实现+源代码)

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;C跬步积累 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日一题 &#x1f7e1; Linux跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0…

Redis安装和Python练习(Windows11 + Python3.X + Pycharm社区版)

环境 Windows11 Python3.X Pycharm社区版 思路 1 github下载redis压缩包 &#xff0c;安装并启动redis服务&#xff0c;在客户端连接redis服务。 2 在pycharm中运行python程序&#xff0c;连接redis服务&#xff0c;熟悉redis的使用和巩固python语言。 3 python开发环境…

【电子通识】能用5V电源去驱动线圈电压12V的继电器吗?

最近新人在使用继电器做一些工装&#xff0c;选择的是一款汽车级的继电器JZC-32F/012-ZS3(555) 。其原因主要是因为封装小&#xff0c;通流能力也OK。 但因为产品是5V USB-TypeC接口供电的&#xff0c;所以他想用5V电源去驱动继电器&#xff0c;从而减少一个电源输入或是电源升…