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接口的描述文件在這里:vs\platform\windows\electron-main\windows.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窗口創建出來了







 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM