Android Framework AMS(16)进程管理
该系列文章总纲链接:专题总纲目录 Android Framework 总纲
本章关键点总结 & 说明:
说明:本章节主要解读AMS 进程方面的知识。关注思维导图中左上侧部分即可。
我们本章节主要是对Android进程管理相关知识有一个基本的了解。先来了解下Linux的进程管理,基于此,我们再谈Android的进程管理体系。最后了解AMS中2个关键的进程管理API:updateLruProcessLocked 和 updateOomAdjLocked。
1 Linux的进程管理
Linux的进程调度是操作系统核心功能之一,它负责决定哪个进程在何时运行以及运行多长时间。以下是Linux进程调度的基本机制,以及setpriority
和sched_setscheduler
两个方法的详细解读。
1.1 Linux进程调度简要解读
Linux进程调度基本机制解读如下
- 调度器(Scheduler):Linux使用调度器来决定进程的执行顺序。调度器根据进程的优先级和调度策略来选择下一个要执行的进程。
- 调度策略(Scheduling Policies):Linux支持多种调度策略,包括
SCHED_NORMAL
(普通)、SCHED_FIFO
(先进先出)、SCHED_RR
(时间片轮转)、SCHED_BATCH
(批处理)和SCHED_IDLE
(空闲)等。 - 优先级(Priority):每个调度策略都有自己的优先级范围。进程的优先级越低(数值越小),它被调度执行的优先级就越高。
- 时间片(Time Slice):在
SCHED_RR
和SCHED_NORMAL
策略下,进程被分配一个时间片,即它在被抢占前可以运行的时间。 - 上下文切换(Context Switching):当一个进程的时间片用完或者它主动放弃CPU时,会发生上下文切换,调度器会选择另一个进程来执行。
1.2 setpriority 系统调用
setpriority
系统调用用于设置进程的静态优先级。这个优先级是在进程创建时分配的,并且通常不会改变,除非显式地调用setpriority
。API详细解读如下:
int setpriority(int which, int who, int value);
hich
:指定要设置的优先级类型,可以是PRIO_PROCESS
(进程)、PRIO_PGRP
(进程组)或PRIO_USER
(用户)。who
:指定要设置优先级的进程ID、进程组ID或用户ID。value
:指定新的优先级值,值越小优先级越高。
接下来我们来看看setpriority具体的使用方法,demo如下:
#include <sys/resource.h>
#include <stdio.h>int main() {// 设置当前进程的nice值,值越小优先级越高int result = setpriority(PRIO_PROCESS, getpid(), -10);if (result == -1) {perror("setpriority");return 1;}printf("Priority set to: %d\n", getpriority(PRIO_PROCESS, getpid()));return 0;
}
这个程序将当前进程的nice值设置为-10,这通常会提高进程的优先级。getpriority
函数用于获取当前进程的优先级,以验证setpriority
是否成功。
1.3 sched_setscheduler 系统调用
sched_setscheduler
系统调用用于设置进程的动态调度策略和相关参数。API详细解读如下:
struct sched_param {int sched_priority; /* Process execution priority */
};int sched_setscheduler(pid_t pid, const struct sched_param *param);
pid
:指定要设置调度策略的进程ID。如果pid
是0,则设置调用进程的调度策略。param
:指向sched_param
结构的指针,该结构包含了新的调度参数,如优先级。
接下来我们来看看sched_setscheduler具体的使用方法,demo如下:
#include <sched.h>
#include <stdio.h>
#include <unistd.h>int main() {// 定义调度参数结构体struct sched_param param;param.sched_priority = 1; // 设置优先级为1// 设置调度策略为SCHED_FIFOint policy = SCHED_FIFO;int result = sched_setscheduler(0, policy, ¶m);if (result == -1) {perror("sched_setscheduler");return 1;}printf("Scheduler set to: %d\n", policy);printf("Priority set to: %d\n", param.sched_priority);// 让进程睡眠一段时间,以便观察调度效果sleep(10);return 0;
}
这个程序将当前进程的调度策略设置为SCHED_FIFO
,并设置优先级为1。sched_setscheduler
函数的第一个参数是0,表示设置调用进程的调度策略。policy
变量指定了新的调度策略,param
结构体指定了调度参数。
注意事项解读:
- 在使用
sched_setscheduler
时,需要确保进程有适当的权限(通常是root权限),因为改变进程的调度策略可能需要管理员权限。 setpriority
和sched_setscheduler
的使用可能会受到操作系统安全策略的限制,特别是在生产环境中。- 这些调用的行为可能会因不同的Linux发行版和内核配置而异。
总结:setpriority
和sched_setscheduler
是Linux进程调度中两个重要的系统调用。setpriority
用于设置进程的静态优先级,而sched_setscheduler
用于设置进程的动态调度策略和优先级。这两个调用共同决定了进程的调度行为,允许操作系统和应用程序精细控制进程的执行顺序和优先级。通过合理配置这些参数,可以优化系统性能,确保关键任务的及时响应。
2 Android进程解读
2.1 Android进程分类
Android进程管理和Linux进程管理之间的关系是密切的,因为Android系统是建立在Linux内核之上的。在Android中,进程的分类和管理与Linux中有许多相似之处,但也做了一些特定的优化和扩展以适应移动设备的特点。以下是Android进程分类的核心调用和它们与Linux进程管理的对应关系:
- 前台进程(Foreground Processes):在Linux中,前台进程通常是指那些与用户直接交互的进程。在Android中,前台进程包括正在运行的Activity和正在执行的BroadcastReceiver。这些进程因为与用户直接交互,所以系统会优先保持它们的运行。
- 可见进程(Visible Processes):可见进程在Android中是指那些对用户可见但不在前台的进程,例如用户最近查看的Activity。Linux中没有直接对应的概念,但可以通过进程的可见性来进行类比。
- 服务进程(Service Processes):在Android中,服务进程是指那些运行Service组件的进程。这些服务可能在后台执行任务,但对应用程序的行为至关重要。Linux中,服务通常作为守护进程运行,与Android中的服务进程概念相似。
- 后台进程(Background Processes):后台进程在Android中是指那些对用户不可见的Activity所在的进程。这些进程可能会被系统根据需要终止以释放内存。在Linux中,后台进程通常是指那些在后台运行的进程,如守护进程。
- 空进程(Empty Processes):空进程在Android中是指那些不包含任何活跃组件的进程,但可能因为某些原因(如服务绑定)而保留在内存中。Linux中没有直接对应的概念,但可以通过进程的资源占用来进行类比。
在Android中,进程的创建和管理是通过ActivityManagerService来协调的,而在Linux中,进程的创建和管理是通过内核的调度器和系统调用(如fork、exec、exit)来实现的。Android在Linux的基础上增加了一些特定的管理机制,如进程的分类和优先级管理,以及与应用程序组件生命周期相关的进程管理策略。这些机制使得Android能够有效地管理有限的资源,并提供良好的用户体验。
2.2 进程调度相关API
在Android Framework中,Process.java
文件提供了多个与进程调度相关的API。以下是一些最关键的API:
public class Process {private static final String LOG_TAG = "Process";//.../*** 设置当前线程的优先级。* * @param priority 要设置的优先级值,值越小优先级越高。* @throws IllegalArgumentException 如果优先级值不在合法范围内。* @throws SecurityException 如果当前进程没有权限设置线程优先级。*/public static final native void setThreadPriority(int priority)throws IllegalArgumentException, SecurityException;/*** 设置指定线程的优先级。* * @param tid 目标线程的ID。* @param priority 要设置的优先级值,值越小优先级越高。* @throws IllegalArgumentException 如果优先级值不在合法范围内或线程ID无效。* @throws SecurityException 如果当前进程没有权限设置指定线程的优先级。*/public static final native void setThreadPriority(int tid, int priority)throws IllegalArgumentException, SecurityException;/*** 获取指定线程的优先级。* * @param tid 目标线程的ID。* @return 线程的当前优先级值。* @throws IllegalArgumentException 如果线程ID无效。*/public static final native int getThreadPriority(int tid)throws IllegalArgumentException;/*** 将线程分组到一个线程组。* * @param tid 目标线程的ID。* @param group 线程组的ID。* @throws IllegalArgumentException 如果线程ID或线程组ID无效。* @throws SecurityException 如果当前进程没有权限更改线程组。*/public static final native void setThreadGroup(int tid, int group)throws IllegalArgumentException, SecurityException;/*** 将进程分组到一个进程组。* * @param pid 目标进程的ID。* @param group 进程组的ID。* @throws IllegalArgumentException 如果进程ID或进程组ID无效。* @throws SecurityException 如果当前进程没有权限更改进程组。*/public static final native void setProcessGroup(int pid, int group)throws IllegalArgumentException, SecurityException;/*** 获取进程所在的进程组ID。* * @param pid 目标进程的ID。* @return 进程所在的进程组ID。* @throws IllegalArgumentException 如果进程ID无效。*/public static final native int getProcessGroup(int pid)throws IllegalArgumentException;//...
}
这些native方法提供了对Android进程和线程调度参数的控制,它们通过JNI(Java Native Interface)与底层的C/C++代码交互,最终调用Linux内核的系统调用来实现具体的调度策略。API解读如下:
- setThreadPriority(int priority):这个方法用于设置当前线程的优先级。在Android中,线程优先级的范围通常是-20(最高)到19(最低)。
- setThreadPriority(int tid, int priority):这个方法用于设置指定线程的优先级。
tid
参数指定了目标线程的ID。 - getThreadPriority(int tid):这个方法用于获取指定线程的优先级。
tid
参数指定了目标线程的ID。 - setThreadGroup(int tid, int group):这个方法用于将线程分组到一个线程组。在Linux中,线程组通常用于调度和资源管理。
- setProcessGroup(int pid, int group):这个方法用于将进程分组到一个进程组。在Linux中,进程组通常用于调度和资源管理。
- getProcessGroup(int pid):这个方法用于获取进程所在的进程组ID。
pid
参数指定了目标进程的ID。
这些方法的使用需要相应的权限,特别是在修改其他进程的调度策略时,通常需要系统权限或者root权限。通过合理使用这些API,开发者可以优化应用的性能和响应速度,或者在特定场景下对系统资源进行更精细的控制。
2.3 Android进程关键变量解读
在Android系统中,OOM_ADJ和LRU是两个关键的进程管理参数,因为它们共同决定了Android系统中进程的内存分配和回收策略,具体如下:
- 内存管理:OOM_ADJ和LRU帮助系统在内存不足时决定哪些进程可以被牺牲以释放内存资源。这对于保持系统稳定性和响应性至关重要,尤其是在内存资源有限的移动设备上。
- 进程优先级:通过调整OOM_ADJ值,系统可以为不同类型的进程设置不同的优先级,确保关键任务(如前台应用)获得足够的资源,而后台或不常用的进程则可以被适当地牺牲。
- 性能优化:合理使用LRU和OOM_ADJ可以提高应用的启动速度和响应性,因为系统可以更快地从缓存中恢复进程,而不是每次都从头开始启动进程。
- 资源平衡:这两个参数使得系统能够在保证用户体验的同时,合理分配有限的资源,平衡不同应用和进程的需求。
可见,它们直接影响系统的内存管理和进程调度策略,对于优化系统性能和用户体验具有重要意义。
2.3.1 OOM_ADJ的解读
OOM_ADJ(Out-Of-Memory Adjustment):OOM_ADJ值是一个整数,用来表示进程的优先级,特别是在系统内存不足时决定哪些进程可以被杀死以回收内存。OOM_ADJ值的设计目的和设计意义如下:
- 设计目的:OOM_ADJ值的主要目的是为了在系统内存不足时,能够根据进程的重要性进行合理的内存回收。系统会优先杀死那些对用户体验影响较小的进程,保护前台进程和对用户体验至关重要的后台进程。
- 设计意义:OOM_ADJ值有助于提高Android设备的用户体验和系统稳定性。它允许系统在面临内存压力时,根据进程的重要性做出合理的决策,从而在保证用户体验的同时,也确保系统的流畅运行。
在Android中,OOM_ADJ值的范围通常是从-1000到1000,其中-1000表示最高优先级(最不容易被杀死),而1000表示最低优先级(最容易被杀死)。OOM_ADJ的值定义在ProcessList.java中,代码实现如下:
final class ProcessList {// 应用崩溃间隔的最短时间,用于判断应用是否不稳定,如果是,则停止其服务并拒绝广播。static final int MIN_CRASH_INTERVAL = 60 * 1000;// 处于不同状态的进程的OOM(Out Of Memory)调整值:// 在某些地方使用,我们还不确定具体的值。// (通常是要被缓存的值,但我们还不知道缓存范围中的确切值。)static final int UNKNOWN_ADJ = 16;// 只托管不可见活动的进程,可以在不造成任何干扰的情况下杀死。static final int CACHED_APP_MAX_ADJ = 15;static final int CACHED_APP_MIN_ADJ = 9;// 服务B列表的调整值——这些是旧的、不受欢迎的服务,不像A列表中的服务那样吸引人。static final int SERVICE_B_ADJ = 8;// 这是用户之前所在的应用进程。这个进程被保持在较高优先级,因为用户经常会切换回之前的应用。// 这对于最近任务切换(在两个最近的应用程序之间切换)和正常UI流程(例如,在电子邮件应用中点击一个URI在浏览器中查看,然后按返回键回到电子邮件)都很重要。static final int PREVIOUS_APP_ADJ = 7;// 这是托管Home应用的进程——我们希望尽量避免杀死它,即使它通常在后台,因为用户与之交互很多。static final int HOME_APP_ADJ = 6;// 这是托管应用服务的进程——杀死它对用户的影响不大。static final int SERVICE_ADJ = 5;// 这是一个托管重量级应用的进程。它在后台,但我们希望尽量避免杀死它。值在系统启动时在system/rootdir/init.rc中设置。static final int HEAVY_WEIGHT_APP_ADJ = 4;// 这是一个当前托管备份操作的进程。杀死它不是完全致命的,但通常是一个坏主意。static final int BACKUP_APP_ADJ = 3;// 这是一个只托管对用户可感知的组件的进程,我们真的希望避免杀死它们,但它们不是立即可见的。一个例子是后台音乐播放。static final int PERCEPTIBLE_APP_ADJ = 2;// 这是一个只托管对用户可见的活动的进程,所以我们希望它们不要消失。static final int VISIBLE_APP_ADJ = 1;// 这是运行当前前台应用的进程。我们真的不想杀死它!static final int FOREGROUND_APP_ADJ = 0;// 这个进程被系统或持久进程绑定,并表示它很重要。static final int PERSISTENT_SERVICE_ADJ = -11;// 这是一个系统持久进程,如电话进程。肯定不想杀死它,但如果这样做了,也不是完全致命的。static final int PERSISTENT_PROC_ADJ = -12;// 系统进程以默认的调整值运行。static final int SYSTEM_ADJ = -16;// 特殊代码,用于没有被系统管理的本地进程(所以没有被系统分配oom调整值)。static final int NATIVE_ADJ = -17;//...// 这些是我们要提供给OOM killer的各种内存级别。// 注意OOM killer只支持6个插槽,所以我们不能为每种可能的进程类型提供不同的值。private final int[] mOomAdj = new int[] {FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ,BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ};// 这些是低端OOM级别限制。这适用于HVGA或更小屏幕的手机,内存少于512MB。// 值以KB为单位。private final int[] mOomMinFreeLow = new int[] {12288, 18432, 24576,36864, 43008, 49152};// 这些是高端OOM级别限制。这适用于1280x800或更大屏幕,大约有1GB RAM。// 值以KB为单位。private final int[] mOomMinFreeHigh = new int[] {73728, 92160, 110592,129024, 147456, 184320};
}
这些常量代表了Android系统中不同类型进程的OOM(内存不足时的杀进程)调整值。这些值用于确定进程被系统杀死的优先级,值越小表示优先级越高,越不容易被系统杀死。这些调整值帮助系统在内存不足时决定哪些进程可以被牺牲以释放内存。用于更新oom_adj的方法为updateOomAdjLocked。
2.3.2 LRU相关变量解读
LRU(Least Recently Used):LRU是最近最少使用算法,核心思想是当缓存满时,优先淘汰那些近期最少使用的缓存对象(如果一个数据最近被访问过,那么它在未来被访问的概率更高;相反,如果一个数据长时间未被访问,那么它在未来被访问的概率较低),有效避免了OOM的出现。
lruWeight是一个用于衡量进程在最近最少使用(LRU)列表中排序权重的参数。它通常与进程的最后活动时间或其他活动相关的因素相关联,用于确定进程在LRU列表中的位置。lruWeight值的设计目的和设计意义如下:
- 设计目的:lruWeight的设计目的是为了在系统内存不足时,根据进程的最近使用情况来决定哪些进程可以被优先杀死以释放内存。它帮助系统维护一个有序的进程列表,确保最近被使用的进程保持在内存中,而长时间未被使用的进程则可以被牺牲。
- 设计意义:lruWeight的设计意义在于优化内存资源的分配和回收。通过动态调整进程的lruWeight值,系统能够更智能地管理内存,提高内存使用效率。这不仅有助于提升用户体验,比如通过快速恢复最近使用的应用程序,还能在内存紧张时减少系统的卡顿或崩溃。
lruWeight与OOM调整值的关系:lruWeight与进程的OOM调整值(OOM_ADJ)紧密相关,因为它们共同决定了进程在内存不足时的生存概率。一个进程的lruWeight值越高,其OOM调整值可能越低,表示它越容易被系统杀死。
此外,lruWeight还考虑了进程的类型和服务状态,比如活动、服务和内容提供者,这使得系统能够根据进程的实际作用和重要性来调整其在LRU列表中的位置,进一步优化内存管理。
lruWeight是Android系统中一个关键的内存管理参数,它通过反映进程的活跃度和重要性来帮助系统在有限的资源下做出合理的内存分配和回收决策。在Android中,LRU算法常用于内存缓存和硬盘缓存的管理。用于更新LRU的方法为updateLruProcessLocked。
3 AMS 进程管理相关函数分析
基于OOM_ADJ和LRU是两个关键的进程管理参数,这里我们主要探讨更新OOM_ADJ和LRU的方法。这里以startProcessLocked
方法启动一个新进程为例,启动相关的代码可参考文章:
Android Framework AMS(04)startActivity分析-1(am启动到ActivityThread启动)
Android Framework AMS(05)startActivity分析-2(ActivityThread启动到Activity拉起)
这里我们主要从AMS的attachApplicationLocked方法入手进行分析。代码实现如下:
//AMSprivate final boolean attachApplicationLocked(IApplicationThread thread,int pid) {ProcessRecord app;//...try {//...// 将应用程序线程与应用程序信息绑定thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,isRestrictedBackupMode || !normalMode, app.persistent,new Configuration(mConfiguration), app.compat,getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked());// 更新进程的LRU列表updateLruProcessLocked(app, false, null);//...} catch (Exception e) {//...}// 如果没有执行任何操作,则更新OOM调整值if (!didSomething) {updateOomAdjLocked();}return true;}
这里开始就已经调用了2个重要函数updateLruProcessLocked和updateOomAdjLocked。接下来对这2个方法进行更详细的解读。
3.1 updateLruProcessLocked分析
updateLruProcessLocked的代码实现,如下所示:
//AMSfinal void updateLruProcessLocked(ProcessRecord app, boolean activityChange,ProcessRecord client) {// 检查进程是否有activityfinal boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities|| app.treatLikeActivity;// 检查进程是否有服务final boolean hasService = false;// 如果没有activity变化且进程有activity,则直接返回if (!activityChange && hasActivity) {return;}// 更新LRU序列号mLruSeq++;final long now = SystemClock.uptimeMillis(); // 获取当前时间app.lastActivityTime = now; // 更新进程的最后activity时间// 如果进程有activity,检查它是否已经在LRU列表的最后if (hasActivity) {final int N = mLruProcesses.size();if (N > 0 && mLruProcesses.get(N - 1) == app) {return; // 如果进程已经是最后一个,直接返回}} else {// 如果进程没有activity,检查它是否在服务启动列表中if (mLruProcessServiceStart > 0&& mLruProcesses.get(mLruProcessServiceStart - 1) == app) {return; // 如果进程在服务启动列表中,直接返回}}// 查找进程在LRU列表中的索引int lrui = mLruProcesses.lastIndexOf(app);//...// 如果进程在LRU列表中,移除它if (lrui >= 0) {if (lrui < mLruProcessActivityStart) {mLruProcessActivityStart--; // 更新activity进程起始索引}if (lrui < mLruProcessServiceStart) {mLruProcessServiceStart--; // 更新服务进程起始索引}mLruProcesses.remove(lrui); // 从LRU列表中移除进程}int nextIndex;// 如果进程有activity,添加到LRU列表if (hasActivity) {final int N = mLruProcesses.size();if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {mLruProcesses.add(N - 1, app); // 将进程添加到LRU列表的倒数第二个位置final int uid = app.info.uid;for (int i = N - 2; i > mLruProcessActivityStart; i--) {ProcessRecord subProc = mLruProcesses.get(i);if (subProc.info.uid == uid) {if (mLruProcesses.get(i - 1).info.uid != uid) {// 交换进程位置ProcessRecord tmp = mLruProcesses.get(i);mLruProcesses.set(i, mLruProcesses.get(i - 1));mLruProcesses.set(i - 1, tmp);i--;}} else {break; // 如果UID不同,停止交换}}} else {mLruProcesses.add(app); // 直接添加进程到LRU列表}nextIndex = mLruProcessServiceStart; // 设置下一个索引} else if (hasService) {// 如果进程有service,添加到LRU列表的activity起始位置mLruProcesses.add(mLruProcessActivityStart, app);nextIndex = mLruProcessServiceStart;mLruProcessActivityStart++;} else {// 处理没有activity和service的进程int index = mLruProcessServiceStart;if (client != null) {int clientIndex = mLruProcesses.lastIndexOf(client);if (clientIndex <= lrui) {clientIndex = lrui; // 确保clientIndex不小于lrui}if (clientIndex >= 0 && index > clientIndex) {index = clientIndex; // 更新索引}}mLruProcesses.add(index, app); // 在指定索引添加进程nextIndex = index - 1; // 更新下一个索引mLruProcessActivityStart++;mLruProcessServiceStart++;}// 更新与进程连接的服务的LRU状态for (int j = app.connections.size() - 1; j >= 0; j--) {ConnectionRecord cr = app.connections.valueAt(j);if (cr.binding != null && !cr.serviceDead && cr.binding.service != null&& cr.binding.service.app != null&& cr.binding.service.app.lruSeq != mLruSeq&& !cr.binding.service.app.persistent) {nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex,"service connection", cr, app);}}// 更新与进程相关的内容提供者的LRU状态for (int j = app.conProviders.size() - 1; j >= 0; j--) {ContentProviderRecord cpr = app.conProviders.get(j).provider;if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq && !cpr.proc.persistent) {nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex,"provider reference", cpr, app);}}}
updateLruProcessLocked方法的主要目的是更新特定进程在LRU列表中的位置,基于进程的活动状态(如是否有活动或服务)。如果进程有活动(Activity),则不会移动该进程在LRU列表中的位置,因为活动进程应该被优先保留。如果进程没有活动,但有服务连接或内容提供者引用,该方法会调整这些进程在LRU列表中的位置,以反映它们的使用情况。通过mLruSeq序列号控制更新,确保只有在进程状态发生变化时才进行更新,减少不必要的列表操作。
在updateLruProcessLocked方法中,调整的具体是mLruProcesses这个关键变量。mLruProcesses是一个列表,它存储了系统中所有进程的最近最少使用(LRU)顺序。以下是对这个方法中涉及的关键变量和操作的分析:
- mLruProcesses:这是一个列表,包含了系统中所有进程的ProcessRecord对象,按照它们的最近使用情况排序。列表的前端(即索引较小的位置)存放最近使用的进程,而列表的后端(即索引较大的位置)存放最少使用的进程。
- app.lastActivityTime:每个ProcessRecord对象中的lastActivityTime字段被更新为当前时间,这表示进程最后一次活跃的时间。这个字段用于确定进程在LRU列表中的位置。
- mLruProcessActivityStart和mLruProcessServiceStart:这两个变量分别标记了LRU列表中活动进程和服务进程的起始索引。它们用于区分不同类型的进程,并在添加进程到LRU列表时确定正确的位置。
- mLruSeq:mLruSeq是一个序列号,每次调用updateLruProcessLocked方法时增加,用于标识LRU列表的更新周期。
在方法中执行的操作包括:
- 更新进程的最后活动时间:通过设置app.lastActivityTime为当前时间来更新。
- 检查进程是否有活动:如果进程有活动(hasActivity),并且没有活动变化(!activityChange),则不进行进一步操作。
- 移除和添加进程:如果进程在LRU列表中的位置发生变化,它会从当前位置移除,并根据其活动状态被重新添加到LRU列表的适当位置。
- 更新服务和内容提供者的LRU状态:对于与进程连接的服务和内容提供者,它们在LRU列表中的位置也可能需要更新,这是通过调用updateLruProcessInternalLocked方法实现的。
总结来说,updateLruProcessLocked方法通过直接操作mLruProcesses列表和相关索引变量来管理进程的LRU顺序,确保列表反映了进程的最新使用情况。这种方法有助于系统在内存不足时决定哪些进程可以被优先杀死。
这里最后在处理service和ContentProcider时调用了updateLruProcessInternalLocked方法,它的代码实现如下所示:
//AMSprivate int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,String what, Object obj, ProcessRecord srcApp) {// 更新进程的最后活动时间app.lastActivityTime = now;// 如果进程有活动,不更新其在LRU列表中的位置if (app.activities.size() > 0) {return index; // 不处理包含活动的进程}// 查找进程在LRU列表中的索引int lrui = mLruProcesses.lastIndexOf(app);// 如果进程不在LRU列表中,记录错误并返回if (lrui < 0) {return index;}// 如果进程在LRU列表中的位置已经小于或等于目标索引,不更新位置if (lrui >= index) {return index; // 不使依赖进程在列表中向后移动}// 如果进程在活动进程的起始索引之后,不更新其位置if (lrui >= mLruProcessActivityStart) {return index; // 不处理包含活动的进程}// 从当前位置移除进程mLruProcesses.remove(lrui);// 如果目标索引大于0,将其减1if (index > 0) {index--;}// 在目标索引位置添加进程mLruProcesses.add(index, app);// 返回更新后的目标索引return index;}
updateLruProcessInternalLocked方法用于在LRU列表中移动依赖进程(即那些没有直接用户交互,但被其他进程依赖的进程)。如果依赖进程没有活动,并且不在活动进程的起始索引之后,该方法会将这些进程向前移动,以确保它们不会被错误地视为最近最少使用的进程。该方法确保依赖进程不会移动到活动进程之后,从而避免降低它们的优先级。如果依赖进程不在LRU列表中,该方法会记录错误并保持列表不变。
总体来说,updateLruProcessLocked负责处理进程的直接状态变化,而updateLruProcessInternalLocked负责处理由这些变化引起的依赖进程的间接影响。通过精确控制进程在LRU列表中的位置,这两个方法帮助系统优化内存管理,确保在内存不足时能够优先杀死那些最近最少使用的进程,同时保护前台进程和关键后台进程。
3.2 updateOomAdjLocked分析
updateOomAdjLocked的代码较长,这里分成2个部分进行解读。
3.2.1 updateOomAdjLocked前半段代码解读
updateOomAdjLocked前半段代码实现如下所示:
final void updateOomAdjLocked() {// 获取当前前台活动的Activity和对应的进程记录final ActivityRecord TOP_ACT = resumedAppLocked();final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;// 获取当前时间,用于计算时间差final long now = SystemClock.uptimeMillis();// 计算30分钟前的时间,用于判断进程是否为空final long oldTime = now - ProcessList.MAX_EMPTY_TIME;// 获取LRU列表中的进程数量final int N = mLruProcesses.size();// 增加OOM调整值的序列号,用于标识调整周期mAdjSeq++;// 初始化服务进程的数量mNewNumServiceProcs = 0;mNewNumAServiceProcs = 0;// 根据系统配置计算空进程和缓存进程的最大数量final int emptyProcessLimit;final int cachedProcessLimit;if (mProcessLimit <= 0) {emptyProcessLimit = cachedProcessLimit = 0;} else if (mProcessLimit == 1) {emptyProcessLimit = 1;cachedProcessLimit = 0;} else {emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit);cachedProcessLimit = mProcessLimit - emptyProcessLimit;}// 计算缓存进程的调整值步长int numSlots = (ProcessList.CACHED_APP_MAX_ADJ- ProcessList.CACHED_APP_MIN_ADJ + 1) / 2;// 计算空进程的数量int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;if (numEmptyProcs > cachedProcessLimit) {numEmptyProcs = cachedProcessLimit;}// 计算空进程和缓存进程的调整值步长因子int emptyFactor = numEmptyProcs / numSlots;if (emptyFactor < 1) emptyFactor = 1;int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1) / numSlots;if (cachedFactor < 1) cachedFactor = 1;// 初始化步长计数器int stepCached = 0;int stepEmpty = 0;// 初始化缓存和空进程的数量计数器int numCached = 0;int numEmpty = 0;int numTrimming = 0;// 初始化非缓存进程和缓存隐藏进程的数量计数器mNumNonCachedProcs = 0;mNumCachedHiddenProcs = 0;// 初始化缓存和空进程的OOM调整值int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;int nextCachedAdj = curCachedAdj + 1;int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ;int nextEmptyAdj = curEmptyAdj + 2;// 遍历LRU列表,更新每个进程的OOM调整值for (int i = N - 1; i >= 0; i--) {ProcessRecord app = mLruProcesses.get(i);// 如果进程没有被杀死并且有线程,更新其OOM调整值if (!app.killedByAm && app.thread != null) {app.procStateChanged = false;// 计算进程的OOM调整值computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {// 根据进程状态调整OOM值switch (app.curProcState) {case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:app.curRawAdj = curCachedAdj;app.curAdj = app.modifyRawOomAdj(curCachedAdj);if (curCachedAdj != nextCachedAdj) {stepCached++;if (stepCached >= cachedFactor) {stepCached = 0;curCachedAdj = nextCachedAdj;nextCachedAdj += 2;if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}break;default:app.curRawAdj = curEmptyAdj;app.curAdj = app.modifyRawOomAdj(curEmptyAdj);if (curEmptyAdj != nextEmptyAdj) {stepEmpty++;if (stepEmpty >= emptyFactor) {stepEmpty = 0;curEmptyAdj = nextEmptyAdj;nextEmptyAdj += 2;if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;}}}break;}}// 应用OOM调整值applyOomAdjLocked(app, TOP_APP, true, now);// 根据进程类型计数switch (app.curProcState) {case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:mNumCachedHiddenProcs++;numCached++;if (numCached > cachedProcessLimit) {app.kill("cached #" + numCached, true);}break;case ActivityManager.PROCESS_STATE_CACHED_EMPTY:if (numEmpty > ProcessList.TRIM_EMPTY_APPS&& app.lastActivityTime < oldTime) {app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) / 1000) + "s", true);} else {numEmpty++;if (numEmpty > emptyProcessLimit) {app.kill("empty #" + numEmpty, true);}}break;default:mNumNonCachedProcs++;break;}// 杀死孤立的进程if (app.isolated && app.services.size() <= 0) {app.kill("isolated not needed", true);}// 计算需要调整的进程数量if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME&& !app.killedByAm) {numTrimming++;}}}//...}
updateOomAdjLocked方法的前半部分主要负责初始化OOM调整值的计算环境,包括确定进程的OOM调整值序列、计算空进程和缓存进程的限制数量、以及遍历LRU列表更新每个进程的OOM调整值。这一部分还涉及到根据进程的活动状态(如前台活动、服务状态等)来调整其在LRU列表中的位置,并决定是否需要杀死超出内存限制的进程。
同时我们这里关注两个关键方法:computeOomAdjLocked和applyOomAdjLocked:
@1 computeOomAdjLocked方法
computeOomAdjLocked方法用于计算进程的OOM调整值,在android的各个版本中该方法差异较大,但核心功能和设计目的不变,核心功能和设计目的解读如下:
- 动态调整OOM值:
computeOomAdjLocked
方法根据进程的活动状态(如前台活动、服务状态等)来计算其OOM值。这个值决定了进程在系统内存不足时被回收的可能性,值越高表示进程越不可能被杀死。 - 进程优先级管理:方法通过评估进程的重要性来调整其优先级,确保在内存紧张时,前台进程和关键后台进程能够获得更多的保护,而后台或不活跃的进程则更容易被牺牲以释放内存。
- 响应系统状态变化:当Android四大组件(Activity、Service、Broadcast Receiver、Content Provider)的状态发生变化时,该方法会被调用来更新相应进程的OOM值,以反映这些变化对进程优先级的影响。
- 优化内存使用:设计目的是在保证用户体验的同时,合理利用系统资源。通过对进程OOM值的动态管理,系统能够在内存紧张时优先保留用户最可能需要的进程,同时释放那些对用户体验影响较小的进程所占用的内存。
- 系统稳定性和响应性:通过合理分配内存资源,
computeOomAdjLocked
方法有助于提高系统的稳定性和响应性。它确保了在内存不足的情况下,系统能够做出合理的决策,避免因内存不足导致的系统崩溃或应用异常。
computeOomAdjLocked方法是Android系统中一个重要的内存管理机制,它通过动态调整进程的OOM值来优化内存使用,保护关键进程,并在内存紧张时做出合理的内存回收决策。
@2 applyOomAdjLocked方法
applyOomAdjLocked方法主要根据进程的状态和重要性,动态调整其OOM值,以优化系统内存管理。applyOomAdjLocked方法的核心功能和设计目的:
- 其核心功能是将之前通过computeOomAdjLocked方法计算得出的OOM值应用到各个进程上,以此来调整进程的内存优先级。
- 设计目的在于确保在系统内存紧张时,能够根据进程的重要性和当前状态,合理地决定哪些进程应该被优先保留,哪些可以被牺牲以释放内存资源。
通过这种方式,系统能够在保证用户体验的同时,有效管理内存资源,避免因内存不足导致的系统不稳定或崩溃。
3.2.2 updateOomAdjLocked后半段代码解读
updateOomAdjLocked后半段代码实现如下所示:
final void updateOomAdjLocked() {//...// 更新服务进程数量mNumServiceProcs = mNewNumServiceProcs;// 计算缓存和空进程的总数final int numCachedAndEmpty = numCached + numEmpty;// 根据缓存和空进程的数量确定内存因子,用于调整进程的内存使用int memFactor;if (numCached <= ProcessList.TRIM_CACHED_APPS&& numEmpty <= ProcessList.TRIM_EMPTY_APPS) {if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) {memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL;} else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) {memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW;} else {memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE;}} else {memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL;}// 如果新的内存因子大于上次的内存因子,且不允许降低内存级别或进程数量没有变化,则保持上次的内存因子if (memFactor > mLastMemoryLevel) {if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) {memFactor = mLastMemoryLevel;}}// 更新上次的内存因子和进程数量mLastMemoryLevel = memFactor;mLastNumProcesses = mLruProcesses.size();// 根据内存因子更新进程的状态boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !isSleeping(), now);final int trackerMemFactor = mProcessStats.getMemFactorLocked();// 如果内存因子不是正常级别,则根据内存状况对进程进行内存修剪if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) {// 记录低内存开始时间if (mLowRamStartTime == 0) {mLowRamStartTime = now;}// 根据内存因子确定修剪级别int fgTrimLevel;switch (memFactor) {case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;break;case ProcessStats.ADJ_MEM_FACTOR_LOW:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW;break;default:fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE;break;}// 计算修剪步长int factor = numTrimming / 3;int minFactor = 2;if (mHomeProcess != null) minFactor++;if (mPreviousProcess != null) minFactor++;if (factor < minFactor) factor = minFactor;// 遍历所有进程,根据修剪级别进行内存修剪int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE;for (int i = N - 1; i >= 0; i--) {ProcessRecord app = mLruProcesses.get(i);if (allChanged || app.procStateChanged) {setProcessTrackerStateLocked(app, trackerMemFactor, now);app.procStateChanged = false;}// 对于HOME和PREVIOUS进程,以及需要修剪的进程,进行内存修剪if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME&& !app.killedByAm) {if (app.trimMemoryLevel < curLevel && app.thread != null) {try {app.thread.scheduleTrimMemory(curLevel);} catch (RemoteException e) {}}app.trimMemoryLevel = curLevel;// 更新修剪级别step++;if (step >= factor) {step = 0;switch (curLevel) {case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:curLevel = ComponentCallbacks2.TRIM_MEMORY_MODERATE;break;case ComponentCallbacks2.TRIM_MEMORY_MODERATE:curLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;break;}}} else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {// 对于重量级进程,进行背景内存修剪if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND&& app.thread != null) {try {app.thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_BACKGROUND);} catch (RemoteException e) {}}app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;} else {// 对于其他进程,根据需要进行内存修剪if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND|| app.systemNoUi) && app.pendingUiClean) {final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN;if (app.trimMemoryLevel < level && app.thread != null) {try {app.thread.scheduleTrimMemory(level);} catch (RemoteException e) {}}app.pendingUiClean = false;}if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) {try {app.thread.scheduleTrimMemory(fgTrimLevel);} catch (RemoteException e) {}}app.trimMemoryLevel = fgTrimLevel;}}} else {// 如果内存因子是正常级别,则重置低内存开始时间if (mLowRamStartTime != 0) {mLowRamTimeSinceLastIdle += now - mLowRamStartTime;mLowRamStartTime = 0;}for (int i = N - 1; i >= 0; i--) {ProcessRecord app = mLruProcesses.get(i);if (allChanged || app.procStateChanged) {setProcessTrackerStateLocked(app, trackerMemFactor, now);app.procStateChanged = false;}// 对于需要清理UI的进程,进行UI隐藏内存修剪if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND|| app.systemNoUi) && app.pendingUiClean) {if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN&& app.thread != null) {try {app.thread.scheduleTrimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);} catch (RemoteException e) {}}app.pendingUiClean = false;}app.trimMemoryLevel = 0;}}// 如果设置了总是结束活动的选项,则安排销毁所有活动if (mAlwaysFinishActivities) {mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish");}// 如果所有进程的状态都发生了变化,则请求所有进程的PSS(Proportional Set Size)数据if (allChanged) {requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());}// 如果进程统计数据应该被写入,则异步写入if (mProcessStats.shouldWriteNowLocked(now)) {mHandler.post(new Runnable() {@Override public void run() {synchronized (ActivityManagerService.this) {mProcessStats.writeStateAsyncLocked();}}});}}
后半部分则专注于根据当前的内存使用情况来调整进程的内存使用级别,即内存因子(memFactor)。它通过计算得出当前的内存因子,并根据该因子对进程进行内存修剪,以释放内存资源。这一部分还涉及到更新进程的内存修剪级别,并在必要时触发进程的内存修剪操作,以响应系统的内存压力。
在updateOomAdjLocked
方法中,根据前半段和后半段的代码,针对oom_adj关键变量的调整主要集中在以下几个方面,解读如下:
- OOM调整值(
curAdj
):每个ProcessRecord
对象中的curAdj
字段被调整,这个字段代表了进程的当前OOM调整值,它直接影响到进程在内存不足时被杀死的优先级。 - 进程状态(
curProcState
):ProcessRecord
中的curProcState
字段被更新,反映了进程的当前状态,如前台、后台、服务等,这有助于确定进程的OOM调整值。 - 内存因子(
memFactor
):memFactor
变量根据系统的内存状况和进程的数量被计算出来,用于决定整体的内存调整策略。 - 服务进程计数(
mNewNumServiceProcs
和mNumServiceProcs
):mNewNumServiceProcs
用于临时计数服务进程的数量,而mNumServiceProcs
是最终的服务进程数量,这些计数用于内存调整决策。 - 非缓存进程和缓存隐藏进程计数(
mNumNonCachedProcs
和mNumCachedHiddenProcs
):这些变量用于计数非缓存进程和缓存隐藏进程的数量,它们在计算OOM调整值时起到重要作用。 - 进程跟踪器状态(
setProcessTrackerStateLocked
):该方法被调用来更新进程的内存修剪级别,这影响了进程如何响应系统的内存压力。 - 内存修剪级别(
trimMemoryLevel
):trimMemoryLevel
字段在ProcessRecord
中被设置,指示进程应该采取的内存修剪行动。 - 低内存开始时间(
mLowRamStartTime
):记录系统进入低内存状态的时间,用于跟踪和管理低内存期间的内存调整策略。
这些关键变量的调整确保了Android系统能够根据当前的内存状况和进程的活动状态,动态地管理内存资源,优化用户体验,并在内存不足时做出合理的进程杀死决策。通过这种方式,系统能够在保证前台和关键进程的稳定性的同时,合理回收后台进程的内存资源。
updateOomAdjLocked方法整体上是Android系统中一个关键的内存管理机制,它确保了在内存资源有限的情况下,系统能够根据进程的重要性和活动状态动态调整其OOM调整值和内存使用级别。通过这种方法,系统能够在内存不足时优先保护前台和关键进程,同时合理地回收后台进程的内存资源,从而优化整体的系统性能和用户体验。这个方法体现了Android系统在面对不同内存压力时的响应策略,包括进程的OOM调整、内存修剪和进程状态的更新,这些都是为了在保证系统稳定性的同时,尽可能地提高内存使用效率。
相关文章:
Android Framework AMS(16)进程管理
该系列文章总纲链接:专题总纲目录 Android Framework 总纲 本章关键点总结 & 说明: 说明:本章节主要解读AMS 进程方面的知识。关注思维导图中左上侧部分即可。 我们本章节主要是对Android进程管理相关知识有一个基本的了解。先来了解下L…...
STM32设计防丢防摔智能行李箱
目录 目录 前言 一、本设计主要实现哪些很“开门”功能? 二、电路设计原理图 1.电路图采用Altium Designer进行设计: 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着科技的不断发展,嵌入式系统、物联网技术、智能设备…...
【异常解决】Linux shell报错:-bash: [: ==: 期待一元表达式 解决方法
博主介绍:✌全网粉丝21W,CSDN博客专家、Java领域优质创作者,掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围:SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...
ML 系列: 第 23 节 — 离散概率分布 (多项式分布)
目录 一、说明 二、多项式分布公式 2.1 多项式分布的解释 2.2 示例 2.3 特殊情况:二项分布 2.4 期望值 (Mean) 2.5 方差 三、总结 3.1 python示例 一、说明 伯努利分布对这样一种情况进行建模:随机变量可以采用两个可能的值&#…...
Webpack 1.13.2 执行 shell 命令解决 打印时没有背景色和文字颜色的问题
这是因为 Webpack 1.13.2 不支持新的插件钩子 API。Webpack 1 的插件系统使用的是 plugin 方法,而不是 Webpack 4 中的 hooks。 在 Webpack 1 中,你可以使用以下代码来确保 sed 命令在打包完成后执行: const { exec } require(child_proce…...
C++构造函数详解
构造函数详解:C 中对象初始化与构造函数的使用 在 C 中,构造函数是一种特殊的成员函数,它在创建对象时自动调用,用来初始化对象的状态。构造函数帮助我们确保每个对象在被创建时就处于一个有效的状态,并且在不传递任何…...
POI实现根据PPTX模板渲染PPT
目录 1、前言 2、了解pptx文件结构 3、POI组件 3.1、引入依赖 3.2、常见的类 3.3、实现原理 3.4、关键代码片段 3.4.1、获取ppt实例 3.4.2、获取每页幻灯片 3.4.3、循环遍历幻灯片处理 3.4.3.1、文本 3.4.3.2、饼图 3.4.3.3、柱状图 3.4.3.4、表格 3.4.3.5、本地…...
【论文模型复现】深度学习、地质流体识别、交叉学科融合?什么情况,让我们来看看
文献:蓝茜茜,张逸伦,康志宏.基于深度学习的复杂储层流体性质测井识别——以车排子油田某井区为例[J].科学技术与工程,2020,20(29):11923-11930. 本文目录 一、前言二、文献阅读-基于深度学习的复杂储层流体性质测井识别2.1 摘要2.2 当前研究不足2.3 本文创新2.4 论文…...
树的直径计算:算法详解与实现
树的直径计算:算法详解与实现 1. 引言2. 算法概述3. 伪代码实现4. C语言实现5. 算法分析6. 结论在图论中,树的直径是一个关键概念,它表示树中任意两点间最长路径的长度。对于给定的树T=(V,E),其中V是顶点集,E是边集,树的直径定义为所有顶点对(u,v)之间最短路径的最大值。…...
conda创建 、查看、 激活、删除 python 虚拟环境
1、创建 python 虚拟环境 ,假设该环境命名为 “name”。 conda create -n name python3.11 2、查看 python 虚拟环境。 conda info -e 3、激活使用 python 虚拟环境。 conda activate name 4、删除 python 虚拟环境 conda remove -n name --all 助力快速掌握数据集…...
vs2022搭建opencv开发环境
1 下载OpenCV库 https://opencv.org/ 下载对应版本然后进行安装 将bin目录添加到系统环境变量opencv\build\x64\vc16\bin 复制该路径 打开高级设置添加环境变量 vs2022新建一个空项目 修改属性添加头文件路径和库路径 修改链接器,将OpenCV中lib库里的o…...
NVIDIA NIM 开发者指南:入门
NVIDIA NIM 开发者指南:入门 NVIDIA 开发者计划 想要了解有关 NIM 的更多信息?加入 NVIDIA 开发者计划,即可免费访问任何基础设施云、数据中心或个人工作站上最多 16 个 GPU 上的自托管 NVIDIA NIM 和微服务。 加入免费的 NVIDIA 开发者计…...
探索Python网络请求新纪元:httpx库的崛起
文章目录 **探索Python网络请求新纪元:httpx库的崛起**第一部分:背景介绍第二部分:httpx库是什么?第三部分:如何安装httpx库?第四部分:简单的库函数使用方法1. 发送GET请求2. 发送POST请求3. 超…...
学了Arcgis的水文分析——捕捉倾泻点,河流提取与河网分级,3D图层转要素失败的解决方法,测量学综合实习网站存着
ArcGIS水文分析实战教程(7)细说流域提取_汇流域栅格-CSDN博客 ArcGIS水文分析实战教程(6)河流提取与河网分级_arcgis的dem河流分级-CSDN博客 ArcGIS水文分析实战教程(5)细说流向与流量-CSDN博客 ArcGIS …...
QQ 小程序已发布,但无法被搜索的解决方案
前言 我的 QQ 小程序在 2024 年 8 月就已经审核通过,上架后却一直无法被搜索到。打开后,再在 QQ 上下拉查看 “最近使用”,发现他出现一下又马上消失。 上线是按正常流程走的,开发、备案、审核,没有任何违规…...
【C++】拷贝构造 和 赋值运算符重载
目录: 一、拷贝构造 (一)拷贝函数的特点 二、赋值运算符重载 (一)运算符重载 (二)赋值运算符重载 正文 一、拷贝构造 如果一个构造函数的第一个参数是自身类类型的引用,且任何…...
21.UE5游戏存档,读档,函数库
2-23 游戏存档、读档、函数库_哔哩哔哩_bilibili 目录 1.存档蓝图 2.函数库 2.1保存存档 2.2读取存档: 3.加载游戏,保存游戏 3.1游戏实例对象 3.2 加载游戏 3.3保存游戏 这一节的内容较为错综复杂,中间没有运行程序进行阶段性成果的验…...
「Mac玩转仓颉内测版14」PTA刷题篇5 - L1-005 考试座位号
本篇将继续讲解PTA平台上的题目 L1-005 考试座位号,通过考生准考证号与座位号的对应关系,掌握简单的数据查询与映射操作,进一步提升Cangjie编程语言的实际应用能力。 关键词 PTA刷题数据查询映射操作输入输出Cangjie语言 一、L1-005 考试座位…...
Vue3引用高德地图,进行位置标记获取标记信息
首先安装地图插件 cnpm i amap/amap-jsapi-loader --save封装地图子组件 <template><el-dialogtitle"选择地点"width"740px"class"select-map-dialog"v-model"dialogShow":close-on-click-modal"false":modal-or…...
《C++设计模式:重塑游戏角色系统类结构的秘籍》
在游戏开发领域,游戏角色系统的类结构设计至关重要。一个良好的类结构可以使游戏更易于扩展、维护和优化,而 C中的设计模式为我们提供了强大的工具来实现这一目标。 一、理解游戏角色系统的复杂性 游戏角色系统通常具有高度的复杂性。每个角色都有自己…...
深入浅出 Go 语言:现代编程的高效选择
深入浅出 Go 语言:现代编程的高效选择 引言 Go 语言(也称 Golang)是由 Google 开发的一种现代编程语言,面向高效、简单和并发。自 2009 年问世以来,它已迅速成长为许多企业和开发者首选的语言,尤其是在后端开发、云计算和微服务领域。 本文旨在从 Go 语言的设计哲学、…...
RDIFramework.NET CS敏捷开发框架 V6.1发布(.NET6+、Framework双引擎、全网唯一)
RDIFramework.NET C/S敏捷开发框架V6.1版本迎来重大更新与调整,全面重新设计业务逻辑代码,代码量减少一半以上,开发更加高效。全系统引入全新字体图标,整个界面焕然一新。底层引入最易上手的ORM框架SqlSugar,让开发更加…...
vue路由的钩子函数?
在 Vue 中,路由的钩子函数可以用来在导航过程中执行一些操作,比如进行权限验证、页面加载前后的处理等。常用的路由钩子函数包括全局前置守卫、全局解析守卫、全局后置钩子以及路由独享守卫。下面是这些路由守卫函数的简要说明: 全局前置守卫…...
【Java】枚举类映射
在数据库中常用数字来代替字符串类型,编写一个枚举映射类 当数据库的介质类型要存储数字,前端可以任意传参,通过枚举转换后端都会转成数字对应类型 import lombok.Getter;/*** <p>* 存档介质类型* </p>** author Jyang* date 2…...
精华帖分享|浅谈金融时间序列分析与股价随机游走
本文来源于量化小论坛公共讨论区板块精华帖,作者为正扬,发布于2024年6月3日。 以下为精华帖正文: 01 引 时间序列分析是个很唬人的术语,实际上它也不是一个很容易接近的话题。我本科曾经短暂地学过一点点,又看到互联…...
任意文件下载漏洞
1.漏洞简介 任意文件下载漏洞是指攻击者能够通过操控请求参数,下载服务器上未经授权的文件。 攻击者可以利用该漏洞访问敏感文件,如配置文件、日志文件等,甚至可以下载包含恶意代码的文件。 这里再导入一个基础: 你要在网站下…...
LeetCode 445.两数相加 II
题目: 给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 你可以假设除了数字 0 之外,这两个数字都不会以零开头。 思路:反转链表 两数相加 I 代码&…...
CentOS 7中查找已安装JDK路径的方法
使用yum安装了jdk8,但是其他中间件需要配置路径的时候,却没办法找到,如何获取jdk路径: 一、确认服务器是否存在jdk java -version 二、查找jdk的 java 命令在哪里 which java 三、找到软链指向的地址 ls -lrt /usr/bin/java l…...
springboot基于Web足球青训俱乐部管理后台系统开发(代码+数据库+LW)
摘 要 随着社会经济的快速发展,人们对足球俱乐部的需求日益增加,加快了足球健身俱乐部的发展,足球俱乐部管理工作日益繁忙,传统的管理方式已经无法满足足球俱乐部管理需求,因此,为了提高足球俱乐部管理效率…...
RHCE的学习(21)
第三章 Shell条件测试 用途 为了能够正确处理Shell程序运行过程中遇到的各种情况,Linux Shell提供了一组测试运算符。 通过这些运算符,Shell程序能够判断某种或者几个条件是否成立。 条件测试在各种流程控制语句,例如判断语句和循环语句中…...
麻涌网站建设制作多少钱/人工智能培训班
假设我想加载谷歌,然后自动在搜索栏中输入文本并按下输入所有没有用户输入,我该怎么办?编辑:虽然我确实需要将其指向特定的URL,但我希望python能够输入文本而不管URL.这就是为什么我需要它能够在谷歌搜索中输入文本,而不是只加载在谷歌搜索时会出现的URL…...
一流高职院校建设计划项目网站/怎么做公司网站
传送门: 柏链项目学院 3. 增加数据库和客户端 之前我们的区块链已经产生了,但是程序终止后,数据就丢失了!为了下次启动后能够继续,我们需要引入一个小型数据库。这一次,我们选择与短跑世界记录保持者同名的数据库-bolt…...
网站设计ps做效果图过程/网站如何被搜索引擎收录
Frameworks.Entity.Core\Commons\Predicate\ 1LinqEntity.cs /// IQueryable扩展方法:条件过滤与排序功能 /// Modify Date:2012-10-8 /// Modify Memo:添加了动态排序功能 2 条件创建者(AND,OR,NOT) PredicateB…...
wordpress 评论 姓名/下载优化大师
fopen fopen(打开文件) 相关函数 open,fclose 表头文件 #include<stdio.h> 定义函数 FILE * fopen(const char * path,const char * mode); 函数说明 參数path字符串包括欲打开的文件路径及文件名称,參数mode字符串则代表着…...
网站的外部推广/信息推广平台
一种多接口ModBus协议的处理办法ModBus 协议是工控行业中十分常用的通信协议,在嵌入式系统中,可能面临多个串口、网口都用到ModBus 协议解析的情况,下面介绍本人使用的方法。刚刚接触此协议的时候,编写的代码很简单,可…...
大连做网站电话/网页搜索引擎
把公司的win7升级为了win10,但是打开应用商店时是白的,什么都没有,过一会之后,会有提示信息。 报的错误是: 错误代码:0X80072ee2 最后,我的解决办法是: 首先,咱们返回到w…...