Framework学习之旅:Zygote进程
概述
在Android系统中,DVM(Dalvik 虚拟机和ART、应用程序进程以及运行系统的关键服务SystemServer进程都是由Zygote进程来创建的。通过fork(复制进程)的形式来创建应用程进程和SystemServer进程,由于Zygote进程在启动时会创建DVM或者ART,因此通过 fork而创建的应用程序进程和SystemServer进程可以在内部获取DVM或者ART实例副本。
Zygote进程是init进程启动时创建的,Zygote进程的名称并不是叫zygote,而是叫app_process,这个名称是在Android.mk中定义的,Zygote进程启动后,Linux 系统下的petri 系统会调用app_process ,将其名称换成了“zygote ”。
Zygote启动脚本
init进程启动后,会解析init.rc文件,然后创建和加载service字段指定的进程。在init.rc 文件中采用了Import类型语句来引人Zygote启动脚本,这些启动脚本都是由Android初始化语言来编写的:
import/init.${ro.zygote}.rc
init.rc不会直接引入一个固定的文件,而是根据属性ro.zygote的内容来引入不同的文件。
Zygote有32位和64位的区别,所以在这里用ro.zygote属性来控制使用不同的Zygote启动脚本,从而也就启动了不同版本的Zygote进程,ro.zygote属性的取值有以下4种:
- init.zygote32.rc
- init.zygote32_64.rc
- init.zygote64.rc
- init.zygote64_32.rc
这些Zygote启动脚本都放在system/core/rootdir目录中。现在的主流厂家基本使用zygote64_32,因此,我们的rc文件为 init.zygote64_32.rc。
Zygote 进程启动过程
app_main.cpp的main函数
init进程启动Zygote时主要是调用app_main.cpp的main函数中的AppRuntime的start 方法来启动Zygote进程的,先从app_ main.cpp的main函数开始分析:
/frameworks/base/cmds/app_process/app_main.cppint main(int argc, char* const argv[]){...while (i < argc) {const char* arg = argv[i++];if (strcmp(arg, "--zygote") == 0) {// 注释1// 如果当前运行在 Zygote 进程中,则将 zygote 设置为 truezygote = true;// 注释2// 对于64位系统nice_name为zygote64; 32位系统为zygoteniceName = ZYGOTE_NICE_NAME;} else if (strcmp(arg, "--start-system-server") == 0) {// 注释3// 如果当前运行在 SystemServer 进程中,则将 startSystemServer 设置为 truestartSystemServer = true;// 注释4} else if (strcmp(arg, "--application") == 0) {//启动进入独立的程序模式application = true;} else if (strncmp(arg, "--nice-name=", 12) == 0) {//niceName 为当前进程别名,区别abi型号niceName.setTo(arg + 12);} else if (strncmp(arg, "--", 2) != 0) {className.setTo(arg);break;} else {--i;break;}}...if (!niceName.isEmpty()) {// 设置昵称zygote\zygote64,之前的名称是app_processruntime.setArgv0(niceName.string(), true /* setProcName */);}if (zygote) {// 注释5// 如果运行在Zygote进程中,则加载ZygoteInitruntime.start("com.android.internal.os.ZygoteInit", args, zygote);// 注释6} else if (className) {//如果是application启动模式,则加载RuntimeInitruntime.start("com.android.internal.os.RuntimeInit", args, zygote);} else {//没有指定类名或zygote,参数错误fprintf(stderr, "Error: no class name or --zygote supplied.\n");app_usage();LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");}
}
Zygote进程都是通过fork自身来创建子进程的,这样Zygote进程以及它的子进程都可以进入app_ main.cpp的main函数,因此main函数中为了区分当前运行在哪个进程中,会在注释1处判断参数arg中是否包含了–zygote,如果包含了则说明main函数是运行在Zygote 进程中的并在注释2处将zygote置为ture。在注释3处判断参数arg 中是否包含了“ start-system server”,如果包含了则说明main函数是运行在SystemServer进程中的并在注释4 处将startSystemServer设置为true。在注释5处,如果zygote为true,就说明当前运行在 Zygote 进程中,就会调用注释6处的AppRuntime的start函数,来加载虚拟机并进入JAVA世界。
frameworks/base/core/jni/AndroidRuntime.cppvoid AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{.../* start the virtual machine */JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;// 启动Java虚拟机if (startVm(&mJavaVM, &env, zygote, primary_zygote) != 0) {return;}onVmCreated(env);// 为Java虚拟机注册JNI方法if (startReg(env) < 0) {//2ALOGE("Unable to register all android natives\n");return;}jclass stringClass;jobjectArray strArray;jstring classNameStr;stringClass = env->FindClass("java/lang/String");assert(stringClass != NULL);strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);assert(strArray != NULL);//从app_main的main函数得知className为com.android.internal.os.ZygoteinitclassNameStr = env->NewStringUTF(className);assert(classNameStr != NULL);env->SetObjectArrayElement(strArray, 0, classNameStr);for (size_t i = 0; i < options.size(); ++i) {jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());assert(optionsStr != NULL);env->SetObjectArrayElement(strArray, i + 1, optionsStr);}// classNarne “.”替换为“/”char* slashClassName = toSlashClassName(className != NULL ? className : "");//找到Zygoteinit类jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {ALOGE("JavaVM unable to locate class '%s'\n", slashClassName);/* keep going */} else {// 找到ZygoteInit的main方法jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");//6if (startMeth == NULL) {ALOGE("JavaVM unable to find main() in '%s'\n", className);/* keep going */} else {// 通过JNI调用Zygoteinit的main 方法env->CallStaticVoidMethod(startClass, startMeth, strArray);#if 0if (env->ExceptionCheck())threadExitUncaughtException(env);
#endif}}//释放相应对象的内存空间free(slashClassName);...
}
从以上代码可以看出,通过JNI调用Zygotelnit的main方法。因为Zygotelnit的main方法是由Java语言编写的,当前的运行逻辑在Native中,这就需要通过JNI调用Java。这样Zygote就从Native进入了Java框架层。start()函数主要做了三件事情:1.调用startVm函数开启虚拟机。2.调用startReg注册JNI方法。3.使用JNI把Zygote进程启动起来。
ZygoteInit的main函数
通过JNI调用ZygoteInit的main函数后,Zygote便进入了Java框架层,此前没有任何代码进入过Java框架层,换句换说Zygote开创了Java框架层。
frameworks/base/core/java/com/android/internal/os/ZygoteInit.javapublic static void main(String argv[]) {// 1. 创建ZygoteServerZygoteServer zygoteServer = null;// 调用native函数,确保当前没有其它线程在运行ZygoteHooks.startZygoteNoThreadCreation(); try {// 设置pid为0,Zygote进入自己的进程Os.setpgid(0, 0);} catch (ErrnoException ex) {throw new RuntimeException("Failed to setpgid(0,0)", ex);}Runnable caller;try{...boolean startSystemServer = false;String zygoteSocketName = "zygote";String abiList = null;boolean enableLazyPreload = false;// 2. 解析app_main.cpp - start()传入的参数for (int i = 1; i < argv.length; i++) {if ("start-system-server".equals(argv[i])) {//启动zygote时,才会传入参数:start-system-serverstartSystemServer = true;} else if ("--enable-lazy-preload".equals(argv[i])) {enableLazyPreload = true;} else if (argv[i].startsWith(ABI_LIST_ARG)) {abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);} }// 根据传入socket name来决定是创建socket还是zygote_secondaryfinal boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);...// 在第一次zygote启动时,enableLazyPreload为false,执行preloadif (!enableLazyPreload) {bootTimingsTraceLog.traceBegin("ZygotePreload");EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,SystemClock.uptimeMillis());// 3.预加载类和资源(详解下文1) preload(bootTimingsTraceLog);EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,SystemClock.uptimeMillis());bootTimingsTraceLog.traceEnd(); // ZygotePreload}...// 4.调用ZygoteServer构造函数,创建socket.(详解下文2) zygoteServer = new ZygoteServer(isPrimaryZygote);if (startSystemServer) {//5.fork出systemServer(详解下文3) Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer);// 启动SystemServerif (r != null) {r.run();// 调用SystemServer 的main 方法return;}} //6. zygote进程进入无限循环,等待AMS请求(详解下文4) caller = zygoteServer.runSelectLoop(abiList);}catch (Throwable ex) {Log.e(TAG, "System zygote died with exception", ex);throw ex;} finally {if (zygoteServer != null) {zygoteServer.closeServerSocket();}}// 7.在子进程中退出了选择循环。继续执行命令if (caller != null) {caller.run();}
}
Zygotelnit的main方法主要做了4件事:1.创建一个Server端的Socket。2.预加载类和资源。3. 启动SystemServer进程。 4.等待AMS请求创建新的应用程序进程。
详解1:preload()
- 预加载
在Zygote进程启动的时候就加载,这样系统只在zygote执行一次加载操作,所有APP用到该资源不需要再重新加载,减少资源加载时间,加快了应用启动速度,一般情况下,系统中App共享的资源会被列为预加载资源。
Zygote fork子进程时,根据fork的copy-on-write机制可知,有些类如果不做改变,甚至都不用复制,子进程可以和父进程共享这部分数据,从而省去不少内存的占用。
- 预加载的原理
Zygote进程启动后将资源读取出来,保存到Resources一个全局静态变量中,下次读取系统资源的时候优先从静态变量中查找。
- 源码
static void preload(TimingsTraceLog bootTimingsTraceLog) {...// 获取字符集转换资源等beginPreload();// 预加载类的列表preloadClasses();cacheNonBootClasspathClassLoaders();// 加载图片、颜色等资源文件,部分定义在 /frameworks/base/core/res/res/values/arrays.xml中preloadResources();nativePreloadAppProcessHALs();maybePreloadGraphicsDriver();// 加载 android、compiler_rt、jnigraphics等librarypreloadSharedLibraries();// 用于初始化文字资源preloadTextResources();// 用于初始化webviewWebViewFactory.prepareWebViewInZygote();// 预加载完成endPreload();warmUpJcaProviders();sPreloadComplete = true;
}
详解2:ZygoteServer()
frameworks/base/core/java/com/android/internal/os/ZygoteServer.javaprivate LocalServerSocket mZygoteSocket;private LocalServerSocket mUsapPoolSocket;ZygoteServer(boolean isPrimaryZygote) {mUsapPoolEventFD = Zygote.getUsapPoolEventFD();if (isPrimaryZygote) {// 创建socket,并获取socket对象,socketname: zygotemZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.PRIMARY_SOCKET_NAME);// 创建socket,并获取socket对象,socketname:usap_pool_primarymUsapPoolSocket =Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_PRIMARY_SOCKET_NAME);} else {// 创建socket,并获取socket对象,socketname:zygote_secondarymZygoteSocket = Zygote.createManagedSocketFromInitSocket(Zygote.SECONDARY_SOCKET_NAME);// 创建socket,并获取socket对象,socketname:usap_pool_secondarymUsapPoolSocket =Zygote.createManagedSocketFromInitSocket(Zygote.USAP_POOL_SECONDARY_SOCKET_NAME);}mUsapPoolSupported = true;fetchUsapPoolPolicyProps();
}frameworks/base/core/java/com/android/internal/os/Zygote.java
static LocalServerSocket createManagedSocketFromInitSocket(String socketName) {int fileDesc;final String fullSocketName = ANDROID_SOCKET_PREFIX + socketName;try {// 在进程被创建时,就会创建对应的文件描述符,并加入到环境变量中,并取出对应的环境变量String env = System.getenv(fullSocketName);fileDesc = Integer.parseInt(env);} catch (RuntimeException ex) {throw new RuntimeException("Socket unset or invalid: " + fullSocketName, ex);}try {FileDescriptor fd = new FileDescriptor();// 获取zygote socket的文件描述符fd.setInt$(fileDesc);return new LocalServerSocket(fd);// 创建Socket的本地服务端} catch (IOException ex) {throw new RuntimeException("Error building socket from file descriptor: " + fileDesc, ex);}}
ZygoteServer 构造函数初始化时,根据传入的参数,利用LocalServerSocket创建了4个本地服务端的socket,用来建立连接,分别是:zygote、usap_pool_primary、zygote_secondary、usap_pool_secondary。
详解3:forkSystemServer
frameworks/base/core/java/com/android/internal/os/ZygoteInit.javaprivate static Runnable forkSystemServer(String abiList, String socketName, ZygoteServer zygoteServer) {...//来创建args数组,这个数组用来保存启动SystemServer 的启动参数String args[] = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"+ "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011","--capabilities=" + capabilities + "," + capabilities,"--nice-name=system_server","--runtime-args","--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,"com.android.server.SystemServer",};ZygoteArguments parsedArgs = null; int pid;try {//将args数组封装成Arguments对象,并供下面的SystemServer 函数调用parsedArgs = new ZygoteArguments(args);Zygote.applyDebuggerSystemProperty(parsedArgs);Zygote.applyInvokeWithSystemProperty(parsedArgs);if (Zygote.nativeSupportsTaggedPointers()) {/* Enable pointer tagging in the system server. Hardware support for this is present* in all ARMv8 CPUs. */parsedArgs.mRuntimeFlags |= Zygote.MEMORY_TAG_LEVEL_TBI;}/* Enable gwp-asan on the system server with a small probability. This is the same* policy as applied to native processes and system apps. */parsedArgs.mRuntimeFlags |= Zygote.GWP_ASAN_LEVEL_LOTTERY;if (shouldProfileSystemServer()) {parsedArgs.mRuntimeFlags |= Zygote.PROFILE_SYSTEM_SERVER;}// 创建一个子进程,也就是 SystemServer 进程pid = Zygote.forkSystemServer(parsedArgs.mUid, parsedArgs.mGid,parsedArgs.mGids,parsedArgs.mRuntimeFlags,null,parsedArgs.mPermittedCapabilities,parsedArgs.mEffectiveCapabilities);} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);}// 当前代码逻辑运行在子进程system_server中if (pid == 0) {// 处理32_64和64_32的情况if (hasSecondZygote(abiList)) {// /需要等待第二个Zygote创建完成waitForSecondaryZygote(socketName);}// 关闭连接zygoteServer.closeServerSocket();// 处理 SystemServer进程return handleSystemServerProcess(parsedArgs);}return null;
}
调用Zygote的forkSystemServer方法,其内部会调用nativeForkSystemServer这个Native方法,nativeForkSystemServer方法最终会通过fork函数在当前进程创建一个子进程,也就是SystemServer进程,如果forkSystemServer方法返回的pid的值为0,就表示当前的代码运行在新创建的子进程中。
详解4:runSelectLoop
frameworks/base/core/java/com/android/internal/os/ZygoteServer.javaRunnable runSelectLoop(String abiList) {ArrayList<FileDescriptor> socketFDs = new ArrayList<>();ArrayList<ZygoteConnection> peers = new ArrayList<>();// 该Socket的fd字段的值并添加到socketFDs列表socketFDs.add(mZygoteSocket.getFileDescriptor());// 无限循环等待AMS的请求while (true) {...int pollReturnValue;try {// 等待事件到来pollReturnValue = Os.poll(pollFDs,pollTimeoutMs);} catch (ErrnoException ex) {throw new RuntimeException("poll failed", ex);}... // 倒序处理,即优先处理已建立链接的信息,后处理新建链接的请求while (--pollIndex >= 0) {if (pollIndex == 0) {// 收到新的建立通信的请求,建立通信连接ZygoteConnection newPeer = acceptCommandPeer(abiList);peers.add(newPeer);//加入到peers和fds, 即下一次也开始监听 socketFDs.add(newPeer.getFileDescriptor());} else if (pollIndex < usapPoolEventFDIndex) {// Session socket accepted from the Zygote server sockettry {//有socket连接,创建ZygoteConnection对象,并添加到fdsZygoteConnection connection = peers.get(pollIndex);final Runnable command = connection.processOneCommand(this);} }...}}
Zygote进程启动总结
- 创建AppRuntime并调用其start方法,启动Zygote进程。
- 创建Java虚拟机并为Java虚拟机注册JNI方法。
- 通过JNI调用Zygotelnit的main函数进入Zygote的Java框架层。
- 通过ZygoteServer创建服务端Socket,预加载类和资源,并通过runSelectLoop方法等待AMS的请求来创建新的应用程序进程。
- 启动SystemServer进程。

相关文章:
Framework学习之旅:Zygote进程
概述 在Android系统中,DVM(Dalvik 虚拟机和ART、应用程序进程以及运行系统的关键服务SystemServer进程都是由Zygote进程来创建的。通过fork(复制进程)的形式来创建应用程进程和SystemServer进程,由于Zygote进程在启动时会创建DVM…...
HTTP基础知识
关键字:一问一答用于和服务器交互什么是HTTPHTTP是个应用层协议,是HTTP客户端和HTTP服务器之间的交互数据格式。所以这里有个实例:在浏览网页的时候,浏览器会向服务器发送一个HTTP请求,告诉服务器我想访问什么..然后服…...
Delphi 10.4.2使用传统代码提示方案(auto complete)(转)
Delphi 10.4重点是实现了LSP,但现在最新的10.4.2还是不成熟,无法满足日常需要,不过没关系,可以设置为原有的方案,如下图:具体操作:Tools->Options->Editor->language->Code Insight…...
存储类别、链接与内存管理(三)
1、malloc函数详解 (1)函数声明 #include <stdlib.h> void* malloc(size_t size);malloc可以申请一定数量的空闲内存,这样的内存是匿名的,也就是malloc不会为其赋名,但是确实返回动态分配内存块的首元素地址&a…...
Java:Linux(CentOS)安装、配置及相关命令
目录一、VMware安装二、CentOS安装1、安装过程2、加载ISO2.1 桌面的设置三、VI/VIM编辑器1、一般模式2、编辑模式3、命令模式4、模式间转换四、网络配置和系统管理操作1、配置子网IP和网关2、配置虚拟机ip地址2.1 ifconfig 查询ip地址2.2 修改IP地址3、配置主机名3.1 hostname …...
Linux 操作系统原理 — 多任务优先级调度策略
目录 文章目录 目录多任务优先级调度策略User Process 调度策略配置调整 User Process 的优先级调整非实时进程的优先级调整实时进程优先级调整 User Process 的调度算法多任务优先级调度策略 在 Linux Kernel 中,Kernel Thread 作为唯一的调度实体,Kernel Scheduler(调度程…...
链表学习之找到两个链表相交的第一个节点
链表解题技巧 额外的数据结构(哈希表);快慢指针;虚拟头节点; 找到两个链表相交的第一个节点 给定两个链表,这两个链表可能有环,可能无环。判断这两个链表是否相交,相交则返回第一…...
【Kubernetes】【十一】Pod详解 Pod的生命周期
Pod生命周期 我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程: pod创建过程 运行初始化容器(init container)过程 运行主容器(main container) 容器启动后钩子&#…...
Connext DDS录制服务 Recording Service(1)
1 序言 1.1 简介 RTI记录服务包括以下工具: •记录服务,一种RTI Connext DDS应用程序,用于记录主题和发现数据。记录服务记录数据更新以及时间戳,因此您可以查看或回放系统中随时间发生的数据更新。默认情况下,记录的数据存储在SQLite文件中。录制服务还具有一个API,用于…...
vTESTstudio - VT System CAPL Functions - VT2004(续2)
不要沮丧,不必惊慌,做努力爬的蜗牛或坚持飞的笨鸟,我们试着长大,一路跌跌撞撞,哪怕遍体鳞伤。vtsSetPWMVoltageLow - 设置PWM输出上的低电压功能:指定数字输出信号(尤其是PWM信号)输…...
每天一个linux命令---awk
awk命令 1. 简介 awk是一种处理文本文件的语言,是一个强大的文本分析工具,grep、sed、awk并称为shell中文本处理的三剑客。 AWK 是一种处理文本文件的语言,是一个强大的文本分析工具。 之所以叫 AWK 是因为其取了三位创始人 Alfred Aho&am…...
Open3D 点云旋转之轴角式(Python版本)
文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 三维空间中表示旋转的方法有很多种,轴角式是其中非常经典的一种表示方式。虽然欧拉角表示旋转的方法很是常用,但欧拉角存在着万向锁这个问题,因此轴角式旋转在旋转使用中更为合适。其原理也很是明了,如下所述:…...
Error: Timeout trying to fetch resolutions from npm
文章目录问题描述【最终解决】我搜索到的解决方案npmjs 该依赖各版本列表及对应的被下载次数github issue 说降级到0.0.3就可以正常运行了SOF 也说降级别到0.0.3问题描述 在项目里用到了 "preinstall": "npx npm-force-resolutions"配置,在一台…...
Python基础3
目录 1. 函数多返回值 2. 函数多种传参方式 3. 匿名函数 3.1 函数作为参数传递 3.2 lambda匿名函数 4. 文件的读取操作 4.1 open()打开函数 4.2 读操作方法 4.3 文件的写入 4.4 文件的追加 5. 异常的捕获方法 5.1 捕获常规异常 5.2 捕获指定…...
高可用集群(HAC)
1、高可用集群keepalive说明 高可用定义: 目的:尽可能的提高服务的可用性 99%、99.9%、99.99%、99.999% 实现原理:心跳检测服务: 有状态: MySQL 无状态: apacheLVS Keepalive原理 案例环境专为 LVS和…...
python基于django微信小程序的适老化老人健康预警小程序
随着信息技术和网络技术的飞速发展,人类已进入全新信息化时代,传统管理技术已无法高效,便捷地管理信息。为了迎合时代需求,优化管理效率,各种各样的管理系统应运而生,各行各业相继进入信息管理时代, 适老化老人健康预警微信小程序就是信息时代变革中的产物之一。 任何系统都要遵…...
基于微信小程序图书馆管理系统
开发工具:IDEA、微信小程序服务器:Tomcat9.0, jdk1.8项目构建:maven数据库:mysql5.7前端技术:vue、uniapp服务端技术:springbootmybatis-plus本系统分微信小程序和管理后台两部分,项…...
将镭神C32激光雷达的PointXYZ数据转化为PointXYZIR格式 - 附代码
之前遇到过“镭神32线激光雷达ROS下运行fromRosMsg()报错 Failed to find match for field “intensity“ 问题”, 当时确定了是镭神C32雷达缺少相应字段,并记录博客【学习记录】镭神32线激光雷达ROS下运行fromRosMsg()报错 Failed to find match for fi…...
高级前端一面面试题集锦
详细说明 Event loop 众所周知 JS 是门非阻塞单线程语言,因为在最初 JS 就是为了和浏览器交互而诞生的。如果 JS 是门多线程的语言话,我们在多个线程中处理 DOM 就可能会发生问题(一个线程中新加节点,另一个线程中删除节点&#…...
Java基础 -- List集合
Java基础 -- List集合1. Introduction1.1 好处1.2 常用泛型2. 交集,差集等2.1 自身的方法2.2 1.8jdk stream 新特性2.3 Apache的CollectionUtils工具类(推荐)3. 限定泛型范围4. Awakening1. Introduction 1.1 好处 代码复用,多种…...
eNSP-Cloud(实现本地电脑与eNSP内设备之间通信)
说明: 想象一下,你正在用eNSP搭建一个虚拟的网络世界,里面有虚拟的路由器、交换机、电脑(PC)等等。这些设备都在你的电脑里面“运行”,它们之间可以互相通信,就像一个封闭的小王国。 但是&#…...
相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了: 这一篇我们开始讲: 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下: 一、场景操作步骤 操作步…...
Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具
文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
《Docker》架构
文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器,docker,镜像,k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
动态规划-1035.不相交的线-力扣(LeetCode)
一、题目解析 光看题目要求和例图,感觉这题好麻烦,直线不能相交啊,每个数字只属于一条连线啊等等,但我们结合题目所给的信息和例图的内容,这不就是最长公共子序列吗?,我们把最长公共子序列连线起…...
