Android 系统启动过程纪要(基于Android 10)

news2024/11/28 23:19:48

前言

看过源码的都知道,Launcher系统启动都会经过这三个进程 init ->zygote -> system_server。今天我们就来讲解一下这三个进程以及Launcher系统启动。

init进程

  1. 准备Android虚拟机环境:创建和挂载系统文件目录;
  2. 初始化属性服务;
  3. 设置子进程信号处理函数(防止init的子进程出现僵尸进程,在子进程暂停和结束是接受信号并进行处理);

僵尸进程:父进程使用fork创建子进程,如果子进程终止,但父进程并不知道已经终止。虽然此时子进程已经退出,但是还是保留了他的信息(进程号。退出状态。运行时间等)。这样,系统进程表就会被无端耗用如果耗尽之后,系统可能就无法再创建新的进程。

  1. 启动属性服务,并为其分配内存用来存储属性:使用一个键值对(注册表)记录用户\软件的一些使用信息(确保系统或者软件重启之后能根据注册表中的信息进行初始化的工作)
  2. 解析init.rc配置文件。 解析init.zygote脚本文件,启动zygote进程

总的来说做了三件事:

  1. 创建和挂在系统启动所需要的文件目录;
  2. 初始化和启动属性服务;
  3. 解析init.rc配置文件并启动zygote进程。

zygote进程

init通过调用app_main.cpp的main函数中的start方法来启动zygote进程。
在这里插入图片描述
DVM,ART,应用程序进程以及SystemServer进程都是有zygote创建。通过fork自身(从zygote进程)来创建新的应用进程。因此,zygote进程和它的子进程都会运行main函数。main函数里会通过标记查看main函数是运行在zygote进程还是子进程。如果实在zygote进程里,那么就会调用AppRuntime的start函数。在start函数里,会创建Java虚拟机以及做一些其他的准备工作。最后通过JNI调用ZygoteInit.java里的mian方法,进入Java框架层。

main方法主要做下面几件事情:

  1. 创建一个Server端的Socket,用来等待AMS请求创建新的应用进程;
  2. 预加载类和资源;
  3. 启动SystemServer进程;
  4. 等待AMS请求创建新的应用程序进程(通过无限循环while(true)等待AMS的请求)。

SystemServer(zygote第一个启动的进程)

SystemServer主要用来创建系统服务,例如AMS,WMS都是有他创建的。Zygote进程通过forkSystemServetr方法复制Zygote进程的地址空间,并关闭SystemServer不需要的Socket进程。然后通过handleSystemServerProcess方法启动进程。

//ZygoteInit.java
private static Runnable forkSystemServer(String abiList, String socketName,
            ZygoteServer zygoteServer) {
            
 		String[] args = {
                "--setuid=1000",
                "--setgid=1000",
                "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"
                        + "1024,1032,1065,3001,3002,3003,3006,3007,3009,3010,3011,3012",
                "--capabilities=" + capabilities + "," + capabilities,
                "--nice-name=system_server",
                "--runtime-args",
                "--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,
                "com.android.server.SystemServer",
        };
        
        ZygoteArguments parsedArgs;
 		try {
            ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args);
            try {
                parsedArgs = ZygoteArguments.getInstance(commandBuffer);
            } catch (EOFException e) {
                throw new AssertionError("Unexpected argument error for forking system server", e);
            }
     		catch (IllegalArgumentException  e) {
              
            }       

        int pid;

        try {
            //...
            /* Request to fork the system server process */
            pid = Zygote.forkSystemServer(
                    parsedArgs.mUid, parsedArgs.mGid,
                    parsedArgs.mGids,
                    parsedArgs.mRuntimeFlags,
                    null,
                    parsedArgs.mPermittedCapabilities,
                    parsedArgs.mEffectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

       
        if (pid == 0) {
            if (hasSecondZygote(abiList)) {
                waitForSecondaryZygote(socketName);
            }
            //关闭Zygote进程创建的Socket
            zygoteServer.closeServerSocket();
            return handleSystemServerProcess(parsedArgs);
        }

        return null;
    }

在handleSystemServerProcess方法中,会启动Binder线程池,这样SystemServer就可以使用Binder和其他应用进程进程通讯。

//ZygoteInit.java
    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
     ///
        if (parsedArgs.mInvokeWith != null) {
            //
        } else {
            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                    parsedArgs.mDisabledCompatChanges,
                    parsedArgs.mRemainingArgs, cl);
        }
    }

//ZygoteInit.java
    private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {
     ///
        if (parsedArgs.mInvokeWith != null) {
            //
        } else {
            return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,
                    parsedArgs.mDisabledCompatChanges,
                    parsedArgs.mRemainingArgs, cl);
        }
    }

启动Binder线程池之后,会调用RuntimeInit.applicationInit:

//RuntimeInit.java
  protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,
            String[] argv, ClassLoader classLoader) {
       
        //
        final Arguments args = new Arguments(argv);
        return findStaticMain(args.startClass, args.startArgs, classLoader);
    }

然后调用findStaticMain 方法:

	RuntimeInit.java
      protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
        //使用反射创建SystemServer。
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
        //找到ServerServer的main方法
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }

        int modifiers = m.getModifiers();
        if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
            throw new RuntimeException(
                    "Main method is not public and static on " + className);
        }

  	 //通过MethodAndArgsCaller调用SystemServer的main方法
        return new MethodAndArgsCaller(m, argv);
    }

可以看到,这里使用反射的方式创建了Systemserver,其中的className实在ZygoteInit.java类中forkSystemServer 方法的变量arg中初始化的。然后通过MethodAndArgsCaller来调用Systemserver的main方法,主要是为了能让ZygoteInit.mian能捕获异常(因为SystemServer的main方法在调用之前SystemServer进程已经做了很多准备工作,而如果慧姐抛出异常,则会清理掉所有的堆栈信帧,使得SystemServer的main看起来就不是SystemServer的入口方法了),MethodAndArgsCaller代码也很简单:

static class MethodAndArgsCaller implements Runnable {
        /** method to call */
        private final Method mMethod;

        /** argument array */
        private final String[] mArgs;

        public MethodAndArgsCaller(Method method, String[] args) {
            mMethod = method;
            mArgs = args;
        }

        public void run() {
            try {
                mMethod.invoke(null, new Object[] { mArgs });
            } catch (IllegalAccessException ex) {
                throw new RuntimeException(ex);
            } catch (InvocationTargetException ex) {
                Throwable cause = ex.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                }
                throw new RuntimeException(ex);
            }
        }
    }

而在ZygoteInit的main方法中,有该异常的捕获的try语句。

在SystemServer中,main方法的关键操作如下:


public static void main(String[] args) {
        new SystemServer().run();
}

private void run() {
        //
        try {
            Looper.prepareMainLooper();
            Looper.getMainLooper().setSlowLogThresholdMs(
                    SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
             System.loadLibrary("android_servers");       
            createSystemContext();        
        }
        //
        // Start services.
        try {
            t.traceBegin("StartServices");
            startBootstrapServices(t);//启动引导服务
            startCoreServices(t);//启动核心服务
            startOtherServices(t);//启动其他服务
        } catch (Throwable ex) {
            throw ex;
        } finally {
            t.traceEnd(); // StartServices
        }
        Looper.loop();
}

SystemServer会创建消息Looper,加载库文件,并创建系统的Context。然后创建和启动各种系统服务:

  1. 引导服务:Installer、AMS、PMS等
  2. 核心服务:DropBoxManagerService、BatteryService等
  3. 其他服务:CameraService、WMS等

以PowerStatsService为例,通过SystemServiceManager的startService方法启动:mSystemServiceManager.startService(PowerStatsService.class);

SystemServiceManager:Manages creating, starting, and other lifecycle events of SystemService system services

   //SystemServiceManager.java
   private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
   
   public <T extends SystemService> T startService(Class<T> serviceClass) {
        try {
            final String name = serviceClass.getName();
            final T service;
            try {
                Constructor<T> constructor = serviceClass.getConstructor(Context.class);
                service = constructor.newInstance(mContext);
            } catch (InvocationTargetException ex) {
                throw new RuntimeException("Failed to create service " + name
                        + ": service constructor threw an exception", ex);
            }

            startService(service);
            return service;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
        }
    }

    public void startService(@NonNull final SystemService service) {
        // Register it.
        mServices.add(service);
        // Start it.
        long time = SystemClock.elapsedRealtime();
        try {
            service.onStart();
        } catch (RuntimeException ex) {
            throw new RuntimeException("Failed to start service " + service.getClass().getName()
                    + ": onStart threw an exception", ex);
        }
        warnIfTooLong(SystemClock.elapsedRealtime() - time, service, "onStart");
    }

通过创建service,然后将service添加到一个ArrayList中,这样就完成了注册工作。最后调用service的start方法启动。

SystemServer进程被创建后做了一下工作:

  1. 启动Binder线程池,用于和其他进程通讯;
  2. 创建SystemServerManager,用于系统服务的创建、启动和管理;
  3. 使用SystemServerManager启动各种服务。

负责拉起AMS等80多个服务

     try {
            traceBeginAndSlog("StartServices");
            startBootstrapServices();
            startCoreServices();
            startOtherServices();
            SystemServerInitThreadPool.shutdown();
        } catch (Throwable ex) {
            Slog.e("System", "******************************************");
            Slog.e("System", "************ Failure starting system services", ex);
            throw ex;
        } finally {
            traceEnd();
        }

在这里插入图片描述

finishBooting负责发送系统启动完成广播

为什么应用启动时要从zygote进程中fork: 若从init fork需要准备虚拟机,预加载类文件;若从system_server App进程不需要大量的服务(AMS WMS PMS等80多种服务)

Launcher启动

系统启动的最后一步是启动一个用来显示系统中已安装应用的应用程序——Launcher。通俗的讲,它就是Android的系统桌面,主要有以下两个特点:

  1. 作为Android系统的启动器,用于启动应用程序;
  2. 作为Android系统的桌面,用于显示和管理应用程序的快捷图标或者其他桌面组件。

在SystemServer启动过程中,由它启动的Ams(ActivityManagerService.)会将Launcher启动。Launcher的启动入口在SystemServer的startOtherServices方法中:

//SystemServer.java
// We now tell the activity manager it is okay to run third party
// code.  It will call back into us once it has gotten to the state
// where third party code can really run (but before it has actually
// started launching the initial applications), for us to complete our
// initialization.
 mActivityManagerService.systemReady(() -> {
      mSystemServiceManager.startBootPhase(t, SystemService.PHASE_ACTIVITY_MANAGER_READY);
    }

);

根据官方注释,我们可以看出,在这里系统服务已经就绪,到这里系统就算启动完成,可以跑第三方的代码了。此时,会调用AMS的systemReady方法,让AMS去启动Launcher。下面是ActivityManagerService.java中systemReady方法启动Launcher的关键代码:

public ActivityTaskManagerInternal mAtmInternal;

public void systemReady(final Runnable goingCallback, TimingsTraceLog traceLog) {
	 synchronized (this) {
	 	mAtmInternal.startHomeOnAllDisplays(currentUserId, "systemReady");
	 }
}	

其中,mAtmInternal 的具体实现是:ActivityTaskManagerService.LocalService。是在ActivityTaskManagerService里的一个内部类。ActivityTaskManagerService.LocalService.startHomeOnAllDisplays代码如下:

@Override
public boolean startHomeOnAllDisplays(int userId, String reason) {
    synchronized (mGlobalLock) {
        return mRootActivityContainer.startHomeOnAllDisplays(userId, reason);
    }
}

RootActivityContainer.startHomeOnAllDisplays的代码如下:

boolean startHomeOnAllDisplays(int userId, String reason) {
	 boolean homeStarted = false;
	 // 由于Android系统支持多用户和多显示,调用startHomeOnAllDisplays启动每个display上的Home Activity
        for (int i = mActivityDisplays.size() - 1; i >= 0; i--) {
            final int displayId = mActivityDisplays.get(i).mDisplayId;
            homeStarted |= startHomeOnDisplay(userId, reason, displayId);
        }
        return homeStarted;
}

继续追踪startHomeOnDisplay方法:



boolean startHomeOnDisplay(int userId, String reason, int displayId) {
    return startHomeOnDisplay(userId, reason, displayId, false /* allowInstrumenting */,
        false /* fromHomeKey */);
}

    
boolean startHomeOnDisplay(int userId, String reason, int displayId, boolean allowInstrumenting,
            boolean fromHomeKey) {

        if (displayId == INVALID_DISPLAY) {
            displayId = getTopDisplayFocusedStack().mDisplayId;
        }

        Intent homeIntent = null;
        ActivityInfo aInfo = null;
        if (displayId == DEFAULT_DISPLAY) {
        	//第一步:获取Intent
        	//这里的mService就是ActivityTaskManagerService
            homeIntent = mService.getHomeIntent();
            aInfo = resolveHomeActivity(userId, homeIntent);
        } else if (shouldPlaceSecondaryHomeOnDisplay(displayId)) {
            Pair<ActivityInfo, Intent> info = resolveSecondaryHomeActivity(userId, displayId);
            aInfo = info.first;
            homeIntent = info.second;
        }
        if (aInfo == null || homeIntent == null) {
            return false;
        }

        if (!canStartHomeOnDisplay(aInfo, displayId, allowInstrumenting)) {
            return false;
        }
        homeIntent.setComponent(new ComponentName(aInfo.applicationInfo.packageName, aInfo.name));
        homeIntent.setFlags(homeIntent.getFlags() | FLAG_ACTIVITY_NEW_TASK);
        if (fromHomeKey) {
            homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true);
        }
        
        final String myReason = reason + ":" + userId + ":" + UserHandle.getUserId(
                aInfo.applicationInfo.uid) + ":" + displayId;
         //  第二步:启动    
        mService.getActivityStartController().startHomeActivity(homeIntent, aInfo, myReason,
                displayId);
        return true;
}

其中,第一步获取Intent的getHomeIntent方法在ActivityTaskManagerService中,代码如下:就是给Intent添加了Intent.CATEGORY_HOME属性

String mTopAction = Intent.ACTION_MAIN;

Intent getHomeIntent() {
    Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null);
        intent.setComponent(mTopComponent);
        intent.addFlags(Intent.FLAG_DEBUG_TRIAGED_MISSING);
        if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
        	//返回一个Category为Intent.CATEGORY_HOME的Intent
            intent.addCategory(Intent.CATEGORY_HOME);
        }
        return intent;
 }

接下来第二步,startHomeActivity方法在ActivityStartController中,关键代码入下:

void startHomeActivity(Intent intent, ActivityInfo aInfo, String reason, int displayId) {
        final ActivityOptions options = ActivityOptions.makeBasic();
        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
        if (!ActivityRecord.isResolverActivity(aInfo.name)) { 
            options.setLaunchActivityType(ACTIVITY_TYPE_HOME);
        }
        options.setLaunchDisplayId(displayId);
        mLastHomeActivityStartResult = obtainStarter(intent, "startHomeActivity: " + reason)
                .setOutActivity(tmpOutRecord)
                .setCallingUid(0)
                .setActivityInfo(aInfo)
                .setActivityOptions(options.toBundle())
                .execute();//启动
}

其中,最后的execute最终是执行的ActivityStarter的execute代码:

 /**
     * Starts an activity based on the request parameters provided earlier.
     * @return The starter result.
     */
    int execute() {
        try {
            // TODO(b/64750076): Look into passing request directly to these methods to allow
            // for transactional diffs and preprocessing.
            if (mRequest.mayWait) {
                return startActivityMayWait(mRequest.caller, mRequest.callingUid,
                        mRequest.callingPackage, mRequest.realCallingPid, mRequest.realCallingUid,
                        mRequest.intent, mRequest.resolvedType,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.startFlags,
                        mRequest.profilerInfo, mRequest.waitResult, mRequest.globalConfig,
                        mRequest.activityOptions, mRequest.ignoreTargetSecurity, mRequest.userId,
                        mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            } else {
                return startActivity(mRequest.caller, mRequest.intent, mRequest.ephemeralIntent,
                        mRequest.resolvedType, mRequest.activityInfo, mRequest.resolveInfo,
                        mRequest.voiceSession, mRequest.voiceInteractor, mRequest.resultTo,
                        mRequest.resultWho, mRequest.requestCode, mRequest.callingPid,
                        mRequest.callingUid, mRequest.callingPackage, mRequest.realCallingPid,
                        mRequest.realCallingUid, mRequest.startFlags, mRequest.activityOptions,
                        mRequest.ignoreTargetSecurity, mRequest.componentSpecified,
                        mRequest.outActivity, mRequest.inTask, mRequest.reason,
                        mRequest.allowPendingRemoteAnimationRegistryLookup,
                        mRequest.originatingPendingIntent, mRequest.allowBackgroundActivityStart);
            }
        } finally {
            onExecutionComplete();
        }
    }

其中,startActivity有三个的重载方法,经过不断的调用,最后会调用:startActivity(final ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask, ActivityRecord[] outActivity, boolean restrictedBgActivity) 方法:

 private int startActivity(final ActivityRecord r, ActivityRecord sourceRecord,
                IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
                int startFlags, boolean doResume, ActivityOptions options, TaskRecord inTask,
                ActivityRecord[] outActivity, boolean restrictedBgActivity) {
     try {
         mService.mWindowManager.deferSurfaceLayout();
            result = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor,
                    startFlags, doResume, options, inTask, outActivity, restrictedBgActivity);
     } 
 }

大致的流程图如下:
在这里插入图片描述

接下来就是Launcher进程和Activity的创建了,这个过程和普通的应用启动就没什么区别了,详情查看Android Activity的创建流程。

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

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

相关文章

Halcon基于灰度值的模板匹配

Halcon基于灰度值的模板匹配 基于灰度值的模板匹配是最经典的模板匹配算法&#xff0c;也是最早提出来的模板匹配算法。这种算法的根本思想是&#xff0c;计算模板图像与检测图像之间的像素灰度差值的绝对值总和&#xff08;SAD方法&#xff09;或者平方差总和&#xff08;SSD…

Java NIO (一)简介

1 NIO简介 在1.4版本之前&#xff0c;Java NIO类库是阻塞IO&#xff0c;从1.4版本开始&#xff0c;引进了新的异步IO库&#xff0c;被称为Java New IO类库&#xff0c;简称为Java NIO。New IO类库的目的 就是要让Java支持非阻塞IO。 Java NIO类库包含三个核心组件&#xff1a; …

Vue3中provide,inject使用

一&#xff0c;provide,inject使用&#xff1a; 应用场景&#xff1a;向孙组件传数据 应用Vue3碎片&#xff1a; ref&#xff0c;reactive&#xff0c;isRef&#xff0c;provide, inject 1.provide,inject使用 a.爷组件引入 import {ref,provide} from vue const drinkListre…

LeetCode、2542. 最大子序列的分数【中等,排序+小顶堆】

文章目录 前言LeetCode、2542. 最大子序列的分数【中等&#xff0c;排序小顶堆】题目及类型思路及代码实现 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领…

安泰射频功率放大器的主要的类型及主要参数

射频功率放大器是一种将射频信号放大到更高功率级别的电子设备。根据其工作原理和应用要求&#xff0c;射频功率放大器可以分为不同的类型。下面西安安泰将介绍一些常见的射频功率放大器类型和相关的主要参数。 A类功率放大器&#xff1a;A类功率放大器是一种广泛使用的线性放大…

Navicat教程

下载连接&#xff08;无限使用版&#xff09; 链接&#xff1a;https://pan.baidu.com/s/1IprYLRv0bSnW-XKn0trRtw 提取码&#xff1a;j6qx 连接使用 1.1 连接数据库 打开navicat&#xff0c;点击连接&#xff0c;选择数据库 1.2 操作数据库 右键连接&#xff0c;点击新建数…

Excel 动态可视化图表分享

AIGC ChatGPT 职场案例 AI 绘画 与 短视频制作 PowerBI 商业智能 68集 数据库Mysql 8.0 54集 数据库Oracle 21C 142集 Office 2021实战应用 Python 数据分析实战&#xff0c; ETL Informatica 数据仓库案例实战 Excel 2021实操 100集&#xff0c; Excel 2021函数大全 80集 Exc…

【C++】入门C++前想要了解的小知识

个人主页 &#xff1a; zxctsclrjjjcph 文章封面来自&#xff1a;艺术家–贤海林 如有转载请先通知 目录 1. 前言2. 什么是C3. C的发展史4. C的重要性4.1 语言的使用广泛度4.2 在工作领域中4.3 在校招领域中 5. 如何学习C5.1 看看别人怎么学习的5.2 自己怎么学 1. 前言 今天开…

【FastAPI】请求体

在 FastAPI 中&#xff0c;请求体&#xff08;Request Body&#xff09;是通过请求发送的数据&#xff0c;通常用于传递客户端提交的信息。FastAPI 使得处理请求体变得非常容易。 请求体是客户端发送给 API 的数据。响应体是 API 发送给客户端的数据 注&#xff1a;不能使用 …

docker容器和常用命令

1.什么是容器 容器是隔离的环境中运行的一个 进程 , 如果进程结束 , 容器就会停止. 细致: 容器的隔离环境 , 拥有自己的 ip 地址 , 系统文件 , 主机名 , 进程管理 , 相当于一个 mini的系统 2.容器 vs 虚拟机 3.Docker极速上手指南 #1.安装相关依赖. sudo yum install -y …

链表的中间节点

链表的中间节点 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能&#xff0c;轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/middle-of-the-…

python之AI视频去码图片修复CodeFormer,把你的老照片变漂亮

CodeFormer介绍 CodeFormer是由南洋理工大学-商汤科技联合研究中心S-Lab在NeurIPS 2022上提出的一种基于VQGANTransformer的人脸复原模型。该方法基于预训练VQGAN离散码本空间&#xff0c;改变复原任务的固有范式&#xff0c;将人脸复原任务转成Code序列的预测任务&#xff0c…

DOM 的 diff 算法

经典面试题&#xff1a; 1&#xff09;react/vue中的 key 有什么作用&#xff1f;&#xff08;key的内部原理是什么&#xff1f;&#xff09; 2&#xff09;为什么遍历列表时&#xff0c;key 最好不用 index&#xff1f; 1、虚拟dom中key的作用&#xff1a; 1&#xff09; 简…

SpringBoot新手入门完整教程和项目示例

文章目录 SpringBoot新手入门完整教程和项目示例1、SpringBoot简介2、Spring Boot的核心功能&#xff1f;&#xff08;优点&#xff09;3、SpringBoot与SpringMVC 的区别&#xff1f;4、构建SpringBoot项目4.1、在官网自动生成下载spring boot项目4.2、手动使用maven创建Spring…

vue中使用高德地图,根据类型显示不同点,点击出现弹框居中显示,并在可视化区域显示所有点

效果图 一、安装 vue-amap 插件 npm install vue-amap --save二、vue页面 <template> <div><ul styledisplay:flex;><liv-for"(item, index) in checkList":key"index"click"onClick(item)":class"item.checked ?…

直观全面的数据库管理 RazorSQL

RazorSQL是一款功能强大的数据库管理工具&#xff0c;适用于各种数据库系统&#xff0c;如MySQL、Oracle、SQL Server、SQLite等。它提供了一个直观的界面&#xff0c;让用户能够轻松地管理数据库&#xff0c;包括查询、编辑、创建和管理数据库对象等操作。RazorSQL还支持多种S…

使用composer生成的DMG和PKG格式软件包有何区别

在使用Composer从包源构建软件包时候&#xff0c;有两种不同类型的包&#xff1a;PKG和DMG。你知道两者之间的区别吗? 以及如何选取吗&#xff1f; 每种格式都有各自的优势具体取决于软件包的预期用途以及用于部署软件包的工具。下面我们来了解一下PKG和DMG格式的区别和用途。…

论文笔记:信息融合的门控多模态单元(GMU)

整理了GMU&#xff08;ICLR2017 GATED MULTIMODAL UNITS FOR INFORMATION FUSION&#xff09;论文的阅读笔记 背景模型实验 论文地址&#xff1a; GMU 背景 多模态指的是同一个现实世界的概念可以用不同的视图或数据类型来描述。比如维基百科有时会用音频的混合来描述一个名人…

详解Vite创建Vue3项目+vue-router+ts+vite+element-plus

前言 在之前的文章中写过“Vue3TSElementPlus的安装和使用教程【详细讲解】”&#xff0c;但那篇文章写的是创建vue3的项目没有使用到Vite构建工具进行创建还是使用的常规webpacket构建工具进行创建的。提到Vite和webpacket的时候我们可以简单说一下。Vite 和 Webpack 都是现代…

SpringBoot框架自定义解析配置项占位符(${})获取配置

一、前言 在最近项目开发中&#xff0c;有一个需求&#xff0c;针对自定义注解中字符串属性值的设置需要支持使用”${xx}“占位符获取在SpringBoot框架配置文件中配置项对应的属性值&#xff0c;而且支持多个”${xx}“标识的配置任意拼接。 二、功能实现 从实现思路上说还是…