mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-05-07 06:12:55 +08:00
Add PWA performance analysis report for background-to-foreground experience
Co-authored-by: jxxghp <jxxghp@163.com>
This commit is contained in:
409
PWA_Performance_Analysis.md
Normal file
409
PWA_Performance_Analysis.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# PWA 后台切换前台体验优化分析报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
从后台切换到前台时会显示加载界面,同时界面会有闪烁,切换的体验不是很好,跟原生APP还有差距。
|
||||
|
||||
## 问题分析
|
||||
|
||||
### 1. 核心问题原因
|
||||
|
||||
#### 1.1 状态恢复时机不当
|
||||
- **PWA状态管理器初始化延迟**:在 `main.ts` 中,PWA状态管理器的初始化是在Vue应用挂载后进行的
|
||||
- **异步状态恢复**:状态恢复是异步的,不会阻塞应用渲染,导致用户先看到初始状态
|
||||
- **渲染完成检测不准确**:`ensureRenderComplete` 只检测Vue组件渲染完成,未考虑PWA状态恢复
|
||||
|
||||
#### 1.2 加载界面移除时机过早
|
||||
```typescript
|
||||
// 在App.vue中的问题代码
|
||||
ensureRenderComplete(() => {
|
||||
nextTick(() => {
|
||||
// 移除加载动画,显示页面
|
||||
animateAndRemoveLoader()
|
||||
// 页面完全显示后,检查未读消息
|
||||
checkAndEmitUnreadMessages()
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
加载界面在Vue组件渲染完成后立即移除,但此时:
|
||||
- PWA状态尚未恢复
|
||||
- 用户界面状态可能不完整
|
||||
- 导致用户看到不一致的状态
|
||||
|
||||
#### 1.3 Service Worker状态同步延迟
|
||||
Service Worker中的状态恢复通过消息传递实现,存在延迟:
|
||||
```typescript
|
||||
// 在service-worker.ts中
|
||||
self.addEventListener('message', function (event) {
|
||||
if (event.data && event.data.type === 'GET_PWA_STATE') {
|
||||
getStateFromCache()
|
||||
.then(response => response.json())
|
||||
.then(state => {
|
||||
event.ports[0]?.postMessage({ state })
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### 2. 具体表现
|
||||
|
||||
1. **界面闪烁**:
|
||||
- 用户首先看到默认状态
|
||||
- 然后看到状态恢复过程
|
||||
- 最后看到完整的恢复状态
|
||||
|
||||
2. **加载界面显示**:
|
||||
- 从后台切换到前台时,某些组件可能需要重新渲染
|
||||
- 背景图片需要重新加载
|
||||
- 网络请求需要重新发起
|
||||
|
||||
3. **状态不一致**:
|
||||
- 滚动位置不对
|
||||
- 表单数据丢失
|
||||
- 用户选择状态丢失
|
||||
|
||||
## 解决方案建议
|
||||
|
||||
### 1. 优化状态恢复时机
|
||||
|
||||
#### 1.1 提前初始化PWA状态管理器
|
||||
```typescript
|
||||
// 修改main.ts,在应用挂载前初始化
|
||||
const initializePWABeforeMount = async () => {
|
||||
const isPWA = window.matchMedia('(display-mode: standalone)').matches ||
|
||||
(window.navigator as any).standalone ||
|
||||
document.referrer.includes('android-app://')
|
||||
|
||||
if (isPWA) {
|
||||
console.log('检测到PWA模式,预初始化状态管理器')
|
||||
const pwaStateController = new PWAStateController()
|
||||
|
||||
// 等待状态恢复完成
|
||||
await pwaStateController.waitForStateRestore()
|
||||
|
||||
// 将状态管理器绑定到全局对象
|
||||
;(window as any).pwaStateController = pwaStateController
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 在创建Vue应用前调用
|
||||
const pwaInitialized = await initializePWABeforeMount()
|
||||
const app = createApp(App)
|
||||
```
|
||||
|
||||
#### 1.2 实现状态恢复等待机制
|
||||
```typescript
|
||||
// 在PWAStateController中添加
|
||||
export class PWAStateController {
|
||||
private stateRestorePromise: Promise<void> | null = null
|
||||
private stateRestoreResolve: (() => void) | null = null
|
||||
|
||||
constructor() {
|
||||
this.stateRestorePromise = new Promise((resolve) => {
|
||||
this.stateRestoreResolve = resolve
|
||||
})
|
||||
this.init()
|
||||
}
|
||||
|
||||
async waitForStateRestore(): Promise<void> {
|
||||
return this.stateRestorePromise
|
||||
}
|
||||
|
||||
private async checkAndRestoreState(): Promise<void> {
|
||||
// 现有的状态恢复逻辑
|
||||
// ...
|
||||
|
||||
// 状态恢复完成后解决Promise
|
||||
if (this.stateRestoreResolve) {
|
||||
this.stateRestoreResolve()
|
||||
this.stateRestoreResolve = null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 优化加载界面管理
|
||||
|
||||
#### 2.1 条件化加载界面移除
|
||||
```typescript
|
||||
// 修改App.vue中的加载界面移除逻辑
|
||||
const removeLoadingWithStateCheck = async () => {
|
||||
// 检查PWA状态是否已恢复
|
||||
const pwaController = (window as any).pwaStateController
|
||||
if (pwaController) {
|
||||
await pwaController.waitForStateRestore()
|
||||
}
|
||||
|
||||
// 确保关键资源已加载
|
||||
await Promise.all([
|
||||
// 等待背景图片加载完成
|
||||
loadBackgroundImages(),
|
||||
// 等待关键API数据加载完成
|
||||
loadCriticalData()
|
||||
])
|
||||
|
||||
// 移除加载界面
|
||||
animateAndRemoveLoader()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await globalSettingsStore.initialize()
|
||||
configureApexCharts()
|
||||
updateHtmlThemeAttribute(globalTheme.name.value)
|
||||
|
||||
// 使用优化后的加载界面移除逻辑
|
||||
ensureRenderComplete(() => {
|
||||
nextTick(removeLoadingWithStateCheck)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### 2.2 实现智能加载状态检测
|
||||
```typescript
|
||||
// 新增工具函数
|
||||
export class PWALoadingStateManager {
|
||||
private loadingStates: Map<string, boolean> = new Map()
|
||||
|
||||
setLoadingState(key: string, loading: boolean) {
|
||||
this.loadingStates.set(key, loading)
|
||||
}
|
||||
|
||||
isAnyLoading(): boolean {
|
||||
return Array.from(this.loadingStates.values()).some(loading => loading)
|
||||
}
|
||||
|
||||
waitForAllComplete(): Promise<void> {
|
||||
return new Promise((resolve) => {
|
||||
const checkComplete = () => {
|
||||
if (!this.isAnyLoading()) {
|
||||
resolve()
|
||||
} else {
|
||||
setTimeout(checkComplete, 100)
|
||||
}
|
||||
}
|
||||
checkComplete()
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 优化页面可见性处理
|
||||
|
||||
#### 3.1 改进页面可见性监听
|
||||
```typescript
|
||||
// 修改VisibilityStateManager
|
||||
export class VisibilityStateManager {
|
||||
private isRestoring = false
|
||||
private restorePromise: Promise<void> | null = null
|
||||
|
||||
private handlePageVisible(): void {
|
||||
if (this.isRestoring) return
|
||||
|
||||
this.isRestoring = true
|
||||
this.restorePromise = this.performStateRestore()
|
||||
}
|
||||
|
||||
private async performStateRestore(): Promise<void> {
|
||||
try {
|
||||
// 显示恢复指示器
|
||||
this.showRestoreIndicator()
|
||||
|
||||
const restoredState = this.stateManager.restoreState()
|
||||
if (restoredState) {
|
||||
await this.restoreAppState(restoredState)
|
||||
console.log('页面显示,已恢复状态')
|
||||
}
|
||||
} finally {
|
||||
this.isRestoring = false
|
||||
this.hideRestoreIndicator()
|
||||
}
|
||||
}
|
||||
|
||||
private showRestoreIndicator(): void {
|
||||
// 显示轻量级的状态恢复指示器,而不是完整的加载界面
|
||||
const indicator = document.createElement('div')
|
||||
indicator.id = 'pwa-restore-indicator'
|
||||
indicator.innerHTML = `
|
||||
<div class="restore-indicator">
|
||||
<div class="restore-spinner"></div>
|
||||
<div class="restore-text">正在恢复状态...</div>
|
||||
</div>
|
||||
`
|
||||
document.body.appendChild(indicator)
|
||||
}
|
||||
|
||||
private hideRestoreIndicator(): void {
|
||||
const indicator = document.getElementById('pwa-restore-indicator')
|
||||
if (indicator) {
|
||||
indicator.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 优化Service Worker缓存策略
|
||||
|
||||
#### 4.1 改进缓存预热
|
||||
```typescript
|
||||
// 修改vite.config.ts中的PWA配置
|
||||
VitePWA({
|
||||
workbox: {
|
||||
// 添加导航预加载
|
||||
navigationPreload: true,
|
||||
// 优化缓存策略
|
||||
runtimeCaching: [
|
||||
{
|
||||
urlPattern: ({ request }) => request.destination === 'document',
|
||||
handler: 'StaleWhileRevalidate', // 改为更快的策略
|
||||
options: {
|
||||
cacheName: 'pages-cache',
|
||||
cacheKeyWillBeUsed: async ({ request }) => {
|
||||
// 忽略状态参数,提高缓存命中率
|
||||
const url = new URL(request.url)
|
||||
url.searchParams.delete('restored')
|
||||
return url.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### 4.2 实现状态预缓存
|
||||
```typescript
|
||||
// 在service-worker.ts中添加
|
||||
self.addEventListener('install', event => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
// 预缓存关键状态数据
|
||||
const cache = await caches.open(STATE_CACHE_NAME)
|
||||
const existingState = await cache.match(STATE_ENDPOINT)
|
||||
|
||||
if (existingState) {
|
||||
// 预热状态数据
|
||||
const state = await existingState.json()
|
||||
console.log('预缓存状态数据:', state)
|
||||
}
|
||||
})()
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
### 5. 增强用户体验
|
||||
|
||||
#### 5.1 添加过渡动画
|
||||
```scss
|
||||
// 添加到App.vue的样式中
|
||||
.pwa-transition {
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
.pwa-restoring {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
|
||||
.restore-indicator {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10000;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.restore-spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid transparent;
|
||||
border-top: 2px solid white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
```
|
||||
|
||||
#### 5.2 实现骨架屏
|
||||
```vue
|
||||
<!-- 添加到App.vue中 -->
|
||||
<template>
|
||||
<div class="app-wrapper">
|
||||
<!-- 骨架屏 -->
|
||||
<div v-if="isRestoring" class="skeleton-screen">
|
||||
<div class="skeleton-header"></div>
|
||||
<div class="skeleton-content">
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line"></div>
|
||||
<div class="skeleton-line short"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主要内容 -->
|
||||
<VApp v-else :class="{ 'transparent-app': isTransparentTheme }">
|
||||
<RouterView />
|
||||
</VApp>
|
||||
</div>
|
||||
</template>
|
||||
```
|
||||
|
||||
## 实施建议
|
||||
|
||||
### 优先级排序
|
||||
|
||||
1. **高优先级**:
|
||||
- 优化状态恢复时机
|
||||
- 改进加载界面管理
|
||||
- 实现状态恢复等待机制
|
||||
|
||||
2. **中优先级**:
|
||||
- 优化页面可见性处理
|
||||
- 添加过渡动画
|
||||
- 改进Service Worker缓存策略
|
||||
|
||||
3. **低优先级**:
|
||||
- 实现骨架屏
|
||||
- 添加高级用户体验功能
|
||||
|
||||
### 测试建议
|
||||
|
||||
1. **真机测试**:
|
||||
- 在iOS Safari上测试PWA模式
|
||||
- 在Android Chrome上测试PWA模式
|
||||
- 测试后台切换前台的各种场景
|
||||
|
||||
2. **性能测试**:
|
||||
- 使用Chrome DevTools监控状态恢复性能
|
||||
- 测试不同网络状况下的表现
|
||||
- 监控内存使用情况
|
||||
|
||||
3. **用户体验测试**:
|
||||
- 测试快速切换应用场景
|
||||
- 测试长时间后台后的恢复
|
||||
- 测试网络断开重连场景
|
||||
|
||||
## 预期效果
|
||||
|
||||
实施以上优化后,预期能够:
|
||||
|
||||
1. **消除界面闪烁**:状态恢复完成后再显示界面
|
||||
2. **减少加载时间**:智能预加载和缓存策略
|
||||
3. **提升用户体验**:平滑的过渡动画和反馈
|
||||
4. **接近原生体验**:快速的状态恢复和响应
|
||||
|
||||
这些优化将显著提升PWA应用从后台切换到前台时的体验,使其更接近原生应用的表现。
|
||||
Reference in New Issue
Block a user