Kaynağa Gözat

功能实现

chendeben 1 yıl önce
ebeveyn
işleme
ef62ec77ac

+ 23 - 2
entry/src/main/ets/common/widget/PlayerControlService.ets

@@ -95,7 +95,7 @@ export class PlayerControlService {
     try {
       const requestData: RequestData = { 
         timestamp: Date.now(),
-        source: 'widget_form_process'
+        source: 'widget_form_process_recovery'
       };
       const requestInfo: commonEventManager.CommonEventPublishData = {
         data: JSON.stringify(requestData)
@@ -104,7 +104,7 @@ export class PlayerControlService {
         if (err) {
           hilog.error(0x0000, TAG, `Failed to request current state: ${err}`);
         } else {
-          hilog.info(0x0000, TAG, 'Current state requested from main app');
+          hilog.info(0x0000, TAG, 'Current state requested from main app for recovery');
         }
       });
     } catch (error) {
@@ -112,6 +112,27 @@ export class PlayerControlService {
     }
   }
 
+  /**
+   * 强制重新连接和同步状态
+   */
+  async forceReconnect(): Promise<void> {
+    try {
+      hilog.info(0x0000, TAG, 'Force reconnecting to main app...');
+      
+      // 重新请求当前状态
+      await this.requestCurrentState();
+      
+      // 等待一段时间后再次请求,确保能收到响应
+      setTimeout(async () => {
+        await this.requestCurrentState();
+      }, 2000);
+      
+      hilog.info(0x0000, TAG, 'Force reconnect completed');
+    } catch (error) {
+      hilog.error(0x0000, TAG, `Force reconnect failed: ${error}`);
+    }
+  }
+
   /**
    * 发送控制命令到主应用
    */

+ 59 - 9
entry/src/main/ets/entryformability/EntryFormAbility.ets

@@ -75,6 +75,10 @@ implements SizeChangeListener, ThemeChangeListener, PreferencesChangeListener {
   // 防抖机制:避免短时间内重复更新
   private lastUpdateTime: number = 0;
   private updateDebounceDelay: number = 100; // 100ms防抖延迟
+  
+  // 进程启动时间,用于检测进程重启
+  private processStartTime: number = Date.now();
+  private lastHealthCheck: number = 0;
 
   /**
    * 初始化服务
@@ -104,22 +108,56 @@ implements SizeChangeListener, ThemeChangeListener, PreferencesChangeListener {
    */
   private setupGlobalStateListener(): void {
     try {
-      hilog.info(0x0000, TAG, 'Heanup EntryFormAbility getting AvSessionWidgetListener instance...');
+      hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] getting AvSessionWidgetListener instance...`);
       const avSessionListener = AvSessionWidgetListener.getInstance();
-      hilog.info(0x0000, TAG, 'Heanup EntryFormAbility adding state listener...');
+      hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] adding state listener...`);
       avSessionListener.addStateListener((data: WidgetData) => {
-        hilog.info(0x0000, TAG, `Heanup EntryFormAbility global listener received state update: isPlaying=${data.playState.isPlaying}, title=${data.currentSong.title}`);
+        const now = Date.now();
+        hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] global listener received state update: isPlaying=${data.playState.isPlaying}, title=${data.currentSong.title}, age=${now - this.processStartTime}ms`);
+        
+        // 更新健康检查时间
+        this.lastHealthCheck = now;
         
         // 总是尝试更新,让updateAllWidgetsWithData自己检查
         this.updateAllWidgetsWithData(data);
       });
       
-      hilog.info(0x0000, TAG, 'Heanup EntryFormAbility global state listener setup successfully');
+      // 启动健康检查定时器
+      this.startHealthCheck();
+      
+      hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] global state listener setup successfully`);
     } catch (error) {
-      hilog.error(0x0000, TAG, `Heanup EntryFormAbility failed to setup global listener: ${error}`);
+      hilog.error(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] failed to setup global listener: ${error}`);
     }
   }
 
+  /**
+   * 启动健康检查,定期检测监听器是否还在工作
+   */
+  private startHealthCheck(): void {
+    setInterval(() => {
+      const now = Date.now();
+      const timeSinceLastUpdate = now - this.lastHealthCheck;
+      
+      // 如果超过30秒没有收到任何状态更新,可能监听器失效了
+      if (timeSinceLastUpdate > 30000) {
+        hilog.warn(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] health check failed: ${timeSinceLastUpdate}ms since last update`);
+        
+        // 尝试强制重连和重新请求当前状态
+        this.playerControlService.forceReconnect().then(() => {
+          return this.playerControlService.getCurrentPlayState();
+        }).then((currentState) => {
+          hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] health check recovery: got current state`);
+          this.updateAllWidgetsWithData(currentState);
+        }).catch(() => {
+          hilog.error(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] health check recovery failed:`);
+        });
+      } else {
+        hilog.info(0x0000, TAG, `Heanup EntryFormAbility [Process:${this.processStartTime}] health check OK: ${timeSinceLastUpdate}ms since last update`);
+      }
+    }, 15000); // 每15秒检查一次
+  }
+
   /**
    * 使用指定数据更新所有卡片
    */
@@ -358,17 +396,29 @@ implements SizeChangeListener, ThemeChangeListener, PreferencesChangeListener {
    * 处理卡片事件(用户交互)
    */
   onFormEvent(formId: string, message: string): void {
-    hilog.info(0x0000, TAG, `Heanup EntryFormAbility onFormEvent called: ${formId}, message: ${message}`);
+    const processId = `${Date.now()}_${Math.random().toString(36).substr(2, 5)}`;
+    hilog.info(0x0000, TAG, `Heanup EntryFormAbility [${processId}] onFormEvent called: ${formId}, message: ${message}`);
 
     // 确保服务已初始化
     if (!this.globalListenerSetup) {
-      hilog.info(0x0000, TAG, 'Heanup EntryFormAbility services not initialized in onFormEvent, initializing now...');
+      hilog.info(0x0000, TAG, `Heanup EntryFormAbility [${processId}] services not initialized in onFormEvent, initializing now...`);
       this.initializeServices();
+      
+      // 强制请求当前状态,确保新进程能获取到最新数据
+      setTimeout(() => {
+        this.playerControlService.getCurrentPlayState().then((currentState) => {
+          hilog.info(0x0000, TAG, `Heanup EntryFormAbility [${processId}] got current state: isPlaying=${currentState.playState.isPlaying}, title=${currentState.currentSong.title}`);
+          // 手动触发一次状态更新,确保widget显示正确
+          this.updateAllWidgetsWithData(currentState);
+        }).catch(() => {
+          hilog.error(0x0000, TAG, `Heanup EntryFormAbility [${processId}] failed to get current state: `);
+        });
+      }, 1000);
     }
 
     // 确保widget已注册到GlobalWidgetManager
     if (!this.globalWidgetManager.hasWidget(formId)) {
-      hilog.info(0x0000, TAG, `Heanup EntryFormAbility widget ${formId} not registered, registering as LARGE size`);
+      hilog.info(0x0000, TAG, `Heanup EntryFormAbility [${processId}] widget ${formId} not registered, registering as LARGE size`);
       // 默认注册为LARGE尺寸,实际尺寸可以后续检测
       this.globalWidgetManager.registerWidget(formId, 'large' as WidgetSize);
     }
@@ -377,7 +427,7 @@ implements SizeChangeListener, ThemeChangeListener, PreferencesChangeListener {
       const eventData = JSON.parse(message) as Object;
       this.handleWidgetEvent(formId, eventData);
     } catch (error) {
-      hilog.error(0x0000, TAG, `Heanup EntryFormAbility failed to parse form event: ${error}`);
+      hilog.error(0x0000, TAG, `Heanup EntryFormAbility [${processId}] failed to parse form event: ${error}`);
     }
   }
 

+ 112 - 0
widget_fix_v5_summary.md

@@ -0,0 +1,112 @@
+# Widget Fix V5 - Process Isolation and Listener Management
+
+## Problem Analysis
+
+The widget was working initially but stopped updating after some time due to several critical issues:
+
+### Root Causes
+
+1. **Process Isolation Issue**
+   - Widget forms run in separate processes (`apppool`) from the main app
+   - Static `globalListenerSetup` flag prevented proper listener registration in new processes
+   - Each widget click creates a new form process, but listeners weren't being set up correctly
+
+2. **Listener Management Problems**
+   - Main app showed `listeners=0` while form process showed `listeners=1`
+   - No duplicate listener prevention in PlayerControlService
+   - AvSessionWidgetListener wasn't properly handling cross-process data synchronization
+
+3. **Data Synchronization Issues**
+   - Form processes weren't requesting current state on startup
+   - No retry mechanism for failed widget updates
+   - Missing process identification for debugging
+
+## Key Fixes Applied
+
+### 1. Fixed Static Listener Setup
+**File**: `entry/src/main/ets/entryformability/EntryFormAbility.ets`
+
+```typescript
+// Changed from static to instance variable
+private globalListenerSetup: boolean = false; // Was: private static globalListenerSetup
+
+// This ensures each process instance can set up its own listeners
+```
+
+### 2. Enhanced AvSessionWidgetListener
+**File**: `entry/src/main/ets/common/widget/AvSessionWidgetListener.ets`
+
+- Added process identification for better debugging
+- Added delayed callback to give main app time to broadcast state
+- Enhanced logging with process ID
+
+### 3. Improved PlayerControlService
+**File**: `entry/src/main/ets/common/widget/PlayerControlService.ets`
+
+- Added duplicate listener prevention
+- Added automatic state request on initialization
+- Enhanced CommonEvent handling with better error recovery
+
+### 4. Added Widget Update Retry Mechanism
+**File**: `entry/src/main/ets/entryformability/EntryFormAbility.ets`
+
+- Split widget update into individual widget updates with retry logic
+- Added exponential backoff for failed updates
+- Enhanced logging for better debugging
+
+## Technical Details
+
+### Process Flow
+1. User clicks widget → New form process starts
+2. EntryFormAbility.onAddForm() called
+3. initializeServices() sets up listeners (now works correctly)
+4. setupGlobalStateListener() registers with AvSessionWidgetListener
+5. PlayerControlService requests current state from main app
+6. Main app broadcasts state via CommonEvent
+7. Form process receives and updates widget UI
+
+### Key Improvements
+- **Process-aware singleton**: Each process gets its own listener setup
+- **State synchronization**: Form processes actively request current state
+- **Retry mechanism**: Failed widget updates are retried with backoff
+- **Better debugging**: Process IDs and enhanced logging
+
+## Expected Behavior After Fix
+
+1. **Initial Load**: Widget shows current playing state immediately
+2. **User Interaction**: Button clicks work and trigger proper state updates
+3. **State Changes**: All widgets update when playback state changes
+4. **Process Resilience**: New widget processes properly sync with main app
+5. **Error Recovery**: Failed updates are retried automatically
+
+## Testing Recommendations
+
+1. **Basic Functionality**
+   - Add widget to desktop
+   - Verify it shows current playing state
+   - Test play/pause, next/previous buttons
+
+2. **Process Isolation**
+   - Add multiple widgets
+   - Click widgets after some time of inactivity
+   - Verify all widgets update correctly
+
+3. **State Synchronization**
+   - Change playback state in main app
+   - Verify all widgets reflect the change
+   - Test with app in background
+
+4. **Error Recovery**
+   - Monitor logs for retry attempts
+   - Verify widgets eventually update even after initial failures
+
+## Log Monitoring
+
+Key log patterns to watch for:
+- `[process_xxx] Updating widget data` - Process-specific updates
+- `State listener registered, total listeners: X` - Listener count tracking
+- `Current state requested from main app` - State synchronization
+- `Widget updated successfully` - Successful updates
+- `update failed, retry count: X` - Retry attempts
+
+This fix addresses the core process isolation issues that were preventing widgets from updating after initial creation.

+ 110 - 0
widget_fix_v6_summary.md

@@ -0,0 +1,110 @@
+# Widget Fix V6 - 解决重复更新和数据同步问题
+
+## 新发现的问题
+
+从最新日志分析发现了以下问题:
+
+### 1. 重复更新问题
+- 每个CommonEvent事件触发多次widget更新
+- AvSession监听器和CommonEvent监听器都在处理同一个事件
+- 导致widget被重复更新,浪费资源
+
+### 2. 数据同步不一致
+- 主应用进程显示 `listeners=0`
+- Form进程显示 `listeners=2` 
+- 主应用状态为 `isPlaying=true`,但form进程有时显示 `isPlaying=false`
+
+### 3. 监听器架构混乱
+- PlayerControlService同时注册了CommonEvent和AvSession监听器
+- 两个监听器都在处理相同的数据,造成重复处理
+
+## 修复方案
+
+### 1. 简化事件处理流程
+**文件**: `entry/src/main/ets/common/widget/PlayerControlService.ets`
+
+```typescript
+// 修改前:CommonEvent和AvSession都通知stateListeners
+// 修改后:只通过AvSession统一处理,避免重复通知
+
+private handlePlayerStateChange(eventData: commonEventManager.CommonEventData): void {
+  // 只更新AvSession监听器的数据,避免重复通知
+  // AvSession监听器会自动通知所有注册的监听器
+  this.avSessionListener.updateWidgetData(widgetData);
+}
+```
+
+### 2. 添加数据变化检测
+**文件**: `entry/src/main/ets/common/widget/AvSessionWidgetListener.ets`
+
+```typescript
+// 添加数据相等性检查,避免无意义的更新
+private isDataEqual(data1: WidgetData, data2: WidgetData): boolean {
+  return data1.playState.isPlaying === data2.playState.isPlaying &&
+         data1.currentSong.title === data2.currentSong.title &&
+         // ... 其他关键字段比较
+}
+```
+
+### 3. 添加防抖机制
+**文件**: `entry/src/main/ets/entryformability/EntryFormAbility.ets`
+
+```typescript
+// 添加100ms防抖延迟,避免短时间内重复更新
+private updateDebounceDelay: number = 100;
+
+private updateAllWidgetsWithData(data: WidgetData): void {
+  const now = Date.now();
+  if (now - this.lastUpdateTime < this.updateDebounceDelay) {
+    return; // 跳过重复更新
+  }
+  // ... 执行更新
+}
+```
+
+## 优化后的数据流
+
+### 新的事件处理流程
+1. **主应用** → 发送CommonEvent
+2. **Form进程** → PlayerControlService接收CommonEvent
+3. **PlayerControlService** → 更新AvSessionWidgetListener数据
+4. **AvSessionWidgetListener** → 检查数据变化,通知监听器
+5. **EntryFormAbility** → 防抖检查,更新widget UI
+
+### 关键改进
+- **单一数据源**:所有更新都通过AvSessionWidgetListener统一处理
+- **重复检测**:避免相同数据的重复更新
+- **防抖机制**:短时间内的多次更新被合并
+- **进程隔离修复**:每个进程都能正确设置监听器
+
+## 预期效果
+
+### 性能优化
+- 减少不必要的widget更新调用
+- 降低CPU和内存使用
+- 提高响应速度
+
+### 数据一致性
+- 确保所有widget显示相同的播放状态
+- 消除主应用和form进程间的数据不同步
+
+### 稳定性提升
+- 减少重复更新导致的潜在错误
+- 提高长时间运行的稳定性
+
+## 测试要点
+
+1. **基本功能**:播放/暂停按钮响应正常
+2. **数据同步**:所有widget状态保持一致
+3. **性能表现**:更新频率合理,无重复调用
+4. **长期稳定性**:长时间使用后仍能正常更新
+
+## 日志监控
+
+关键日志模式:
+- `Data unchanged, skipping update` - 重复数据被过滤
+- `update debounced, skipping` - 防抖机制生效
+- `Widget data updated and broadcasted to X listeners` - 监听器数量正常
+- 减少重复的 `formProvider.updateForm` 调用
+
+这个版本应该能显著减少重复更新,提高widget的响应性能和数据一致性。

+ 100 - 0
widget_fix_v7_summary.md

@@ -0,0 +1,100 @@
+# Widget Fix V7 - 解决EntryFormAbility未初始化问题
+
+## 问题分析
+
+从最新日志分析发现了关键问题:
+
+### 1. EntryFormAbility生命周期问题
+- **onAddForm未被调用**:日志中没有看到 `onAddForm called`
+- **但onFormEvent正常工作**:能看到 `onFormEvent called` 和用户交互
+- **服务未初始化**:全局监听器没有被设置,导致widget无法更新
+
+### 2. 数据流正常但UI不更新
+- **CommonEvent接收正常**:`CommonEvent received in form process`
+- **AvSession数据更新正常**:`AvSession data received: isPlaying=true`
+- **但缺少EntryFormAbility的全局监听器**:没有看到 `global listener received state update`
+- **没有formProvider.updateForm调用**:widget UI没有被更新
+
+### 3. 可能的原因
+1. **卡片已存在**:可能卡片之前已经创建,现在只是恢复,所以只调用onFormEvent而不调用onAddForm
+2. **初始化时机问题**:服务初始化只在onAddForm中进行,但onAddForm可能不会每次都被调用
+3. **进程重启**:form进程重启后,EntryFormAbility实例重新创建,但没有重新初始化
+
+## 修复方案
+
+### 1. 在多个生命周期方法中确保初始化
+**文件**: `entry/src/main/ets/entryformability/EntryFormAbility.ets`
+
+```typescript
+// 在onUpdateForm中添加初始化检查
+onUpdateForm(formId: string): void {
+  if (!this.globalListenerSetup) {
+    this.initializeServices();
+  }
+  // ... 其他逻辑
+}
+
+// 在onFormEvent中添加初始化检查
+onFormEvent(formId: string, message: string): void {
+  if (!this.globalListenerSetup) {
+    this.initializeServices();
+  }
+  // ... 其他逻辑
+}
+```
+
+### 2. 移除PlayerControlService中的冗余AvSession注册
+**文件**: `entry/src/main/ets/common/widget/PlayerControlService.ets`
+
+```typescript
+// 简化AvSession监听器初始化,避免重复注册
+private initializeAvSessionListener(): void {
+  // 不要在这里注册,让EntryFormAbility直接注册到AvSessionWidgetListener
+  hilog.info(0x0000, TAG, 'AvSession listener initialized successfully (no direct registration needed)');
+}
+```
+
+### 3. 增强日志调试
+添加更详细的日志来跟踪:
+- EntryFormAbility的所有生命周期方法调用
+- 服务初始化状态
+- 全局监听器的注册和触发
+
+## 预期修复效果
+
+### 1. 确保服务初始化
+- 无论通过哪个生命周期方法进入,都能确保服务被正确初始化
+- 全局监听器能够正确注册到AvSessionWidgetListener
+
+### 2. 恢复widget更新
+- EntryFormAbility的全局监听器能够接收到状态变化
+- `updateAllWidgetsWithData` 方法能够被正确调用
+- `formProvider.updateForm` 能够更新widget UI
+
+### 3. 数据流完整性
+完整的数据流应该是:
+1. **主应用** → 发送CommonEvent
+2. **Form进程** → PlayerControlService接收CommonEvent
+3. **PlayerControlService** → 更新AvSessionWidgetListener数据
+4. **AvSessionWidgetListener** → 通知EntryFormAbility全局监听器
+5. **EntryFormAbility** → 调用updateAllWidgetsWithData
+6. **FormProvider** → 更新widget UI
+
+## 测试要点
+
+1. **生命周期测试**:
+   - 重新添加widget,检查onAddForm是否被调用
+   - 点击widget按钮,检查onFormEvent是否正确初始化服务
+   - 检查onUpdateForm是否能正确处理未初始化状态
+
+2. **数据流测试**:
+   - 检查是否能看到 `Heanup EntryFormAbility global listener received state update`
+   - 检查是否能看到 `formProvider.updateForm` 调用
+   - 验证widget UI是否正确更新
+
+3. **日志监控**:
+   - `Heanup EntryFormAbility services not initialized, initializing now...`
+   - `Heanup EntryFormAbility global listener received state update`
+   - `Heanup widget 1450551168 calling formProvider.updateForm`
+
+这个修复应该能解决widget无法更新的核心问题,确保EntryFormAbility在任何情况下都能正确初始化并接收状态更新。

+ 92 - 0
widget_fix_v8_final.md

@@ -0,0 +1,92 @@
+# Widget Fix V8 - 最终修复:Widget注册问题
+
+## 问题确认
+
+从最新日志确认了问题的根本原因:
+
+### ✅ 已解决的问题
+1. **EntryFormAbility初始化** - 现在能看到服务正确初始化
+2. **全局监听器注册** - 能看到 `global listener received state update`
+3. **数据接收** - AvSessionWidgetListener正常接收和广播数据
+
+### ❌ 仍存在的问题
+**Widget未注册到GlobalWidgetManager**
+- `updateAllWidgetsWithData` 被调用,但 `activeWidgets.size = 0`
+- 没有看到 `formProvider.updateForm` 调用
+- 原因:onAddForm没有被调用,widget没有注册
+
+## 根本原因
+
+**Widget生命周期问题**:
+1. **onAddForm未调用**:卡片可能已存在,系统直接调用onFormEvent而跳过onAddForm
+2. **Widget未注册**:GlobalWidgetManager中没有活跃的widget记录
+3. **更新被跳过**:updateAllWidgetsWithData发现没有widget需要更新
+
+## 最终修复方案
+
+### 1. 在所有生命周期方法中确保Widget注册
+**文件**: `entry/src/main/ets/entryformability/EntryFormAbility.ets`
+
+```typescript
+// onFormEvent中添加widget注册检查
+onFormEvent(formId: string, message: string): void {
+  // 确保widget已注册到GlobalWidgetManager
+  if (!this.globalWidgetManager.hasWidget(formId)) {
+    this.globalWidgetManager.registerWidget(formId, 'large' as WidgetSize);
+  }
+  // ... 其他逻辑
+}
+
+// onUpdateForm中添加widget注册检查
+onUpdateForm(formId: string): void {
+  // 确保widget已注册到GlobalWidgetManager
+  if (!this.globalWidgetManager.hasWidget(formId)) {
+    this.globalWidgetManager.registerWidget(formId, 'large' as WidgetSize);
+  }
+  // ... 其他逻辑
+}
+```
+
+### 2. 增强调试日志
+添加详细的widget状态日志:
+- 活跃widget数量
+- 每个widget的ID和尺寸
+- 更新过程的详细跟踪
+
+## 完整的数据流
+
+修复后的完整数据流:
+1. **用户点击widget** → onFormEvent被调用
+2. **检查服务初始化** → 如果未初始化则初始化服务
+3. **检查widget注册** → 如果未注册则注册到GlobalWidgetManager
+4. **处理用户事件** → 发送控制命令到主应用
+5. **主应用状态变化** → 发送CommonEvent
+6. **Form进程接收** → PlayerControlService处理CommonEvent
+7. **更新AvSession** → AvSessionWidgetListener接收数据
+8. **通知全局监听器** → EntryFormAbility接收状态更新
+9. **更新所有widget** → updateAllWidgetsWithData被调用
+10. **检查活跃widget** → GlobalWidgetManager返回已注册的widget
+11. **更新widget UI** → formProvider.updateForm被调用
+
+## 预期日志输出
+
+修复后应该能看到以下日志序列:
+```
+Heanup EntryFormAbility onFormEvent called: 678330255
+Heanup EntryFormAbility widget 678330255 not registered, registering as LARGE size
+Heanup EntryFormAbility global listener received state update: isPlaying=true
+Heanup EntryFormAbility updateAllWidgetsWithData called with isPlaying=true
+Heanup EntryFormAbility active widgets count: 1
+Heanup EntryFormAbility found active widget: 678330255, size: large
+Heanup EntryFormAbility updating widget: 678330255
+Heanup widget 678330255 calling formProvider.updateForm with isPlaying=true
+Heanup widget 678330255 updated successfully: isPlaying=true
+```
+
+## 测试验证
+
+1. **点击widget按钮**:检查是否能看到widget注册日志
+2. **状态更新**:检查是否能看到formProvider.updateForm调用
+3. **UI更新**:验证widget界面是否正确显示播放状态
+
+这个修复应该能彻底解决widget无法更新的问题,确保在任何生命周期情况下widget都能正确注册和更新。