• vscode源码分析【二】程序的启动逻辑,第一个窗口是如何创建的


    上一篇文章:https://www.cnblogs.com/liulun/
    (小广告:我做的开源免费的,个人知识管理及自媒体营销工具“想学吗”:https://github.com/xland/xiangxuema

    我们在package.json里能找到他的入口文件;

    "main": "./out/main",

    electron是分主进程和渲染进程的;
    渲染进程是主进程启动的;
    ./out/main.js显然这就是主进程的入口程序;
    确实不假
    但别着急去分析这个文件;
    因为它是在out目录下,明显是什么东西输出出来的;
    我们先打扫一遍src目录下的东西;
    发现了tsconfig.json

    "outDir": "../out",

    哈,这是typescript代码,编译后输出到./out/目录下的;

    那么我们来看src下的main.js
    分析代码最主要的就是目的明确,我们的目的是看看他的启动逻辑(主窗口是怎么打开的)
    无关的东西先不管,要不然很容易迷失...;
    我们在main.js里找electron的ready事件

    app.once('ready', function () {
    	if (args['trace']) {
    		// @ts-ignore
    		const contentTracing = require('electron').contentTracing;
    
    		const traceOptions = {
    			categoryFilter: args['trace-category-filter'] || '*',
    			traceOptions: args['trace-options'] || 'record-until-full,enable-sampling'
    		};
    
    		contentTracing.startRecording(traceOptions, () => onReady());
    	} else {
    		onReady();
    	}
    });

    先去看onReady方法
    onReady里主要就是执行这个方法:

    const startup = nlsConfig => {
    				nlsConfig._languagePackSupport = true;
    				process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig);
    				process.env['VSCODE_NODE_CACHED_DATA_DIR'] = cachedDataDir || '';
    
    				// Load main in AMD
    				perf.mark('willLoadMainBundle');
    				require('./bootstrap-amd').load('vs/code/electron-main/main', () => {
    					perf.mark('didLoadMainBundle');
    				});
    			};

    到这里,我们先看看bootstrap-amd都干了啥
    发现他其实调用了/vs/loader里的方法(下面这行代码里面entrypoint就是:vs/code/electron-main/main)

    loader([entrypoint], onLoad, onError);

    loader是微软自家的AMD模块加载开源项目:https://github.com/Microsoft/vscode-loader/
    没啥好说的,我们接着来看vs/code/electron-main/main.ts的代码,
    发现它一开始就加载了一大堆模块,头大!
    先不管它加载的这些模块都是干嘛的,我们看它本身的入口,代码拉到末尾,发现:

        const code = new CodeMain();
        code.main();

    马上去看这个模块的main函数;发现main函数对于我们唯一有用的就是:

    this.startup(args);

    这个函数启动了一堆服务之后,就执行了:

    const mainIpcServer = yield this.doStartup(logService, environmentService, lifecycleService, instantiationService, true);

    return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup();

    我们先看第一行,在doStartup里,只有这行代码看起来有用:

    server = await serve(environmentService.mainIPCHandle);

    serve是上面加载的那一大堆模块之一:vs/base/parts/ipc/node/ipc.net
    发现它的serve其实就是启动了一个服务:

        function serve(hook) {
            return new Promise((c, e) => {
                const server = net_1.createServer();
                server.on('error', e);
                server.listen(hook, () => {
                    server.removeListener('error', e);
                    c(new Server(server));
                });
            });
        }

    对我们目前的分析,帮助不大!
    我们再返回头看第二行代码:
    instantiationService.ts在vs/platform/instantiation/common/instantiationService.ts
    他的createInstance是个工厂函数,第一个参数是类型(或构造函数),后面的参数都是这个类型的构造函数所需要的参数。
    那么我们主要看第一个参数CodeApplication,这个类型的代码在这里:vs/code/electron-main/app.ts
    我们找到CodeApplication的startup方法,看到这一句:

    // Open Windows
    		const windows = appInstantiationService.invokeFunction(accessor => this.openFirstWindow(accessor, electronIpcServer, sharedProcessClient));

    这应该就是我们找的启动主窗口的方法了,跟进去看看:
    一开始是一大堆IPC通信相关的代码(主线程和渲染线程通信的代码)
    之后创建了IWindowsMainservice的实例

    const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService);

    然后用这个实例创建了窗口

    		return windowsMainService.open({
    			context,
    			cli: args,
    			forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']),
    			diffMode: args.diff,
    			noRecentEntry,
    			waitMarkerFileURI,
    			initialStartup: true
    		});

    IWindowsMainservice接口具体实例的类型是WindowsManager(可以在app.ts文件中找到下面的代码)

    services.set(IWindowsMainService, new SyncDescriptor(WindowsManager, [machineId, this.userEnv]));

    (IWindowsMainservice接口的描述文件在这里:vsplatformwindowselectron-mainwindows.ts)
    WindowsManager在vs/code/electron-main/windows.ts文件中定义,
    那我们去看看WindowsManager的open方法,发现了:

    const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd);

    好,再去看doOpen,发现最后的:

    			// Finally, if no window or folder is found, just open the files in an empty window
    			else {
    				usedWindows.push(this.openInBrowserWindow({
    					userEnv: openConfig.userEnv,
    					cli: openConfig.cli,
    					initialStartup: openConfig.initialStartup,
    					fileInputs,
    					forceNewWindow: true,
    					remoteAuthority: fileInputs.remoteAuthority,
    					forceNewTabbedWindow: openConfig.forceNewTabbedWindow
    				}));
    
    				// Reset these because we handled them
    				fileInputs = undefined;
    			}

    注意:这两个方法有一个重要的逻辑就是:如果已经有一个窗口了,那么就用现成的窗口打开目录(或文件)
    再去看openInBrowserWindow

    // Create the window
    			window = this.instantiationService.createInstance(CodeWindow, {
    				state,
    				extensionDevelopmentPath: configuration.extensionDevelopmentPath,
    				isExtensionTestHost: !!configuration.extensionTestsPath
    			});

    它创建了一个CodeWindow的实例,这个类型在:vs/code/electron-main/window.ts中定义
    这个类型的构造函数里调用了这个方法:

    this.createBrowserWindow(config);

    在这个方法里完成了窗口的创建:

    // Create the browser window.
    		this._win = new BrowserWindow(options);

    至此:VSCode窗口创建出来了







     

  • 相关阅读:
    md5编码的两个程序
    DotNetNuke 5 User's Guide Get Your Website Up and Running读书摘录3
    纪念今天DNN密码破解
    DotNetNuke 5 User's Guide Get Your Website Up and Running读书摘录4
    文件与目录的默认权限与隐藏权限(转)
    EXT2 文件系统
    磁盘与目录的容量(转)
    文件的搜寻(转)
    权限与命令间的关系(转)
    账户切换(转)
  • 原文地址:https://www.cnblogs.com/liulun/p/11037550.html
Copyright © 2020-2023  润新知