Android系统启动-Zygote详解(Android 14)

一、什么是Zygote

在上一篇文章Android系统启动-init进程详解(Android 14)中,分析了init进程,在init进程启动的第二阶段会解析init.*.rc文件,启动多个进程,其中包括Zygote。

Zygote又叫孵化器,是 Android 系统创建的第一个Java进程,它是所有Java进程的父进程。包括 system_server 进程以及所有的App进程都是Zygote的子进程。

Zygote 进程作为 Socket 的 Server 端,接收处理系统中创建进程的请求。Android中的应用进程的创建都是应用进程通过 Binder 发送请求给 system_server 进程中的 ActivityManagerService(AMS) ,AMS 再发送 Socket 消息给 Zygote 进程,统一由 Zygote 进程创建出来的。整个过程如下图所示:

二、Zygote的启动

app_main.cpp

在init进程启动后,会解析 init.rc 文件,创建和加载 service 字段指定的 Zygote 进程。在 /system/core/rootdir/init.rc 中,通过如下引用来 load zygote 的 rc:

import /system/etc/init/hw/init.${ro.zygote}.rc

这里根据属性 ro.zygote 的内容来引入不同的 Zygote 启动脚本。Android 5.0以后,Android开始支持64位编译,Zygote 进程也随之引入了32/64位的区别。所以,这里通过 ro.zygote 属性来控制启动不同版本的 Zygote 进程。
ro.zygote属性会有四种不同的值:

  • zygote32:代表32位模式
  • zygote32_64:代表32模式为主,64位模式为辅
  • zygote64:代表64位模式
  • zygote64_32:代表64模式为主,32位模式为辅

这里我们以64位处理器为例,init.zygote64.rc代码如下:

// system/core/rootdir/init.zygote64.rcservice zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygoteclass mainpriority -20user rootgroup root readproc reserved_disksocket zygote stream 660 root systemsocket usap_pool_primary stream 660 root systemonrestart exec_background - system system -- /system/bin/vdc volume abort_fuseonrestart write /sys/power/state on# NOTE: If the wakelock name here is changed, then also# update it in SystemSuspend.cpponrestart write /sys/power/wake_lock zygote_kwlonrestart restart audioserveronrestart restart cameraserveronrestart restart mediaonrestart restart media.tuneronrestart restart netdonrestart restart wificondtask_profiles ProcessCapacityHigh MaxPerformancecritical window=${zygote.critical_window.minute:-off} target=zygote-fatal

这段脚本要求 init 进程创建一个名为 zygote 的进程,该进程要执行的程序是“/system/bin/app_process”。并且为 zygote 进程创建一个 socket 资源 (用于进程间通信,ActivityManagerService 就是通过该 socket 请求 zygote 进程 fork 一个应用程序进程)。

app_process64对应的代码定义在 frameworks/base/cmds/app_process 中,不管是app_process、app_process32 还是 app_process64 对应的源文件都是 app_main.cpp。

因此,Zygote 对应的可执行程序为 app_process,该程序对应的源文件为 app_main.cpp,入口函数为 main 函数,进程名为 Zygote。

// frameworks/base/cmds/app_process/app_main.cpp
int main(int argc, char* const argv[])
{if (!LOG_NDEBUG) {String8 argv_String;for (int i = 0; i < argc; ++i) {argv_String.append("\"");argv_String.append(argv[i]);argv_String.append("\" ");}ALOGV("app_process main with argv: %s", argv_String.string());}//zygote传入的参数argv为“-Xzygote /system/bin --zygote --start-system-server --socket-name=zygote”//zygote_secondary传入的参数argv为“-Xzygote /system/bin --zygote --socket-name=zygote_secondary”AppRuntime runtime(argv[0], computeArgBlockSize(argc, argv));argc--;argv++;const char* spaced_commands[] = { "-cp", "-classpath" };// Allow "spaced commands" to be succeeded by exactly 1 argument (regardless of -s).bool known_command = false;int i;for (i = 0; i < argc; i++) {if (known_command == true) {runtime.addOption(strdup(argv[i]));ALOGV("app_process main add known option '%s'", argv[i]);known_command = false;continue;}for (int j = 0;j < static_cast<int>(sizeof(spaced_commands) / sizeof(spaced_commands[0]));++j) {if (strcmp(argv[i], spaced_commands[j]) == 0) {known_command = true;ALOGV("app_process main found known command '%s'", argv[i]);}}if (argv[i][0] != '-') {break;}if (argv[i][1] == '-' && argv[i][2] == 0) {++i; // Skip --.break;}runtime.addOption(strdup(argv[i]));ALOGV("app_process main add option '%s'", argv[i]);}bool zygote = false;bool startSystemServer = false;bool application = false;String8 niceName;String8 className;++i;  // Skip unused "parent dir" argument.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;} else if (strcmp(arg, "--application") == 0) {application = true;} else if (strncmp(arg, "--nice-name=", 12) == 0) {niceName.setTo(arg + 12);} else if (strncmp(arg, "--", 2) != 0) {className.setTo(arg);break;} else {--i;break;}}Vector<String8> args;if (!className.isEmpty()) {//className不为空,说明是application启动模式args.add(application ? String8("application") : String8("tool"));runtime.setClassNameAndArgs(className, argc - i, argv + i);if (!LOG_NDEBUG) {String8 restOfArgs;char* const* argv_new = argv + i;int argc_new = argc - i;for (int k = 0; k < argc_new; ++k) {restOfArgs.append("\"");restOfArgs.append(argv_new[k]);restOfArgs.append("\" ");}ALOGV("Class name = %s, args = %s", className.string(), restOfArgs.string());}} else {//进入zygote模式,新建Dalvik的缓存目录:/data/dalvik-cachemaybeCreateDalvikCache();if (startSystemServer) {args.add(String8("start-system-server"));}char prop[PROP_VALUE_MAX];if (property_get(ABI_LIST_PROPERTY, prop, NULL) == 0) {LOG_ALWAYS_FATAL("app_process: Unable to determine ABI list from property %s.",ABI_LIST_PROPERTY);return 11;}String8 abiFlag("--abi-list=");abiFlag.append(prop);args.add(abiFlag);// In zygote mode, pass all remaining arguments to the zygote// main() method.for (; i < argc; ++i) {args.add(String8(argv[i]));}}//设置一个“好听的昵称” zygote\zygote64,之前的名称是app_processif (!niceName.isEmpty()) {runtime.setArgv0(niceName.string(), true /* setProcName */);}if (zygote) {runtime.start("com.android.internal.os.ZygoteInit", args, zygote);} else if (!className.isEmpty()) {runtime.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.");}
}

AndroidRuntime.cpp

在 app_main.cpp 的 main 函数中最后调用了 AndroidRuntime.start 函数:

// frameworks/base/core/jni/AndroidRuntime.cpp
void AndroidRuntime::start(const char* className, const Vector<String8>& options, bool zygote)
{static const String8 startSystemServer("start-system-server");for (size_t i = 0; i < options.size(); ++i) {if (options[i] == startSystemServer) {const int LOG_BOOT_PROGRESS_START = 3000;}}const char* rootDir = getenv("ANDROID_ROOT");if (rootDir == NULL) {rootDir = "/system";if (!hasDir("/system")) {return;}setenv("ANDROID_ROOT", rootDir, 1);}JniInvocation jni_invocation;jni_invocation.Init(NULL);JNIEnv* env;// 【虚拟机创建】if (startVm(&mJavaVM, &env, zygote) != 0) {return;}onVmCreated(env);// 【JNI方法注册】if (startReg(env) < 0) {return;}jclass stringClass;jobjectArray strArray;jstring classNameStr;//等价 strArray= new String[options.size() + 1];stringClass = env->FindClass("java/lang/String");strArray = env->NewObjectArray(options.size() + 1, stringClass, NULL);//等价 strArray[0] = "com.android.internal.os.ZygoteInit"classNameStr = env->NewStringUTF(className);env->SetObjectArrayElement(strArray, 0, classNameStr);//等价 strArray[1] = "start-system-server";// strArray[2] = "--abi-list=xxx";//其中xxx为系统响应的cpu架构类型,比如arm64-v8a.for (size_t i = 0; i < options.size(); ++i) {jstring optionsStr = env->NewStringUTF(options.itemAt(i).string());env->SetObjectArrayElement(strArray, i + 1, optionsStr);}//将"com.android.internal.os.ZygoteInit"转换为"com/android/internal/os/ZygoteInit"char* slashClassName = toSlashClassName(className);jclass startClass = env->FindClass(slashClassName);if (startClass == NULL) {...} else {jmethodID startMeth = env->GetStaticMethodID(startClass, "main","([Ljava/lang/String;)V");// 【通过JNI调用ZygoteInit.main()方法】env->CallStaticVoidMethod(startClass, startMeth, strArray);}//释放相应对象的内存空间free(slashClassName);mJavaVM->DetachCurrentThread();mJavaVM->DestroyJavaVM();
}

start函数主要做了三件事:

  1. 调用 startVm 创建虚拟机
  2. 调用 startReg 注册 JNI 方法
  3. 通过JNI调用到 ZygoteInit.main(),进入 Java 框架层(此前没有任何代码进入过 Java 框架层)

startVM

如果每个应用程序在启动之时都需要单独运行和初始化一个虚拟机,会大大降低系统性能,因此系统首先在 Zygote 进程中创建一个虚拟机,然后通过它孵化出其他的进程,这样子进程会获得 Zygote 进程中的 Dalvik 虚拟机实例拷贝,进而共享虚拟机内存和框架层资源,大幅度提高应用程序的启动和运行速度。

下面只列举部分在调试优化过程中常用参数的源码。

// frameworks/base/core/jni/AndroidRuntime.cpp
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote)
{// JNI检测功能,用于native层调用jni函数时进行常规检测,比较弱字符串格式是否符合要求,资源是否正确释放。该功能一般用于早期系统调试或手机Eng版,对于User版往往不会开启,引用该功能比较消耗系统CPU资源,降低系统性能。const bool checkJni = GetBoolProperty("dalvik.vm.checkjni", false);if (checkJni) {ALOGD("CheckJNI is ON");/* extended JNI checking */addOption("-Xcheck:jni");/* with -Xcheck:jni, this provides a JNI function call trace *///addOption("-verbose:jni");}//虚拟机产生的trace文件,主要用于分析系统问题,路径默认为/data/anr/traces.txtparseRuntimeOption("dalvik.vm.stack-trace-file", stackTraceFileBuf, "-Xstacktracefile:");//对于不同的软硬件环境,这些参数往往需要调整、优化,从而使系统达到最佳性能parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree=");parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree=");parseRuntimeOption("dalvik.vm.heaptargetutilization",heaptargetutilizationOptsBuf, "-XX:HeapTargetUtilization=");...//初始化虚拟机if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {ALOGE("JNI_CreateJavaVM failed\n");return -1;}
}

startReg

int AndroidRuntime::startReg(JNIEnv* env)
{//设置线程创建方法为javaCreateThreadEtcandroidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc);env->PushLocalFrame(200);//进程 JNI 方法的注册if (register_jni_procs(gRegJNI, NELEM(gRegJNI), env) < 0) {env->PopLocalFrame(NULL);return -1;}env->PopLocalFrame(NULL);return 0;
}

register_jni_procs:

//frameworks/base/core/jni/AndroidRuntime.cppstatic int register_jni_procs(const RegJNIRec array[], size_t count, JNIEnv* env){for (size_t i = 0; i < count; i++) {if (array[i].mProc(env) < 0) {//调用gRegJNI的mProc#ifndef NDEBUGALOGD("----------!!! %s failed to load\n", array[i].mName);#endifreturn -1;}}return 0;}//gRegJNI是一个全局数组,定义如下:
//frameworks/base/core/jni/AndroidRuntime.cppstatic const RegJNIRec gRegJNI[] = {REG_JNI(register_com_android_internal_os_RuntimeInit),REG_JNI(register_com_android_internal_os_ZygoteInit_nativeZygoteInit),REG_JNI(register_android_os_SystemClock),........//frameworks/base/core/jni/AndroidRuntime.cpp#ifdef NDEBUG#define REG_JNI(name)      { name }struct RegJNIRec {int (*mProc)(JNIEnv*);};#else#define REG_JNI(name)      { name, #name }struct RegJNIRec {int (*mProc)(JNIEnv*);const char* mName;};#endif

gRegJNI数组有100多个成员,其中每一项成员都是通过REG_JNI宏定义的。调用 mProc,就等价于调用其参数名所指向的函数。 例如 REG_JNI(register_com_android_internal_os_RuntimeInit).mProc 也就是指进入 register_com_android_internal_os_RuntimeInit 方法。

ZygoteInit.java

AndroidRuntime.start() 执行到最后通过反射调用到 ZygoteInit.main(),至此第一次进入 Java 世界。

public static void main(String argv[]) {// 1.创建ZygoteServerZygoteServer zygoteServer = null; // 调用native函数,确保当前没有其它线程在运行ZygoteHooks.startZygoteNoThreadCreation();        //设置pid为0,Zygote进入自己的进程组Os.setpgid(0, 0);........Runnable caller;try {........//得到systrace的监控TAGString bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,Trace.TRACE_TAG_DALVIK);//通过systradce来追踪 函数ZygoteInit, 可以通过systrace工具来进行分析//traceBegin 和 traceEnd 要成对出现,而且需要使用同一个tagbootTimingsTraceLog.traceBegin("ZygoteInit"); //开启DDMS(Dalvik Debug Monitor Service)功能//注册所有已知的Java VM的处理块的监听器。//线程监听、内存监听、native 堆内存监听、debug模式监听等等RuntimeInit.preForkInit(); 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])) {startSystemServer = true;//启动zygote时,才会传入参数:start-system-server} else if ("--enable-lazy-preload".equals(argv[i])) {enableLazyPreload = true;//启动zygote_secondary时,才会传入参数:enable-lazy-preload} else if (argv[i].startsWith(ABI_LIST_ARG)) { //通过属性ro.product.cpu.abilist64\ro.product.cpu.abilist32 从C空间传来的值abiList = argv[i].substring(ABI_LIST_ARG.length());} else if (argv[i].startsWith(SOCKET_NAME_ARG)) {zygoteSocketName = argv[i].substring(SOCKET_NAME_ARG.length());//会有两种值:zygote和zygote_secondary} else {throw new RuntimeException("Unknown command line argument: " + argv[i]);}} // 根据传入socket name来决定是创建zygote还是zygote_secondaryfinal boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);// 在第一次zygote启动时,enableLazyPreload为false,执行preloadif (!enableLazyPreload) {//systrace 追踪 ZygotePreloadbootTimingsTraceLog.traceBegin("ZygotePreload");// 3.加载进程的资源和类preload(bootTimingsTraceLog);//systrae结束 ZygotePreload的追踪bootTimingsTraceLog.traceEnd(); // ZygotePreload}//结束ZygoteInit的systrace追踪bootTimingsTraceLog.traceEnd(); // ZygoteInit//禁用systrace追踪,以便fork的进程不会从zygote继承过时的跟踪标记Trace.setTracingEnabled(false, 0);            // 4.调用ZygoteServer 构造函数,创建socket,会根据传入的参数,// 创建两个socket:/dev/socket/zygote 和 /dev/socket/zygote_secondaryzygoteServer = new ZygoteServer(isPrimaryZygote);if (startSystemServer) {//5.fork出system serverRunnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); // 启动SystemServerif (r != null) {r.run();return;}} // 6.zygote进程进入无限循环,处理请求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();}}

ZygoteInit 的 main 函数主要完成了以下工作:

  1. 调用 preload() 来预加载类和资源
  2. 调用 ZygoteServer() 创建了两个 Server 端的 Socket 分别为 /dev/socket/zygote 和 /dev/socket/zygote_secondary,Socket 用来等待 AMS 发送过来的请求,请求 zygote 进程创建新的应用程序进程。
  3. 调用 forkSystemServer 来启动 SystemServer 进程,这样系统的关键服务也会通过 SystemServer 进程启动起来。
  4. 最后调用 runSelectLoop 函数来等待客户端请求

总结

1.首先解析 init.rc 文件,创建 Zygote 进程,执行 app_process 程序,该程序入口为 app_main.cpp 的 main 函数。

2.在 app_main.cpp 中调用 AndroidRuntime.start 函数,在 start() 函数中调用 startVm() 创建 Java 虚拟机,然后调用 startReg() 来注册 JNI 函数,最后通过 JNI 调用到 ZygoteInit.main(),进入 Java 框架层(此前没有任何代码进入过 Java 框架层)。

3.在 ZygoteInit.main() 中调用 preload() 预加载类和资源,调用 ZygoteServer() 构造函数创建了两个Socket,用于响应 AMS 发送的请求。调用 forkSystemServer() 启动 SystemServer 进程。最后调用 runSelectLoop() 循环等待 AMS 的请求。

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

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

相关文章

SSM框架整合:掌握Spring+Spring MVC+MyBatis的完美结合!

SSM整合 1.1 流程分析1.2 整合配置步骤1&#xff1a;创建Maven的web项目步骤2:添加依赖步骤3:创建项目包结构步骤4:创建SpringConfig配置类步骤5:创建JdbcConfig配置类步骤6:创建MybatisConfig配置类步骤7:创建jdbc.properties步骤8:创建SpringMVC配置类步骤9:创建Web项目入口配…

部署 LVS-DR 群集

本章内容&#xff1a; -了解LVS-DR群集的工作原理 -会构建LVS-DR负载均衡群集 2.1 LVS-DR 集群 LVS-DR &#xff08; Linux Virtual Server Director Server &#xff09;工作模式&#xff0c;是生产环境中最常用的一 种工作模式。 2.1.1 &#xff0e; LVS-DR 工作原理 …

Idea Community社区版,新建module不使用maven archetype

最近使用 idea 社区版&#xff0c;新建module时始终要选择maven archetype&#xff0c;如下图&#xff1a; 后来发现需要手动切换左侧的菜单栏&#xff0c;在"New Module"上点击一下&#xff0c;就可以了&#xff1a; 记录一下&#xff0c;方便以后查阅

模板设计模式-实例

在软件开发中&#xff0c;有时也会遇到类似的情况&#xff0c;某个方法的实现需要多个步骤&#xff08;类似“请客”&#xff09;&#xff0c;其 中有些步骤是固定的&#xff08;类似“点单”和“买单”&#xff09;&#xff0c;而有些步骤并不固定&#xff0c;存在可变性&…

性能测试分析案例-使用动态追踪定位性能瓶颈

所谓动态追踪&#xff0c;就是在系统或者应用程序正常运行的时候&#xff0c;通过内核中提供的探针&#xff0c;来动态追踪它们的行为&#xff0c;从而辅助排查出性能问题的瓶颈。 使用动态追踪&#xff0c;可以在不修改代码、不重启服务的情况下&#xff0c;动态了解应用程序或…

点的旋转变换

情形一&#xff08;active or alibi transformation主动变换&#xff09; 在坐标系 x − y x-y x−y中&#xff0c;点 p 1 p_1 p1​逆时针旋转 α \alpha α后到达点 p 2 p_2 p2​。 p 1 p_1 p1​在 x − y x-y x−y中的表示与 p 2 p_2 p2​在 x ′ − y ′ x-y x′−y′中的表…

数据结构初探:揭开数据结构奥秘

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、算法模板、汇编语言 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 数组结构起源二. 基本概念和术语2.1 数据2.2 数据元素2.3 数据项2.4 …

Linux第27步_在虚拟机中安装“设备树编译工具”

设备树英文名字叫做Device tree&#xff0c;用来描述板子硬件信息的&#xff0c;比如开发板上的 CPU有几个核 、每个CPU核主频是多少&#xff0c;IIC、SPI这些外设的寄存器范围是多少&#xff0c;IIC接口下都挂了哪些设备等等。 设备树文件是一种文本格式的文件&#xff0c;方…

小程序中滚动字幕

需求&#xff1a;在录像时需要在屏幕上提示字幕&#xff0c;整体匀速向上滚动 html部分&#xff1a; <view class"subtitles_main"><view style"font-size:34rpx;color: #fff;line-height: 60rpx;" animation"{{animation}}">人生的…

React16源码: React中的schedule调度整体流程

schedule调度的整体流程 React Fiber Scheduler 是 react16 最核心的一部分&#xff0c;这块在 react-reconciler 这个包中这个包的核心是 fiber reconciler&#xff0c;也即是 fiber 结构fiber的结构帮助我们把react整个树的应用&#xff0c;更新的流程&#xff0c;能够拆成每…

对git中tag, branch的重新理解

1. 问题背景 项目中之前一个tag&#xff08;v1.0&#xff09;打错了&#xff0c;想删除它&#xff0c;但我们从此tag v1.0中迁出新建分支Branch_v1.0,在此分支下修复了bug&#xff0c;想重新打一个tag v1.0&#xff0c;原来的tag v1.0可以删除掉吗&#xff1f; 错误的理解&am…

Mysql如何优化慢查询

如何优化慢查询 慢 SQL 的优化&#xff0c;主要从两个方面考虑&#xff0c;SQL 语句本身的优化&#xff0c;以及数据库设计的优化。 1、避免不必要的列 覆盖索引会导致回表&#xff0c;且增大了IO 2、分页优化 深分页解决方案 使用子查询in 使用连接表 left join 使用游标&a…

计算机毕业设计-----SSH计算机等级考试报名系统

项目介绍 该项目分为前后台&#xff0c;分为管理员与普通用户两种角色&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,修改个人密码&#xff0c;院系信息管理&#xff0c;注册用户管理&#xff0c;留…

JAVA数组以及小练习

目录 数组的概述和静态初始化 数组的地址值和元素访问 数组的遍历 数组的动态初始化 数组练习 数组的概述和静态初始化 package 数组;public class array1 {public static void main(String[] args){//格式//静态初始化//数据类型 [] 数组名 new 数组类型[]{元素1&#xf…

java期末复习题

1.任何一个Java程序都默认引入一个包&#xff0c;这个包的名字是________________。 正确答案&#xff1a;java.lang Java程序默认引入的包是java.lang包。这个包是Java语言的核心&#xff0c;它提供了Java中的基础类&#xff0c;包括基本Object类、Class类、String类、基本类…

【模块系列】STM32BMP280

前言 最进想练习下I2C的应用&#xff0c;手上好有BMP280也没用过&#xff0c;就看着机翻手册和原版手册&#xff0c;开始嘎嘎写库函数了。库的命名应该还1是比较规范了吧&#xff0c;就是手册对于最终值的计算方式很迷糊&#xff0c;所以现在也不能保证有可靠性啊&#xff0c;大…

磁盘直通卡/阵列卡讲解

服务器SAS卡 ① 华为SR120 (LSI 2308 6Gb SAS直通卡),适合数据安全等级不高或 更换简单 硬盘即插即用 ② 华为SR320 (LSI 2208 6Gb SAS阵列卡 带512M缓存),适合对数据安全等级要求高或追求磁盘性能的客户 推荐上阵列卡 ③ 华为SR130 (LSI 3008 12Gb SAS直通卡),适合数据安全等…

【Spring 篇】深入探索:Spring集成Web环境的奇妙世界

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇有关Spring集成Web环境的博客。如果你曾对如何在Spring中构建强大的Web应用程序感到好奇&#xff0c;那么这里将为你揭示Web开发的神秘面纱。我们将用情感丰富、语句通顺的文字&#xff0c;以小白友好的方式&#xff0c;一探…

Mac M2芯片pycharm配置conda python环境

Mac M2芯片pycharm配置conda python环境 详细步骤如下 1、pycharm界面右上方的小齿轮⚙️&#xff0c;进入Setting…状态 2、进入setting界面后&#xff0c;选择左边栏的Project-->python Interpreter,然后选择右边的Add Interpreter 3、进入Add Interpreter后&#xff0c…

竞赛保研 基于深度学的图像修复 图像补全

1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于深度学的图像修复 图像补全 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-se…