Android系统的进程管理(创建->优先级->回收)
一、进程的创建
1、概述
Android系统以Linux内核为基础,所以对于进程的管理自然离不开Linux本身提供的机制。例如:
- 通过fork来创建进行
- 通过信号量来管理进程
- 通过proc文件系统来查询和调整进程状态 等
对于Android来说,进程管理的主要内容包括以下几个部分内容:
- 进程的创建
- 进程的优先级管理
- 进程的内存管理
- 进程的回收和死亡处理
本文会专门讲解进程的创建,其余部分将在后面的文章中讲解。
2、主要模块
为了便于下文的讲解,这里先介绍一下Android系统中牵涉到进程创建的几个主要模块。
同时为了便于读者更详细的了解这些模块,这里也同时提供了这些模块的代码路径。
这里提到的代码路径是指AOSP的源码数中的路径。
关于如何获取AOSP源码请参见这里:Downloading the Source。
本文以Android N版本的代码为示例,所用到的Source Code Tags是:android-7.0.0_r1。
相关模块:
1. app_process
代码路径:frameworks/base/cmds/app_process
说明:app_process是一个可执行程序,该程序的主要作用是启动zygote和system_server进程。
2. Zygote
代码路径:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
说明:zygote进程是所有应用进程的父进程,这是系统中一个非常重要的进程,下文我们会详细讲解。
3. ActivityManager
代码路径:frameworks/base/services/core/java/com/android/server/am/
说明:am是ActivityManager的缩写。
这个目录下的代码负责了Android全部四大组件(Activity,Service,ContentProvider,BroadcastReceiver)的管理,并且还掌控了所有应用程序进程的创建和进程的优先级管理。
因此,这个部分的内容将是本系列文章讲解的重点。
3、进程与线程
Android官方开发网站的这篇文章:Processes and Threads 非常好的介绍了Android系统中进程相关的一些基本概念和重要知识。
在阅读下文之前,请务必将这篇文章浏览一遍。
4、关于进程
在Android系统中,进程可以大致分为系统进程和应用进程两大类。
系统进程是系统内置的(例如:init,zygote,system_server进程),属于操作系统必不可少的一部分。系统进程的作用在于:
- 管理硬件设备
- 提供访问设备的基本能力
- 管理应用进程
应用进程是指应用程序运行的进程。这些应用程序可能是系统出厂自带的(例如Launcher,电话,短信等应用),也可能是用户自己安装的(例如:微信,支付宝等)。
系统进程的数量通常是固定的(出厂或者系统升级之后就确定了),并且系统进程通常是一直存活,常驻内存的。系统进程的异常退出将可能导致设备无法正常使用。
而应用程序和应用进程在每个人使用的设备上通常是各不一样的。如何管理好这些不确定的应用进程,就是操作系统本身要仔细考虑的内容。也是衡量一个操作系统好坏的标准之一。
在本文中,我们会介绍init,zygote和system_server三个系统进程。
除此之外,本系列文章将会把主要精力集中在讲解Android系统如何管理应用进程上。
5、init进程
init进程是一切的开始,在Android系统中,所有进程的进程号都是不确定的,唯独init进程的进程号一定是1。
因为这个进程一定是系统起来的第一个进程。
并且,init进程掌控了整个系统的启动逻辑。
我们知道,Android可能运行在各种不同的平台,不同的设备上。因此,启动的逻辑是不尽相同的。 为了适应各种平台和设备的需求,init进程的初始化工作通过init.rc配置文件来管理。
你可以在AOSP源码的system/core/rootdir/路径找到这些配置文件。
配置文件的主入口文件是init.rc,这个文件会通过import引入其他几个文件。
在本文中,我们统称这些文件为init.rc。
init.rc通过Android Init Language来进行配置。 建议读者大致阅读一下其 语法说明 。
init.rc中配置了系统启动的时候该做哪些事情,以及启动哪些系统进程。
这其中有两个特别重要的进程就是:zygote和system_server进程。
- zygote的中文意思是“受精卵“。这是一个很有寓意的名称:所有的应用进程都是由zygote fork出来的子进程,因此zygote进程是所有应用进程的父进程。
- system_server 这个进程正如其名称一样,这是一个系统服务器。Framework层的几乎所有服务都位于这个进程中。这其中就包括管理四大组件的ActivityManagerService。
6、Zygote进程
init.rc文件会根据平台不一样,选择下面几个文件中的一个来启动zygote进程:
- init.zygote32.rc
- init.zygote32_64.rc
- init.zygote64.rc
- init.zygote64_32.rc
这几个文件的内容是大致一致的,仅仅是为了不同平台服务的。这里我们以init.zygote32.rc的文件为例,来看看其中的内容:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 660 root system onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart netd writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
在这段配置文件中(如果你不明白这段配置的含义,请阅读一下文档:Android Init Language),启动了一个名称叫做zygote的服务进程。这个进程是通过/system/bin/app_process 这个可执行程序创建的。
并且在启动这个可执行程序的时候,传递了-Xzygote /system/bin --zygote --start-system-server class main 这些参数。
要知道这里到底做了什么,我们需要看一下app_process的源码。
app_process的源码在这个路径:frameworks/base/cmds/app_process/app_main.cpp。
这个文件的main函数的有如下代码:
int main(int argc, char* const argv[]) { ...while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {zygote = true;niceName = ZYGOTE_NICE_NAME;} else if (strcmp(arg, "--start-system-server") == 0) {startSystemServer = true;...}...if (!className.isEmpty()) {...} else {...if (startSystemServer) {args.add(String8("start-system-server"));}} ...if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (className) {runtime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");return 10;} }
这里会判断,
- 如果执行这个命令时带了--zygote参数,就会通过runtime.start启动com.android.internal.os.ZygoteInit。
- 如果参数中带有--start-system-server参数,就会将start-system-server添加到args中。
这段代码是C++实现的。在执行这段代码的时候还没有任何Java的环境。而runtime.start就是启动Java虚拟机,并在虚拟机中启动指定的类。于是接下来的逻辑就在ZygoteInit.java中了。
这个文件的main函数主要代码如下::
public static void main(String argv[]) {...try {...boolean startSystemServer = false;String socketName = "zygote";String abiList = null;for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {startSystemServer = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {...}}...registerZygoteSocket(socketName);...preload();...Zygote.nativeUnmountStorageOnInit();ZygoteHooks.stopZygoteNoThreadCreation();if (startSystemServer) {startSystemServer(abiList, socketName);}Log.i(TAG, "Accepting command socket connections");runSelectLoop(abiList);closeServerSocket();} catch (MethodAndArgsCaller caller) {caller.run();} catch (RuntimeException ex) {Log.e(TAG, "Zygote died with exception", ex);closeServerSocket();throw ex;} }
在这段代码中,我们主要关注如下几行:
- 通过 registerZygoteSocket(socketName); 注册Zygote Socket
- 通过 preload(); 预先加载所有应用都需要的公共资源
- 通过 startSystemServer(abiList, socketName); 启动system_server
- 通过 runSelectLoop(abiList); 在Looper上等待连接
这里需要说明的是:zygote进程启动之后,会启动一个socket套接字,并通过Looper一直在这个套接字上等待连接。
所有应用进程都是通过发送数据到这个套接字上,然后由zygote进程创建的。
这里还有一点说明的是: 在Zygote进程中,会通过preload函数加载需要应用程序都需要的公共资源。
预先加载这些公共资源有如下两个好处:
- 加快应用的启动速度 因为这些资源已经在zygote进程启动的时候加载好了
- 通过共享的方式节省内存 这是Linux本身提供的机制:父进程已经加载的内容可以在子进程中进行共享,而不用多份数据拷贝(除非子进程对这些数据进行了修改。)
preload的资源主要是Framework相关的一些基础类和Resource资源,而这些资源正是所有应用都需要的:
开发者通过Android SDK开发应用所调用的API实现都在Framework中。
static void preload() {Log.d(TAG, "begin preload");Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning");beginIcuCachePinning();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses");preloadClasses();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources");preloadResources();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL");preloadOpenGL();Trace.traceEnd(Trace.TRACE_TAG_DALVIK);preloadSharedLibraries();preloadTextResources();WebViewFactory.prepareWebViewInZygote();endIcuCachePinning();warmUpJcaProviders();Log.d(TAG, "end preload"); }
7、system_server进程
上文已经提到,zygote进程起来之后会根据需要启动system_server进程。
system_server进程中包含了大量的系统服务。例如:
- 负责网络管理的NetworkManagementService
- 负责窗口管理的WindowManagerService
- 负责震动管理的VibratorService
- 负责输入管理的InputManagerService
等等。关于system_server,我们今后会其他的文章中专门讲解,这里不做过多说明。
在本文中,我们只关注system_server中的ActivityManagerService这个系统服务。
8、ActivityManagerService
上文中提到:zygote进程在启动之后会启动一个socket,然后一直在这个socket等待连接。
而会连接它的就是ActivityManagerService。
因为ActivityManagerService掌控了所有应用进程的创建。
所有应用程序的进程都是由ActivityManagerService通过socket发送请求给Zygote进程,然后由zygote fork创建的。
ActivityManagerService通过Process.start方法来请求zygote创建进程:
public static final ProcessStartResult start(final String processClass,final String niceName,int uid, int gid, int[] gids,int debugFlags, int mountExternal,int targetSdkVersion,String seInfo,String abi,String instructionSet,String appDataDir,String[] zygoteArgs) {try {return startViaZygote(processClass, niceName, uid, gid, gids,debugFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, zygoteArgs);} catch (ZygoteStartFailedEx ex) {Log.e(LOG_TAG,"Starting VM process through Zygote failed");throw new RuntimeException("Starting VM process through Zygote failed", ex);} }
这个函数会将启动进程所需要的参数组装好,并通过socket发送给zygote进程。然后zygote进程根据发送过来的参数将进程fork出来。
在ActivityManagerService中,调用Process.start的地方是下面这个方法:
private final void startProcessLocked(ProcessRecord app, String hostingType,String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {...Process.ProcessStartResult startResult = Process.start(entryPoint,app.processName, uid, uid, gids, debugFlags, mountExternal,app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,app.info.dataDir, entryPointArgs); ... }
下文中我们会看到,所有四大组件进程的创建,都是调用这里的startProcessLocked这个方法而创建的。
对于每一个应用进程,在ActivityManagerService中,都有一个ProcessRecord与之对应。这个对象记录了应用进程的所有详细状态。
PS:对于ProcessRecord的内部结构,在下一篇文章中,我们会讲解。
为了查找方便,对于每个ProcessRecord会存在下面两个集合中。
- 按名称和uid组织的集合:
/** * All of the applications we currently have running organized by name. * The keys are strings of the application package name (as * returned by the package manager), and the keys are ApplicationRecord * objects. */ final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
- 按pid组织的集合:
/** * All of the processes we currently have running organized by pid. * The keys are the pid running the application. * * <p>NOTE: This object is protected by its own lock, NOT the global * activity manager lock! */ final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();
下面这幅图小节了上文的这些内容:
9、关于应用组件
Processes and Threads 提到:
“当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。”
因此,四大组件中的任何一个先起来都会导致应用进程的创建。下文我们就详细看一下,它们启动时,各自是如何导致应用进程的创建的。
PS:四大组件的管理本身又是一个比较大的话题,限于篇幅关系,这里不会非常深入的讲解,这里主要是讲解四大组件与进程创建的关系。
在应用程序中,开发者通过:
- startActivity(Intent intent) 来启动Activity
- startService(Intent service) 来启动Service
- sendBroadcast(Intent intent) 来发送广播
- ContentResolver 中的接口来使用ContentProvider
这其中,startActivity,startService和sendBroadcast还有一些重载方法。
其实这里提到的所有这些方法,最终都是通过Binder调用到ActivityManagerService中,由其进行处理的。
这里特别说明一下:应用进程和ActivityManagerService所在进程(即system_server进程)是相互独立的,两个进程之间的方法通常是不能直接互相调用的。
而Android系统中,专门提供了Binder框架来提供进程间通讯和方法调用的能力。
调用关系如下图所示:
10、Activity与进程创建
在ActivityManagerService中,对每一个运行中的Activity都有一个ActivityRecord对象与之对应,这个对象记录Activity的详细状态。
ActivityManagerService中的startActivity方法接受Context.startActivity的请求,该方法代码如下:
@Override public final int startActivity(IApplicationThread caller, String callingPackage,Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode,int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,resultWho, requestCode, startFlags, profilerInfo, bOptions,UserHandle.getCallingUserId()); }
Activity的启动是一个非常复杂的过程。这里我们简单介绍一下背景知识:
- ActivityManagerService中通过Stack和Task来管理Activity
- 每一个Activity都属于一个Task,一个Task可能包含多个Activity。一个Stack包含多个Task
- ActivityStackSupervisor类负责管理所有的Stack
- Activity的启动过程会牵涉到:
-
- Intent的解析
- Stack,Task的查询或创建
- Activity进程的创建
- Activity窗口的创建
- Activity的生命周期调度
Activity的管理结构如下图所示:
在Activity启动的最后,会将前一个Activity pause,将新启动的Activity resume以便被用户看到。
在这个时候,如果发现新启动的Activity进程还没有启动,则会通过startSpecificActivityLocked将其启动。整个调用流程如下:
- ActivityManagerService.activityPaused =>
- ActivityStack.activityPausedLocked =>
- ActivityStack.completePauseLocked =>
- ActivityStackSupervisor.ensureActivitiesVisibleLocked =>
- ActivityStack.makeVisibleAndRestartIfNeeded =>
- ActivityStackSupervisor.startSpecificActivityLocked =>
- ActivityManagerService.startProcessLocked
ActivityStackSupervisor.startSpecificActivityLocked 关键代码如下:
void startSpecificActivityLocked(ActivityRecord r,boolean andResume, boolean checkConfig) {// Is this activity's application already running?ProcessRecord app = mService.getProcessRecordLocked(r.processName,r.info.applicationInfo.uid, true);r.task.stack.setLaunchTime(r);if (app != null && app.thread != null) {...}mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,"activity", r.intent.getComponent(), false, false, true); }
这里的ProcessRecord app 描述了Activity所在进程。
11、Service与进程创建
Service的启动相对于Activity来说要简单一些。
在ActivityManagerService中,对每一个运行中的Service都有一个ServiceRecord对象与之对应,这个对象记录Service的详细状态。
ActivityManagerService中的startService方法处理Context.startServiceAPI的请求,相关代码:
@Override public ComponentName startService(IApplicationThread caller, Intent service,String resolvedType, String callingPackage, int userId)throws TransactionTooLargeException {...synchronized(this) {final int callingPid = Binder.getCallingPid();final int callingUid = Binder.getCallingUid();final long origId = Binder.clearCallingIdentity();ComponentName res = mServices.startServiceLocked(caller, service,resolvedType, callingPid, callingUid, callingPackage, userId);Binder.restoreCallingIdentity(origId);return res;} }
这段代码中的mServices对象是ActiveServices类型的,这个类专门负责管理活动的Service。
启动Service的调用流程如下:
- ActivityManagerService.startService =>
- ActiveServices.startServiceLocked =>
- ActiveServices.startServiceInnerLocked =>
- ActiveServices.bringUpServiceLocked =>
- ActivityManagerService.startProcessLocked
ActiveServices.bringUpServiceLocked会判断如果Service所在进程还没有启动,
则通过ActivityManagerService.startProcessLocked将其启动。相关代码如下:
// Not running -- get it started, and enqueue this service record // to be executed when the app comes up. if (app == null && !permissionsReviewRequired) {if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags,"service", r.name, false, isolated, false)) == null) {String msg = "Unable to launch app "+ r.appInfo.packageName + "/"+ r.appInfo.uid + " for service "+ r.intent.getIntent() + ": process is bad";Slog.w(TAG, msg);bringDownServiceLocked(r);return msg;}if (isolated) {r.isolatedProc = app;} }
这里的mAm 就是ActivityManagerService。
12、Provider与进程创建
在ActivityManagerService中,对每一个运行中的ContentProvider都有一个ContentProviderRecord对象与之对应,这个对象记录ContentProvider的详细状态。
开发者通过ContentResolver中的insert, delete, update, query这些API来使用ContentProvider。在ContentResolver的实现中,无论使用这里的哪个接口,ContentResolver都会先通过acquireProvider 这个方法来获取到一个类型为IContentProvider的远程接口。这个远程接口对接了ContentProvider的实现提供方。
同一个ContentProvider可能同时被多个模块使用,而调用ContentResolver接口的进程只是ContentProvider的一个客户端而已,真正的ContentProvider提供方是运行自身的进程中的,两个进程的通讯需要通过Binder的远程接口形式来调用。如下图所示:
ContentResolver.acquireProvider 最终会调用到ActivityManagerService.getContentProvider中,该方法代码如下:
@Override public final ContentProviderHolder getContentProvider(IApplicationThread caller, String name, int userId, boolean stable) {enforceNotIsolatedCaller("getContentProvider");if (caller == null) {String msg = "null IApplicationThread when getting content provider "+ name;Slog.w(TAG, msg);throw new SecurityException(msg);}// The incoming user check is now handled in checkContentProviderPermissionLocked() to deal// with cross-user grant.return getContentProviderImpl(caller, name, null, stable, userId); }
而在getContentProviderImpl这个方法中,会判断对应的ContentProvider进程有没有启动,
如果没有,则通过startProcessLocked方法将其启动。
13、Receiver与进程创建
开发者通过Context.sendBroadcast接口来发送广播。ActivityManagerService.broadcastIntent 方法了对应广播发送的处理。
广播是一种一对多的消息形式,广播接受者的数量是不确定的。因此发送广播本身可能是一个很耗时的过程(因为要逐个通知)。
在ActivityManagerService内部,是通过队列的形式来管理广播的:
- BroadcastQueue 描述了一个广播队列
- BroadcastRecord 描述了一个广播事件
在ActivityManagerService中,如果收到了一个发送广播的请求,会先创建一个BroadcastRecord接着将其放入BroadcastQueue中。
然后通知队列自己去处理这个广播。然后ActivityManagerService自己就可以继续处理其他请求了。
广播队列本身是在另外一个线程处理广播的发送的,这样保证的ActivityManagerService主线程的负载不会太重。
在BroadcastQueue.processNextBroadcast(boolean fromMsg) 方法中真正实现了通知广播事件到接受者的逻辑。在这个方法,如果发现接受者(即BrodcastReceiver)还没有启动,便会通过ActivityManagerService.startProcessLocked 方法将其启动。相关如下所示:
final void processNextBroadcast(boolean fromMsg) {...// Hard case: need to instantiate the receiver, possibly// starting its application process to host it.ResolveInfo info =(ResolveInfo)nextReceiver;ComponentName component = new ComponentName(info.activityInfo.applicationInfo.packageName,info.activityInfo.name);...// Not running -- get it started, to be executed when the app comes up.if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST,"Need to start app ["+ mQueueName + "] " + targetProcess + " for broadcast " + r);if ((r.curApp=mService.startProcessLocked(targetProcess,info.activityInfo.applicationInfo, true,r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND,"broadcast", r.curComponent,(r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false))== null) {// Ah, this recipient is unavailable. Finish it if necessary,// and mark the broadcast record as ready for the next.Slog.w(TAG, "Unable to launch app "+ info.activityInfo.applicationInfo.packageName + "/"+ info.activityInfo.applicationInfo.uid + " for broadcast "+ r.intent + ": process is bad");logBroadcastReceiverDiscardLocked(r);finishReceiverLocked(r, r.resultCode, r.resultData,r.resultExtras, r.resultAbort, false);scheduleBroadcastsLocked();r.state = BroadcastRecord.IDLE;return;}mPendingBroadcast = r;mPendingBroadcastRecvIndex = recIdx;} }
至此,四大组件的启动就已经分析完了。
二、进程的优先级
1、前言
进程的优先级反应了系统对于进程重要性的判定。
在Android系统中,进程的优先级影响着以下三个因素:
- 当内存紧张时,系统对于进程的回收策略
- 系统对于进程的CPU调度策略
- 虚拟机对于进程的内存分配和垃圾回收策略
本文会主要讲解系统对于进程优先级的判断依据和计算方法。
在Processes and Threads (如果你还没有阅读,请立即阅读一下这篇文章)一文中,我们已经了解到,系统对于进程的优先级有如下五个分类:
- 前台进程
- 可见进程
- 服务进程
- 后台进程
- 空进程
实际上这只是一个粗略的划分。在系统的内部实现中,优先级远不止这么五种。
2、优先级的依据
我们来简单列一下应用组件与进程的相关信息:
- 每一个Android的应用进程中,都可能包含四大组件中的一个/种或者多个/种。
- 对于运行中的Service和ContentProvider来说,可能有若干个客户端进程正在对其使用。
- 应用进程是由ActivityManagerService发送请求让zygote创建的,并且ActivityManagerService中对于每一个运行中的进程都有一个ProcessRecord对象与之对应。
ProcessRecord简化图如下所示:
在ProcessRecord中,详细记录了应用组件的相关信息,相关代码如下:
// all activities running in the process final ArrayList<ActivityRecord> activities = new ArrayList<>(); // all ServiceRecord running in this process final ArraySet<ServiceRecord> services = new ArraySet<>(); // services that are currently executing code (need to remain foreground). final ArraySet<ServiceRecord> executingServices = new ArraySet<>(); // All ConnectionRecord this process holds final ArraySet<ConnectionRecord> connections = new ArraySet<>(); // all IIntentReceivers that are registered from this process. final ArraySet<ReceiverList> receivers = new ArraySet<>(); // class (String) -> ContentProviderRecord final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>(); // All ContentProviderRecord process is using final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();
这里的:
- activities 记录了进程中运行的Activity
- services,executingServices 记录了进程中运行的Service
- receivers 记录了进程中运行的BroadcastReceiver
- pubProviders 记录了进程中运行的ContentProvider
而:
- connections 记录了对于Service连接
- conProviders 记录了对于ContentProvider的连接
连接就是对于客户端使用状态的记录,对于Service和ContentProvider是类似的,每有一个客户端就需要记录一个连接。连接的意义在于:连接的客户端的进程优先级会影响被使用的Service和ContentProvider所在进程的优先级。 例如:当一个后台的Service正在被一个前台的Activity使用,那么这个后台的Service就需要设置一个较高的优先级以便不会被回收。(否则后台Service进程一旦被回收,便会对前台的Activity造成影响。)
而所有这些组件的状态就是其所在进程优先级的决定性因素。 组件的状态是指:
- Activity是否在前台,用户是否可见
- Service正在被哪些客户端使用
- ContentProvider正在被哪些客户端使用
- BroadcastReceiver是否正在接受广播
3、优先级的基础
3.1 oom_score_adj
对于每一个运行中的进程,Linux内核都通过proc文件系统暴露这样一个文件来允许其他程序修改指定进程的优先级:
/proc/[pid]/oom_score_adj。(修改这个文件需要root权限)
这个文件允许的值的范围是:-1000 ~ +1000之间。值越小,表示进程越重要。
当内存非常紧张时,系统便会遍历所有进程,以确定哪个进程需要被杀死以回收内存,此时便会读取oom_score_adj 这个文件的值。关于这个值的使用,在后面讲解进程回收的的时候,我们会详细讲解。
PS:在Linux 2.6.36之前的版本中,Linux 提供调整优先级的文件是/proc/[pid]/oom_adj。这个文件允许的值的范围是-17 ~ +15之间。数值越小表示进程越重要。 这个文件在新版的Linux中已经废弃。
但你仍然可以使用这个文件,当你修改这个文件的时候,内核会直接进行换算,将结果反映到oom_score_adj这个文件上。
Android早期版本的实现中也是依赖oom_adj这个文件。但是在新版本中,已经切换到使用oom_score_adj这个文件。
ProcessRecord中下面这些属性反应了oom_score_adj的值:
int maxAdj; // Maximum OOM adjustment for this process int curRawAdj; // Current OOM unlimited adjustment for this process int setRawAdj; // Last set OOM unlimited adjustment for this process int curAdj; // Current OOM adjustment for this process int setAdj; // Last set OOM adjustment for this process
maxAdj 指定了该进程允许的oom_score_adj最大值。这个属性主要是给系统应用和常驻内存的进程使用,这些进程的优先级的计算方法与应用进程的计算方法不一样,通过设定maxAdj保证这些进程一直拥有较高的优先级(在后面”优先级的算法“中,我们会看到对于这个属性的使用)。
除此之外,还有四个属性。
这其中,curXXX这一组记录了这一次优先级计算的结果。在计算完成之后,会将curXXX复制给对应的setXXX这一组上进行备份。 (下文的其他属性也会看到curXXX和setXXX的形式,和这里的原理是一样的。)
另外,xxxRawAdj记录了没有经过限制的adj值,“没有经过限制”是指这其中的值可能是超过了oom_score_adj文件所允许的范围(-1000 ~ 1000)。
为了便于管理,ProcessList.java中预定义了oom_score_adj的可能取值。
其实这里的预定义值也是对应用进程的一种分类,它们是:
static final int UNKNOWN_ADJ = 1001; // 未知进程 static final int PREVIOUS_APP_ADJ = 700; // 前一个应用 static final int HOME_APP_ADJ = 600; // 桌面进程 static final int SERVICE_ADJ = 500; // 包含了Service的进程 static final int HEAVY_WEIGHT_APP_ADJ = 400; // 重量级进程 static final int BACKUP_APP_ADJ = 300; // 备份应用进程 static final int PERCEPTIBLE_APP_ADJ = 200; // 可感知的进程 static final int VISIBLE_APP_ADJ = 100; // 可见进程 static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1; static final int FOREGROUND_APP_ADJ = 0; // 前台进程 static final int PERSISTENT_SERVICE_ADJ = -700; // 常驻服务进程 static final int PERSISTENT_PROC_ADJ = -800; // 常驻应用进程 static final int SYSTEM_ADJ = -900; // 系统进程 static final int NATIVE_ADJ = -1000; // native系统进程
这里我们看到,FOREGROUND_APP_ADJ = 0,这个是前台应用进程的优先级。这是用户正在交互的应用,它们是很重要的,系统不应当把它们回收了。
FOREGROUND_APP_ADJ = 0是普通应用程序能够获取到的最高优先级。
而VISIBLE_APP_ADJ,PERCEPTIBLE_APP_ADJ,PREVIOUS_APP_ADJ这几个级别的优先级就逐步降低了。
VISIBLE_APP_ADJ是具有可见Activity进程的优先级:同一时刻,不一定只有一个Activity是可见的,如果前台Activity设置了透明属性,那么背后的Activity也是可见的。
PERCEPTIBLE_APP_ADJ是指用户可感知的进程,可感知的进程包括:
- 进程中包含了处于pause状态或者正在pause的Activity
- 进程中包含了正在stop的Activity
- 进程中包含了前台的Service
另外,PREVIOUS_APP_ADJ描述的是前一个应用的优先级。所谓“前一个应用”是指:在启动新的Activity时,如果新启动的Activity是属于一个新的进程的,那么当前即将被stop的Activity所在的进程便会成为“前一个应用”进程。
而HEAVY_WEIGHT_APP_ADJ 描述的重量级进程是指那些通过Manifest指明不能保存状态的应用进程。
除此之外,Android系统中,有一些系统应用会常驻内存,这些应用通常是系统实现的一部分,如果它们不存在,系统将处于比较奇怪的状态,例如SystemUI(状态栏,Keyguard都处于这个应用中)。
所以它们的优先级比所有应用进程的优先级更高:PERSISTENT_SERVICE_ADJ = -700,PERSISTENT_PROC_ADJ = -800。
另外,还有一些系统服务的实现,如果这些系统服务不存在,系统将无法工作,所以这些应用的优先级最高,几乎是任何任何时候都需要存在的:SYSTEM_ADJ = -900,NATIVE_ADJ = -1000。
3.2 Schedule Group
内核负责了进程的CPU调度,所有运行中的进程并非能平等的能获取相等的时间片。在ProcessRecord中,通过Schedule Group来记录进程的调度组:
int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class
它们可能的取值定义在ProcessList.java中:
// Activity manager's version of Process.THREAD_GROUP_BG_NONINTERACTIVE static final int SCHED_GROUP_BACKGROUND = 0; // Activity manager's version of Process.THREAD_GROUP_DEFAULT static final int SCHED_GROUP_DEFAULT = 1; // Activity manager's version of Process.THREAD_GROUP_TOP_APP static final int SCHED_GROUP_TOP_APP = 2; // Activity manager's version of Process.THREAD_GROUP_TOP_APP // Disambiguate between actual top app and processes bound to the top app static final int SCHED_GROUP_TOP_APP_BOUND = 3;
3.3 Process State
进程的状态会影响虚拟机对于进程的内存分配和垃圾回收策略,ProcessRecord中的下面这几个属性记录了进程的状态:
int curProcState; // Currently computed process state int repProcState; // Last reported process state int setProcState; // Last set process state in process tracker int pssProcState; // Currently requesting pss for
这些属性可能的取值定义在ActivityManager中,这些常量的名称已经说明了其作用:
public static final int PROCESS_STATE_NONEXISTENT = -1;public static final int PROCESS_STATE_PERSISTENT = 0;public static final int PROCESS_STATE_PERSISTENT_UI = 1;public static final int PROCESS_STATE_TOP = 2;public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3;public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;public static final int PROCESS_STATE_TOP_SLEEPING = 5;public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;public static final int PROCESS_STATE_BACKUP = 8;public static final int PROCESS_STATE_HEAVY_WEIGHT = 9;public static final int PROCESS_STATE_SERVICE = 10;public static final int PROCESS_STATE_RECEIVER = 11;public static final int PROCESS_STATE_HOME = 12;public static final int PROCESS_STATE_LAST_ACTIVITY = 13;public static final int PROCESS_STATE_CACHED_ACTIVITY = 14;public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15;public static final int PROCESS_STATE_CACHED_EMPTY = 16;
4、优先级的更新
前文已经提到,系统会对处于不同状态的进程设置不同的优先级。但实际上,进程的状态是一直在变化中的。例如:用户可以随时会启动一个新的Activity,或者将一个前台的Activity切换到后台。在这个时候,发生状态变化的Activity的所在进程的优先级就需要进行更新。
并且,Activity可能会使用其他的Service或者ContentProvider。当Activity的进程优先级发生变化的时候,它所使用的Service或者ContentProvider的优先级也应当发生变化。
ActivityManagerService中有如下两个方法用来更新进程的优先级:
- final boolean updateOomAdjLocked(ProcessRecord app)
- final void updateOomAdjLocked()
第一个方法是针对指定的单个进程更新优先级。第二个是对所有进程更新优先级。
在下面的这些情况下,需要对指定的应用进程更新优先级:
- 当有一个新的进程开始使用本进程中的ContentProvider
- 当本进程中的一个Service被其他进程bind或者unbind
- 当本进程中的Service的执行完成或者退出了
- 当本进程中一个BroadcastReceiver正在接受广播
- 当本进程中的BackUpAgent启动或者退出了
final boolean updateOomAdjLocked(ProcessRecord app) 被调用的关系如下图所示:
在有些情况下,系统需要对所有应用进程的优先级进行更新,譬如:
- 当有一个新的进程启动时
- 当有一个进程退出时
- 当系统在清理后台进程时
- 当有一个进程被标记为前台进程时
- 当有一个进程进入或者退出cached状态时
- 当系统锁屏或者解锁时
- 当有一个Activity启动或者退出时
- 当系统正在处理一个广播事件时
- 当前台Activity发生改变时
- 当有一个Service启动时
final void updateOomAdjLocked() 被调用的关系图如下所示:
5、优先级的算法
ActivityManagerService中的computeOomAdjLocked方法负责计算进程的优先级,这个方法总计约700行,执行流程主要包含如下10个步骤:
下面我们来详细看其中的每一个步骤:
- 1.确认该进程是否是空进程空进程中没有任何组件,因此主线程也为null(ProcessRecord.thread描述了应用进程的主线程)。如果是空进程,则不需要再做后面的计算了。直接设置为ProcessList.CACHED_APP_MAX_ADJ级别即可。
if (app.thread == null) {app.adjSeq = mAdjSeq;app.curSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ); }
- 2.确认是否设置了maxAdj上文已经提到过,系统进程或者Persistent进程会通过设置maxAdj来保持其较高的优先级,对于这类进程不用按照普通进程的算法进行计算,直接按照maxAdj的值设置即可。
if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {app.adjType = "fixed";app.adjSeq = mAdjSeq;app.curRawAdj = app.maxAdj;app.foregroundActivities = false;app.curSchedGroup = ProcessList.SCHED_GROUP_DEFAULT;app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT;app.systemNoUi = true;if (app == TOP_APP) {app.systemNoUi = false;app.curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP;app.adjType = "pers-top-activity";} else if (activitiesSize > 0) {for (int j = 0; j < activitiesSize; j++) {final ActivityRecord r = app.activities.get(j);if (r.visible) {app.systemNoUi = false;}}}if (!app.systemNoUi) {app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI;}return (app.curAdj=app.maxAdj);}
- 3.确认进程中是否有前台优先级的组件前台优先级的组件是指:a.前台的Activity; b.正在接受广播的Receiver; c.正在执行任务的Service;注:除此之外,还有Instrumentation被认为是具有较高优先级的。Instrumentation应用是辅助测试用的,正常运行的系统中不用考虑这种应用。假设进程中包含了以上提到的前台优先级的任何一个组件,则直接设置进程优先级为FOREGROUND_APP_ADJ即可。因为这已经是应用程序能够获取的最高优先级了。
int adj;int schedGroup;int procState;boolean foregroundActivities = false;BroadcastQueue queue;if (app == TOP_APP) {adj = ProcessList.FOREGROUND_APP_ADJ;schedGroup = ProcessList.SCHED_GROUP_TOP_APP;app.adjType = "top-activity";foregroundActivities = true;procState = PROCESS_STATE_CUR_TOP;} else if (app.instrumentationClass != null) {adj = ProcessList.FOREGROUND_APP_ADJ;schedGroup = ProcessList.SCHED_GROUP_DEFAULT;app.adjType = "instrumentation";procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;} else if ((queue = isReceivingBroadcast(app)) != null) {adj = ProcessList.FOREGROUND_APP_ADJ;schedGroup = (queue == mFgBroadcastQueue)? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;app.adjType = "broadcast";procState = ActivityManager.PROCESS_STATE_RECEIVER;} else if (app.executingServices.size() > 0) {adj = ProcessList.FOREGROUND_APP_ADJ;schedGroup = app.execServicesFg ?ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;app.adjType = "exec-service";procState = ActivityManager.PROCESS_STATE_SERVICE;} else {schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;adj = cachedAdj;procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;app.cached = true;app.empty = true;app.adjType = "cch-empty";}
- 4.确认进程中是否有较高优先级的Activity这里需要遍历进程中的所有Activity,找出其中优先级最高的设置为进程的优先级。即便Activity不是前台Activity,但是处于下面这些状态的Activity优先级也是被认为是较高优先级的:
-
- 该Activity处于可见状态
- 该Activity处于Pause正在Pause状态
- 该Activity正在stop
if (!foregroundActivities && activitiesSize > 0) {int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX;for (int j = 0; j < activitiesSize; j++) {final ActivityRecord r = app.activities.get(j);if (r.app != app) {Log.e(TAG, "Found activity " + r + " in proc activity list using " + r.app+ " instead of expected " + app);if (r.app == null || (r.app.uid == app.uid)) {// Only fix things up when they look saner.app = app;} else {continue;}}if (r.visible) {// App has a visible activity; only upgrade adjustment.if (adj > ProcessList.VISIBLE_APP_ADJ) {adj = ProcessList.VISIBLE_APP_ADJ;app.adjType = "visible";}if (procState > PROCESS_STATE_CUR_TOP) {procState = PROCESS_STATE_CUR_TOP;}schedGroup = ProcessList.SCHED_GROUP_DEFAULT;app.cached = false;app.empty = false;foregroundActivities = true;if (r.task != null && minLayer > 0) {final int layer = r.task.mLayerRank;if (layer >= 0 && minLayer > layer) {minLayer = layer;}}break;} else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) {if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {adj = ProcessList.PERCEPTIBLE_APP_ADJ;app.adjType = "pausing";}if (procState > PROCESS_STATE_CUR_TOP) {procState = PROCESS_STATE_CUR_TOP;}schedGroup = ProcessList.SCHED_GROUP_DEFAULT;app.cached = false;app.empty = false;foregroundActivities = true;} else if (r.state == ActivityState.STOPPING) {if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {adj = ProcessList.PERCEPTIBLE_APP_ADJ;app.adjType = "stopping";}if (!r.finishing) {if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;}}app.cached = false;app.empty = false;foregroundActivities = true;} else {if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) {procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;app.adjType = "cch-act";}}}if (adj == ProcessList.VISIBLE_APP_ADJ) {adj += minLayer;} }
- 5.确认进程中是否有前台Service通过startForeground启动的Service被认为是前台Service。给予这类进程PERCEPTIBLE_APP_ADJ级别的优先级。
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {if (app.foregroundServices) {// The user is aware of this app, so make it visible.adj = ProcessList.PERCEPTIBLE_APP_ADJ;procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;app.cached = false;app.adjType = "fg-service";schedGroup = ProcessList.SCHED_GROUP_DEFAULT;} else if (app.forcingToForeground != null) {// The user is aware of this app, so make it visible.adj = ProcessList.PERCEPTIBLE_APP_ADJ;procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;app.cached = false;app.adjType = "force-fg";app.adjSource = app.forcingToForeground;schedGroup = ProcessList.SCHED_GROUP_DEFAULT;} }
- 6.确认是否是特殊类型进程特殊类型的进程包括:重量级进程,桌面进程,前一个应用进程,正在执行备份的进程。 “重量级进程”和“前一个应用”进程在上文中已经说过了。而桌面就是指Android上的Launcher。
if (app == mHeavyWeightProcess) {if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;app.cached = false;app.adjType = "heavy";}if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;} }if (app == mHomeProcess) {if (adj > ProcessList.HOME_APP_ADJ) {adj = ProcessList.HOME_APP_ADJ;schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;app.cached = false;app.adjType = "home";}if (procState > ActivityManager.PROCESS_STATE_HOME) {procState = ActivityManager.PROCESS_STATE_HOME;} }if (app == mPreviousProcess && app.activities.size() > 0) {if (adj > ProcessList.PREVIOUS_APP_ADJ) {adj = ProcessList.PREVIOUS_APP_ADJ;schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;app.cached = false;app.adjType = "previous";}if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) {procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY;} }if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj+ " reason=" + app.adjType);app.adjSeq = mAdjSeq; app.curRawAdj = adj; app.hasStartedServices = false;if (mBackupTarget != null && app == mBackupTarget.app) {if (adj > ProcessList.BACKUP_APP_ADJ) {if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);adj = ProcessList.BACKUP_APP_ADJ;if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;}app.adjType = "backup";app.cached = false;}if (procState > ActivityManager.PROCESS_STATE_BACKUP) {procState = ActivityManager.PROCESS_STATE_BACKUP;} }
- 7.根据所有Service的客户端计算优先级这里需要遍历所有的Service,并且还需要遍历每一个Service的所有连接。然后根据连接的关系确认客户端进程的优先级来确定当前进程的优先级。ConnectionRecord.binding.client即为客户端进程ProcessRecord,由此便可以知道客户端进程的优先级。
for (int is = app.services.size()-1;is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND|| procState > ActivityManager.PROCESS_STATE_TOP);is--) {ServiceRecord s = app.services.valueAt(is);if (s.startRequested) {app.hasStartedServices = true;if (procState > ActivityManager.PROCESS_STATE_SERVICE) {procState = ActivityManager.PROCESS_STATE_SERVICE;}if (app.hasShownUi && app != mHomeProcess) {if (adj > ProcessList.SERVICE_ADJ) {app.adjType = "cch-started-ui-services";}} else {if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) {if (adj > ProcessList.SERVICE_ADJ) {adj = ProcessList.SERVICE_ADJ;app.adjType = "started-services";app.cached = false;}}if (adj > ProcessList.SERVICE_ADJ) {app.adjType = "cch-started-services";}}}for (int conni = s.connections.size()-1;conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND|| procState > ActivityManager.PROCESS_STATE_TOP);conni--) {ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);for (int i = 0;i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND|| procState > ActivityManager.PROCESS_STATE_TOP);
- 8.根据所有Provider的客户端确认优先级这里与Service类似,需要遍历所有的Provider,以及每一个Provider的所有连接。然后根据连接的关系确认客户端进程的优先级来确定当前进程的优先级。类似的,ContentProviderConnection.client为客户端进程的ProcessRecord。
for (int provi = app.pubProviders.size()-1;provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND|| procState > ActivityManager.PROCESS_STATE_TOP);provi--) {ContentProviderRecord cpr = app.pubProviders.valueAt(provi);for (int i = cpr.connections.size()-1;i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ|| schedGroup == ProcessList.SCHED_GROUP_BACKGROUND|| procState > ActivityManager.PROCESS_STATE_TOP);i--) {ContentProviderConnection conn = cpr.connections.get(i);ProcessRecord client = conn.client;if (client == app) {// Being our own client is not interesting.continue;}int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now);...
- 9.收尾工作 收尾工作主要是根据进程中的Service,Provider的一些特殊状态做一些处理,另外还有针对空进程以及设置了maxAdj的进程做一些处理,这里就不贴出代码了。
这里想专门说明一下的是,在这一步还会对Service进程做ServiceB的区分。系统将Service进程分为ServiceA和ServiceB。ServiceA是相对来说较新的Service,而ServiceB相对来说是比较“老旧”的,对用户来说可能是不那么感兴趣的,因此ServiceB的优先级会相对低一些。
static final int SERVICE_B_ADJ = 800; static final int SERVICE_ADJ = 500;
而ServiceB的标准是:app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); 即:所有Service进程的前1/3为ServiceA,剩下为ServiceB。
if (adj == ProcessList.SERVICE_ADJ) {if (doingAll) {app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);mNewNumServiceProcs++;if (!app.serviceb) {if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL&& app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {app.serviceHighRam = true;app.serviceb = true;} else {mNewNumAServiceProcs++;}} else {app.serviceHighRam = false;}}if (app.serviceb) {adj = ProcessList.SERVICE_B_ADJ;} }app.curRawAdj = adj;
- 10.保存结果 最终需要把本次的计算结果保存到ProcessRecord中:
app.curAdj = app.modifyRawOomAdj(adj); app.curSchedGroup = schedGroup; app.curProcState = procState; app.foregroundActivities = foregroundActivities;
6、优先级的生效
优先级的生效是指:将计算出来的优先级真正应用到系统中,applyOomAdjLocked 方法负责了此项工作。
前文中我们提到,优先级意味着三个方面,这里的生效就对应了这三个方面:
- ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj); 将计算出来的adj值写入到procfs中,即:/proc/[pid]/oom_score_adj 这个文件中。
- Process.setProcessGroup(app.pid, processGroup); 用来设置进程的调度组。
- app.thread.setProcessState(app.repProcState); 这个方法会最终调用到 VMRuntime.getRuntime().updateProcessState();将进程的状态设置到虚拟机中。
7、结束语
前言中我们提到,“优先级反应了系统对于进程重要性的判定。”
那么,系统如何评价进程的优先级,便是系统本身一个很重要的特性。了解系统的这一特性对于我们开发应用程序,以及对于应用程序运行的行为分析是很有意义的。
系统在判定优先级的时候,应当做到公平公正,并且不能让开发者有机可乘。
“公平公正”是指系统需要站在一个中间人的状态下,不偏倚任何一个应用,公正的将系统资源分配给真正需要的进程。并且在系统资源紧张的时候,回收不重要的进程。
通过上文的分析,我们看到,Android系统认为“重要”的进程主要有三类:
- 系统进程
- 前台与用户交互的进程
- 前台进程所使用到的进程
不过对于这一点是有改进的空间的,例如,可以引入用户习惯的分析:如果是用户频繁使用的应用,可以给予这些应用更高的优先级以提升这些应用的响应速度。目前,国内一些Android定制厂商已经开始做这类功能的支持。
“不能让开发者有机可乘”是指:系统对于进程优先级的判定的因素应当是不能被开发者利用的。因为一旦开发者可以利用,每个开发者都肯定会将自己的设置为高优先级,来抢占更多的资源。
需要说明的是,Android在这个方面是存在缺陷的:在Android系统上,可以通过startForeground拿到前台的优先级的。后来Google也意识到这个问题,于是在API Level 18以上的版本上,调用startForeground这个API会在通知栏显示一条通知以告知用户。但是,这个改进是有Bug的:开发者可以同时通过startForeground启动两个Service,指定同样的通知id,然后退出其中一个,这样应用的不会在通知栏显示通知图标,并且拿到了前台的优先级。这个便是让开发者“有机可乘”了。
三、内存的回收
1、前言
内存是系统中非常宝贵的资源,即便如今的移动设备上,内存已经达到4G甚至6G的级别,但对于内存的回收也依然重要,因为在Android系统上,同时运行的进程有可能会有几十甚至上百个之多。
如何将系统内存合理的分配给每个进程,以及如何进行内存回收,便是操作系统需要处理的问题之一。
本文会讲解Android系统中内存回收相关的知识。
对于内存回收,主要可以分为两个层次:
- 进程内的内存回收:通过释放进程中的资源进行内存回收
- 进程级的内存回收:通过杀死进程来进行内存回收
这其中,进程内的内存回收主要分为两个方面:
- 虚拟机自身的垃圾回收机制
- 在系统内存状态发生变化时,通知应用程序,让开发者进行内存回收
而进程级的内存回收主要是依靠系统中的两个模块,它们是:
- Linux OOM Killer
- LowMemoryKiller
在特定场景下,他们都会通过杀死进程来进行内存回收。
下图描述了几种内存回收机制:
2、Android系统的内存管理简介
在Android系统中,进程可以大致分为系统进程和应用进程两大类。
系统进程是系统内置的(例如:init,zygote,system_server进程),属于操作系统必不可少的一部分。系统进程的作用在于:
- 管理硬件设备
- 提供访问设备的基本能力
- 管理应用进程
应用进程是指应用程序运行的进程。这些应用程序可能是系统出厂自带的(例如Launcher,电话,短信等应用),也可能是用户自己安装的(例如:微信,支付宝等)。
Android中应用进程通常都运行在Java虚拟机中。在Android 5.0之前的版本,这个虚拟机是Dalvik,5.0及之后版本,Android引入了新的虚拟机,称作Android Runtime,简称“ART”。
关于ART和Dalvik可以参见这里:ART and Dalvik。无论是Dalvik还是ART,本身都具有垃圾回收的能力,关于这一点,我们在后面专门讲解。
Android的应用程序都会依赖一些公共的资源,例如:Android SDK提供的类和接口,以及Framework公开的图片,字符串等。为了达到节省内存的目的,这些资源在内存中并不是每个应用进程单独一份拷贝。而是会在所有应用之间共享,因为所有应用进程都是作为Zygote进程fork出来的子进程。关于这部分内容,我们已经在Android系统中的进程管理:进程的创建一文中讲解过。
在Java语言中,通过new创建的对象都会在堆中分配内存。应用程序堆的大小是有限的。系统会根据设备的物理内存大小来确定每个应用程序所允许使用的内存大小,一旦应用程序使用的内存超过这个大小,便会发生OutOfMemoryError。
因此开发者需要关心应用的内存使用状况。关于如何监测应用程序的内存使用,可以参见这里:Investigating Your RAM Usage。
3、开发者相关的API
下面是一些与内存相关的开发者API,它们是Android SDK的一部分。
3.1 ComponentCallbacks2
Android系统会根据当前的系统内存状态和应用的自身状态对应用进行通知。这种通知的目的是希望应用能够感知到系统和自身的状态变化,以便开发者可以更准确的把握应用的运行。
例如:在系统内存充足时,为了提升响应性能,应用可以缓存更多的资源。但是当系统内存紧张时,开发者应当释放一定的资源来缓解内存紧张的状态。
ComponentCallbacks2接口中的void onTrimMemory(int level) 回调函数用来接收这个通知。关于这一点,在“开发者的内存回收”一节,我们会详细讲解。
3.2 ActivityManager
ActivityManager,从名称中就可以看出,这个类是用来管理Activity的系统服务。但这个类中也包含了很多运行时状态查询的接口,这其中就包括与内存相关的几个:
- int getMemoryClass () 获取当前设备上,单个应用的内存大小限制,单位是M。注意,这个函数的返回值只是一个大致的值。
- void getMemoryInfo (ActivityManager.MemoryInfo outInfo) 获取系统的内存信息,具体结构可以查看ActivityManager.MemoryInfo,开发者最关心的可能就是availMem以及totalMem。
- void getMyMemoryState (ActivityManager.RunningAppProcessInfo outState) 获取调用进程的内存信息
- MemoryInfo[] getProcessMemoryInfo (int[] pids) 通过pid获取指定进程的内存信息
- boolean isLowRamDevice() 查询当前设备是否是低内存设备
3.3 Runtime
Java应用程序都会有一个Runtime接口的实例,通过这个实例可以查询运行时的一些状态,与内存相关的接口有:
- freeMemory() 获取Java虚拟机的剩余内存
- maxMemory() 获取Java虚拟机所能使用的最大内存
- totalMemory() 获取Java虚拟机拥有的最大内存
4、虚拟机的垃圾回收
垃圾回收是指:虚拟机会监测应用程序的对象创建和使用,并在一些特定的时候销毁无用的对象以回收内存。
垃圾回收的基本想法是要找出虚拟机中哪些对象已经不会再被使用然后将其释放。其最常用的算法有下面两种:
引用计数算法
引用计数算法是为每个对象维护一个被引用的次数:对象刚创建时的初始引用计数为0,每次被一个对象引用时,引用计数加1,反之减1。当一个对象的引用计数重新回到0时便可以认为是不会被使用的,这些对象便可以被垃圾回收。
读者可能马上会想到,当有两个对象互相引用时,这时引用计数该如何计算。关于这部分内容,这里不再展开讲解。有兴趣的读者可以查询Google或者维基百科:Garbage collection
4.1 对象追踪算法
对象追踪算法是通过GC root类型的对象为起点,追踪所有被这些对象所引用的对象,并顺着这些被引用的对象继续往下追踪,在追踪的过程中,对所有被追踪到的对象打上标记。
而剩下的那些没有被打过标记的对象便可以认为是没有被使用的,因此这些对象可以将其释放。
这里提到的的GC root类型的对象有四类:
- 栈中的local变量,即方法中的局部变量
- 活动的线程(例如主线程或者开发者创建的线程)
- static变量
- JNI中的引用
下面这幅图描述了这种算法:
a)表示算法开始时,所有对象的标记为false,然后以GC root为起点开始追踪和打标记,b)中被追踪到的对象打上了标记。剩下的没有打上标记的对象便可以释放了。算法结束之后,c)中将所有对象的标记全部置为false。下一轮计算时,重新以GC root开始追踪。
Dalvik虚拟机用的就是对象追踪算法,这里是其源码:MarkSweep.cpp
5、开发者的内存回收
内存回收并不是仅仅是系统的事情,作为开发者,也需要在合适的场合下进行内存释放。无节制的消耗内存将导致应用程序OutOfMemoryError。
上文中提到,虚拟机的垃圾回收会回收那些不会再被使用到的对象。因此,开发者所需要做的就是:当确定某些对象不会再被使用时,要主动释放对其引用,这样虚拟机才能将其回收。对于不再被用到对象,仍然保持对其引用导致其无法释放,将导致内存泄漏的发生。
为了更好的进行内存回收,系统会一些场景下会通知应用,希望应用能够配合进行一些内存的释放。
ComponentCallbacks2接口中的 void onTrimMemory(int level)回调就是用来接收这个事件的。
Activity, Service, ContentProvider和Application都实现了这个接口,因此这些类的子类都可以接收这个事件。
onTrimMemory回调的参数是一个级别,系统会根据应用本身的状态以及系统的内存状态发送不同的级别,具体的包括:
- 应用处于Runnig状态可能收到的级别
-
- TRIM_MEMORY_RUNNING_MODERATE 表示系统内存已经稍低
- TRIM_MEMORY_RUNNING_LOW 表示系统内存已经相当低
- TRIM_MEMORY_RUNNING_CRITICAL 表示系统内存已经非常低,你的应用程序应当考虑释放部分资源
- 应用的可见性发生变化时收到的级别
-
- TRIM_MEMORY_UI_HIDDEN 表示应用已经处于不可见状态,可以考虑释放一些与显示相关的资源
- 应用处于后台时可能收到的级别
-
- TRIM_MEMORY_BACKGROUND 表示系统内存稍低,你的应用被杀的可能性不大。但可以考虑适当释放资源
- TRIM_MEMORY_MODERATE 表示系统内存已经较低,当内存持续减少,你的应用可能会被杀死
- TRIM_MEMORY_COMPLETE 表示系统内存已经非常低,你的应用即将被杀死,请释放所有可能释放的资源
这里是这个方法实现的示例代码:Release memory in response to events
在前面的文章中我们提到过:ActivityManagerService负责管理所有的应用进程。
而这里的通知也是来自ActivityManagerService。在updateOomAdjLocked的时候,ActivityManagerService会根据系统内存以及应用的状态通过app.thread.scheduleTrimMemory发送通知给应用程序。
这里的app是ProcessRecord,即描述应用进程的对象,thread是应用的主线程。而scheduleTrimMemory是通过Binder IPC的方式将消息发送到应用进程上。这些内容在前面的文章中已经介绍过,如果觉得陌生,可以阅读一下前面两篇文章。
在ActivityThread中(这个是应用程序的主线程),接受到这个通知之后,便会遍历应用进程中所有能接受这个通知的组件,然后逐个回调通知。
相关代码如下:
final void handleTrimMemory(int level) {if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);final int N = callbacks.size();for (int i = 0; i < N; i++) {callbacks.get(i).onTrimMemory(level);}WindowManagerGlobal.getInstance().trimMemory(level); }
6、Linux OOM Killer
前面提到的机制都是在进程内部通过释放对象来进行内存回收。
而实际上,系统中运行的进程数量,以及每个进程所消耗的内存都是不确定的。
在极端的情况下,系统的内存可能处于非常严峻的状态,假设这个时候所有进程都不愿意释放内存,系统将会卡死。
为了使系统能够继续运转不至于卡死,系统会尝试杀死一些不重要的进程来进行内存回收,这其中涉及的模块主要是:Linux OOM Killer和LowMemoryKiller。
Linux OOM Killer是Linux内核的一部分,其源码可以在这里查看:/mm/oom_kill.c。
Linux OOM Killer的基本想法是:
当系统已经没法再分配内存的时候,内核会遍历所有的进程,对每个进程计算badness值,得分(badness)最高的进程将会被杀死。
即:badness得分越低表示进程越重要,反之表示不重要。
Linux OOM Killer的执行流程如下:
_alloc_pages -> out_of_memory() -> select_bad_process() -> oom_badness()
这其中,_alloc_pages 是内核在分配内存时调用的函数。当内核发现无法再分配内存时,便会计算每个进程的badness值,然后选择最大的(系统认为最不重要的)将其杀死。
那么,内核是如何计算进程的badness值的呢?请看下面的代码:
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg,const nodemask_t *nodemask, unsigned long totalpages) {long points;long adj;...points = get_mm_rss(p->mm) + p->mm->nr_ptes + get_mm_counter(p->mm, MM_SWAPENTS);task_unlock(p);if (has_capability_noaudit(p, CAP_SYS_ADMIN))points -= (points * 3) / 100;adj *= totalpages / 1000;points += adj;return points > 0 ? points : 1; }
从这段代码中,我们可以看到,影响进程badness值的因素主要有三个:
- 进程的oom_score_adj值
- 进程的内存占用大小
- 进程是否是root用户的进程
即,oom_score_adj(关于oom_score_adj,在Android系统中的进程管理:进程的优先级一文中我们专门讲解过。)值越小,进程占用的内存越小,并且如果是root用户的进程,系统就认为这个进程越重要。
反之则被认为越不重要,越容易被杀死。
7、LowMemoryKiller
OOM Killer是在系统内存使用情况非常严峻的时候才会起作用。但直到这个时候才开始杀死进程来回收内存是有点晚的。因为在进程被杀死之前,其他进程都无法再申请内存了。
因此,Google在Android上新增了一个LowMemoryKiller模块。LowMemoryKiller通常会在Linux OOM Killer工作之前,就开始杀死进程。
LowMemoryKiller的做法是:
提供6个可以设置的内存级别,当系统内存每低于一个级别时,将oom_score_adj大于某个指定值的进程全部杀死。
这么说会有些抽象,但具体看一下LowMemoryKiller的配置文件我们就好理解了。
LowMemoryKiller在sysfs上暴露了两个文件来供系统调整参数,这两个文件的路径是:
- /sys/module/lowmemorykiller/parameters/minfree
- /sys/module/lowmemorykiller/parameters/adj
如果你手上有一个Android设备,你可以通过adb shell连上去之后,通过cat命令查看这两个文件的内容。
这两个文件是配对使用的,每个文件中都是由逗号分隔的6个整数值。
在某个设备上,这两个文件的值可能分别是下面这样:
- 18432,23040,27648,32256,55296,80640
- 0,100,200,300,900,906
这组配置的含义是;当系统内存低于80640k时,将oom_score_adj值大于906的进程全部杀死;当系统内存低于55296k时,将oom_score_adj值大于900的进程全部杀死,其他类推。
LowMemoryKiller杀死进程的时候会在内核留下日志,你可以通过dmesg 命令中看到。这个日志可能是这样的:
lowmemorykiller: Killing 'gnunet-service-' (service adj 0, to free 327224kB on behalf of 'kswapd0' (21) because cache 6064kB is below limit 6144kB for oom_score_adj 0
从这个日志中,我们可以看到被杀死进程的名称,进程pid和oom_score_adj值。另外还有系统在杀死这个进程之前系统内存还剩多少,以及杀死这个进程释放了多少。
LowMemoryKiller的源码也在内核中,路径是:kernel/drivers/staging/android/lowmemorykiller.c。
lowmemorykiller.c中定义了如下几个函数:
- lowmem_shrink
- lowmem_init
- lowmem_exit
- lowmem_oom_adj_to_oom_score_adj
- lowmem_autodetect_oom_adj_values
- lowmem_adj_array_set
- lowmem_adj_array_get
- lowmem_adj_array_free
LowMemoryKiller本身是一个内核驱动程序的形式存在,lowmem_init和lowmem_exit 分别负责模块的初始化和退出清理工作。
在lowmem_init函数中,就是通过register_shrinker向内核中注册了register_shrinker 函数:
static int __init lowmem_init(void) {register_shrinker(&lowmem_shrinker);return 0; }
register_shrinker函数就是LowMemoryKiller的算法核心,这个函数的代码和说明如下:
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) {struct task_struct *tsk;struct task_struct *selected = NULL;int rem = 0;int tasksize;int i;short min_score_adj = OOM_SCORE_ADJ_MAX + 1;int minfree = 0;int selected_tasksize = 0;short selected_oom_score_adj;int array_size = ARRAY_SIZE(lowmem_adj);int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages;int other_file = global_page_state(NR_FILE_PAGES) -global_page_state(NR_SHMEM) -total_swapcache_pages();if (lowmem_adj_size < array_size)array_size = lowmem_adj_size;if (lowmem_minfree_size < array_size)array_size = lowmem_minfree_size;// lowmem_minfree 和lowmem_adj 记录了两个配置文件中配置的数据for (i = 0; i < array_size; i++) {minfree = lowmem_minfree[i];// 确定当前系统处于低内存的第几档if (other_free < minfree && other_file < minfree) {// 确定需要杀死的进程的oom_score_adj的上限min_score_adj = lowmem_adj[i];break;}}if (sc->nr_to_scan > 0)lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd\n",sc->nr_to_scan, sc->gfp_mask, other_free,other_file, min_score_adj);rem = global_page_state(NR_ACTIVE_ANON) +global_page_state(NR_ACTIVE_FILE) +global_page_state(NR_INACTIVE_ANON) +global_page_state(NR_INACTIVE_FILE);if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX + 1) {lowmem_print(5, "lowmem_shrink %lu, %x, return %d\n",sc->nr_to_scan, sc->gfp_mask, rem);return rem;}selected_oom_score_adj = min_score_adj;rcu_read_lock();// 遍历所有进程for_each_process(tsk) {struct task_struct *p;short oom_score_adj;if (tsk->flags & PF_KTHREAD)continue;p = find_lock_task_mm(tsk);if (!p)continue;if (test_tsk_thread_flag(p, TIF_MEMDIE) &&time_before_eq(jiffies, lowmem_deathpending_timeout)) {task_unlock(p);rcu_read_unlock();return 0;}oom_score_adj = p->signal->oom_score_adj;// 跳过那些oom_score_adj值比目标值小的if (oom_score_adj < min_score_adj) {task_unlock(p);continue;}tasksize = get_mm_rss(p->mm);task_unlock(p);if (tasksize <= 0)continue;// selected 是将要杀死的备选进程if (selected) {// 跳过那些oom_score_adj比备选的小的if (oom_score_adj < selected_oom_score_adj)continue;// 如果oom_score_adj一样,跳过那些内存消耗更小的if (oom_score_adj == selected_oom_score_adj &&tasksize <= selected_tasksize)continue;}// 更换备选的目标,因为又发现了一个oom_score_adj更大,// 或者内存消耗更大的进程selected = p;selected_tasksize = tasksize;selected_oom_score_adj = oom_score_adj;lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n",p->comm, p->pid, oom_score_adj, tasksize);}// 已经选中目标,记录日志并杀死进程if (selected) {long cache_size = other_file * (long)(PAGE_SIZE / 1024);long cache_limit = minfree * (long)(PAGE_SIZE / 1024);long free = other_free * (long)(PAGE_SIZE / 1024);trace_lowmemory_kill(selected, cache_size, cache_limit, free);lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \" to free %ldkB on behalf of '%s' (%d) because\n" \" cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \" Free memory is %ldkB above reserved\n",selected->comm, selected->pid,selected_oom_score_adj,selected_tasksize * (long)(PAGE_SIZE / 1024),current->comm, current->pid,cache_size, cache_limit,min_score_adj,free);lowmem_deathpending_timeout = jiffies + HZ;set_tsk_thread_flag(selected, TIF_MEMDIE);send_sig(SIGKILL, selected, 0);rem -= selected_tasksize;}lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n",sc->nr_to_scan, sc->gfp_mask, rem);rcu_read_unlock();return rem; }
8、进程的死亡处理
在任何时候,应用进程都可能死亡,例如被OOM Killer或者LowMemoryKiller杀死,自身crash死亡又或者被用户手动杀死。无论哪种情况,作为应用进程的管理者ActivityManagerService都需要知道。
在应用进程死亡之后,ActivityManagerService需要执行如下工作:
- 执行清理工作 ActivityManagerService内部的ProcessRecord以及可能存在的四大组件的相关结构需要全部清理干净
- 重新计算进程的优先级 上文已经提到过,进程的优先级是有关联性的,有其中一个进程死亡了,可能会连到影响到其他进程的优先级需要调整。
ActivityManagerService是利用Binder提供的死亡通知机制来进行进程的死亡处理的。关于Binder请参阅其他资料,限于篇幅关系,这里不再展开讲解。
简单来说,死亡通知机制就提供了进程间的一种死亡监听的能力:当目标进程死亡的时候,监听回调会执行。
ActivityManagerService中的AppDeathRecipient监听了应用进程的死亡消息,该类代码如下:
private final class AppDeathRecipient implements IBinder.DeathRecipient {final ProcessRecord mApp;final int mPid;final IApplicationThread mAppThread;AppDeathRecipient(ProcessRecord app, int pid,IApplicationThread thread) {mApp = app;mPid = pid;mAppThread = thread;}@Overridepublic void binderDied() {synchronized(ActivityManagerService.this) {appDiedLocked(mApp, mPid, mAppThread, true);}} }
每一个应用进程在启动之后,都会attach到ActivityManagerService上通知它自己的进程已经启动完成了。这时ActivityManagerService便会为其创建一个死亡通知的监听器。在这之后如果进程死亡了,ActivityManagerService便会收到通知。
private final boolean attachApplicationLocked(IApplicationThread thread,int pid) {...try {AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread);thread.asBinder().linkToDeath(adr, 0);app.deathRecipient = adr;} catch (RemoteException e) {app.resetPackageList(mProcessStats);startProcessLocked(app, "link fail", processName);return false;}... }
进程死亡之后的处理工作是appDiedLocked这个方法中处理的,这部分还是比较容易理解的,这里就不过多讲解了。
原文地址:
Android系统中的进程管理:内存的回收
Android系统中的进程管理:进程的创建
Android系统中的进程管理:进程的优先级
相关文章:

Android系统的进程管理(创建->优先级->回收)
一、进程的创建 1、概述 Android系统以Linux内核为基础,所以对于进程的管理自然离不开Linux本身提供的机制。例如: 通过fork来创建进行通过信号量来管理进程通过proc文件系统来查询和调整进程状态 等 对于Android来说,进程管理的主要内容…...

C#界面美化小技巧
1.窗体设置为无边框 FormBorderStyle的属性设置为none 2.窗体无边框,可以拖拽 private Point mPoint new Point(); private void Download_MouseDown(object sender, MouseEventArgs e) { mPoint.X e.X; mPoint.Y e.Y; …...

‘vite‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。
1.切换到工程目录下 2.执行npm install(最关键的一步了!!) 3. 最后直接运行:npm run dev 4.浏览器直接打开就行了!...

Linux下查阅帮助文档必学命令 man
Linux操作系统的使用中,我们经常会遇到很多问题,这个时候查询文档的能力至关重要,黄老师来推荐大家使用man,这时我们必须掌握的查阅能力: 最常用的命令: man 名称 man 数字(1~9) 名称 这里的数字分别代表:...

uniapp scroll-view显示滚动条
在style中添加样式: ::v-deep ::-webkit-scrollbar {/* 滚动条整体样式 */display: block;width: 10rpx !important;height: 10rpx !important;-webkit-appearance: auto !important;background: transparent;overflow: auto !important;}::v-deep ::-webkit-scroll…...

15、PHP神奇的数组索引替代
1、有数字索引指定的数组元素时,以数字索引的为准。 <?php $aarray(a,b,1>c,5>"d","e"); print_r($a); ?> 输出结果:b的位置直接被c替代了,e 的值为最大的整数索引1。 PHP不这么搞,怎么可能成…...

同为科技(TOWE)带热插拔功能机柜PDU插座的应用
所谓热插拔(hot-plugging或Hot Swap),即带电插拔,指的是在不关闭系统电源的情况下,将模块、板卡插入或拔出系统而不影响系统的正常工作,从而提高了系统的可靠性、快速维修性、冗余性和对灾难的及时恢复能力…...

GR5526 128BIT UUID改16BIT UUID
以下两个宏定义是我添加的。其中USING_128BIT_UUID的条件编译部分是SDK原生部分,USING_16BIT_UUID条件编译部分则是由我修改,通过这样的修改,128BIT UUID就变更为16BIT UUID了。如果你的广播、扫描响应有涉及UUID,不要忘记更改它。…...

【Android】使用 CameraX 实现基础拍照功能
目录 目录 1. 基础开发环境 2. 添加相关依赖 3. APP 布局 4. 主流程逻辑 5. 调试或安装 APK 1. 基础开发环境 JDK:JDK17 Android Studio:Android Studio Giraffe | 2022.3.1 Android SDK:Android API 34 Gradle: gradle-7.2-bin.zip Ca…...

刷题笔记 day2
力扣 1089 复写零 思路:双指针 第一步:利用指针 cur 去记录最后一位要复写的数 , 利用指针 dest 指向最后一位数所要复写的位置; 实现过程:最开始 cur 指向0,dest 指向 -1 , 当arr[cur] ! …...

回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测
回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLAB实现SO-CNN-LS…...

使用UltraISO制作麒麟v10系统盘
大家好,我是早九晚十二,目前是做运维相关的工作。写博客是为了积累,希望大家一起进步! 我的主页:早九晚十二 文章目录 1 背景2 准备工作2.1 镜像准备2.2 制作工具2.3 启动U盘 3 制作步骤3.1 找到ISO文件,右…...

【RabbitMQ】之消息的可靠性方案
目录 一、数据丢失场景二、数据可靠性方案 1、生产者丢失消息解决方案2、MQ 队列丢失消息解决方案3、消费者丢失消息解决方案 一、数据丢失场景 MQ 消息数据完整的链路为:从 Producer 发送消息到 RabbitMQ 服务器中,再由 Broker 服务的 Exchange 根据…...

性能测试/负载测试/压力测试之间的区别
做测试一年多来,虽然平时的工作都能很好的完成,但最近突然发现自己在关于测试的整体知识体系上面的了解很是欠缺,所以,在工作之余也做了一些测试方面的知识的补充。不足之处,还请大家多多交流,互相学习。 …...

Mybatis ,Mybatis-plus列表多字段排序,包含sql以及warpper
根据 mybatis 根据多字段排序已经wrapper 根据多字段排序 首先根据咱们返回前端的数据列来规划好排序字段 如下: 这里的字段为返回VO的字段,要转换成数据库字段然后加入到排序中 示例,穿了 surname,cerRank 多字段,然后是倒序 false 首先创建好映射&am…...

sonarqube PHP编码规范检查
一、PSR规范整理 PHP 已有的编码规范如下 https://blog.csdn.net/qq_40876291/article/details/103848172 1.1 基本编码规范:PSR1 官网规范链接 https://www.php-fig.org/psr/psr-1/ 文件只能使用<?php和<?标记。文件必须仅使用UTF-8,而不使…...

Kylin 麒麟 Qt软件 QtCreator 中文输入法问题
Kylin 麒麟 Qt软件 QtCreator 中文输入法问题 背景: QtCreator 和程序在麒麟系统下没法进行输入,或没法进行输入法的切换。 包括麒麟自带默认搜狗输入法的切换也不行。 使用下面的命令进行安装后,可以正常在QtCreator和程序中使用输入法。 …...

租赁固定资产管理
智能租赁资产管理系统可以为企业单位提供RFID资产管理系统。移动APP资产管理,准确总结易损耗品和固定资金,从入库到仓库库存实时跟踪,控制出库和入库的全过程。同时,备件和耗材与所属资产设备有关,便于备件的申请和管理…...

【Kubernetes】Kubernetes的概念
Kubernetes 一、Kubernetes 概述1.Kubernetes 是什么?2. Kubernetes 的作用3. 为什么要用 Kubernetes?4. Kubernetes 的概念5. Kubernetes 的主要功能6. Kubernetes 集群架构与组件二、Kubernetes 的组件1. Master 组件1.1 Kube-apiserver1.2 Kube-controller-manager1.3 Kub…...

抖音短视频seo源码矩阵系统开发
一、前言: 抖音SEO源码矩阵系统开发是一项专为抖音平台设计的SEO优化系统,能够帮助用户提升抖音视频的搜索排名和曝光度。为了确保系统运行正常,需要安装FFmpeg和FFprobe工具。FFmpeg是一个用于处理多媒体数据的开源工具集,而FFpr…...

npm install pnpm -g报错解决!
目录 报错信息:(反正就是各种err) 报错分析: 错误处理: 其它pnpm报错传送门: 报错信息:(反正就是各种err) npm ERR! code EPERM npm ERR! syscall mkdir npm ERR! pa…...

vue2、vue3生命周期详解以及对比
文章目录 对比vue2-vue3vue3生命周期生命周期的主要阶段详情 vue2 生命周期生命周期钩子函数 总共11个 常用的8个按照这四个阶段我们对应有八个生命周期钩子函数vue生命周期使用场景 对比vue2-vue3 如果熟悉vue2的话,vue3信手拈来,看图 vue3生命周期 on…...

JSON动态生成表格
<!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><script>var fromjava"{\"total\":3,\"students\":[{\"name\":\"张三\",\&q…...

C# Winform中使用SendMessage方法(发送消息与接收消息)
C# Winform窗口间消息通知,使用Windows API SendMessage方法跨进程实现消息发送,重写WndProc方法接收消息并消息处理 主要使用到如下三个方法函数: WndProc:主要用在拦截并处理系统消息和自定义消息 可以重写WndProc函数…...

Netty各组件基本用法、入站和出站详情、群聊系统的实现、粘包和拆包
Netty Bootstrap和ServerBootstrapFuture和ChannelFutureChannelSelectorNioEventLoop和NioEventLoopGroupByteBuf示例代码 Channel相关组件入站详情出站详情对象编解码ProtoBuf和ProtoStuffnetty实现群聊系统粘包和拆包TCP协议特点举个例子 Bootstrap和ServerBootstrap Boots…...

Day03-作业(AxiosElementUI)
作业1: 根据需求完成如下页面数据列表展示 需求:Vue挂载完成后,通过axios发送异步请求到服务端,获取学生列表数据,并通过Vue展示在页面上 获取数据url:http://yapi.smart-xwork.cn/mock/169327/student 素材: <!DOCTYPE html…...

低代码开发平台源码:基于模型驱动,内置功能强大的建模引擎,零代码也能快速创建智能化、移动化的企业应用程序
管理后台低代码PaaS平台是一款基于 Salesforce Platform 的开源替代方案,旨在为企业提供高效、灵活、易于使用的低代码开发平台。低代码PaaS平台的10大核心引擎功能:1.建模引擎 2.移动引擎 3.流程引擎 4.页面引擎 5.报表引擎 6.安全引擎 7.API引擎 8.应用集成引擎 9…...

下载JMeter的历史版本——个人推荐5.2.1版本
官网地址:https://archive.apache.org/dist/jmeter/binaries/...

2023-07-30 LeetCode每日一题(环形链表 II)
2023-07-30每日一题 一、题目编号 142. 环形链表 II二、题目链接 点击跳转到题目位置 三、题目描述 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 n…...

设计模式——简单工厂模式
1 概述 将创造对象的工作交给一个单独的类来实现 ,这个单独的类就是工厂。 2 实现 假设要做一个计算器的需求,通常我们想到的是这样写: package com.example.easyfactory;import java.util.Scanner;public class Demo1 {public static vo…...