Android 中 app freezer 原理详解(一):R 版本

news2024/11/23 9:07:31

基于版本:Android R

0. 前言

在之前的两篇博文《Android 中app内存回收优化(一)》 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理,为什么叫这个名字,而不叫 AppCompact 等?在之前的两篇博文中也提到了,因为该类中还管理了一个重要功能:freezer,一个针对应用进程长期处于 Cached 状态的优化。

本文将继续分析 CachedAppOptimizer 类另一个功能 freezer。

1. Freezer 触发

《Android oom_adj 更新原理(二)》中详细剖析了 OomAdjuster.applyOomAdjLocked() 函数,在 oom_adj 发生变化之后会重新 compute 然后在 apply, 在该函数中就是通过调用 updateAppFreezeStateLocked(app) 来确认是否冻结进程。

1.1 updateAppFreezeStateLocked()

frameworks/base/services/core/java/com/android/server/am/OomAdjuster.java

    void updateAppFreezeStateLocked(ProcessRecord app) {
        // 确定该功能是使能的
        if (!mCachedAppOptimizer.useFreezer()) {
            return;
        }

        // 如果该进程处于 frozen状态,但sholudNoFreeze变为true,需要解冻
        if (app.frozen && app.shouldNotFreeze) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }

        // 如果该进程的 adj处于 CACHED,并且可以冻结,则调用 freezeAppAsync() 冻结
        // 如果该进程的 adj离开 CACHED,则解冻
        if (app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ && !app.frozen && !app.shouldNotFreeze) {
            mCachedAppOptimizer.freezeAppAsync(app);
        } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ && app.frozen) {
            mCachedAppOptimizer.unfreezeAppLocked(app);
        }
    }

在开始分析 freezer 之前,首先通过触发的代码了解 freezer 会有哪些状态,以及发生freeze、unfreeze 的情况。

这里主要分三个部分:

  • 确认是否使能了 freezer;
  • 进程状态是否已经发生变化,已经被冻结,但进程状态改成 shouldNotFreeze,此时需要解冻。但这里的逻辑显然有些难以理解,已经进行了 unfreeze,为何要继续下面的流程?好在Android S 中解决了这个逻辑问题;
  • 根据 app 现在adj 是否处于 Cached,以及 frozen 的情况,确定是进入 freeze 流程,还是 unfreeze 流程;

2. CachedAppOptimizer.init()

对于CachedAppOptimizer 的构造调用,以及 init() 函数的触发流程,可以参考《Android 中app内存回收优化(一)》 一文第 1 节 和 第 2 节。

frameworks/base/services/core/java/com/android/server/am/CachedAppOptimizer.java

    public void init() {
        ...
        synchronized (mPhenotypeFlagLock) {
            ...
            updateUseFreezer();
        }
    }

2.1 updateUseFreezer()

    private void updateUseFreezer() {
        // 获取settings 中属性 cached_apps_freezer 的值,根据属性值初始化变量mUseFreezer
        final String configOverride = Settings.Global.getString(mAm.mContext.getContentResolver(),
                Settings.Global.CACHED_APPS_FREEZER_ENABLED);

        if ("disabled".equals(configOverride)) {
            mUseFreezer = false;
        } else if ("enabled".equals(configOverride)
                || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                    KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
            mUseFreezer = isFreezerSupported();
        }

        if (mUseFreezer && mFreezeHandler == null) {
            Slog.d(TAG_AM, "Freezer enabled");
            enableFreezer(true);

            if (!mCachedAppOptimizerThread.isAlive()) {
                mCachedAppOptimizerThread.start();
            }

            mFreezeHandler = new FreezeHandler();

            Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
                    Process.THREAD_GROUP_SYSTEM);
        } else {
            enableFreezer(false);
        }
    }

首先确认 freezer 功能是否使能,用个流程图来说明比较清晰:

当 freezer 使能,就会:

  • 调用 enableFreezer() 进行使能,详细的流程可以查看第 4 节;
  • 如果 CachedAppOptimizer 中的 ServiceThread 没有启动,则启动;
  • 创建 free handler,用以处理 freezer 相关消息;
  • 设置 ServiceThread 的优先级为 THREAD_GROUP_SYSTEM;

3. cgroups 简介

cgroups (全称:control groups) 是 Linux 内核提供的一种可以限制单个进程或者多个进程所使用资源的机制,可以对 CPU、memory 等资源实现精细化的控制。目前越来越活的轻量级容器 Docker 就使用了 cgroups 提供的资源限制能力来完成 CPU、memory 等部门的资源控制。

cgroups 为每种可以控制的资源定义了一个子系统,典型的子系统如下:

  • cpu:主要限制进程的 cpu 使用率;
  • cpuaat:可以统计 cgroups 中的进程的 cpu 使用报告;
  • cpuset:可以为 cgroups 中的进程分配单独的 cpu 节点或内存节点;
  • memory:可以限制进程的 memory 使用量;
  • blkio:可以限制进程的块设备 io;
  • devices:可以控制进程能够访问某些设备;
  • freezer:可以挂起或恢复 cgroups 中的进程;
  • net_cls:可以标记 cgroups 中进程的网络数据包,然后可以使用 tc (traffic control)模块对数据包进行控制;
  • ns:可以使不同的 cgroups 下面的进程使用不同的 namespace;

Android Q 或更高版本通过 task profiles 使用 cgroup 抽象层,task profiles 可以用来描述应用于某个线程或进程的一个set 或 sets 的限制。系统依照 task profiles 的规定选择一个或多个适当的 cgroups。通过这种限制,可以对底层的 cgroup 功能集进行更改,而不会影响较高的软件层。

具体的细节可以查看博文:《Android 中 cgroup抽象层详解》

本文的很多重要特性都是通过 profile 的方式完成。

4. enableFreezer()

主要是调用 enableFreezerInternal() 函数:

    private static native void enableFreezerInternal(boolean enable);
frameworks/base/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp

static void com_android_server_am_CachedAppOptimizer_enableFreezerInternal(
        JNIEnv *env, jobject clazz, jboolean enable) {
    bool success = true;

    if (enable) {
        success = SetTaskProfiles(0, {"FreezerEnabled"}, true);
    } else {
        success = SetTaskProfiles(0, {"FreezerDisabled"}, true);
    }

    if (!success) {
        jniThrowException(env, "java/lang/RuntimeException", "Unknown error");
    }
}

 通过接口 SetTaskProfiles() 往对应的节点写入特定的 value 值,详细可以查看博文:《Android 中 cgroup抽象层详解》

5. freezeAppAsync()

参数为 ProcessRecord 类型,也就是对应的进程。

    void freezeAppAsync(ProcessRecord app) {
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        mFreezeHandler.sendMessageDelayed(
                mFreezeHandler.obtainMessage(
                    SET_FROZEN_PROCESS_MSG, DO_FREEZE, 0, app),
                FREEZE_TIMEOUT_MS);
    }

注意,

  • 如果该进程已经发送 freeze 请求,再次发送请求时,先取消原来的消息;
  • 发送消息 SET_FROZEN_PROCESS_MSG,请求 freeze;
  • 消息处理的延时时长为 FREEZE_TIMEOUT_MS(10 min),如果10 分钟之后,冻结该进程的消息还没有被取消,则进入冻结进程的流程;

5.1 freeze 消息处理

        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SET_FROZEN_PROCESS_MSG:
                    freezeProcess((ProcessRecord) msg.obj);
                    break;
                case REPORT_UNFREEZE_MSG:
                    int pid = msg.arg1;
                    int frozenDuration = msg.arg2;
                    String processName = (String) msg.obj;

                    reportUnfreeze(pid, frozenDuration, processName);
                    break;
                default:
                    return;
            }
        }

对于 freezer 一共有两个消息,REPORT_UNFREEZE_MSG 这个消息是在 unfreeze 之后进行记录的。

本文重点来看下 freeze 的消息处理,这里看到最终调用的是 freezeProcess() 函数,详细查看下一小节。

5.2 freezeProcess()

        private void freezeProcess(ProcessRecord proc) {
            final int pid = proc.pid;
            final String name = proc.processName;
            final long unfrozenDuration;
            final boolean frozen;

            try {
                // 确认进程是否存在任意的文件锁,避免不必要的 free/unfreeze操作
                //   这是为了防止冻结进程持有文件锁,从而引起死锁
                //   冻结成功之后,还会再次确认文件锁,如果有锁,则立即解冻
                if (Process.hasFileLocks(pid)) {
                    if (DEBUG_FREEZER) {
                        Slog.d(TAG_AM, name + " (" + pid + ") holds file locks, not freezing");
                    }
                    return;
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Not freezing. Unable to check file locks for " + name + "(" + pid
                        + "): " + e);
                return;
            }

            // 使用ASM做锁,在S 版本中换成专一的锁
            synchronized (mAm) {
                // 如果进程没有变成Cached或者不能freeze,则退出此次freeze操作
                if (proc.curAdj < ProcessList.CACHED_APP_MIN_ADJ
                        || proc.shouldNotFreeze) {
                    return;
                }

                // 已经处于frozen,或者不是一个应用进程,则退出此次 freeze操作
                //   pid为0,有可能进程还没有launch完成,或者进程被kill了
                if (pid == 0 || proc.frozen) {
                    return;
                }

                long unfreezeTime = proc.freezeUnfreezeTime;

                // 核心函数setProcessFrozen(),同步冻结进程
                try {
                    Process.setProcessFrozen(pid, proc.uid, true);

                    proc.freezeUnfreezeTime = SystemClock.uptimeMillis();
                    proc.frozen = true;
                } catch (Exception e) {
                    Slog.w(TAG_AM, "Unable to freeze " + pid + " " + name);
                }

                unfrozenDuration = proc.freezeUnfreezeTime - unfreezeTime;
                frozen = proc.frozen;
            }

            if (!frozen) {
                return;
            }

            //此次freeze记录到event log
            EventLog.writeEvent(EventLogTags.AM_FREEZE, pid, name);

            // 这里逻辑应该是颠倒了,先冻结进程,再去冻结binder,逻辑上不通
            //   在 S 版本中已经将freeBinder提前到 process冻结之前
            try {
                freezeBinder(pid, true);
            } catch (RuntimeException e) {
                Slog.e(TAG_AM, "Unable to freeze binder for " + pid + " " + name);
                proc.kill("Unable to freeze binder interface",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            }

            // See above for why we're not taking mPhenotypeFlagLock here
            if (mRandom.nextFloat() < mFreezerStatsdSampleRate) {
                FrameworkStatsLog.write(FrameworkStatsLog.APP_FREEZE_CHANGED,
                        FrameworkStatsLog.APP_FREEZE_CHANGED__ACTION__FREEZE_APP,
                        pid,
                        name,
                        unfrozenDuration);
            }

            try {
                // 再次check文件锁,如果该冻结进程持有文件锁,立即unfreeze
                if (Process.hasFileLocks(pid)) {
                    synchronized (mAm) {
                        unfreezeAppLocked(proc);
                    }
                }
            } catch (Exception e) {
                Slog.e(TAG_AM, "Unable to check file locks for " + name + "(" + pid + "): " + e);
                synchronized (mAm) {
                    unfreezeAppLocked(proc);
                }
            }
        }

 

5.2.1 setProcessFrozen()

frameworks/base/core/java/android/os/Process.java

    public static final native void setProcessFrozen(int pid, int uid, boolean frozen);
frameworks/base/core/jni/android_util_Process.cpp

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"});
    } else {
        success = SetProcessProfiles(uid, pid, {"Unfrozen"});
    }

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

此处的调用在博文《cgroup抽象层》中已经分析过,通过接口 SetProcessProfiles() 精细是 SetCgroupAction 类型的profile,最终调用 ExecuteForProcess():

system/core/libprocessgroup/task_profiles.cpp
 
bool SetCgroupAction::ExecuteForProcess(uid_t uid, pid_t pid) const {
    std::string procs_path = controller()->GetProcsFilePath(path_, uid, pid);
    unique_fd tmp_fd(TEMP_FAILURE_RETRY(open(procs_path.c_str(), O_WRONLY | O_CLOEXEC)));
    if (tmp_fd < 0) {
        PLOG(WARNING) << "Failed to open " << procs_path;
        return false;
    }
    if (!AddTidToCgroup(pid, tmp_fd)) {
        LOG(ERROR) << "Failed to add task into cgroup";
        return false;
    }
 
    return true;
}

通过函数,先通过 Controller 的 GetProcsFilePath() 接口获取该profile 需要修改的path,参数为该 profile 配置的 Path:

system/core/libprocessgroup/cgroup_map.cpp
std::string CgroupController::GetProcsFilePath(const std::string& rel_path, uid_t uid,
                                               pid_t pid) const {
    std::string proc_path(path());
    proc_path.append("/").append(rel_path);
    proc_path = regex_replace(proc_path, std::regex("<uid>"), std::to_string(uid));
    proc_path = regex_replace(proc_path, std::regex("<pid>"), std::to_string(pid));
 
    return proc_path.append(CGROUP_PROCS_FILE);
}

最终写的文件就是 CGROUP_PROCS_FILE,也就是 cgroup.procs 文件。

 

6. unfreezeAppLocked()

    void unfreezeAppLocked(ProcessRecord app) {
        // 首先,取消之前该进程的冻结请求
        mFreezeHandler.removeMessages(SET_FROZEN_PROCESS_MSG, app);

        // 如果进程还没有冻结,则无需做解冻处理
        if (!app.frozen) {
            return;
        }

        /********进程处于冻结,进行解冻处理*********/
        boolean processKilled = false;

        // 冻住的进程可以接收异步binder请求,但是不会处理,只是放入binder buffer, 过多的请求会导致buffer耗尽;
        // 这里需要确认下该进程在解冻之前,进程是否在冰冻期间收到同步的binder 请求,有则kill该进程
        try {
            int freezeInfo = getBinderFreezeInfo(app.pid);

            if ((freezeInfo & SYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received sync transactions while frozen, killing");
                app.kill("Sync transaction while in frozen state",
                        ApplicationExitInfo.REASON_OTHER,
                        ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
                processKilled = true;
            }

            if ((freezeInfo & ASYNC_RECEIVED_WHILE_FROZEN) != 0) {
                Slog.d(TAG_AM, "pid " + app.pid + " " + app.processName + " "
                        + " received async transactions while frozen");
            }
        } catch (Exception e) {
            Slog.d(TAG_AM, "Unable to query binder frozen info for pid " + app.pid + " "
                    + app.processName + ". Killing it. Exception: " + e);
            app.kill("Unable to query binder frozen stats",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            processKilled = true;
        }

        //进程被kill 了,退出unfreeze
        if (processKilled) {
            return;
        }

        // app.freezeUnfreezeTime记录的是上次free、unfreeze的时间
        long freezeTime = app.freezeUnfreezeTime;

        try {
            freezeBinder(app.pid, false);
        } catch (RuntimeException e) {
            Slog.e(TAG_AM, "Unable to unfreeze binder for " + app.pid + " " + app.processName
                    + ". Killing it");
            app.kill("Unable to unfreeze",
                    ApplicationExitInfo.REASON_OTHER,
                    ApplicationExitInfo.SUBREASON_INVALID_STATE, true);
            return;
        }

        try {
            Process.setProcessFrozen(app.pid, app.uid, false);

            app.freezeUnfreezeTime = SystemClock.uptimeMillis();
            app.frozen = false;
        } catch (Exception e) {
            Slog.e(TAG_AM, "Unable to unfreeze " + app.pid + " " + app.processName
                    + ". This might cause inconsistency or UI hangs.");
        }

        if (!app.frozen) {
            if (DEBUG_FREEZER) {
                Slog.d(TAG_AM, "sync unfroze " + app.pid + " " + app.processName);
            }

            mFreezeHandler.sendMessage(
                    mFreezeHandler.obtainMessage(REPORT_UNFREEZE_MSG,
                        app.pid,
                        (int) Math.min(app.freezeUnfreezeTime - freezeTime, Integer.MAX_VALUE),
                        app.processName));
        }
    }

核心处理函数是 setProcessFrozen(),详细的流程在上面第 5.2.1 节中已经分析过。主要需要注意的是 unfrozen 在 task_profiles.json 中:

    {
      "Name": "Frozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": ""
          }
        }
      ]
    },
    {
      "Name": "Unfrozen",
      "Actions": [
        {
          "Name": "JoinCgroup",
          "Params":
          {
            "Controller": "freezer",
            "Path": "../"
          }
        }
      ]
    },

与Frozen 不同,Unfrozen 的Path 为上一级目录,即最终修改的是 sys/fs/cgroup/cgroup.procs,而frozen 修改的是 sys/fs/cgroup/freezer/cgroup.procs

至此,Android R 版本中关于 freezer这里就全部完成了。

从本文的逻辑分析来看Android R 上还有些逻辑问题,通过本文我们大致了解了 freezer 大致流程,以及使用 cgroup 抽象层的接口,但因为版本不稳定,这里就不去过多分析Kernel 中的实现。

最后,附上一个 app 冻结的旅程图:

 

 

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

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

相关文章

第五章:linux进程控制

系列文章目录 文章目录 系列文章目录前言进程创建fork函数初识fork写时拷贝fork常规用法fork调用失败的原因 进程终止进程退出场景进程的退出码系统自带的退出码strerrorC语言提供的退出码 进程退出深度理解进程常见退出方法正常退出缓冲区 进程等待进程等待必要性进程等待的方…

SAP中获取成品物料的全部配置(SAP配置BOM攻略四)

基于系统内的全配置BOM设定&#xff0c;全部的子配置是由四大配置产生&#xff08;即车身颜色、内饰颜色、车型、选装&#xff09;。如果按某一车型&#xff0c;要带出该车的全部BOM子物料&#xff0c;首先需要具备通过四大配置&#xff0c;得到全部子配置的能力&#xff0c;然…

【iVX】在百花齐放的低代码平台中独领风骚

&#x1f482;作者简介&#xff1a; THUNDER王&#xff0c;一名热爱财税和SAP ABAP编程以及热爱分享的博主。目前于江西师范大学本科在读&#xff0c;同时任汉硕云&#xff08;广东&#xff09;科技有限公司ABAP开发顾问。在学习工作中&#xff0c;我通常使用偏后端的开发语言A…

IntelliJ IDEA 2023.2 新版本,拥抱 AI

IntelliJ IDEA 近期连续发布多个EAP版本&#xff0c;官方在对用户体验不断优化的同时&#xff0c;也新增了一些不错的功能&#xff0c;尤其是人工智能助手补充&#xff0c;AI Assistant&#xff0c;相信在后续IDEA使用中&#xff0c;会对开发者工作效率带来不错的提升。 以下是…

mybatisPlus入门篇

文章目录 初窥门径1.1 初识MybatisPlus1.2 MybatisPlus的特性1.3 MybatisPlus的架构模型 入门案例2.1 准备相关开发环境2.2 搭建springboot工程2.3 创建数据库2.4 引入相关依赖2.5 创建实体类2.6 集成MybatisPlus2.7 单元测试2.8 springboot日志优化 初窥门径 1.1 初识Mybatis…

Rust之包、单元包及模块

包&#xff1a;一个用于构建、测试并分享单元包的Cargo功能&#xff1b;单元包&#xff1a;一个用于生成库或可执行文件的树形模块结构&#xff1b;模块及use关键字&#xff1a;被用于控制文件结构、作用域及路径的私有性&#xff1b;路径&#xff1a;一种用于命名条目的方法&a…

Windows之XSshell7运行程序找不到mfc140u.dll解决方案

Xshell7依赖C库如下&#xff0c;下载如下2个x86&#xff08;32位&#xff09;运行库安装即可使用。 官网地址&#xff1a;《C运行库》

城市之星中山TOP3

城市之星中山TOP3 不断努力&#xff0c;突破自己。

《吐血整理》保姆级系列教程-玩转Fiddler抓包教程(5)-Fiddler监控面板详解

1.简介 按照从上往下&#xff0c;从左往右的计划&#xff0c;今天就轮到介绍和分享Fiddler的监控面板了。监控面板主要是一些辅助标签工具栏。有了这些就会让你的会话请求和响应时刻处监控中毫无隐私可言。监控面板是fiddler最核心的功能之一。记录了来自于服务器端&#xff0…

机器学习深度学习——softmax回归从零开始实现

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习——向量求导问题 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助 …

39. Linux系统下在Qt5.9.9中搭建Android开发环境

1. 说明 QT版本:5.9.9 电脑系统:Linux JDK版本:openjdk-8-jdk SDK版本:r24.4.1 NDK版本:android-ndk-r14b 效果展示: 2. 具体步骤 大致安装的步骤如下:①安装Qt5.9.9,②安装jdk,③安装ndk,④安装sdk,⑤在qt中配置前面安装的环境路径 2.1 安装Qt5.9.9 首先下载…

国产化的接口测试、接口自动化测试工具Apipost的介绍及使用

Apipost介绍&#xff1a; Apipost是 API 文档、API 调试、API Mock、API 自动化测试一体化的研发协作赋能平台&#xff0c;它的定位 Postman Swagger Mock JMeter。 Apipost 是接口管理、开发、测试全流程集成工具&#xff0c;能支撑整个研发技术团队同平台工作&#xff0…

win10日程怎么同步到安卓手机?电脑日程同步到手机方法

在如今快节奏的生活中&#xff0c;高效地管理时间变得至关重要。而对于那些经常在电脑上安排日程的人来说&#xff0c;将这些重要的事务同步到手机上成为了一个迫切的需求。因为目前国内使用win10系统电脑、安卓手机的用户较多&#xff0c;所以越来越多的职场人士想要知道&…

手机怎么把word转换成pdf?这几种方法超简单

手机怎么把word转换成pdf&#xff1f;现在很多人在手机上处理文档&#xff0c;但是可能会遇到将Word文档转换为PDF的需求&#xff0c;以便更好地分享和传输文件。在下面这篇文章中&#xff0c;就给大家介绍几种将Word文档转换为PDF的方法。 方法一&#xff1a;使用迅捷PDF转换器…

spring复习:(55)注解配置的情况下@ComponentScan指定的包中的组件是怎么被注册到容器的?

配置类&#xff1a; 主类&#xff1a; 结论&#xff1a;是在context.refresh()处完成扫描和注册的。 fresh()的代码片段如下&#xff1a; 其中调用的invokeBeanFactoryPostProcessor代码如下&#xff1a; 其中调用的静态方法invokeBeanFactoryPostProcessors代码如下&#…

一些联动树形数据组装

export const pieselectdata [{entrustOrganization: 智慧法院电子诉讼平台,entrustOrganizationId: 161,productNames: [{batchCodes: [],productName: CL测试调解产品,},{batchCodes: [2022927_001,2022927_003,2022927_004,2022927_005,2022927_006,2022927_008,2022927_00…

文本预处理——文本数据增强

目录 文本数据增强回译数据增强法 文本数据增强 回译数据增强法

windows 系统安装sonarqube

SonarQube是一种自动代码审查工具&#xff0c;用于检测代码中的错误&#xff0c;漏洞和代码异味。它可以与您现有的工作流程集成&#xff0c;以便在项目分支和拉取请求之间进行连续的代码检查。 官方网站&#xff1a; https://www.sonarqube.org/ 1. 使用前提条件 运行SonarQ…

Excel双向柱状图的绘制

Excel双向柱状图在绘制增减比较的时候经常用到&#xff0c;叫法繁多&#xff0c;双向柱状图、上下柱状图、增减柱状图都有。 这里主要介绍一下Excel的基础绘制方法和复杂一点的双向柱状图的绘制 基础双向柱状图的绘制 首先升降的数据如下&#xff1a; 月份上升下降20220359-…

【二叉树】刷题(以递归写法为主)

226.翻转二叉树 101. 对称二叉树 104.二叉树的最大深度 111.二叉树的最小深度 222.完全二叉树的节点个数 110.平衡二叉树 102. 二叉树的所有路径 226.翻转二叉树 class Solution:def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:if not root:return…