Android应用程序进程的启动过程

news2025/1/24 22:45:02

Android应用程序进程的启动过程

在这里插入图片描述

导语

到这篇文章为止,我们已经简要地了解过了Android系统的启动流程了,其中比较重要的内容有Zygote进程的启动和SystemService以及Launcher的启动,接下来我们将要学习的是Android应用程序的启动过程,这篇文章将会比较简单地介绍这个过程,下面是一张个人总结出来的流程图:
在这里插入图片描述

ActivityManagerService向Zygote服务发送请求

其实在之前Zygote服务启动的过程中,我们已经提到了这个Zygote服务将会创建出一个Socket来用于给ActivityManagerService使用,应用程序进程的启动就需要ActivityManagerService来给Zygote的服务端发送请求。

首先我们介绍ProcessRecord类,这个类是Android系统中用于表示应用程序进程的数据结构。每当应用程序启动时,系统都会为其创建一个ProcessRecord对象来跟踪该进程的状态和信息。

具体来说,在ActivityMangerService中也是通过这个类来启动进程的,来看看这个方法:

   @GuardedBy("mService")
    boolean startProcessLocked(ProcessRecord app, HostingRecord hostingRecord,
            int zygotePolicyFlags, boolean disableHiddenApiChecks, boolean disableTestApiChecks,
            String abiOverride) {
        if (app.isPendingStart()) {
            return true;
        }
        final long startUptime = SystemClock.uptimeMillis();
        final long startElapsedTime = SystemClock.elapsedRealtime();
        if (app.getPid() > 0 && app.getPid() != ActivityManagerService.MY_PID) {
            checkSlow(startUptime, "startProcess: removing from pids map");
            mService.removePidLocked(app.getPid(), app);
            app.setBindMountPending(false);
            mService.mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
            checkSlow(startUptime, "startProcess: done removing from pids map");
            app.setPid(0);
            app.setStartSeq(0);
        }
        // Clear any residual death recipient link as the ProcessRecord could be reused.
        app.unlinkDeathRecipient();
        app.setDyingPid(0);

        if (DEBUG_PROCESSES && mService.mProcessesOnHold.contains(app)) Slog.v(
                TAG_PROCESSES,
                "startProcessLocked removing on hold: " + app);
        mService.mProcessesOnHold.remove(app);

        checkSlow(startUptime, "startProcess: starting to update cpu stats");
        mService.updateCpuStats();
        checkSlow(startUptime, "startProcess: done updating cpu stats");

        try {
            final int userId = UserHandle.getUserId(app.uid);
            try {
                AppGlobals.getPackageManager().checkPackageStartable(app.info.packageName, userId);
            } catch (RemoteException e) {
                throw e.rethrowAsRuntimeException();
            }

            int uid = app.uid;
            int[] gids = null;
            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
            boolean externalStorageAccess = false;
            if (!app.isolated) {
                int[] permGids = null;
                try {
                    checkSlow(startUptime, "startProcess: getting gids from package manager");
                    final IPackageManager pm = AppGlobals.getPackageManager();
                    permGids = pm.getPackageGids(app.info.packageName,
                            MATCH_DIRECT_BOOT_AUTO, app.userId);
                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
                            StorageManagerInternal.class);
                    mountExternal = storageManagerInternal.getExternalStorageMountMode(uid,
                            app.info.packageName);
                    externalStorageAccess = storageManagerInternal.hasExternalStorageAccess(uid,
                            app.info.packageName);
                    if (pm.checkPermission(Manifest.permission.INSTALL_PACKAGES,
                            app.info.packageName, userId)
                            == PackageManager.PERMISSION_GRANTED) {
                        Slog.i(TAG, app.info.packageName + " is exempt from freezer");
                        app.mOptRecord.setFreezeExempt(true);
                    }
                } catch (RemoteException e) {
                    throw e.rethrowAsRuntimeException();
                }

                if (app.processInfo != null && app.processInfo.deniedPermissions != null) {
                    for (int i = app.processInfo.deniedPermissions.size() - 1; i >= 0; i--) {
                        int[] denyGids = mService.mPackageManagerInt.getPermissionGids(
                                app.processInfo.deniedPermissions.valueAt(i), app.userId);
                        if (denyGids != null) {
                            for (int gid : denyGids) {
                                permGids = ArrayUtils.removeInt(permGids, gid);
                            }
                        }
                    }
                }

                gids = computeGidsForProcess(mountExternal, uid, permGids, externalStorageAccess);
            }
            app.setMountMode(mountExternal);
            checkSlow(startUptime, "startProcess: building args");
            if (mService.mAtmInternal.isFactoryTestProcess(app.getWindowProcessController())) {
                uid = 0;
            }
            int runtimeFlags = 0;

 			...........
            app.setGids(gids);
            app.setRequiredAbi(requiredAbi);
            app.setInstructionSet(instructionSet);

            ApplicationInfo definingAppInfo;
            if (hostingRecord.getDefiningPackageName() != null) {
                definingAppInfo = new ApplicationInfo(app.info);
                definingAppInfo.packageName = hostingRecord.getDefiningPackageName();
                definingAppInfo.uid = uid;
            } else {
                definingAppInfo = app.info;
            }

            runtimeFlags |= Zygote.getMemorySafetyRuntimeFlags(
                    definingAppInfo, app.processInfo, instructionSet, mPlatformCompat);

            if (TextUtils.isEmpty(app.info.seInfoUser)) {
                Slog.wtf(ActivityManagerService.TAG, "SELinux tag not defined",
                        new IllegalStateException("SELinux tag not defined for "
                                + app.info.packageName + " (uid " + app.uid + ")"));
            }
            final String seInfo = app.info.seInfo
                    + (TextUtils.isEmpty(app.info.seInfoUser) ? "" : app.info.seInfoUser);

            final String entryPoint = "android.app.ActivityThread";

            return startProcessLocked(hostingRecord, entryPoint, app, uid, gids,
                    runtimeFlags, zygotePolicyFlags, mountExternal, seInfo, requiredAbi,
                    instructionSet, invokeWith, startUptime, startElapsedTime);
        } catch (RuntimeException e) {
            Slog.e(ActivityManagerService.TAG, "Failure starting process " + app.processName, e);

            mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
                    false, false, true, false, false, app.userId, "start failure");
            return false;
        }
    }

这里截取了部分代码,其实这个方法里做的主要就是获取到了应用程序的一些信息,比如Gid,Uid等,然后把获取到的信息设置到要启动的App中:

     app.setGids(gids);
     app.setRequiredAbi(requiredAbi);
     app.setInstructionSet(instructionSet);

然后最后还需要调用一层重载方法开始正式进行进程的启动:

    boolean startProcessLocked(HostingRecord hostingRecord, String entryPoint, ProcessRecord app,
            int uid, int[] gids, int runtimeFlags, int zygotePolicyFlags, int mountExternal,
            String seInfo, String requiredAbi, String instructionSet, String invokeWith,
            long startUptime, long startElapsedTime) {
       		............
            try {
                final Process.ProcessStartResult startResult = startProcess(hostingRecord,
                        entryPoint, app,
                        uid, gids, runtimeFlags, zygotePolicyFlags, mountExternal, seInfo,
                        requiredAbi, instructionSet, invokeWith, startUptime);
                handleProcessStartedLocked(app, startResult.pid, startResult.usingWrapper,
                        startSeq, false);
            } catch (RuntimeException e) {
                Slog.e(ActivityManagerService.TAG, "Failure starting process "
                        + app.processName, e);
                app.setPendingStart(false);
                mService.forceStopPackageLocked(app.info.packageName, UserHandle.getAppId(app.uid),
                        false, false, true, false, false, app.userId, "start failure");
            }
            return app.getPid() > 0;
        }
    }

这里它又会调用到startProcess和handleProcessStartedLocked方法,startProcessLocked 方法用于启动新的进程,而 handleProcessStartedLocked 方法用于处理进程启动完成后的回调,更新进程状态并通知其他模块有关进程启动完成的事件。这两个方法共同协作以实现进程的启动和管理。

在这里我们主要还是看进程的启动,所以着重来看startProcess方法,这个方法又将调用到Process的start方法:

startResult = Process.start(entryPoint,
                        app.processName, uid, uid, gids, runtimeFlags, mountExternal,
                        app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet,
                        app.info.dataDir, invokeWith, app.info.packageName, zygotePolicyFlags,
                        isTopApp, app.getDisabledCompatChanges(), pkgDataInfoMap,
                        allowlistedAppDataInfoMap, bindMountAppsData, bindMountAppStorageDirs,
                        new String[]{PROC_START_SEQ_IDENT + app.getStartSeq()});

所以接下来看Process的start方法,这个方法又会调转到ZygoteProcess的start方法,并继续跳转到startViaZygote方法中,我们接下来看一看这个方法:

private Process.ProcessStartResult startViaZygote(@NonNull final String processClass,
                                                      @Nullable final String niceName,
                                                      final int uid, final int gid,
                                                      @Nullable final int[] gids,
                                                      int runtimeFlags, int mountExternal,
                                                      int targetSdkVersion,
                                                      @Nullable String seInfo,
                                                      @NonNull String abi,
                                                      @Nullable String instructionSet,
                                                      @Nullable String appDataDir,
                                                      @Nullable String invokeWith,
                                                      boolean startChildZygote,
                                                      @Nullable String packageName,
                                                      int zygotePolicyFlags,
                                                      boolean isTopApp,
                                                      @Nullable long[] disabledCompatChanges,
                                                      @Nullable Map<String, Pair<String, Long>>
                                                              pkgDataInfoMap,
                                                      @Nullable Map<String, Pair<String, Long>>
                                                              allowlistedDataInfoList,
                                                      boolean bindMountAppsData,
                                                      boolean bindMountAppStorageDirs,
                                                      @Nullable String[] extraArgs)
                                                      throws ZygoteStartFailedEx {
        ArrayList<String> argsForZygote = new ArrayList<>();

        // --runtime-args, --setuid=, --setgid=,
        // and --setgroups= must go first
        argsForZygote.add("--runtime-args");
        argsForZygote.add("--setuid=" + uid);
        argsForZygote.add("--setgid=" + gid);
        argsForZygote.add("--runtime-flags=" + runtimeFlags);
        if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
            argsForZygote.add("--mount-external-default");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_INSTALLER) {
            argsForZygote.add("--mount-external-installer");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
            argsForZygote.add("--mount-external-pass-through");
        } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
            argsForZygote.add("--mount-external-android-writable");
        }

        argsForZygote.add("--target-sdk-version=" + targetSdkVersion);

        // --setgroups is a comma-separated list
        if (gids != null && gids.length > 0) {
            final StringBuilder sb = new StringBuilder();
            sb.append("--setgroups=");

            final int sz = gids.length;
            for (int i = 0; i < sz; i++) {
                if (i != 0) {
                    sb.append(',');
                }
                sb.append(gids[i]);
            }

            argsForZygote.add(sb.toString());
        }
		..........
       
        synchronized(mLock) {
            // The USAP pool can not be used if the application will not use the systems graphics
            // driver.  If that driver is requested use the Zygote application start path.
            return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              zygotePolicyFlags,
                                              argsForZygote);
        }
    }

这个变量名argsForZygote的数组显然是用作传参用的,这个方法做的主要也就是创建一个字符串列表argsForZygote,并将应用程序的启动参数保存在这个列表中,最后调用zygoteSendArgsAndGetResult方法将其传给zygote执行,这实际上就是达到了AMS向zygote发送请求的目的:

private Process.ProcessStartResult attemptZygoteSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr) throws ZygoteStartFailedEx {
        try {
            final BufferedWriter zygoteWriter = zygoteState.mZygoteOutputWriter;
            final DataInputStream zygoteInputStream = zygoteState.mZygoteInputStream;

            zygoteWriter.write(msgStr);
            zygoteWriter.flush();

            Process.ProcessStartResult result = new Process.ProcessStartResult();
            result.pid = zygoteInputStream.readInt();
            result.usingWrapper = zygoteInputStream.readBoolean();

            if (result.pid < 0) {
                throw new ZygoteStartFailedEx("fork() failed");
            }

            return result;
        } catch (IOException ex) {
            zygoteState.close();
            Log.e(LOG_TAG, "IO Exception while communicating with Zygote - "
                    + ex.toString());
            throw new ZygoteStartFailedEx(ex);
        }
    }

在这个过程中会调用到上面这个方法,将应用参数的启动数据等写入zygote中,实际上就是向zygote发送了请求了。里面出现的ZygoteState类就是用于表示与Zygote的通信状况的。

再回到之前的

zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),
                                              zygotePolicyFlags,
                                              argsForZygote);

这里的请求就是通过第一个参数(其实也是方法)的socket传达的,我们来看一看这个方法:

    private ZygoteState openZygoteSocketIfNeeded(String abi) throws ZygoteStartFailedEx {
        try {
            attemptConnectionToPrimaryZygote();

            if (primaryZygoteState.matches(abi)) {
                return primaryZygoteState;
            }

            if (mZygoteSecondarySocketAddress != null) {
                // The primary zygote didn't match. Try the secondary.
                attemptConnectionToSecondaryZygote();

                if (secondaryZygoteState.matches(abi)) {
                    return secondaryZygoteState;
                }
            }
        } catch (IOException ioe) {
            throw new ZygoteStartFailedEx("Error connecting to zygote", ioe);
        }

        throw new ZygoteStartFailedEx("Unsupported zygote ABI: " + abi);
    }

实际上就是根据abi参数来建立与Zygote的Socket连接,其实就是根据Zygote的启动脚本类型来建立不同的连接,这里我们就不深入了。

所以到目前为止,ActivityManagerService就成功地读取应用程序的相关信息并将其打包发给Zygote,请求Zygote创建进程了,接下来我们看Zygote是如何响应AMS的请求的。

Zygote服务创建应用程序的进程

在之前的文章中,其实我们已经提到过了,Zygote将在创建完自身的Socket后等待AMS的请求,具体是在ZygoteInit的main方法中:

caller = zygoteServer.runSelectLoop(abiList);

其实这个方法里还是继续跳转,我们直接跳过中间步骤,来到最终调用到的ZygoteConnection方法中:

Runnable processCommand(ZygoteServer zygoteServer, boolean multipleOK) {
        ZygoteArguments parsedArgs;

        try (ZygoteCommandBuffer argBuffer = new ZygoteCommandBuffer(mSocket)) {
            while (true) {
                try {
                    parsedArgs = ZygoteArguments.getInstance(argBuffer);//1-----1
                  
                } 
               .........

                if (parsedArgs.mInvokeWith != null || parsedArgs.mStartChildZygote
                        || !multipleOK || peer.getUid() != Process.SYSTEM_UID) {
                    // Continue using old code for now. TODO: Handle these cases in the other path.
                    pid = Zygote.forkAndSpecialize(parsedArgs.mUid, parsedArgs.mGid,
                            parsedArgs.mGids, parsedArgs.mRuntimeFlags, rlimits,
                            parsedArgs.mMountExternal, parsedArgs.mSeInfo, parsedArgs.mNiceName,
                            fdsToClose, fdsToIgnore, parsedArgs.mStartChildZygote,
                            parsedArgs.mInstructionSet, parsedArgs.mAppDataDir,
                            parsedArgs.mIsTopApp, parsedArgs.mPkgDataInfoList,
                            parsedArgs.mAllowlistedDataInfoList, parsedArgs.mBindMountAppDataDirs,
                            parsedArgs.mBindMountAppStorageDirs);//2-----2

                    try {
                        if (pid == 0) {
                            // in child
                            zygoteServer.setForkChild();

                            zygoteServer.closeServerSocket();
                            IoUtils.closeQuietly(serverPipeFd);
                            serverPipeFd = null;

                            return handleChildProc(parsedArgs, childPipeFd,
                                    parsedArgs.mStartChildZygote);//3--------3
                        } else {
                            IoUtils.closeQuietly(childPipeFd);
                            childPipeFd = null;
                            handleParentProc(pid, serverPipeFd);
                            return null;
                        }
                    } finally {
                        IoUtils.closeQuietly(childPipeFd);
                        IoUtils.closeQuietly(serverPipeFd);
                    }
                } else {
                    ZygoteHooks.preFork();
                    Runnable result = Zygote.forkSimpleApps(argBuffer,
                            zygoteServer.getZygoteSocketFileDescriptor(),
                            peer.getUid(), Zygote.minChildUid(peer), parsedArgs.mNiceName);
                    if (result == null) {
                        // parent; we finished some number of forks. Result is Boolean.
                        // We already did the equivalent of handleParentProc().
                        ZygoteHooks.postForkCommon();
                        // argBuffer contains a command not understood by forksimpleApps.
                        continue;
                    } else {
                        // child; result is a Runnable.
                        zygoteServer.setForkChild();
                        Zygote.setAppProcessName(parsedArgs, TAG);  // ??? Necessary?
                        return result;
                    }
                }
            }
        }
		........
    }

方法很长,我们主要看注释出的三处,第一处的

parsedArgs = ZygoteArguments.getInstance(argBuffer);

方法显然是将我们之间通过AMS发送过来的启动参数给获取出来了。接着注释二处通过zygote将要启动的应用程序的进程就已经fork出来了,到此为止,实际上要启动的进程已经被创建出来了,只不过创建出来后我们还需要将这个进程进行一些处理,所以接下来会调用到注释三处的handleChildProc方法,到了这里之后又会跳转,具体来说会跳转到ZygoteInit.zygoteInit方法,这个方法我们在之前的Zygote的启动中也提到过,它首先会启动进程的Binder进程池,这样这个进程就可以与其他进程进行Binder进程间通信了,然后它会执行具体类的main方法。在这里,他将会启动ActivityThread的main方法。

这个ActivityThread类是Android系统中的核心类之一,它负责管理应用程序的主线程(UI线程)以及应用程序的生命周期和消息处理。每个应用程序在启动时都会创建一个ActivityThread实例,并在该实例的主线程中执行应用程序的主要逻辑。ActivityThread类的主要职责包括:

  1. 应用程序的初始化:在ActivityThread的main()方法中,会创建Application实例,并调用其onCreate()方法来进行应用程序的初始化工作。

  2. 管理Activity的生命周期:ActivityThread负责跟踪和管理应用程序中所有Activity的生命周期。它会接收来自系统的生命周期回调消息,并分发给相应的Activity进行处理,例如调用Activity的onCreate()、onStart()、onResume()等方法。

  3. 处理消息和事件:ActivityThread通过一个消息循环机制,处理来自系统和应用程序的消息和事件。它会接收并分发消息给对应的Handler进行处理,例如处理用户界面事件、处理来自其他组件的消息等。

  4. 启动Activity和Service:ActivityThread负责启动应用程序中的Activity和Service组件。它会接收来自系统的启动请求,并负责创建和启动相应的组件实例。

  5. 处理窗口和界面更新:ActivityThread负责处理窗口和界面的更新操作,包括创建和管理窗口、更新界面布局、处理用户界面事件等。

那么接下来我们就来简单看看它的main方法:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

        // Install selective syscall interception
        AndroidOs.install();

        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        // Call per-process mainline module initialization.
        initializeMainlineModules();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

可以看到这个main方法中会自动创建出一个消息Looper和一个默认的Handler,然后会自动开启消息队列的循环使它可以处理消息。接下来Activity就将由ActivityThread管理启动了,这就已经完成了应用程序进程的启动了。

总结

最后再来回顾一下上面那张总结图:
在这里插入图片描述
这就是应用程序进程的启动过程。

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

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

相关文章

Python爱好者的自我修养(1):简单输入与输出

Python简单输入与输出 1.输出1.1 简单输出1.2 转义字符1.2.1 定义1.2.2 常见的转义字符用法 2.输入3.温馨提示 终于…… 终于…… 我开始玩Python了 &#xff08;不是C不学了哈&#xff0c;C还是照更~&#xff09; 今天先来简单讲下输入和输出 1.输出 1.1 简单输出 输出的函…

【一篇让你学会】Web接口测试工具--Jmeter

关于Jmeter性能测试工具不再过多介绍。如果你要学习软件性能测试&#xff0c;那么多少应该会对它有所耳闻。 强烈建议阅读官方文档学习&#xff1a;http://jmeter.apache.org/index.html 还有比这个更权威更全面的介绍Jmeter工具使用的么&#xff1f; 不过&#xff0c;此处要介…

Win7批量执行Python文件

问题背景 平时都是用Pycharm跑代码&#xff0c;但是每次都需要在Configuration里修改Parameters&#xff0c;跑完一个才能重新修改跑下一个&#xff0c;很不方便&#xff0c;于是决定借助.bat文件实现批量执行。 困难一 电脑存在cmd闪退问题&#xff0c;之前一直逃避懒得解决…

ExtractOfficeContent: 提取Office文件中文本、表格和图像

引言 最近有空写了一下这个库&#xff0c;用来提取Office文件中的文本和图像内容&#xff0c;用作后续整理训练语料使用。最新更新请移步&#xff1a;Github Extract Office Content Use Installextract_office_content$ pip install extract_office_contentRun by CLI. Ext…

STM32——04-初识STM32单片机

什么是单片机&#xff1f; 单片机&#xff08; Single-Chip Microcomputer &#xff09;是一种集成电路芯片&#xff0c;把具有数据处理能力的中央处 理器 CPU 、随机存储器 RAM 、只读存储器 ROM 、多种 I/O 口和中断系统、定时器 / 计数器等功 能&#xff08;可能还包括显示驱…

day49_mybatis

今日内容 1 引言 2 MyBatis介绍 3 环境搭建 4 入门演示(MyBatis开发步骤) 5 CRUD 6 配置细节 一、引言 1.1 复习 第一阶段(JavaSE) java基本语法流程控制面向对象常用类集合异常IO多线程JDK新特性 第二阶段(JavaWeb) 前端 htmlcssjs --> JQueryboostraplayui 页面元素内置模…

攻防世界—file_include

打开之后发现是一段php代码 可以看出这是段代码有文件包含漏洞 下面是学习部分。。。着急看题解继续往下滑。。。谢谢。。。 文件包含漏洞&#xff08;File Inclusion Vulnerability&#xff09;是一种Web应用程序常见的安全漏洞&#xff0c;也是攻击者常用的攻击手段之一。这种…

一文讲完Java常用设计模式(全23种)

文章目录 介绍设计模式的六大原则一、创建型模式1、单例模式&#xff08;Singleton Pattern&#xff09;1&#xff09;饿汉式2&#xff09;懒汉式&#xff0c;双检锁3&#xff09;静态内部类4&#xff09;枚举 2、原型模式&#xff08;Prototype Pattern&#xff09;3、工厂模式…

基于Java+SpringBoot+Vue前后端分离教学资源共享平台系统

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

10:00面试,10:08就出来了 ,问的实在是太...

从外包出来&#xff0c;没想到算法死在另一家厂子 自从加入这家公司&#xff0c;每天都在加班&#xff0c;钱倒是给的不少&#xff0c;所以也就忍了。没想到8月一纸通知&#xff0c;所有人不许加班&#xff0c;薪资直降30%&#xff0c;顿时有吃不起饭的赶脚。 好在有个兄弟内…

如何利用google的protobuf设计、实现自己的RPC框架

一、前言 这篇文章我们就来聊一聊 RPC 的相关内容&#xff0c;来看一下如何利用 Google 的开源序列化工具 protobuf&#xff0c;来实现一个我们自己的 RPC 框架&#xff0c;内容有点长&#xff0c;请耐心看完。 序列化[1]&#xff1a;将结构数据或对象转换成能够被存储和传输&…

有趣的机器人工具坐标系

当机器人旋转轴上的夹爪中心不在旋转轴中心时&#xff0c;如何让旋转轴围绕夹爪中心旋转&#xff0c;这就是工具坐标系&#xff0c;怎末实现呢&#xff1f;机器人都是建立工具坐标系实现&#xff0c;这是什么原理&#xff1f;我们来探索一下&#xff1a; 世界上的有些特例让人…

【AIOT】手势捕捉调研

title: Data Glove Record date: 2020-06-06 20:40:13 author: liudongdong1 img: https://gitee.com/github-25970295/blogImage/raw/master/img/gloves-1268930__340.webp reprintPolicy: cc_by cover: false categories: AIOT tags: Sense 动作捕捉(Motion capture)&#x…

程序员凡尔赛,工作三年晒出5月工资条,直言加班太累了

最近有工作3年的程序员晒出自己9月份的工资条&#xff0c;并直言加班太累了。 从工资条上可以看到&#xff0c;这个收入确实不算低&#xff0c;才3年时间&#xff0c;月工资就已经到了二万五了&#xff0c;这个工资已经可以击败绝大多数行业了。 不过二万五只是税前工资&…

【Python opencv 】零基础也能轻松掌握的学习路线与参考资料

Python opencv 是一种强大的计算机视觉库&#xff0c;它为计算机视觉和图像处理任务提供了必要的工具和技术实现。接下来&#xff0c;将介绍Python opencv的学习路线、重点和优秀实践。 一、学习路线 Python基础 在学习Python opencv之前&#xff0c;需要掌握Python的基础知…

STM32F105RBT6 使用定时器TIM3输出PWM波

1. TIM3的GPIO口&#xff0c;查阅STM32F105RBT6 数据手册&#xff0c;TIM3的4通道用的是PB1 2. 初始化GPIO口和定时器TIM3 2.1 相关函数 RCC_APB1PeriphClockCmd、GPIO_Init、TIM_TimeBaseInit、TIM_OC4Init、TIM_OC4PreloadConfig、NVIC_Init、TIM_ITConfig、TIM_Cmd、 voi…

中国范围逐月夜间灯光数据(2012-2021年)

人类以其文明和智慧创造了城市&#xff0c;城市最美的一面就是夜晚的灯光。对于经济学家来说&#xff0c;城市灯光的背后隐藏着巨大的社会经济秘密。一个城市夜晚的灯光不仅可以反映这个城市人口的数量、预测房价的趋势还能反映这个城市的经济发达程度。 根据灯光的亮度我们还可…

(七)CSharp-CSharp图解教程版-事件

一、发布者和订阅者 发布者/订阅者模式&#xff08;publish/subscriber pattern&#xff09;&#xff1a; 很多程序都有一个共同的需求&#xff0c;即当一个特定的程序事件发生时&#xff0c;程序的其他部分可以得到该事件已经发生的通知。 发布者&#xff1a; 发布者类定义…

SpringSecurity 总结

SpringSecurity 总结 第一章 权限管理 权限管理SpringSecurity 简介整体架构 权限管理&#xff1a; 实现: "对用户访问系统的控制"(身份认证) &#xff0c; 按照 "安全规则"或者 "安全策略" (对已经认证的用户进行授权) 控制&#xff0c;用…

C++教程(05)——数据类型

C 数据类型 使用编程语言进行编程时&#xff0c;需要用到各种变量来存储各种信息。变量保留的是它所存储的值的内存位置。这意味着&#xff0c;当您创建一个变量时&#xff0c;就会在内存中保留一些空间。 您可能需要存储各种数据类型&#xff08;比如字符型、宽字符型、整型…