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.rc
 service zygote /system/bin/app_process64 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
     class main
     priority -20
     user root
     group root readproc reserved_disk
     socket zygote stream 660 root system
     socket usap_pool_primary stream 660 root system
     onrestart exec_background - system system -- /system/bin/vdc volume abort_fuse
     onrestart write /sys/power/state on
     # NOTE: If the wakelock name here is changed, then also
     # update it in SystemSuspend.cpp
     onrestart write /sys/power/wake_lock zygote_kwl
     onrestart restart audioserver
     onrestart restart cameraserver
     onrestart restart media
     onrestart restart media.tuner
     onrestart restart netd
     onrestart restart wificond
     task_profiles ProcessCapacityHigh MaxPerformance
     critical 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-cache
        maybeCreateDalvikCache();

        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_process
    if (!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");
        // 【通过反射调用ZygoteInit.main()方法】
        env->CallStaticVoidMethod(startClass, startMeth, strArray);
    }
    //释放相应对象的内存空间
    free(slashClassName);
    mJavaVM->DetachCurrentThread();
    mJavaVM->DestroyJavaVM();
}

start函数主要做了三件事:

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

startVM

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

// 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.txt
    parseRuntimeOption("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)
{
    //设置线程创建方法为javaCreateThreadEtc
    androidSetCreateThreadFunc((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.cpp
  static 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 NDEBUG
              ALOGD("----------!!! %s failed to load\n", array[i].mName);
  #endif
              return -1;
          }
      }
      return 0;
  }
 
//gRegJNI是一个全局数组,定义如下:
//frameworks/base/core/jni/AndroidRuntime.cpp
  static 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.创建ZygoteServer
        ZygoteServer zygoteServer = null; 
        // 调用native函数,确保当前没有其它线程在运行
        ZygoteHooks.startZygoteNoThreadCreation();        
        //设置pid为0,Zygote进入自己的进程组
        Os.setpgid(0, 0);
        ........
        Runnable caller;
        try {
            ........
            //得到systrace的监控TAG
            String bootTimeTag = Process.is64Bit() ? "Zygote64Timing" : "Zygote32Timing";
            TimingsTraceLog bootTimingsTraceLog = new TimingsTraceLog(bootTimeTag,
                    Trace.TRACE_TAG_DALVIK);
            //通过systradce来追踪 函数ZygoteInit, 可以通过systrace工具来进行分析
            //traceBegin 和 traceEnd 要成对出现,而且需要使用同一个tag
            bootTimingsTraceLog.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_secondary
            final boolean isPrimaryZygote = zygoteSocketName.equals(Zygote.PRIMARY_SOCKET_NAME);
             
            // 在第一次zygote启动时,enableLazyPreload为false,执行preload
            if (!enableLazyPreload) {
                //systrace 追踪 ZygotePreload
                bootTimingsTraceLog.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_secondary
            zygoteServer = new ZygoteServer(isPrimaryZygote);
 
            if (startSystemServer) {
                //5.fork出system server
                Runnable r = forkSystemServer(abiList, zygoteSocketName, zygoteServer); 
                // 启动SystemServer
                if (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 函数,最后通过反射调用到 ZygoteInit.main(),进入 Java 框架层(此前没有任何代码进入过 Java 框架层)。

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1、Redis核心数据结构: 魔法般的数据存储之旅

在当今世界的软件开发中&#xff0c;数据的快速、高效存储和检索是至关重要的。而在这个领域&#xff0c;Redis&#xff08;Remote Dictionary Server&#xff09;以其强大的性能和多样化的数据结构而脱颖而出。本文将带您深入探讨Redis的核心数据结构&#xff0c;揭示其背后的…

1.12寒假集训

A: 解题思路&#xff1a; 输出整体可以分成两部分&#xff1a; 第一部分循环3*n次&#xff0c;第一部分又可以分成三个部分&#xff0c;分别是*,.,*&#xff0c;分别循环n,2 * n,n次 第二部分循环n次,又可以分成五个部分&#xff0c;.,*,.,*,.,循环i,n(n - i) * 2,n,i次 下…

(1)(1.13) SiK无线电高级配置(六)

文章目录 前言 15 使用FTDI转USB调试线配置SiK无线电设备 16 强制启动加载程序模式 17 名词解释 前言 本文提供 SiK 遥测无线电(SiK Telemetry Radio)的高级配置信息。它面向"高级用户"和希望更好地了解无线电如何运行的用户。 15 使用FTDI转USB调试线配置SiK无线…

国际化翻译系统V2正式上线了

1、前言 之前上线了移动端国际化翻译系统V1版本&#xff0c;其中有一些弊端&#xff0c;例如&#xff1a; 1、项目仅能适用于Android和iOS项目&#xff0c;针对前端项目&#xff0c;Flutter项目&#xff0c;和后端项目无法支持2、之前的桌面程序需要搭建本地java环境才能运行…

【2023年度总结与2024展望】---23年故事不长,且听我来讲

文章目录 前言一、学习方面1.1 攥写博客1.2 学习内容1.3 参加比赛获得证书 二、生活方面2.1写周报记录生活 三、运动方面四、CSDN的鼓励五、24年展望总结 前言 时光飞逝&#xff0c;又是新的一年&#xff0c;遥想去年2023年我也同样在这个时间段参加了CSDN举办的年度总结活动&a…

别再纠结,这8款设计工具助你轻松绘制原型图!

原型图设计工具有很多优点。除了帮助设计师快速与客户达成协议&#xff0c;避免项目前景的冲突外&#xff0c;原型图设计工具还可以让客户看到正在创建的内容。如果需要更改&#xff0c;原型图设计工具也可以轻松完成。本文快速总结了8种原型图设计工具。无论你是专业设计师还是…

如何更改路由器Wi-Fi密码,这里提供通用步骤

这篇文章解释了如何通过路由器的设置更改Wi-Fi密码&#xff0c;即使你不知道当前的密码。 如何更改你的Wi-Fi密码 该过程按照以下一般步骤展开。 ​重要&#xff1a;这些是更改Wi-Fi密码的通用说明。更改路由器设置所需的步骤因不同制造商的路由器而异&#xff0c;甚至可能在…

Python入门0基础学习笔记

1.编程之前 在编写代码之前&#xff0c;还有两件事需要做&#xff1a; 安装 Python 解释器&#xff1a;计算机是没法直接读懂 Python 代码的&#xff0c;需要一个解释器作为中间的翻译&#xff0c;把代码转换成字节码之后再执行。 Python 是翻译一行执行一行。一般说的安装 …

【解决】Unity Project 面板资源显示丢失的异常问题处理

开发平台&#xff1a;Unity 2021.3.7f1c1   一、问题描述 在开发过程中&#xff0c;遭遇 Project 面板资源显示丢失、不全的问题。但 Unity Console 并未发出错误提示。   二、解决方案&#xff1a;删除 Library 目录 前往 “工程目录/Library” 删除内部所有文件并重打开该…

IntelliJ IDEA 如何编译 Maven 工程项目

在当今的Java开发领域&#xff0c;Maven已经成为项目构建和依赖管理的标准工具。IntelliJ IDEA作为一款集成度高的Java开发环境&#xff0c;提供了许多强大的功能来简化和优化Maven项目的构建流程。本文将深入介绍如何使用IntelliJ IDEA编译Maven工程的详细步骤以及一些高级技巧…

用java搞定时任务,将hashmap里面的值存到文件里面去

要实现这个功能&#xff0c;你可以使用Java的Timer和TimerTask类来创建一个定时任务。 首先&#xff0c;你需要创建一个继承自TimerTask的类&#xff0c;重写run方法&#xff0c;将HashMap中的内容写入文本文件。 然后&#xff0c;使用Timer类的schedule方法来设置定时任务的执…

记忆泊车PNC模块架构设计说明书

目 录 0 修订历史......... 2 1. 概要 ............... 5 1.1. 目的 ............... 5 1.2. 参考文档 ......... 5 2. 名词解释 ...... 5 3. 需求概述 ............. 6 3.1. 业务视图 .............. 6 3.2. 功能描述 ............... 6 3.3. 性能指标 ............ 6 3.4. 资…

【软件安全:软件安全技术课后习题及答案】

第一章 1-1 零日漏洞、零日攻击 零日漏洞是指未被公开披露的软件漏洞&#xff0c;没有给软件的作者或厂商以时间去为漏洞打补丁或是给出建议解决方案&#xff0c;从而攻击者能够利用这种漏洞破坏计算机程序、数据及设备。 利用零日漏洞开发攻击工具进行的攻击称为零日攻击。 1-…

Ubuntu server搭建dhcp服务器

安装 直接使用一下命令进行安装 apt-get install isc-dhcp-server 以下就是安装好的图片 然后进入dhcp目录 cd /etc/dhcp 进入后用ls查看当前目录存在哪些文件 使用如下进入dhcp.conf vim dhcpd.conf 红&#xff1a;设置ip域和子网掩码 绿&#xff1a;设置ip池范围 黄…

爬虫利器一览

前言 爬虫&#xff08;英文&#xff1a;spider&#xff09;&#xff0c;可以理解为简单的机器人&#xff0c;如此一个“不为名利而活&#xff0c;只为数据而生&#xff0c;目标单纯&#xff0c;能量充沛&#xff0c;不怕日晒雨淋&#xff0c;不惧寒冬酷暑”的家伙&#xff0c;…

【深度学习】Anaconda3 + PyCharm 的环境配置 1:手把手带你安装 PyTorch 并创建 PyCharm 项目

前言 文章性质&#xff1a;实操记录 &#x1f4bb; 主要内容&#xff1a;这篇文章记录了 PyTorch 的安装过程&#xff0c;包括&#xff1a; 1. 创建并激活新的虚拟环境&#xff1b; 2. 查看电脑是否支持 CUDA 以及 CUDA 的版本&#xff1b; 3. 根据 CUDA 的版本安装 PyTorch&am…

归并排序例题——逆序对的数量

做道简单一点的题巩固一下 归并排序实现步骤 将整个区间 [l, r] 划分为 [l, mid] 和 [mid1, r]。 递归排序 [l, mid] 和 [mid1, r]。 将左右两个有序序列合并为一个有序序列。 题目描述 给定一个长度为 n 的整数数列&#xff0c;请计算数列中的逆序对的数量。 逆序对的定义…

简单的推箱子游戏实战

目录 项目分析 地图初始化 背景图片 游戏场景图片: 热键控制 按键设置 确定人物位置 实现人物移动(非箱子,目的地) 推箱子控制 游戏结束 最终代码 合法性判断: 项目分析 墙:0,地板:1,箱子目的地:2,小人:3,箱子:4,箱子命中目标:5 地图初始化 背景图片 #include <…

煤炭行业电力能源消耗监测管理系统的作用有哪些?

如果说&#xff0c;通风是煤炭的呼吸系统&#xff0c;那么供电就是煤矿的神经系统。安全供电对安全生产有着重要的意义。一旦供电系统出现故障或停电&#xff0c;煤矿的生产活动将无法正常进行&#xff0c;这将产生严重的经济损失甚至危及工人的生命安全。 为了提高煤矿供电系统…

机器视觉检测设备在连接器外观缺陷检测中的应用

作为传输电流或信号连接两个有源器件的器件&#xff0c;连接器被广泛应用于各个行业&#xff0c;从手机、平板、电脑&#xff0c;到冰箱、空调、洗衣机&#xff0c;再到汽车、国防、航空&#xff0c;处处是它的所在。每个电子产品少了连接器将无法运作&#xff0c;因此&#xf…