mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-08-16 11:42:04 +08:00
feat(ios): fire onDisplayed event when first frame is ready to be displayed (#5344)
* feat: add a 'profiling: lifecycle' to track startup times * feat: log when displayed event fires * feat(ios): fire onDisplayed event when first frame is ready to be displayed
This commit is contained in:

committed by
Alexander Vakrilov

parent
5bae124604
commit
1c78e4784c
@ -2,6 +2,12 @@
|
|||||||
require("globals");
|
require("globals");
|
||||||
|
|
||||||
import { Observable, EventData } from "../data/observable";
|
import { Observable, EventData } from "../data/observable";
|
||||||
|
import {
|
||||||
|
trace as profilingTrace,
|
||||||
|
time,
|
||||||
|
uptime,
|
||||||
|
level as profilingLevel,
|
||||||
|
} from "../profiling";
|
||||||
|
|
||||||
const events = new Observable();
|
const events = new Observable();
|
||||||
let launched = false;
|
let launched = false;
|
||||||
@ -11,6 +17,15 @@ function setLaunched() {
|
|||||||
}
|
}
|
||||||
events.on("launch", setLaunched);
|
events.on("launch", setLaunched);
|
||||||
|
|
||||||
|
if (profilingLevel() > 0) {
|
||||||
|
events.on("displayed", () => {
|
||||||
|
const duration = uptime();
|
||||||
|
const end = time();
|
||||||
|
const start = end - duration;
|
||||||
|
profilingTrace(`Displayed in ${duration.toFixed(2)}ms`, start, end);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function hasLaunched(): boolean {
|
export function hasLaunched(): boolean {
|
||||||
return launched;
|
return launched;
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,12 @@ import { ios as iosView, View } from "../ui/core/view";
|
|||||||
import { Frame, NavigationEntry } from "../ui/frame";
|
import { Frame, NavigationEntry } from "../ui/frame";
|
||||||
import { ios } from "../ui/utils";
|
import { ios } from "../ui/utils";
|
||||||
import * as utils from "../utils/utils";
|
import * as utils from "../utils/utils";
|
||||||
import { profile } from "../profiling";
|
import { profile, level as profilingLevel, Level } from "../profiling";
|
||||||
|
|
||||||
class Responder extends UIResponder {
|
class Responder extends UIResponder {
|
||||||
//
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayedOnce = false;
|
|
||||||
|
|
||||||
class NotificationObserver extends NSObject {
|
class NotificationObserver extends NSObject {
|
||||||
private _onReceiveCallback: (notification: NSNotification) => void;
|
private _onReceiveCallback: (notification: NSNotification) => void;
|
||||||
|
|
||||||
@ -46,6 +44,24 @@ class NotificationObserver extends NSObject {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let displayedOnce = false;
|
||||||
|
let displayedLinkTarget;
|
||||||
|
let displayedLink;
|
||||||
|
class CADisplayLinkTarget extends NSObject {
|
||||||
|
onDisplayed(link: CADisplayLink) {
|
||||||
|
link.invalidate();
|
||||||
|
const ios = utils.ios.getter(UIApplication, UIApplication.sharedApplication);
|
||||||
|
const object = iosApp;
|
||||||
|
displayedOnce = true;
|
||||||
|
notify(<ApplicationEventData>{ eventName: displayedEvent, object, ios });
|
||||||
|
displayedLinkTarget = null;
|
||||||
|
displayedLink = null;
|
||||||
|
}
|
||||||
|
public static ObjCExposedMethods = {
|
||||||
|
"onDisplayed": { returns: interop.types.void, params: [CADisplayLink] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class IOSApplication implements IOSApplicationDefinition {
|
class IOSApplication implements IOSApplicationDefinition {
|
||||||
private _delegate: typeof UIApplicationDelegate;
|
private _delegate: typeof UIApplicationDelegate;
|
||||||
private _currentOrientation = utils.ios.getter(UIDevice, UIDevice.currentDevice).orientation;
|
private _currentOrientation = utils.ios.getter(UIDevice, UIDevice.currentDevice).orientation;
|
||||||
@ -101,6 +117,13 @@ class IOSApplication implements IOSApplicationDefinition {
|
|||||||
|
|
||||||
@profile
|
@profile
|
||||||
private didFinishLaunchingWithOptions(notification: NSNotification) {
|
private didFinishLaunchingWithOptions(notification: NSNotification) {
|
||||||
|
if (!displayedOnce && profilingLevel() >= Level.lifecycle) {
|
||||||
|
displayedLinkTarget = CADisplayLinkTarget.new();
|
||||||
|
displayedLink = CADisplayLink.displayLinkWithTargetSelector(displayedLinkTarget, "onDisplayed");
|
||||||
|
displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, NSDefaultRunLoopMode);
|
||||||
|
displayedLink.addToRunLoopForMode(NSRunLoop.mainRunLoop, UITrackingRunLoopMode);
|
||||||
|
}
|
||||||
|
|
||||||
this._window = UIWindow.alloc().initWithFrame(utils.ios.getter(UIScreen, UIScreen.mainScreen).bounds);
|
this._window = UIWindow.alloc().initWithFrame(utils.ios.getter(UIScreen, UIScreen.mainScreen).bounds);
|
||||||
// TODO: Expose Window module so that it can we styled from XML & CSS
|
// TODO: Expose Window module so that it can we styled from XML & CSS
|
||||||
this._window.backgroundColor = utils.ios.getter(UIColor, UIColor.whiteColor);
|
this._window.backgroundColor = utils.ios.getter(UIColor, UIColor.whiteColor);
|
||||||
@ -126,11 +149,6 @@ class IOSApplication implements IOSApplicationDefinition {
|
|||||||
if (rootView && !rootView.isLoaded) {
|
if (rootView && !rootView.isLoaded) {
|
||||||
rootView.callLoaded();
|
rootView.callLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!displayedOnce) {
|
|
||||||
notify(<ApplicationEventData>{ eventName: displayedEvent, object, ios });
|
|
||||||
displayedOnce = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private didEnterBackground(notification: NSNotification) {
|
private didEnterBackground(notification: NSNotification) {
|
||||||
|
32
tns-core-modules/profiling/profiling.d.ts
vendored
32
tns-core-modules/profiling/profiling.d.ts
vendored
@ -11,10 +11,25 @@ interface TimerInfo {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Profiling mode to use.
|
* Profiling mode to use.
|
||||||
* - `counters` Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default.
|
* - `counters` Accumulates method call counts and times until dumpProfiles is called and then prints aggregated statistic in the console. This is the default.
|
||||||
* - `timeline` Outputs method names along start/end timestamps in the console on the go.
|
* - `timeline` Outputs method names along start/end timestamps in the console on the go.
|
||||||
|
* - `lifecycle` Outputs basic non-verbose times for startup, navigation, etc.
|
||||||
*/
|
*/
|
||||||
type InstrumentationMode = "counters" | "timeline";
|
type InstrumentationMode = "counters" | "timeline" | "lifecycle";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logging levels in order of verbosity.
|
||||||
|
*/
|
||||||
|
export enum Level {
|
||||||
|
none,
|
||||||
|
lifecycle,
|
||||||
|
timeline,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current logging level.
|
||||||
|
*/
|
||||||
|
export function level(): Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables profiling.
|
* Enables profiling.
|
||||||
@ -32,8 +47,9 @@ type InstrumentationMode = "counters" | "timeline";
|
|||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param type Profiling mode to use.
|
* @param type Profiling mode to use.
|
||||||
* - "counters" - Accumulates method call counts and times until dumpProfiles is called and then prints agregated statistic in the console. This is the default.
|
* - "counters" - Accumulates method call counts and times until dumpProfiles is called and then prints aggregated statistic in the console. This is the default.
|
||||||
* - "timeline" - Outputs method names along start/end timestamps in the console on the go.
|
* - "timeline" - Outputs method names along start/end timestamps in the console on the go.
|
||||||
|
* - "lifecycle" - Outputs basic non-verbose times for startup, navigation, etc.
|
||||||
*/
|
*/
|
||||||
export declare function enable(type?: InstrumentationMode): void;
|
export declare function enable(type?: InstrumentationMode): void;
|
||||||
|
|
||||||
@ -133,3 +149,13 @@ export function uptime(): number;
|
|||||||
* Logs important messages. Contrary to console.log's behavior, the profiling log should output even for release builds.
|
* Logs important messages. Contrary to console.log's behavior, the profiling log should output even for release builds.
|
||||||
*/
|
*/
|
||||||
export function log(message: string): void;
|
export function log(message: string): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually output profiling messages. The `@profile` decorator is useful when measuring times that function calls take on the stack
|
||||||
|
* but when measuring times between longer periods (startup times, times between the navigatingTo - navigatedTo events etc.)
|
||||||
|
* you can call this method and provide manually the times to be logged.
|
||||||
|
* @param message A string message
|
||||||
|
* @param start The start time (see `time()`)
|
||||||
|
* @param end The end time (see `time()`)
|
||||||
|
*/
|
||||||
|
export function trace(message: string, start: number, end: number): void;
|
@ -10,9 +10,8 @@ export function uptime() {
|
|||||||
export function log(message: string): void {
|
export function log(message: string): void {
|
||||||
if ((<any>global).__nslog) {
|
if ((<any>global).__nslog) {
|
||||||
(<any>global).__nslog("CONSOLE LOG: " + message);
|
(<any>global).__nslog("CONSOLE LOG: " + message);
|
||||||
} else {
|
|
||||||
console.log(message);
|
|
||||||
}
|
}
|
||||||
|
console.log(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TimerInfo extends TimerInfoDefinition {
|
interface TimerInfo extends TimerInfoDefinition {
|
||||||
@ -130,12 +129,24 @@ const enum MemberType {
|
|||||||
Instance
|
Instance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum Level {
|
||||||
|
none,
|
||||||
|
lifecycle,
|
||||||
|
timeline,
|
||||||
|
}
|
||||||
|
let tracingLevel: Level = Level.none;
|
||||||
|
|
||||||
let profileFunctionFactory: <F extends Function>(fn: F, name: string, type?: MemberType) => F;
|
let profileFunctionFactory: <F extends Function>(fn: F, name: string, type?: MemberType) => F;
|
||||||
export function enable(mode: InstrumentationMode = "counters") {
|
export function enable(mode: InstrumentationMode = "counters") {
|
||||||
profileFunctionFactory = mode && {
|
profileFunctionFactory = mode && {
|
||||||
counters: countersProfileFunctionFactory,
|
counters: countersProfileFunctionFactory,
|
||||||
timeline: timelineProfileFunctionFactory
|
timeline: timelineProfileFunctionFactory
|
||||||
}[mode];
|
}[mode];
|
||||||
|
|
||||||
|
tracingLevel = {
|
||||||
|
lifecycle: Level.lifecycle,
|
||||||
|
timeline: Level.timeline,
|
||||||
|
}[mode] || Level.none;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -294,3 +305,11 @@ export function stopCPUProfile(name: string) {
|
|||||||
__stopCPUProfiler(name);
|
__stopCPUProfiler(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function level(): Level {
|
||||||
|
return tracingLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function trace(message: string, start: number, end: number): void {
|
||||||
|
log(`Timeline: Modules: ${message} (${start}ms. - ${end}ms.)`);
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user