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

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.mfbz.cn/a/27559.html

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

相关文章

华为OD机试真题 JavaScript 实现【最多几个直角三角形】【2023Q1 100分】

一、题目描述 有 N 条线段&#xff0c;长度分别为 a[1]-a[n]。 现要求你计算这 N 条线段最多可以组合成几个直角三角形&#xff0c;每条线段只能使用一次&#xff0c;每个三角形包含三条线段。 二、输入描述 第一行输入一个正整数 T (1< T< 100) &#xff0c;表示有…

2023蓝桥杯大学A组C++决赛游记+个人题解

Day0 发烧了一晚上没睡着&#xff0c;感觉鼻子被打火机烧烤一样难受&#xff0c;心情烦躁 早上6点起来吃了个早饭&#xff0c;思考能力完全丧失了&#xff0c;开始看此花亭奇谭 看了六集&#xff0c;准备复习数据结构考试&#xff0c;然后秒睡 一睁眼就是下午2点了 挂了个…

springboot项目外卖管理 day05-新增与删除套餐

文章目录 一、新增菜品1.1、需求分析1.2、数据模型setmealsetmeal_dish 1.3、代码开发-梳理交互过程1.3.1、下拉框展示1.3.2、菜品窗口展示1.3.3、新增套餐 2、套餐分页查询 一、新增菜品 1.1、需求分析 套餐就是菜品的集合。 后台系统中可以管理套餐信息&#xff0c;通过新…

一文打通:从字节码指令的角度解读前置后置自增自减(加加++减减--)

文章目录 1.前置了解的知识1.1 栈这种数据结构1.2 局部变量表和操作数栈1.3 三个字节码指令 2.单独使用后置与前置2.1 后置字节码指令2.2 前置字节码指令2.3 总结 3.需要返回值的情况下使用后置与前置3.1 后置字节码指令3.2 前置字节码指令3.3 总结3.4 练习&#x1f340; 练习一…

了解ASEMI代理英飞凌TLE6208-6G其功能和应用的综合指南

编辑-Z TLE6208-6G是一款高度集成、通用且高效的汽车半桥驱动器&#xff0c;由英飞凌设计。这种功能强大的设备专门设计用于满足汽车应用的苛刻要求&#xff0c;如控制直流电机、螺线管和电阻负载。在本文中&#xff0c;我们将深入研究TLE6208-6G的功能、优点和应用&#xff0…

实现表白墙

我们已经学习了Http以及Servlet类的相关知识 今天我们来实操一下,实现一个简单的既有前端又有后端的网站–表白墙 之前在学习前端的时候已经写过了表白墙的前端代码,存在两个问题 1.页面重启,数据丢失 2.数据只是在本地的,别人看不见 那么这样的问题我们要咋样解决呢? 引入…

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

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

Excel函数VLOOKUP常用方法

一、基础用法 1、精确匹配 公式&#xff1a;VLOOKUP(待匹配值&#xff0c;查找范围&#xff0c;范围列数&#xff0c;查找方式) 定义好要输出表的表头和第一列&#xff0c;第一列即为要查找和匹配的父内容&#xff0c;在第二列输入公式&#xff0c;被查找表中一定也要将待查…

基于SPAD / SiPM技术的激光雷达方案

激光雷达(LiDAR)是一种测距技术&#xff0c;近年来越来越多地用于汽车先进驾驶辅助系统(ADAS)、手势识别和3D映射等应用。尤其在汽车领域&#xff0c;随着传感器融合的趋势&#xff0c;LiDAR结合成像、超声波、毫米波雷达&#xff0c;互为补足&#xff0c;为汽车提供全方位感知…

【力扣刷题 | 第六天】

目录 前言&#xff1a; 344. 反转字符串 - 力扣&#xff08;LeetCode&#xff09; 541. 反转字符串 II - 力扣&#xff08;LeetCode&#xff09; 今天我们进入字符串章节的刷题旅程&#xff0c;希望各位小伙伴可以和我一起坚持下去&#xff0c;一起征服力扣&#xff01; 前言…

前端前端学习不断

卷吧卷吧...&#xff0c;这东西什么时候是个头啊……

半导体器件基础(期末模电速成)

目录 1、半导体分类 2、PN结 3、二极管 4、稳压二极管 5、三极管 6、场效应管 1、半导体分类 2、PN结 3、二极管 伏安特性&#xff1a; 我们第七版模电书上给的正向导通压降分别约为0.7和0.2V&#xff0c;且硅的单向导电性更好 如何确定二极管状态&#xff1f; 阳极电压…

怎么快速掌握Python爬虫技术?

Python总的来说是一门比较容易入门的编程语言&#xff0c;因为它的语法简洁易懂&#xff0c;而且有很多优秀的教程和资源可供学习。相比其他编程语言&#xff0c;Python 的学习曲线较为平缓&#xff0c;初学者可以很快上手&#xff0c;但要想深入掌握 Python&#xff0c;还需要…

6款AI绘画生成器,让你的创作更有灵感

人工智能绘画听起来很高深&#xff0c;其原理是通过集成文本、图片和其他大数据数据来生成信息库&#xff0c;在输入文本描述的要求后&#xff0c;可以找到相应的视觉元素&#xff0c;然后拼凑起来生成符合文本描述的图片。 本文介绍非常好用的6款AI绘画生成工具 1.即时 AI 绘…

location.href 和 document.URL 与 document.documentURI

location.href 和 document.URL 与 document.documentURI 相同点 获取到的值相同 不同点 location.hrefurl可以赋值, 效果类似location.assign(url) , 可以后退 document.URL 与 document.documentURI 是只读的, 赋值无效 location.href locationwindow.location true lo…

HTTP编码杂谈

一 HTTP编码杂谈 ① 知识铺垫 1) 编码的英文叫encode --> 常见HTTP URL编码、Base64编码等目的&#xff1a; 转变为二进制的stream(字节流),便于网络传输备注&#xff1a; 一般都是基于utf-8编码2) 解码叫decode3) 乱码的根源&#xff1a; 编码和解码的方式不一致4) url…

Flask开发简易网站疑难点梳理

文章目录 整体总结创建项目独立的python环境windows下python独立环境目录结构linux下python独立环境目录结构 大概需要安装的第三方库使用websockt实现python代码与html界面的通讯界面F12中看到提示连接成功后立马连接关闭。 linux下数据库查询异常初次登录web的时候背景图片和…

智能指针(2)

智能指针&#xff08;2&#xff09; shared_ptr(共享型智能指针)基础知识特点引用计数器共享型智能指针结构理解 shared_ptr仿写删除器类计数器类shared_ptr类使用以及仿写代码的理解 循环引用_Weaks 初始化智能指针的方法 shared_ptr(共享型智能指针) 基础知识 在java中有一…

Hive | 报错锦集

知识目录 一、写在前面✨二、Hive启动hiveserver2报错&#x1f525;三、HiveServer2启动方式✨四、Hive执行SQL语句报一大堆日志&#x1f349;五、Hive使用Load加载数据报错&#x1f36d;六、Hive执行含Count的SQL语句报错&#x1f349;七、Hive执行SQL语句报/bin/java&#x1…

openGauss5.0之学习环境 Docker安装

文章目录 0.前言1. 准备软硬件安装环境1.1 软硬件环境要求1.2 修改操作系统配置1.2.1 关闭操作系统防火墙 1.3 设置字符集参数1.4 设置时区和时间&#xff08;可选&#xff09;关闭swap交换内存1.5 关闭RemoveIPC1.6 关闭HISTORY记录 2. 容器安装2. 1支持的架构和操作系统版本2…
最新文章