Android系统的启动过程(三):Launcher启动过程

news2024/12/23 5:38:36

Android系统的启动过程(三):Launcher启动过程

在这里插入图片描述

摘要&概述

前两篇文章中我们已经将系统启动的过程推进到了系统服务启动完毕之后,本篇文章就来介绍Android系统启动的最后一步:启动Launcher。

这个Launcher我们可以通俗地理解为桌面,系统启动的最后一步就是启动一个应用程序来显示系统中已经安装完成的应用程序,这个应用程序就叫做Launcher。

在 Android 系统启动的最后一步,是启动系统的默认桌面(Launcher)。桌面是用户与 Android 设备交互的主要界面,它提供了应用程序图标、小部件、壁纸等元素,使用户可以访问和启动应用程序。Launcher 在 Android 系统中是一个独立的应用程序,负责管理桌面上的图标、布局和交互逻辑。它通常是作为系统的默认桌面应用程序预装在设备上,并在系统启动时自动启动。启动 Launcher 的过程通常是由系统服务(System Service)负责调用。在系统启动的最后阶段,当其他系统组件已经准备就绪后,系统服务会启动 Launcher 进程,并加载 Launcher 应用程序的主要组件,例如主活动(MainActivity)。一旦 Launcher 进程启动并加载完成,它就会显示在屏幕上,并呈现用户熟悉的桌面界面。用户可以通过在桌面上滑动、点击图标等方式与 Launcher 进行交互,启动其他应用程序、访问设备设置等操作。

启动 Launcher 是 Android 系统启动过程中的最后一步,它标志着整个系统已经初始化完成,并提供了用户与设备交互的入口。

前两篇文章:

  1. Android系统的启动流程(一):进入Zygote进程的初始化
  2. Android系统的启动流程(二):SystemServer处理过程

Launcher启动过程

Launcher的入口

这个Launcher实际上是属于otherService的范畴,也就是其他服务,我们先点进startOtherService方法,其中有一段:

mActivityManagerService.systemReady(() -> {
            Slog.i(TAG, "Making services ready");
            t.traceBegin("StartActivityManagerReadyPhase");
            mSystemServiceManager.startBootPhase(t, SystemService.PHASE_ACTIVITY_MANAGER_READY);
            t.traceEnd();
            t.traceBegin("StartObservingNativeCrashes");
            try {
                mActivityManagerService.startObservingNativeCrashes();
            } catch (Throwable e) {
                reportWtf("observing native crashes", e);
            }
            t.traceEnd();

            t.traceBegin("RegisterAppOpsPolicy");
            try {
                mActivityManagerService.setAppOpsPolicy(new AppOpsPolicy(mSystemContext));
            } catch (Throwable e) {
                reportWtf("registering app ops policy", e);
            }
            t.traceEnd();

会调用ActivityManagerService的systemReady方法,接着进入这个方法,其实还是继续跳转,我们看这个方法:

mActivityTaskManager.onSystemReady();

调用到了ActivityTaskManager的onSystemReady方法,继续跳转到RootWindowContainer的startHomeOnAllDisplay方法,让我们细看这个方法:

    boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
            boolean fromHomeKey) {
        // Fallback to top focused display or default display if the displayId is invalid.
        if (displayId == INVALID_DISPLAY) {
            final Task rootTask = getTopDisplayFocusedRootTask();
            displayId = rootTask != null ? rootTask.getDisplayId() : DEFAULT_DISPLAY;
        }

        final DisplayContent display = getDisplayContent(displayId);
        return display.reduceOnAllTaskDisplayAreas((taskDisplayArea, result) ->
                        result | startHomeOnTaskDisplayArea(userId, reason, taskDisplayArea,
                                allowInstrumenting, fromHomeKey),
                false /* initValue */);
    }

这段代码是 Android 框架中的一部分,用于在指定的显示区域启动主屏幕(Launcher)。让我们逐行解释代码的含义:

首先,代码检查 displayId 是否为无效值(INVALID_DISPLAY)。如果 displayId 无效,它会回退到当前焦点所在的顶级显示区域(Task)或默认显示区域(DEFAULT_DISPLAY)。

接下来,代码通过 getDisplayContent(displayId) 方法获取指定 displayId 的显示内容(DisplayContent)。这个方法用于获取与给定 displayId 相关联的显示区域。

然后,代码调用 reduceOnAllTaskDisplayAreas() 方法,在所有的任务显示区域上执行指定的操作。该方法会遍历所有的任务显示区域,并将每个任务显示区域上的结果进行归约。在每个任务显示区域上,代码调用 startHomeOnTaskDisplayArea() 方法来尝试在该区域上启动主屏幕。它会传递一些参数,如用户ID、原因、是否允许检测仪器、是否来自Home键等。

最后,reduceOnAllTaskDisplayAreas() 方法返回归约的结果,表示是否在任何一个任务显示区域上成功启动了主屏幕。

总体来说,这段代码的作用是在指定的显示区域上启动主屏幕。它会遍历所有的任务显示区域,并尝试在每个任务显示区域上启动主屏幕,返回是否成功启动了主屏幕的结果。

我们接着点开里面的startHomeOnTaskDisplayArea,我们挑选里面最重要的几段:

boolean startHomeOnTaskDisplayArea(int userId, String reason, TaskDisplayArea taskDisplayArea,
            boolean allowInstrumenting, boolean fromHomeKey) {
 		......
        Intent homeIntent = null;
        ActivityInfo aInfo = null;
        if (taskDisplayArea == getDefaultTaskDisplayArea()) {
            homeIntent = mService.getHomeIntent();
            aInfo = resolveHomeActivity(userId, homeIntent);
        } else if (shouldPlaceSecondaryHomeOnDisplayArea(taskDisplayArea)) {
            Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, taskDisplayArea);
            aInfo = info.first;
            homeIntent = info.second;
        }
 		.....
        final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
                aInfo.applicationInfo.uid) + ":" + taskDisplayArea.getDisplayId();
        mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
                taskDisplayArea);
        return true;
    }

这里获取了用于启动Launcher的Intent,然后进行一系列设置,最后通过startHomeActivity方法启动了这个Launcher:

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason,
            TaskDisplayArea taskDisplayArea) {
		.......
        mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
                .setOutActivity(tmpOutRecord)
                .setCallingUid(0)
                .setActivityInfo(aInfo)
                .setActivityOptions(options.toBundle())
                .execute();
        mLastHomeActivityStartRecord = tmpOutRecord[0];
        if (rootHomeTask.mInResumeTopActivity) {
            mSupervisor.scheduleResumeTopActivities();
        }
    }

这里主要看obtainStarter这一串代码,这一串代码用Intent和reason构建出来了一个ActivityStarter对象,很显然这个对象是用来启动Activity的,那么我们也可以知道这个Launcher实际上也是一个Activity,所以说,作为一个Activity,Launcher具有与其他Activity相似的生命周期和功能。当用户点击设备上的主屏幕按钮或通过其他手势启动Launcher时,系统会创建一个Launcher的实例,并调用其生命周期方法,如onCreate()、onStart()、onResume()等。Launcher可以包含自定义的布局和逻辑,以展示应用程序列表、搜索功能、小部件等。

到这里,Launcher的启动过程也完成了。

Launcher中应用图标显示过程

上面说到,这个Launcher本质上也是一个Activity,所以我们先从onCreate入手:

    protected void onCreate(Bundle savedInstanceState) {
	.......
        super.onCreate(savedInstanceState);

        LauncherAppState app = LauncherAppState.getInstance(this);//1-----1
        mModel = app.getModel();//2----2

        mRotationHelper = new RotationHelper(this);
        InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
        initDeviceProfile(idp);
        idp.addOnChangeListener(this);
        mSharedPrefs = LauncherPrefs.getPrefs(this);
        mIconCache = app.getIconCache();
        mAccessibilityDelegate = createAccessibilityDelegate();

        initDragController();
        ......
    }

这里还是截取最重要的一段代码,主要让我们看注释一二处的代码,在注释一处获取了LauncherAppState的实例,然后在第二处代码处设置当前实例的Model为app的Model。接下来这个model会调用addCallbacksAndLoad方法:

  if (!mModel.addCallbacksAndLoad(this)) {
            if (!internalStateHandled) {
                // If we are not binding synchronously, pause drawing until initial bind complete,
                // so that the system could continue to show the device loading prompt
                mOnInitialBindListener = Boolean.FALSE::booleanValue;
            }
        }

这个方法相当于是将当前这个Launcher对象添加尽量LauncherModel的callBacks中:

    public boolean addCallbacksAndLoad(@NonNull final Callbacks callbacks) {
        synchronized (mLock) {
            addCallbacks(callbacks);
            return startLoader(new Callbacks[] { callbacks });

        }
    }

    /**
     * Adds a callbacks to receive model updates
     */
    public void addCallbacks(@NonNull final Callbacks callbacks) {
        Preconditions.assertUIThread();
        synchronized (mCallbacksList) {
            if (TestProtocol.sDebugTracing) {
                Log.d(TestProtocol.NULL_INT_SET, "addCallbacks pointer: "
                        + callbacks
                        + ", name: "
                        + callbacks.getClass().getName(), new Exception());
            }
            mCallbacksList.add(callbacks);
        }
    }

添加完毕之后就会紧接着调用到startLoader方法:

    private boolean startLoader(@NonNull final Callbacks[] newCallbacks) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        ItemInstallQueue.INSTANCE.get(mApp.getContext())
                .pauseModelPush(ItemInstallQueue.FLAG_LOADER_RUNNING);
        synchronized (mLock) {
            // If there is already one running, tell it to stop.
            boolean wasRunning = stopLoader();
            boolean bindDirectly = mModelLoaded && !mIsLoaderTaskRunning;
            boolean bindAllCallbacks = wasRunning || !bindDirectly || newCallbacks.length == 0;
            final Callbacks[] callbacksList = bindAllCallbacks ? getCallbacks() : newCallbacks;

            if (callbacksList.length > 0) {
                // Clear any pending bind-runnables from the synchronized load process.
                for (Callbacks cb : callbacksList) {
                    MAIN_EXECUTOR.execute(cb::clearPendingBinds);
                }

                LoaderResults loaderResults = new LoaderResults(
                        mApp, mBgDataModel, mBgAllAppsList, callbacksList);
                if (bindDirectly) {
                    // Divide the set of loaded items into those that we are binding synchronously,
                    // and everything else that is to be bound normally (asynchronously).
                    loaderResults.bindWorkspace(bindAllCallbacks);
                    // For now, continue posting the binding of AllApps as there are other
                    // issues that arise from that.
                    loaderResults.bindAllApps();
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
                    stopLoader();
                    mLoaderTask = new LoaderTask(
                            mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);//1---1

                    // Always post the loader task, instead of running directly
                    // (even on same thread) so that we exit any nested synchronized blocks
                    MODEL_EXECUTOR.post(mLoaderTask);//2---2
                }
            }
        }
        return false;
    }

这段代码是Launcher中的一个私有方法startLoader(),用于启动加载器(Loader)。在Launcher中,加载器负责加载和准备主屏幕的数据,如应用程序列表、小部件等,并将其绑定到相应的界面上。

在注释一处,创建类一个LoaderTask,这显然是一个runnable对象,紧接着在注释二处将其提交到线程池中进行。而这个MODEL_EXECUTOR实在Executors中定义的:

    public static final LooperExecutor MODEL_EXECUTOR =
            new LooperExecutor(createAndStartNewLooper("launcher-loader"));

相当于一个有消息循环的线程,可以处理消息,其内部也有handler用于处理消息。

接下来看它执行的LoaderTask,直接看它的run方法:

public void run() {
        synchronized (this) {
            // Skip fast if we are already stopped.
            if (mStopped) {
                return;
            }
        }

        Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
        TimingLogger logger = new TimingLogger(TAG, "run");
        LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            List<ShortcutInfo> allShortcuts = new ArrayList<>();
            Trace.beginSection("LoadWorkspace");
            try {
                loadWorkspace(allShortcuts, memoryLogger); //1--加载工作区空间
            } finally {
                Trace.endSection();
            }
            logASplit(logger, "loadWorkspace");

            if (mApp.getInvariantDeviceProfile().dbFile.equals(mDbName)) {
                verifyNotStopped();
                sanitizeData();
                logASplit(logger, "sanitizeData");
            }

            verifyNotStopped();
            mResults.bindWorkspace(true /* incrementBindId */);//2--绑定工作空间
            logASplit(logger, "bindWorkspace");

            mModelDelegate.workspaceLoadComplete();
            // Notify the installer packages of packages with active installs on the first screen.
            sendFirstScreenActiveInstallsBroadcast();
            logASplit(logger, "sendFirstScreenActiveInstallsBroadcast");

            // Take a break
            waitForIdle();
            logASplit(logger, "step 1 complete");
            verifyNotStopped();

            // second step
            Trace.beginSection("LoadAllApps");
            List<LauncherActivityInfo> allActivityList;
            try {
               allActivityList = loadAllApps();//3--加载所有APP应用程序的信息
            } finally {
                Trace.endSection();
            }
        	.........
    }

Launcher是以工作区的形式来显示系统安装的应用程序的快捷图标的,每一个工作区都是用来描述一个抽象桌面的,它由n个屏幕组成,每个屏幕又分成n个单元格,每个单元格用来显示一个用用程序的快捷图标。

我们这里仍然是截取了重要部分的代码,让我们从上往下看,在注释一处通过loadWorkspace方法加载了工作区信息,注释二处则是绑定了工作区信息,注释三处的loadAllApps方法则是加载了所有APP应用程序的信息。

接下来看这个loadAllApps方法:

private List<LauncherActivityInfo> loadAllApps() {
        final List<UserHandle> profiles = mUserCache.getUserProfiles();
        List<LauncherActivityInfo> allActivityList = new ArrayList<>();
        // Clear the list of apps
        mBgAllAppsList.clear();

        List<IconRequestInfo<AppInfo>> iconRequestInfos = new ArrayList<>();
        for (UserHandle user : profiles) {
            final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);
            if (apps == null || apps.isEmpty()) {
                return allActivityList;
            }
            boolean quietMode = mUserManagerState.isUserQuiet(user);
            // Create the ApplicationInfos
            for (int i = 0; i < apps.size(); i++) {
                LauncherActivityInfo app = apps.get(i);
                AppInfo appInfo = new AppInfo(app, user, quietMode);

                iconRequestInfos.add(new IconRequestInfo<>(
                        appInfo, app, /* useLowResIcon= */ false));
                mBgAllAppsList.add(
                        appInfo, app, !FeatureFlags.ENABLE_BULK_ALL_APPS_ICON_LOADING.get());
            }
            allActivityList.addAll(apps);
        }
       ........
        return allActivityList;
    }

看中间这一段遍历的过程,实际上就是将所有APP的信息都给遍历然后添加到响应的列表中,然后调用一个allActivityList.addAll(apps)完成注册,最后会将这个List给返回出去,以便在Launcher进行操作。

接下来会进行图标的绑定,这个会调用到Launcher类的bindAllApplications方法:

    public void bindAllApplications(AppInfo[] apps, int flags) {
        mAppsView.getAppsStore().setApps(apps, flags);
        PopupContainerWithArrow.dismissInvalidPopup(this);
        if (Utilities.ATLEAST_S) {
            Trace.endAsyncSection(DISPLAY_ALL_APPS_TRACE_METHOD_NAME,
                    DISPLAY_ALL_APPS_TRACE_COOKIE);
        }
    }

    public void setApps(AppInfo[] apps, int flags) {
        mApps = apps;
        mModelFlags = flags;
        notifyUpdate();
    }

具体就是调用这个setApps方法来设置之前获得到的一系列App信息,接下来的notifyUpdate方法又会调用到BaseAllAppsContainerView的onFinishInflater方法:

   protected void onFinishInflate() {
        super.onFinishInflate();

        mAH.get(AdapterHolder.SEARCH).setup(mSearchRecyclerView,
                /* Filter out A-Z apps */ itemInfo -> false);
        rebindAdapters(true /* force */);
        float cornerRadius = Themes.getDialogCornerRadius(getContext());
        mBottomSheetCornerRadii = new float[]{
                cornerRadius,
                cornerRadius, // Top left radius in px
                cornerRadius,
                cornerRadius, // Top right radius in px
                0,
                0, // Bottom right
                0,
                0 // Bottom left
        };
        final TypedValue value = new TypedValue();
        getContext().getTheme().resolveAttribute(android.R.attr.colorBackground, value, true);
        mBottomSheetBackgroundColor = value.data;
        updateBackground(mActivityContext.getDeviceProfile());
    }

可以着重看第二个和第三个方法,代码通过mAH.get(AdapterHolder.SEARCH)获取到一个适配器持有者,并调用其setup()方法。这个方法将一个RecyclerView(mSearchRecyclerView)与适配器持有者关联起来,并传入一个过滤器函数itemInfo -> false,用于过滤掉一些特定的应用程序。然后,代码调用了rebindAdapters(true /* force */)方法,重新绑定适配器。传入的force参数为true表示强制重新绑定。

总体而言,这段代码在View布局加载完成后进行了一些后续的初始化操作,包括设置适配器、设置圆角信息、获取背景属性值等。这样也就最终将各种各样的应用图标显示到了Launcher上。

Android系统流程最终总结

在这里插入图片描述

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

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

相关文章

深度相机介绍

一、什么是深度相机 &#xff08;五&#xff09;深度相机&#xff1a;结构光、TOF、双目相机 - 知乎 传统的RGB彩色普通相机称为2D相机&#xff0c;只能拍摄相机视角内的物体&#xff0c;没有物体到相机的距离信息&#xff0c;只能凭感觉感知物体的远近&#xff0c;没有明确的数…

V90 PN伺服驱动器转矩控制(750报文)

主要介绍通过标准报文加附加报文 750 实现发送驱动报文的控制字、速度给定、转矩限幅及附加转矩给定的功能,首先就是V90在博途环境下的组态,安装GSD文件,GSD文件下载地址如下: https://download.csdn.net/download/m0_46143730/86542047https://download.csdn.net/downloa…

Qt线程的几种使用方法

目录 引言使用方法重写QThread::run()moveToThreadQRunnable使用QtConcurrent使用 完整代码 引言 多线程不应该是一个复杂而令人生畏的东西&#xff0c;它应该只是程序员的一个工具&#xff0c;不应该是调用者过多记忆相关概念&#xff0c;而应该是被调用方应该尽可能的简化调…

Java网络开发(Tomcat)——登陆和注册功能 的 迭代升级 从Jsp到JavaScript + axios + vue 同步到异步

目录 引出前置工作vueaxiosresp0.vue版本的jsp模板1.导包--Json&#xff1a;pom.xml文件&#xff1a;2.新建一个专门用来处理响应的实体类ResData3.在axios中&#xff0c;所有响应必须是 resp.getWriter().write() 的方式&#xff0c;核心代码如下4.在jsp前端代码中导包&#x…

浅谈一级机电管道设计中的压力与介质温度

管道设计是工程设计中的一个非常重要的部分&#xff0c;管道的设计需要考虑到许多因素&#xff0c;其中就包括管道设计压力分类和介质温度分类。这两个因素是在设计管道时必须非常严格考虑的&#xff0c; 首先是管道设计压力分类。在管道设计中&#xff0c;根据工作要求和要传输…

详解 Ansible 自动化运维,提升工作效率

概要 Ansible 是一个模型驱动的配置管理器&#xff0c;支持多节点发布、远程任务执行。默认使用 SSH 进行远程连接。无需在被管理节点上安装附加软件&#xff0c;可使用各种编程语言进行扩展。 一、Ansible基本架构 上图为ansible的基本架构&#xff0c;从上图可以了解到其由以…

算法刷题-关于链表,你该了解这些!

关于链表&#xff0c;你该了解这些&#xff01; 什么是链表&#xff0c;链表是一种通过指针串联在一起的线性结构&#xff0c;每一个节点由两部分组成&#xff0c;一个是数据域一个是指针域&#xff08;存放指向下一个节点的指针&#xff09;&#xff0c;最后一个节点的指针域…

mybatis-plus分页查询(springboot中实现单表和多表查询)

一、mybatis-plus单表查询 使用mybatis-plus实现单表分页查询 非常方便&#xff0c;主要操作步骤如下&#xff1a; 配置分页查询拦截器进行分页查询 1.首先&#xff0c;打开mybatis-plus官网的插件&#xff08;插件主体&#xff09; 或者点击mybatis-plus插件 我是配置在s…

KameAI:探索AI驱动的未来,体验聊天GPT与AI绘画的奇妙世界

人工智能的崛起与发展随着科技的飞速发展&#xff0c;人工智能(AI)已经逐渐成为我们生活中不可或缺的一部分。它的出现不仅改变了我们与世界的互动方式&#xff0c;还为各行各业带来巨大的便利。今天&#xff0c;我们就来聊一聊一个类似ChatGPT的人工智能网站—KameAI&#xff…

Nautilus Chain全球行分享会,上海站圆满举办

在北京时间 6 月 9 日&#xff0c;由 Nautilus Chain 主办的“Layer3 模块化区块链的发展探讨”为主题的全球行活动&#xff0c;在上海顺利举办&#xff0c;本次分享会联合主办方还包 括 Stanford Blockchain Accelerator、Zebec Protocol、Tiger VC DAO、Crypto PHD、Rootz L…

Nginx【反向代理负载均衡动静分离】--上

Nginx【反向代理负载均衡动静分离】–上 先看2 个实际需求&#xff0c;引出Nginx 需求1: 访问不同微服务 示意图 需求2: 轮询访问服务 示意图 解决方案: Nginx 反向代理 负载均衡 动静分离 高可用集群 Nginx 在分布式微服务架构的位置 基本介绍 Nginx 是什么? 能干什…

solr快速上手:配置IK中文分词器(七)

0. 引言 solr作为搜索引擎&#xff0c;常用在我们对于搜索速度有较高要求且大数据量的业务场景&#xff0c;我们之前已经配置过英文分词器&#xff0c;但是针对中文分词不够灵活和实用&#xff0c;要实现真正意义上的中文分词&#xff0c;还需要单独安装中文分词器 solr快速上…

【shell 基础13】输入输出与重定向

文章目录 一. 标准输入和标准输出二、重定向1. 定义2. 输出的重定向3. 对标准错误输出重定向4. 输入的重定向 一. 标准输入和标准输出 linux中有三种标准输入输出&#xff0c;分别是STDIN&#xff0c;STDOUT&#xff0c;STDERR&#xff0c;文件描述符分别是 0、1、2。 当运行…

Android Paging3分页+ConcatAdapter+空数据视图+下拉刷新(SwipeRefreshLayout)+加载更多+错误重试 (示例)

文章目录 引入库数据模型定义分页 adapter加载更多 adapter空数据 adapter分页数据源ViewModel 提供加载数据源的方法结合以上实现的 Fragment数据重复问题 引入库 implementation androidx.paging:paging-runtime-ktx:3.1.1paging 库&#xff0c;目前还是有点小bug &#xff…

Java开发技巧-数据结构-使用HashSet判断主键是否存在、使用Pair成对结果返回/Triple三个对象返回

场景 Java中使用HashSet判断主键是否存在 HashSet实现Set接口&#xff0c;由哈希表&#xff08;实际上是HashMap&#xff09;实现&#xff0c;但不保证set的迭代顺序&#xff0c;并允许使用null元素。 HashSet的时间复杂度跟HashMap一致&#xff0c;如果没有哈希冲突则时间复…

EXCEL函数笔记1(数学函数、文本函数、日期函数)

数学函数 取整&#xff1a;INT(number) 取余&#xff1a;MOD(number,除数) 四舍五入&#xff1a;ROUND(number&#xff0c;保留几位小数) 取绝对值&#xff1a;ABS(number) 根号处理&#xff1a;SQRT&#xff08;number&#xff09; 0到1随机数&#xff1a;RAND&#xff08;&am…

Python神器Anaconda图文安装教程

来源&#xff1a;投稿 作者&#xff1a;Fairy 编辑&#xff1a;学姐 Anaconda简介 Anaconda是一种数据科学和机器学习的开发环境&#xff0c;它包含了大量的Python包、工具和库&#xff0c;以及可视化界面和集成开发环境。「Anaconda可以方便地管理Python环境和安装第三方软件…

⑧电子产品拆解分析-1拖4USB拓展坞

⑧电子产品拆解分析-1拖4USB拓展坞 一、功能介绍二、电路分析以及器件作用1、内部电路拆解 三、参考资料学习 一、功能介绍 ①USB2.0一拖四通讯&#xff1b;②具备OTG功能&#xff0c;可适配大部分USB接口设备&#xff1b; 二、电路分析以及器件作用 1、内部电路拆解 分析&am…

【分布式存储】聊一下分布式存储中分片机制

为什么需要分片 在服务端领域&#xff0c;主要特点是支撑7*24小时不间断的服务&#xff0c;而最终对各种行为会生产对应的数据&#xff0c;比如用户登陆/注册&#xff0c;发起订单交易、支付、身份验证&#xff0c;短信验证等情况都需要存储起来&#xff0c;其中包括各种各样的…

浏览器工作原理分析与首屏加载

正文 1. 页面加载时间线 我们先来一个老生常谈的面试题&#xff1a;从输入 URL 到页面加载完成的过程中都发生了什么事情&#xff1f; 这个面试题本身也是一个开放题&#xff0c;不同方向的工程师侧重也不一样。大抵的过程可以简化为&#xff1a; st>start: 输入URL e>…