提交 e647165f 编写于 作者: H hdx

feat(performance): 支持 x iOS

上级 87be17cf
......@@ -33,6 +33,8 @@ export { removeInterceptor, addInterceptor } from './base/interceptor'
export { getLaunchOptionsSync } from './base/getLaunchOptionsSync'
export { env } from './base/env'
export * from './performance'
export {
initUTSProxyClass,
initUTSProxyFunction,
......
import { getCurrentPage } from '@dcloudio/uni-core'
import {
API_NAVIGATE_BACK as NAVIGATE_BACK,
API_NAVIGATE_TO as NAVIGATE_TO,
API_REDIRECT_TO as REDIRECT_TO,
API_SWITCH_TAB as SWITCH_TAB,
} from '@dcloudio/uni-api'
import type {
GetPerformance,
Performance,
PerformanceEntry,
PerformanceObserver,
PerformanceObserverCallback,
PerformanceObserverEntryList,
PerformanceObserverOptions,
} from '@dcloudio/uni-app-x/types/uni'
import { onAfterRoute, onBeforeRoute, onPageReady } from '../route/performance'
// TODO
const APP_LAUNCH = 'appLaunch'
const PERFORMANCE_BUFFER_SIZE = 30
const ENTRY_TYPE_RENDER = 'render'
const ENTRY_TYPE_NAVIGATION = 'navigation'
// const RENDER_TYPE_FIRST_PAINT = "firstPaint"
const RENDER_TYPE_FIRST_LAYOUT = 'firstLayout'
const RENDER_TYPE_FIRST_RENDER = 'firstRender'
const AppStartDuration = 1
// const PageRenderDuration = 1
// const PageLayoutDuration = 2
// const PageRenderCount = 3
// const PageLayoutCount = 4
// const PageFirstRenderStartTime = 5
// const PageFirstLayoutStartTime = 6
const PageFirstPageRenderDuration = 7
const PageFirstPageLayoutDuration = 8
/// status machine
class PerformanceEntryStatus {
static STATE_EMPTY: number = 0
static STATE_BEFORE: number = 1
static STATE_AFTER: number = 2
static STATE_READY: number = 3
private _state: number = PerformanceEntryStatus.STATE_EMPTY
get state(): number {
return this._state
}
set state(state: number) {
this._state = state
if (this._state == PerformanceEntryStatus.STATE_BEFORE) {
this.executeBefore()
} else if (this._state == PerformanceEntryStatus.STATE_AFTER) {
this.executeAfter()
} else if (this._state == PerformanceEntryStatus.STATE_READY) {
this.executeReady()
}
}
_entryData: PerformanceEntry
get entryData(): PerformanceEntry {
return this._entryData
}
constructor(entryType: string, name: string) {
this._entryData = {
entryType,
name,
duration: 0,
startTime: 0,
} as PerformanceEntry
}
executeBefore() {
const page = getCurrentPage()
if (page != null) {
this._entryData.referrerPath = page.route!
}
}
executeAfter() {
const page = getCurrentPage()
if (page != null) {
// @ts-expect-error
this._entryData.pageId = parseInt(page.$nativePage.pageId)
this._entryData.path = page.route!
}
}
executeReady() {}
getCurrentInnerPage(): IPage | null {
const currentPage = getCurrentPage()
if (currentPage == null) {
return null
}
// @ts-expect-error
return currentPage.$nativePage
}
}
/// layout status machine
class PerformanceEntryStatusLayout extends PerformanceEntryStatus {
constructor() {
super(ENTRY_TYPE_RENDER, RENDER_TYPE_FIRST_LAYOUT)
}
override executeAfter() {
super.executeAfter()
this._entryData.startTime = Date.now()
}
override executeReady() {
super.executeReady()
const innerPage = super.getCurrentInnerPage()
if (innerPage != null) {
// @ts-expect-error
this._entryData.duration = nativePage.getDuration(
innerPage.pageId,
PageFirstPageLayoutDuration
)
}
}
}
/// render status machine
class PerformanceEntryStatusRender extends PerformanceEntryStatus {
constructor() {
super(ENTRY_TYPE_RENDER, RENDER_TYPE_FIRST_RENDER)
}
override executeAfter() {
super.executeAfter()
this._entryData.startTime = Date.now()
}
override executeReady() {
super.executeReady()
const innerPage = super.getCurrentInnerPage()
if (innerPage != null) {
// @ts-expect-error
this._entryData.duration = nativePage.getDuration(
innerPage.pageId,
PageFirstPageRenderDuration
)
}
}
}
/// navigation status machine
class PerformanceEntryStatusNavigation extends PerformanceEntryStatus {
constructor(name: string, navigationType: string) {
super(ENTRY_TYPE_NAVIGATION, name)
this._entryData.navigationType = navigationType
}
override executeBefore() {
super.executeBefore()
this._entryData.startTime = Date.now()
}
override executeReady() {
const innerPage = super.getCurrentInnerPage()
if (innerPage != null) {
this._entryData.duration = Date.now() - this._entryData.startTime
if (this._entryData.name == APP_LAUNCH) {
// @ts-expect-error
this._entryData.duration += nativePage.getDuration(AppStartDuration)
}
}
}
}
class PerformanceEntryQueue<T> extends Array<T> {
private _queueSize: number = PERFORMANCE_BUFFER_SIZE
get queueSize(): number {
return this._queueSize
}
set queueSize(value: number) {
this._queueSize = value
if (this.length > value) {
this.dequeue(this.length - value)
}
}
override push(...do_not_transform_spread: T[]): number {
return this.enqueue(...do_not_transform_spread)
}
enqueue(...do_not_transform_spread: T[]): number {
if (this.length > this._queueSize - 1) {
this.shift()
}
return super.push(...do_not_transform_spread)
}
dequeue(count = 1) {
this.splice(0, count)
}
}
class PerformanceObserverEntryListImpl implements PerformanceObserverEntryList {
private _queue = new PerformanceEntryQueue<PerformanceEntry>()
push(...do_not_transform_spread: PerformanceEntry[]) {
this._queue.push(...do_not_transform_spread)
}
getEntries(): PerformanceEntry[] {
return this._queue
}
getEntriesByType(entryType: string): PerformanceEntry[] {
return this._queue.filter(
(entry: PerformanceEntry): boolean => entry.entryType == entryType
)
}
getEntriesByName(name: string, entryType: string): PerformanceEntry[] {
return this._queue.filter(
(entry: PerformanceEntry): boolean =>
entry.entryType == entryType && entry.name == name
)
}
clear() {
this._queue.length = 0
}
get bufferSize(): number {
return this._queue.queueSize
}
set bufferSize(size: number) {
this._queue.queueSize = size
}
}
class PerformanceObserverImpl implements PerformanceObserver {
private _owner: PerformanceImpl
private _entryTypes: string[] = []
private _callback: PerformanceObserverCallback | null = null
private _entryList = new PerformanceObserverEntryListImpl()
constructor(
performance: PerformanceImpl,
callback: PerformanceObserverCallback
) {
this._owner = performance
this._callback = callback
}
observe(options: PerformanceObserverOptions) {
if (options?.entryTypes != null) {
this._entryTypes.length = 0
this._entryTypes.push(...options.entryTypes!)
}
if (this._entryTypes.length > 0) {
this._owner.connect(this)
} else {
this.disconnect()
}
}
disconnect() {
this._entryList.clear()
this._owner.disconnect(this)
}
dispatchCallback() {
this._callback?.(this._entryList)
}
get entryTypes(): string[] {
return this._entryTypes
}
get entryList(): PerformanceObserverEntryListImpl {
return this._entryList
}
}
class PerformanceProvider {
private _entryStatus: PerformanceEntryStatus[] = []
get entryStatus(): PerformanceEntryStatus[] {
return this._entryStatus
}
onBefore(type: string) {
// create navigation status machine
if (
type == APP_LAUNCH ||
type == SWITCH_TAB ||
type == NAVIGATE_TO ||
type == REDIRECT_TO ||
type == NAVIGATE_BACK
) {
this._pushEntryStatus(
ENTRY_TYPE_NAVIGATION,
this._navigationToName(type),
type
)
}
// create render status machine
if (type == APP_LAUNCH || type == NAVIGATE_TO || type == REDIRECT_TO) {
this._pushEntryStatus(ENTRY_TYPE_RENDER, RENDER_TYPE_FIRST_LAYOUT, type)
this._pushEntryStatus(ENTRY_TYPE_RENDER, RENDER_TYPE_FIRST_RENDER, type)
}
// start status machine
this._forwardState()
}
onAfter(type: string) {
this._forwardState()
}
onReady() {
this._forwardState()
}
removeAllStatus() {
this._entryStatus.length = 0
}
_pushEntryStatus(entryType: string, name: string, navigationType: string) {
let entry: PerformanceEntryStatus | null = null
if (entryType == ENTRY_TYPE_NAVIGATION) {
entry = new PerformanceEntryStatusNavigation(name, navigationType)
} else if (entryType == ENTRY_TYPE_RENDER) {
if (name == RENDER_TYPE_FIRST_LAYOUT) {
entry = new PerformanceEntryStatusLayout()
} else if (name == RENDER_TYPE_FIRST_RENDER) {
entry = new PerformanceEntryStatusRender()
}
}
if (entry != null) {
this._entryStatus.push(entry)
}
}
_forwardState() {
this._entryStatus.forEach((entry) => {
entry.state += 1
})
}
_navigationToName(type: string): string {
if (type == APP_LAUNCH) {
return APP_LAUNCH
}
return 'route'
}
}
class PerformanceAllocate {
private _allEntryList: PerformanceObserverEntryListImpl
private _observerList: PerformanceObserverImpl[]
constructor(
allEntryList: PerformanceObserverEntryListImpl,
observerList: PerformanceObserverImpl[]
) {
this._allEntryList = allEntryList
this._observerList = observerList
}
pushEntryStatus(status: PerformanceEntryStatus[]) {
this.pushAllEntryData(status)
this.pushObserverList(status)
}
pushAllEntryData(status: PerformanceEntryStatus[]) {
status.forEach((entryStatus) => {
this._allEntryList.push(entryStatus.entryData)
})
}
pushObserverList(status: PerformanceEntryStatus[]) {
this._observerList.forEach((observer) => {
const entryList = observer.entryList
entryList.clear()
status.forEach((entryStatus) => {
const entryData = entryStatus.entryData
if (observer.entryTypes.includes(entryData.entryType)) {
entryList.push(entryData)
}
})
observer.dispatchCallback()
})
}
}
class PerformanceImpl implements Performance {
private _allEntryList = new PerformanceObserverEntryListImpl()
private _observerList: PerformanceObserverImpl[] = []
private _allocate: PerformanceAllocate
private _provider: PerformanceProvider = new PerformanceProvider()
constructor() {
this._allocate = new PerformanceAllocate(
this._allEntryList,
this._observerList
)
onBeforeRoute((type: string) => {
this._provider.onBefore(type)
})
onAfterRoute((type: string) => {
this._provider.onAfter(type)
if (type == NAVIGATE_BACK) {
this.dispatchObserver()
}
})
onPageReady((page) => {
this.dispatchObserver()
})
}
dispatchObserver() {
this._provider.onReady()
this._allocate.pushEntryStatus(this._provider.entryStatus)
this._provider.removeAllStatus()
}
createObserver(callback: PerformanceObserverCallback): PerformanceObserver {
return new PerformanceObserverImpl(this, callback)
}
connect(observer: PerformanceObserverImpl) {
const index = this._observerList.indexOf(observer)
if (index < 0) {
this._observerList.push(observer)
}
}
disconnect(observer: PerformanceObserverImpl) {
const index = this._observerList.indexOf(observer)
if (index >= 0) {
this._observerList.splice(index, 1)
}
}
getEntries(): PerformanceEntry[] {
return this._allEntryList.getEntries()
}
getEntriesByType(entryType: string): PerformanceEntry[] {
return this._allEntryList.getEntriesByType(entryType)
}
getEntriesByName(name: string, entryType: string): PerformanceEntry[] {
return this._allEntryList.getEntriesByName(name, entryType)
}
setBufferSize(size: number) {
this._allEntryList.bufferSize = size
}
}
export const getPerformance: GetPerformance = function (): Performance {
return new PerformanceImpl()
}
......@@ -15,6 +15,7 @@ import { showWebview } from './webview'
import { registerPage } from '../../framework/page'
import { getWebviewId } from '../../../service/framework/webview/utils'
import { setStatusBarStyle } from '../../statusBar'
import { invokeAfterRouteHooks, invokeBeforeRouteHooks } from './performance'
export const $navigateTo: DefineAsyncApiFn<API_TYPE_NAVIGATE_TO> = (
args,
......@@ -60,12 +61,14 @@ function _navigateTo({
aniType,
aniDuration,
}: NavigateToOptions): Promise<void | { eventChannel: EventChannel }> {
invokeBeforeRouteHooks(API_NAVIGATE_TO)
// 当前页面触发 onHide
invokeHook(ON_HIDE)
const eventChannel = new EventChannel(getWebviewId() + 1, events)
return new Promise((resolve) => {
const noAnimation = aniType === 'none' || aniDuration === 0
function callback(page: IPage) {
invokeAfterRouteHooks(API_NAVIGATE_TO)
showWebview(page, aniType, aniDuration, () => {
resolve({ eventChannel })
setStatusBarStyle()
......
import { invokeArrayFns } from '@vue/shared'
type OnBeforeRoute = (type: string) => void
type OnAfterRoute = (type: string) => void
type OnPageReady = (page) => void
const beforeRouteHooks: any[] = []
const afterRouteHooks: any[] = []
const pageReadyHooks: any[] = []
export function onBeforeRoute(hook: OnBeforeRoute) {
beforeRouteHooks.push(hook)
}
export function onAfterRoute(hook: OnAfterRoute) {
afterRouteHooks.push(hook)
}
export function onPageReady(hook: OnPageReady) {
pageReadyHooks.push(hook)
}
export function invokeBeforeRouteHooks(type: string) {
invokeArrayFns(beforeRouteHooks, type)
}
export function invokeAfterRouteHooks(type: string) {
invokeArrayFns(afterRouteHooks, type)
}
export function invokePageReadyHooks(page) {
invokeArrayFns(pageReadyHooks, page)
}
function clearBeforeRouteHooks() {
beforeRouteHooks.length = 0
}
function clearAfterRouteHooks() {
afterRouteHooks.length = 0
}
function clearPageReadyHooks() {
pageReadyHooks.length = 0
}
export function clearRouteHooks() {
clearBeforeRouteHooks()
clearAfterRouteHooks()
clearPageReadyHooks()
}
......@@ -15,6 +15,7 @@ import type { ComponentPublicInstance } from 'vue'
import { setStatusBarStyle } from '../../statusBar'
import { isTabPage } from '../../framework/app/tabBar'
import { closePage } from './utils'
import { invokeAfterRouteHooks, invokeBeforeRouteHooks } from './performance'
export const redirectTo = defineAsyncApi<API_TYPE_REDIRECT_TO>(
API_REDIRECT_TO,
......@@ -43,6 +44,7 @@ function _redirectTo({
// 与 uni-app x 安卓一致,后移除页面
return new Promise((resolve) => {
invokeAfterRouteHooks(API_REDIRECT_TO)
showWebview(
registerPage({
url,
......@@ -63,6 +65,7 @@ function _redirectTo({
setStatusBarStyle()
}
)
invokeBeforeRouteHooks(API_REDIRECT_TO)
})
}
......
......@@ -8,6 +8,10 @@ import type { ComponentPublicInstance } from 'vue'
import { ON_HIDE, ON_SHOW } from '@dcloudio/uni-shared'
import { registerPage } from '../page'
import { getAppThemeFallbackOS, normalizeTabBarStyles } from '../theme'
import {
invokeAfterRouteHooks,
invokeBeforeRouteHooks,
} from '../../api/route/performance'
// 存储 callback
export let onTabBarMidButtonTapCallback: Function[] = []
......@@ -271,9 +275,10 @@ export function switchSelect(
}
const currentPage = getCurrentPage() as Page
// const type = currentPage == null ? 'appLaunch' : 'switchTab'
const type = currentPage == null ? 'appLaunch' : 'switchTab'
// 执行beforeRoute
// invokeArrayFns(beforeRouteHooks, type)
invokeBeforeRouteHooks(type)
const pageInfo = getTabPage(getRealPath(path, true), query, rebuild, callback)
const page = pageInfo.page
......@@ -294,4 +299,5 @@ export function switchSelect(
// 执行afterRoute
// invokeArrayFns(afterRouteHooks, type)
invokeAfterRouteHooks(type)
}
......@@ -25,6 +25,7 @@ import type { VuePageComponent } from '../../../service/framework/page/define'
import { getPageManager } from '../app/app'
import { ON_POP_GESTURE } from '../../constants'
import { getAppThemeFallbackOS, normalizePageStyles } from '../theme'
import { invokePageReadyHooks } from '../../api/route/performance'
type PageNodeOptions = {}
......@@ -170,6 +171,7 @@ export function registerPage(
invokeHook(page, ON_UNLOAD)
})
nativePage.addPageEventListener(ON_READY, (_) => {
invokePageReadyHooks(page)
invokeHook(page, ON_READY)
})
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册