Skip to content
This repository was archived by the owner on Feb 2, 2021. It is now read-only.

Commit 98fa47a

Browse files
feat: Speed up device detection
Speed up device detection by: * removing duplicate calls to all device detection services * cache the Promise of initialize method - this way even when multiple calls are executed simultaneously, the result of the first one will be used. This allows Sidekick to call the method asynchronously. * do not execute iOS device detection login in case `--emulator` is passed Remove several public methods from the service - make them protected as we already have tests for them. Remove all calls to detectCurrentlyAttachedDevices method outside of the service - the initialize method will do it anyway, so no need to call it externally. Remove duplicate calls in the startLookingForDevices method - previously it had to filter which device detection services to call. As now each device detection service checks its platform, we can safely call all of them and rely on their filters to skip the execution.
1 parent cef91c4 commit 98fa47a

File tree

5 files changed

+149
-111
lines changed

5 files changed

+149
-111
lines changed

declarations.d.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,7 +1246,11 @@ interface IProfileDir {
12461246
profileDir: string;
12471247
}
12481248

1249-
interface ICommonOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvailableDevices, IProfileDir {
1249+
interface IEmulator {
1250+
emulator: boolean;
1251+
}
1252+
1253+
interface ICommonOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvailableDevices, IProfileDir, IEmulator {
12501254
argv: IYargArgv;
12511255
validateOptions(commandSpecificDashedOptions?: IDictionary<IDashedOption>): void;
12521256
options: IDictionary<any>;
@@ -1276,7 +1280,6 @@ interface ICommonOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd,
12761280
analyticsClient: string;
12771281
force: boolean;
12781282
companion: boolean;
1279-
emulator: boolean;
12801283
sdk: string;
12811284
template: string;
12821285
certificate: string;

definitions/mobile.d.ts

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,10 @@ declare module Mobile {
408408
* Specifies whether we should skip the emulator starting.
409409
*/
410410
skipEmulatorStart?: boolean;
411+
/**
412+
* Defines if the initialization should await the whole iOS detection to finish or it can just start the detection.
413+
*/
414+
shouldReturnImmediateResult?: boolean;
411415
}
412416

413417
interface IDeviceActionResult<T> extends IDeviceIdentifier {
@@ -418,13 +422,6 @@ declare module Mobile {
418422
hasDevices: boolean;
419423
deviceCount: number;
420424

421-
/**
422-
* Optionally starts emulator depending on the passed options.
423-
* @param {IDevicesServicesInitializationOptions} data Defines wheather to start default or specific emulator.
424-
* @return {Promise<void>}
425-
*/
426-
startEmulatorIfNecessary(data?: Mobile.IDevicesServicesInitializationOptions): Promise<void>;
427-
428425
execute<T>(action: (device: Mobile.IDevice) => Promise<T>, canExecute?: (dev: Mobile.IDevice) => boolean, options?: { allowNoDevices?: boolean }): Promise<IDeviceActionResult<T>[]>;
429426

430427
/**
@@ -458,11 +455,8 @@ declare module Mobile {
458455
isAppInstalledOnDevices(deviceIdentifiers: string[], appIdentifier: string, framework: string): Promise<IAppInstalledInfo>[];
459456
setLogLevel(logLevel: string, deviceIdentifier?: string): void;
460457
deployOnDevices(deviceIdentifiers: string[], packageFile: string, packageName: string, framework: string): Promise<void>[];
461-
startDeviceDetectionInterval(): Promise<void>;
462458
getDeviceByIdentifier(identifier: string): Mobile.IDevice;
463459
mapAbstractToTcpPort(deviceIdentifier: string, appIdentifier: string, framework: string): Promise<string>;
464-
detectCurrentlyAttachedDevices(options?: Mobile.IDeviceLookingOptions): Promise<void>;
465-
startEmulator(platform?: string, emulatorImage?: string): Promise<void>;
466460
isCompanionAppInstalledOnDevices(deviceIdentifiers: string[], framework: string): Promise<IAppInstalledInfo>[];
467461
getDebuggableApps(deviceIdentifiers: string[]): Promise<Mobile.IDeviceApplicationInformation[]>[];
468462
getDebuggableViews(deviceIdentifier: string, appIdentifier: string): Promise<Mobile.IDebugWebViewInfo[]>;
@@ -748,9 +742,9 @@ declare module Mobile {
748742
resolveProductName(deviceType: string): string;
749743
}
750744

751-
interface IDeviceLookingOptions {
745+
interface IDeviceLookingOptions extends IEmulator {
752746
shouldReturnImmediateResult: boolean;
753-
platform: string
747+
platform: string;
754748
}
755749

756750
interface IAndroidDeviceHashService {

mobile/mobile-core/devices-service.ts

Lines changed: 89 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
157157
/**
158158
* Starts looking for devices. Any found devices are pushed to "_devices" variable.
159159
*/
160-
public async detectCurrentlyAttachedDevices(options?: Mobile.IDeviceLookingOptions): Promise<void> {
160+
protected async detectCurrentlyAttachedDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
161+
const options = this.getDeviceLookingOptions(deviceInitOpts);
162+
161163
for (const deviceDiscovery of this._allDeviceDiscoveries) {
162164
try {
163165
await deviceDiscovery.startLookingForDevices(options);
@@ -167,49 +169,42 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
167169
}
168170
}
169171

170-
public async startDeviceDetectionInterval(): Promise<void> {
172+
protected async startDeviceDetectionInterval(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
171173
this.$processService.attachToProcessExitSignals(this, this.clearDeviceDetectionInterval);
172174

173175
if (this.deviceDetectionInterval) {
174176
this.$logger.trace("Device detection interval is already started. New Interval will not be started.");
175-
} else {
176-
let isFirstExecution = true;
177+
return;
178+
}
179+
let isFirstExecution = true;
177180

178-
return new Promise<void>((resolve, reject) => {
179-
this.deviceDetectionInterval = setInterval(async () => {
180-
if (this.isDeviceDetectionIntervalInProgress) {
181-
return;
182-
}
181+
return new Promise<void>((resolve, reject) => {
182+
this.deviceDetectionInterval = setInterval(async () => {
183+
if (this.isDeviceDetectionIntervalInProgress) {
184+
return;
185+
}
183186

184-
this.isDeviceDetectionIntervalInProgress = true;
187+
this.isDeviceDetectionIntervalInProgress = true;
185188

186-
for (const deviceDiscovery of this._allDeviceDiscoveries) {
187-
try {
188-
const deviceLookingOptions = this.getDeviceLookingOptions();
189-
await deviceDiscovery.startLookingForDevices(deviceLookingOptions);
190-
} catch (err) {
191-
this.$logger.trace("Error while checking for new devices.", err);
192-
}
193-
}
189+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
194190

195-
try {
196-
const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS);
197-
await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates()));
198-
} catch (err) {
199-
this.$logger.trace("Error checking for application updates on devices.", err);
200-
}
191+
try {
192+
const trustedDevices = _.filter(this._devices, device => device.deviceInfo.status === constants.CONNECTED_STATUS);
193+
await settlePromises(_.map(trustedDevices, device => device.applicationManager.checkForApplicationUpdates()));
194+
} catch (err) {
195+
this.$logger.trace("Error checking for application updates on devices.", err);
196+
}
201197

202-
if (isFirstExecution) {
203-
isFirstExecution = false;
204-
resolve();
205-
this.deviceDetectionInterval.unref();
206-
}
198+
if (isFirstExecution) {
199+
isFirstExecution = false;
200+
resolve();
201+
this.deviceDetectionInterval.unref();
202+
}
207203

208-
this.isDeviceDetectionIntervalInProgress = false;
204+
this.isDeviceDetectionIntervalInProgress = false;
209205

210-
}, DevicesService.DEVICE_LOOKING_INTERVAL);
211-
});
212-
}
206+
}, DevicesService.DEVICE_LOOKING_INTERVAL);
207+
});
213208
}
214209

215210
/**
@@ -234,30 +229,14 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
234229
/**
235230
* Starts looking for running devices. All found devices are pushed to _devices variable.
236231
*/
237-
private async startLookingForDevices(options?: Mobile.IDeviceLookingOptions): Promise<void> {
232+
private startLookingForDevices(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
238233
this.$logger.trace("startLookingForDevices; platform is %s", this._platform);
239-
if (!options) {
240-
options = this.getDeviceLookingOptions();
241-
}
242-
if (!this._platform) {
243-
await this.detectCurrentlyAttachedDevices(options);
244-
await this.startDeviceDetectionInterval();
245-
} else {
246-
if (this.$mobileHelper.isiOSPlatform(this._platform)) {
247-
await this.$iOSDeviceDiscovery.startLookingForDevices(options);
248-
await this.$iOSSimulatorDiscovery.startLookingForDevices(options);
249-
} else if (this.$mobileHelper.isAndroidPlatform(this._platform)) {
250-
await this.$androidDeviceDiscovery.startLookingForDevices(options);
251-
}
252234

253-
for (const deviceDiscovery of this._otherDeviceDiscoveries) {
254-
try {
255-
await deviceDiscovery.startLookingForDevices(options);
256-
} catch (err) {
257-
this.$logger.trace("Error while checking for new devices.", err);
258-
}
259-
}
235+
if (this._platform) {
236+
return this.detectCurrentlyAttachedDevices(deviceInitOpts);
260237
}
238+
239+
return this.startDeviceDetectionInterval(deviceInitOpts);
261240
}
262241

263242
/**
@@ -276,8 +255,6 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
276255
* @param identifier parameter passed by the user to --device flag
277256
*/
278257
public async getDevice(deviceOption: string): Promise<Mobile.IDevice> {
279-
const deviceLookingOptions = this.getDeviceLookingOptions();
280-
await this.detectCurrentlyAttachedDevices(deviceLookingOptions);
281258
let device: Mobile.IDevice = null;
282259

283260
let emulatorIdentifier = null;
@@ -390,34 +367,34 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
390367
/**
391368
* Starts emulator or simulator if necessary depending on --device or --emulator flags.
392369
* If no options are passed runs default emulator/simulator if no devices are connected.
393-
* @param data mainly contains information about --emulator and --deviceId flags.
370+
* @param deviceInitOpts mainly contains information about --emulator and --deviceId flags.
394371
*/
395-
public async startEmulatorIfNecessary(data?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
396-
if (data && data.deviceId && data.emulator) {
372+
protected async startEmulatorIfNecessary(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
373+
if (deviceInitOpts && deviceInitOpts.deviceId && deviceInitOpts.emulator) {
397374
this.$errors.failWithoutHelp(`--device and --emulator are incompatible options.
398375
If you are trying to run on specific emulator, use "${this.$staticConfig.CLIENT_NAME} run --device <DeviceID>`);
399376
}
400377

401-
if (data && data.platform && !data.skipEmulatorStart) {
378+
if (deviceInitOpts && deviceInitOpts.platform && !deviceInitOpts.skipEmulatorStart) {
402379
// are there any running devices
403-
this._platform = data.platform;
380+
this._platform = deviceInitOpts.platform;
404381
try {
405-
await this.startLookingForDevices();
382+
await this.startLookingForDevices(deviceInitOpts);
406383
} catch (err) {
407384
this.$logger.trace("Error while checking for devices.", err);
408385
}
409386
const deviceInstances = this.getDeviceInstances();
410387

411-
if (!data.deviceId && _.isEmpty(deviceInstances)) {
412-
if (!this.$hostInfo.isDarwin && this.$mobileHelper.isiOSPlatform(data.platform)) {
388+
if (!deviceInitOpts.deviceId && _.isEmpty(deviceInstances)) {
389+
if (!this.$hostInfo.isDarwin && this.$mobileHelper.isiOSPlatform(deviceInitOpts.platform)) {
413390
this.$errors.failWithoutHelp(constants.ERROR_NO_DEVICES_CANT_USE_IOS_SIMULATOR);
414391
}
415392
}
416393

417394
try {
418-
await this._startEmulatorIfNecessary(data);
395+
await this._startEmulatorIfNecessary(deviceInitOpts);
419396
} catch (err) {
420-
const errorMessage = this.getEmulatorError(err, data.platform);
397+
const errorMessage = this.getEmulatorError(err, deviceInitOpts.platform);
421398

422399
this.$errors.failWithoutHelp(errorMessage);
423400
}
@@ -429,15 +406,15 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
429406

430407
//if no --device is passed and no devices are found, the default emulator is started
431408
if (!data.deviceId && _.isEmpty(deviceInstances)) {
432-
return this.startEmulator(data.platform);
409+
return this.startEmulator(data);
433410
}
434411

435412
//check if --device(value) is running, if it's not or it's not the same as is specified, start with name from --device(value)
436413
if (data.deviceId) {
437414
if (!helpers.isNumber(data.deviceId)) {
438415
const activeDeviceInstance = _.find(deviceInstances, (device: Mobile.IDevice) => device.deviceInfo.identifier === data.deviceId);
439416
if (!activeDeviceInstance) {
440-
return this.startEmulator(data.platform, data.deviceId);
417+
return this.startEmulator(data);
441418
}
442419
}
443420
}
@@ -446,11 +423,12 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
446423
if (data.emulator && deviceInstances.length) {
447424
const runningDeviceInstance = _.some(deviceInstances, (value) => value.isEmulator);
448425
if (!runningDeviceInstance) {
449-
return this.startEmulator(data.platform);
426+
return this.startEmulator(data);
450427
}
451428
}
452429
}
453430

431+
private _deviceInitializePromise: Promise<void>;
454432
/**
455433
* Takes care of gathering information about all running devices.
456434
* Sets "_isInitialized" to true after infomation is present.
@@ -459,46 +437,64 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
459437
*/
460438
@exported("devicesService")
461439
public async initialize(data?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
440+
if (!this._deviceInitializePromise) {
441+
this._deviceInitializePromise = this.initializeCore(data);
442+
}
443+
444+
try {
445+
await this._deviceInitializePromise;
446+
} catch (err) {
447+
// In case the initalization fails, we want to allow calling `initlialize` again with other arguments for example, so remove the cached promise value.
448+
this.$logger.trace(`Error while initializing devicesService: ${err}`);
449+
this._deviceInitializePromise = null;
450+
throw err;
451+
}
452+
}
453+
454+
private async initializeCore(deviceInitOpts?: Mobile.IDevicesServicesInitializationOptions): Promise<void> {
462455
if (this._isInitialized) {
463456
return;
464457
}
465458

466459
this.$logger.out("Searching for devices...");
467460

468-
data = data || {};
461+
deviceInitOpts = deviceInitOpts || {};
469462

470-
if (!data.skipEmulatorStart) {
471-
await this.startEmulatorIfNecessary(data);
463+
if (!deviceInitOpts.skipEmulatorStart) {
464+
// TODO: Remove from here as it calls startLookingForDevices, so we double the calls to specific device detection services
465+
await this.startEmulatorIfNecessary(deviceInitOpts);
472466
}
473467

474-
this._data = data;
475-
const platform = data.platform;
476-
const deviceOption = data.deviceId;
468+
this._data = deviceInitOpts;
469+
const platform = deviceInitOpts.platform;
470+
const deviceOption = deviceInitOpts.deviceId;
477471

478472
if (platform && deviceOption) {
479-
this._platform = this.getPlatform(data.platform);
473+
this._platform = this.getPlatform(deviceInitOpts.platform);
474+
this.detectCurrentlyAttachedDevices(deviceInitOpts);
480475
this._device = await this.getDevice(deviceOption);
481476
if (this._device.deviceInfo.platform !== this._platform) {
482477
this.$errors.fail(constants.ERROR_CANNOT_RESOLVE_DEVICE);
483478
}
484479
this.$logger.warn("Your application will be deployed only on the device specified by the provided index or identifier.");
485480
} else if (!platform && deviceOption) {
481+
this.detectCurrentlyAttachedDevices(deviceInitOpts);
486482
this._device = await this.getDevice(deviceOption);
487483
this._platform = this._device.deviceInfo.platform;
488484
} else if (platform && !deviceOption) {
489485
this._platform = this.getPlatform(platform);
490-
await this.startLookingForDevices();
486+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
491487
} else {
492488
// platform and deviceId are not specified
493-
if (data.skipInferPlatform) {
494-
if (data.skipDeviceDetectionInterval) {
495-
await this.detectCurrentlyAttachedDevices();
489+
if (deviceInitOpts.skipInferPlatform) {
490+
if (deviceInitOpts.skipDeviceDetectionInterval) {
491+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
496492
} else {
497-
const deviceLookingOptions = this.getDeviceLookingOptions(this._platform, true);
498-
await this.startLookingForDevices(deviceLookingOptions);
493+
deviceInitOpts.shouldReturnImmediateResult = true;
494+
await this.startLookingForDevices(deviceInitOpts);
499495
}
500496
} else {
501-
await this.detectCurrentlyAttachedDevices();
497+
await this.detectCurrentlyAttachedDevices(deviceInitOpts);
502498

503499
const devices = this.getDeviceInstances();
504500
const platforms = _(devices)
@@ -630,17 +626,17 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
630626
* @param platform (optional) platform to start emulator/simulator for
631627
* @param emulatorImage (optional) emulator/simulator image identifier
632628
*/
633-
public async startEmulator(platform?: string, emulatorImage?: string): Promise<void> {
634-
635-
platform = platform || this._platform;
636-
const deviceLookingOptions = this.getDeviceLookingOptions(platform);
629+
protected async startEmulator(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Promise<void> {
630+
const { deviceId } = deviceInitOpts;
631+
const platform = deviceInitOpts.platform || this._platform;
637632
const emulatorServices = this.resolveEmulatorServices(platform);
638633
if (!emulatorServices) {
639634
this.$errors.failWithoutHelp("Unable to detect platform for which to start emulator.");
640635
}
641636

642-
await emulatorServices.startEmulator(emulatorImage);
637+
await emulatorServices.startEmulator(deviceId);
643638

639+
const deviceLookingOptions = this.getDeviceLookingOptions(deviceInitOpts);
644640
if (this.$mobileHelper.isAndroidPlatform(platform)) {
645641
await this.$androidDeviceDiscovery.startLookingForDevices(deviceLookingOptions);
646642
} else if (this.$mobileHelper.isiOSPlatform(platform) && this.$hostInfo.isDarwin) {
@@ -697,10 +693,11 @@ export class DevicesService extends EventEmitter implements Mobile.IDevicesServi
697693
};
698694
}
699695

700-
private getDeviceLookingOptions(platform?: string, shouldReturnImmediateResult?: boolean): Mobile.IDeviceLookingOptions {
701-
platform = platform || this._platform;
702-
shouldReturnImmediateResult = shouldReturnImmediateResult || false;
703-
return { shouldReturnImmediateResult: shouldReturnImmediateResult, platform: platform };
696+
private getDeviceLookingOptions(deviceInitOpts: Mobile.IDevicesServicesInitializationOptions = {}): Mobile.IDeviceLookingOptions {
697+
const { shouldReturnImmediateResult, emulator } = deviceInitOpts;
698+
const platform = deviceInitOpts.platform || this._platform;
699+
700+
return { platform, shouldReturnImmediateResult: !!shouldReturnImmediateResult, emulator: !!emulator };
704701
}
705702

706703
private getEmulatorError(error: Error, platform: string): string {

0 commit comments

Comments
 (0)