EntryAbility.ets 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855
  1. /**
  2. * 应用主Ability入口文件
  3. * 功能:
  4. * 1. 管理应用生命周期
  5. * 2. 处理窗口创建和尺寸变化
  6. * 3. 响应外部调用请求
  7. * 4. 全局状态管理
  8. */
  9. import UIAbility from '@ohos.app.ability.UIAbility';
  10. import hilog from '@ohos.hilog';
  11. import window from '@ohos.window';
  12. import { AbilityConstant, Want } from '@kit.AbilityKit';
  13. import { BusinessError, emitter } from '@kit.BasicServicesKit';
  14. import { AppUtil, GlobalContext, LogUtil, StrUtil } from '@pura/harmony-utils';
  15. import { rpc } from '@kit.IPCKit';
  16. import { Utility } from '../common/util/Utility';
  17. import { WXApi, WXEventHandler } from '../common/util/WXApiWrap';
  18. import { UnifiedPlayerService } from '../common/service/UnifiedPlayerService';
  19. import { systemShare } from '@kit.ShareKit';
  20. import { CustomCrashHandler } from '../common/utils/CustomCrashHandler';
  21. import { smartMobilityCommon } from '@kit.CarKit';
  22. import { display } from '@kit.ArkUI';
  23. import { PreferencesUtil } from '../common/utils/PreferencesUtil'
  24. /**
  25. * 播放状态广播数据接口
  26. */
  27. interface PlayStateBroadcast {
  28. isPlaying: boolean;
  29. isPaused: boolean;
  30. isLoading: boolean;
  31. }
  32. interface SongBroadcast {
  33. id: string;
  34. title: string;
  35. artist: string;
  36. album: string;
  37. coverImagePath: string;
  38. duration: number;
  39. }
  40. interface ProgressBroadcast {
  41. currentPosition: number;
  42. duration: number;
  43. percentage: number;
  44. currentTimeText: string;
  45. totalTimeText: string;
  46. }
  47. interface PlaylistBroadcast {
  48. hasNext: boolean;
  49. hasPrevious: boolean;
  50. currentIndex: number;
  51. totalCount: number;
  52. }
  53. interface BroadcastData {
  54. playState: PlayStateBroadcast;
  55. currentSong: SongBroadcast;
  56. progress: ProgressBroadcast;
  57. playlist: PlaylistBroadcast;
  58. }
  59. interface PublishInfo {
  60. data: string;
  61. }
  62. interface EventDataWrapper {
  63. data: BroadcastData;
  64. }
  65. /**
  66. * RPC通信返回类型的实现,用于RPC通信数据序列化和反序列化
  67. */
  68. class MyParcelable implements rpc.Parcelable {
  69. num: number;
  70. str: string;
  71. constructor(num: number, str: string) {
  72. this.num = num;
  73. this.str = str;
  74. }
  75. marshalling(messageSequence: rpc.MessageSequence): boolean {
  76. messageSequence.writeInt(this.num);
  77. messageSequence.writeString(this.str);
  78. return true;
  79. }
  80. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
  81. this.num = messageSequence.readInt();
  82. this.str = messageSequence.readString();
  83. return true;
  84. }
  85. }
  86. /**
  87. * 主Ability类,继承自UIAbility
  88. * 负责:
  89. * - 应用初始化
  90. * - 窗口管理
  91. * - 事件分发
  92. */
  93. export default class EntryAbility extends UIAbility {
  94. // UI上下文对象,用于获取窗口信息
  95. private uiContext?: UIContext;
  96. // private awareness: smartMobilityCommon.SmartMobilityAwareness = smartMobilityCommon.getSmartMobilityAwareness();
  97. private awareness: smartMobilityCommon.SmartMobilityAwareness | undefined =
  98. canIUse("SystemCapability.SmartOptimizer.SmartMobility") ? smartMobilityCommon.getSmartMobilityAwareness() :
  99. undefined;
  100. /**
  101. * 窗口尺寸变化回调函数
  102. * @param windowSize 新的窗口尺寸对象
  103. * 功能:
  104. * 1. 获取最新的窗口断点尺寸
  105. * 2. 更新AppStorage中的尺寸状态
  106. * 3. 记录尺寸变化日志
  107. */
  108. private onWindowSizeChange: (windowSize: window.Size) => void = async (windowSize: window.Size) => {
  109. // 获取宽度断点并更新全局状态
  110. let widthBp = this.uiContext!.getWindowWidthBreakpoint();
  111. AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
  112. // 获取高度断点并更新全局状态
  113. let heightBp = this.uiContext!.getWindowHeightBreakpoint();
  114. AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
  115. // 记录尺寸变化日志
  116. // LogUtil.info('pura onWindowSizeChange currentHeightBreakpoint= '+heightBp);
  117. // LogUtil.info( 'pura onWindowSizeChange currentWidthBreakpoint= '+widthBp);
  118. AppStorage.setOrCreate('windowWidth', windowSize.width);
  119. AppStorage.setOrCreate('windowHeight', windowSize.height);
  120. if (windowSize.width > windowSize.height) {
  121. AppStorage.setOrCreate('isLandscape', true);
  122. } else {
  123. AppStorage.setOrCreate('isLandscape', false);
  124. }
  125. // LogUtil.info('hicar onWindowSizeChange width= '+windowSize.width);
  126. // LogUtil.info( 'hicar onWindowSizeChange height= '+windowSize.height);
  127. };
  128. onAvoidAreaChange = (data: window.AvoidAreaOptions) => {
  129. if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
  130. let topRectHeight = px2vp(data.area.topRect.height);
  131. AppStorage.setOrCreate('topRectHeight', topRectHeight);
  132. } else if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
  133. let bottomRectHeight = px2vp(data.area.bottomRect.height);
  134. AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
  135. }
  136. }
  137. async onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
  138. AppUtil.init(this.context);
  139. AppStorage.setOrCreate('context', this.context);
  140. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onCreate');
  141. // 注册卡片call事件监听器
  142. this.registerWidgetCallListeners();
  143. // 异步初始化统一播放器服务,避免阻塞生命周期
  144. this.initializePlayerServiceAsync();
  145. // 异步处理Want参数,避免阻塞生命周期
  146. this.handleWantAsync(want);
  147. this.handleWeChatCallIfNeed(want)
  148. this.getHiCarStatus()
  149. }
  150. async onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
  151. hilog.info(0x0000, 'Heanup2', `onNewWant, want=${JSON.stringify(want)}`);
  152. super.onNewWant(want, launchParam);
  153. // 异步处理Want参数,避免阻塞生命周期
  154. this.handleWantAsync(want);
  155. this.handleWeChatCallIfNeed(want)
  156. }
  157. //处理其他app点击其他应用打开播放器播放视频或者音频
  158. loadDoWant(want: Want) {
  159. // console.info('onecold KnockController 碰一碰 want ='+JSON.stringify(want));
  160. let uri = want.uri;
  161. if (uri == null || uri == undefined || StrUtil.isEmpty(uri)) {
  162. console.info('uri is invalid');
  163. return;
  164. }
  165. hilog.info(0x0000, 'Heanup2', `onCreate or onNewWant, uri=${uri}`);
  166. this.doSendEmit(uri)
  167. }
  168. //广播通知打开播放器播放视频或者音频
  169. doSendEmit(uri: string) {
  170. setTimeout(async () => {
  171. let eventData: emitter.EventData = {
  172. data: {
  173. message: uri
  174. }
  175. };
  176. if (Utility.isMediaByExtension(uri)) {
  177. emitter.emit({ eventId: 2 }, eventData); // 发送音频打开广播事件
  178. } else {
  179. emitter.emit({ eventId: 1 }, eventData); // 发送视频打开广播事件
  180. }
  181. }, 300)
  182. }
  183. // 1. 改造 handleParam 为异步函数,让其返回 Promise
  184. async handleParam(want: Want) {
  185. try {
  186. // 通过 await 等待异步操作完成
  187. const data = await systemShare.getSharedData(want);
  188. const records = data.getRecords();
  189. let uri = want.uri;
  190. for (const record of records) {
  191. if (record.uri) {
  192. uri = record.uri;
  193. this.doSendEmit(uri)
  194. break;
  195. }
  196. }
  197. } catch (error) {
  198. const businessError = error as BusinessError;
  199. console.error(`Failed: Code ${businessError.code}, ${businessError.message}`);
  200. }
  201. }
  202. // 华为分享拉起接收 处理分享数据
  203. onDestroy() {
  204. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onDestroy');
  205. // 关键修复:在APP销毁时保存播放器状态
  206. try {
  207. const unifiedPlayerService = UnifiedPlayerService.getInstance();
  208. if (unifiedPlayerService) {
  209. // 确保在APP被杀掉时保存当前播放状态
  210. unifiedPlayerService.release();
  211. hilog.info(0x0000, 'Heanup2', 'UnifiedPlayerService released and state saved on app destroy');
  212. }
  213. } catch (error) {
  214. hilog.error(0x0000, 'Heanup2', `Failed to release UnifiedPlayerService: ${error}`);
  215. }
  216. // 清理所有Form ID(应用卸载时)
  217. try {
  218. this.clearAllFormIds();
  219. } catch (error) {
  220. hilog.error(0x0000, 'Heanup2', `Failed to clear form IDs: ${error}`);
  221. }
  222. // 注销卡片call事件监听器
  223. this.unregisterWidgetCallListeners();
  224. hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  225. if (this.awareness) {
  226. let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.CAR_HOP];
  227. // 出行连接状态回调函数
  228. const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
  229. hilog.info(0x0000, 'Received smart mobility info: ', JSON.stringify(info));
  230. };
  231. // 解注册智慧出行连接状态的监听 示例2
  232. this.awareness.off('smartMobilityStatus', types, callBack);
  233. }
  234. }
  235. onWindowStageCreate(windowStage: window.WindowStage) {
  236. // Main window is created, set main page for this ability
  237. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageCreate');
  238. // 使用自定义崩溃处理器替代 SpiderMan.init()
  239. // SpiderMan.init();
  240. CustomCrashHandler.init();
  241. AppUtil.init(this.context);
  242. //1.获取应用主窗口。
  243. let windowClass: window.Window | null = null;
  244. windowStage.getMainWindow((err: BusinessError, data) => {
  245. windowClass = data;
  246. // LogUtil.info( 'getMainWindow = ');
  247. GlobalContext.getContext().setObject('windowClass', data); // 使用GlobalContext替代globalThis
  248. let curDisplay = display.getDisplayByIdSync(windowClass.getWindowProperties().displayId);
  249. console.info('twocold curDisplay = ' + curDisplay.name);
  250. if (curDisplay.name == 'HiCar' || curDisplay.name == 'SuperLauncher') {
  251. AppStorage.setOrCreate('curDisplayIsHiCar', true);
  252. } else {
  253. AppStorage.setOrCreate('curDisplayIsHiCar', false);
  254. }
  255. windowClass.setWindowLayoutFullScreen(true).then(() => {
  256. console.info('Succeeded in setting the window layout to full-screen mode.');
  257. }).catch((e: BusinessError) => {
  258. console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(e));
  259. })
  260. // let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
  261. // let topRectHeight = px2vp(avoidArea.topRect.height);
  262. // AppStorage.setOrCreate('topRectHeight', topRectHeight);
  263. let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
  264. let avoidArea = windowClass.getWindowAvoidArea(type);
  265. let bottomRectHeight = px2vp(avoidArea.bottomRect.height); // 获取到导航条区域的高度
  266. AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
  267. type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例
  268. avoidArea = windowClass.getWindowAvoidArea(type);
  269. let topRectHeight = px2vp(avoidArea.topRect.height) // 获取状态栏区域高度
  270. AppStorage.setOrCreate('topRectHeight', topRectHeight);
  271. LogUtil.info('onecold topRectHeight = ' + topRectHeight);
  272. windowClass.on('avoidAreaChange', this.onAvoidAreaChange)
  273. })
  274. AppStorage.setOrCreate('windowStage', windowStage);
  275. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageCreate');
  276. windowStage.loadContent('pages/SplashIndex', (err, data) => {
  277. if (err.code) {
  278. hilog.error(0x0000, 'Heanup2', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  279. return;
  280. }
  281. //一多断点开发
  282. windowStage.getMainWindow().then((data: window.Window) => {
  283. this.uiContext = data.getUIContext();
  284. let widthBp = this.uiContext.getWindowWidthBreakpoint();
  285. let heightBp = this.uiContext.getWindowHeightBreakpoint();
  286. AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
  287. AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
  288. LogUtil.info('getMainWindow currentHeightBreakpoint= ' + heightBp);
  289. LogUtil.info('getMainWindow currentWidthBreakpoint= ' + widthBp);
  290. data.on('windowSizeChange', this.onWindowSizeChange);
  291. AppStorage.setOrCreate('windowWidth', data.getWindowProperties().windowRect.width);
  292. AppStorage.setOrCreate('windowHeight', data.getWindowProperties().windowRect.height);
  293. if (data.getWindowProperties().windowRect.width > data.getWindowProperties().windowRect.height) {
  294. AppStorage.setOrCreate('isLandscape', true);
  295. } else {
  296. AppStorage.setOrCreate('isLandscape', false);
  297. }
  298. // LogUtil.info('hicar getMainWindow width= '+data.getWindowProperties().windowRect.width);
  299. // LogUtil.info( 'hicar getMainWindow height= '+data.getWindowProperties().windowRect.height);
  300. }).catch((err: BusinessError) => {
  301. console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
  302. });
  303. hilog.info(0x0000, 'Heanup2', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  304. });
  305. }
  306. onWindowStageDestroy() {
  307. // Main window is destroyed, release UI related resources
  308. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageDestroy');
  309. }
  310. onForeground() {
  311. }
  312. onBackground() {
  313. // Ability has back to background
  314. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onBackground start');
  315. // 异步处理后台逻辑,避免阻塞生命周期
  316. setTimeout(() => {
  317. this.handleBackgroundAsync();
  318. }, 10);
  319. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onBackground end');
  320. }
  321. getHiCarStatus() {
  322. if (this.awareness) {
  323. // this.awareness = smartMobilityCommon.getSmartMobilityAwareness();
  324. // 业务类型
  325. let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.HICAR];
  326. // 获取出行业务连接状态
  327. let info = this.awareness.getSmartMobilityStatus(types[0]);
  328. hilog.info(0x0000, 'getHiCarStatus info: ', JSON.stringify(info));
  329. if (info && info.status == 1) {
  330. AppStorage.setOrCreate('isHiCarStatus', true);
  331. } else {
  332. AppStorage.setOrCreate('isHiCarStatus', false);
  333. }
  334. // 出行连接状态回调函数
  335. const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
  336. hilog.info(0x0000, 'getHiCarStatus Received smart mobility info: ', JSON.stringify(info));
  337. if (info && info.status == 1) {
  338. AppStorage.setOrCreate('isHiCarStatus', true);
  339. } else {
  340. AppStorage.setOrCreate('isHiCarStatus', false);
  341. }
  342. this.sendChangeEvent()
  343. };
  344. // 注册智慧出行连接状态的监听
  345. this.awareness.on('smartMobilityStatus', types, callBack);
  346. } else {
  347. AppStorage.setOrCreate('isHiCarStatus', false);
  348. }
  349. }
  350. //发送广播通知更新UI
  351. sendChangeEvent() {
  352. const eventData: emitter.EventData = {};
  353. emitter.emit({ eventId: 333 }, eventData); // 发送广播通知更新doSwipBack
  354. }
  355. private handleWeChatCallIfNeed(want: Want) {
  356. WXApi.handleWant(want, WXEventHandler)
  357. }
  358. /**
  359. * 注册卡片call事件监听器(增强版本:服务就绪检查)
  360. */
  361. private registerWidgetCallListeners(): void {
  362. try {
  363. // 监听播放/暂停事件
  364. this.callee.on('playPause', (data: rpc.MessageSequence) => {
  365. try {
  366. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  367. hilog.info(0x0000, 'Heanup2', `🎵 Widget call: playPause received`);
  368. // 异步发送播放/暂停事件到主应用(包含服务就绪检查)
  369. this.sendWidgetControlEvent('PLAY_PAUSE', params).catch((error: Error) => {
  370. hilog.error(0x0000, 'Heanup2', `❌ playPause async error: ${error}`);
  371. });
  372. return new MyParcelable(1, 'playPause_success');
  373. } catch (error) {
  374. hilog.error(0x0000, 'Heanup2', `❌ playPause handler error: ${error}`);
  375. return new MyParcelable(-1, 'playPause_error');
  376. }
  377. });
  378. // 监听下一首事件
  379. this.callee.on('nextSong', (data: rpc.MessageSequence) => {
  380. try {
  381. hilog.info(0x0000, 'Heanup2', `🎵 Widget call: nextSong received`);
  382. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  383. hilog.info(0x0000, 'Heanup2', `Widget nextSong params: ${JSON.stringify(params)}`);
  384. // 异步发送下一首事件到主应用(包含服务就绪检查)
  385. this.sendWidgetControlEvent('NEXT_SONG', params).catch((error: Error) => {
  386. hilog.error(0x0000, 'Heanup2', `❌ nextSong async error: ${error}`);
  387. });
  388. return new MyParcelable(2, 'nextSong_success');
  389. } catch (error) {
  390. hilog.error(0x0000, 'Heanup2', `❌ nextSong handler error: ${error}`);
  391. return new MyParcelable(-2, 'nextSong_error');
  392. }
  393. });
  394. // 监听上一首事件
  395. this.callee.on('prevSong', (data: rpc.MessageSequence) => {
  396. try {
  397. hilog.info(0x0000, 'Heanup2', `🎵 Widget call: prevSong received`);
  398. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  399. hilog.info(0x0000, 'Heanup2', `Widget prevSong params: ${JSON.stringify(params)}`);
  400. // 异步发送上一首事件到主应用(包含服务就绪检查)
  401. this.sendWidgetControlEvent('PREV_SONG', params).catch((error: Error) => {
  402. hilog.error(0x0000, 'Heanup2', `❌ prevSong async error: ${error}`);
  403. });
  404. return new MyParcelable(3, 'prevSong_success');
  405. } catch (error) {
  406. hilog.error(0x0000, 'Heanup2', `❌ prevSong handler error: ${error}`);
  407. return new MyParcelable(-3, 'prevSong_error');
  408. }
  409. });
  410. // 监听打开应用事件
  411. this.callee.on('openApp', (data: rpc.MessageSequence) => {
  412. try {
  413. hilog.info(0x0000, 'Heanup2', `🎵 Widget call: openApp received`);
  414. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  415. hilog.info(0x0000, 'Heanup2', `Widget openApp params: ${JSON.stringify(params)}`);
  416. // 异步发送打开应用事件到主应用
  417. this.sendWidgetControlEvent('OPEN_APP', params).catch((error: Error) => {
  418. hilog.error(0x0000, 'Heanup2', `❌ openApp async error: ${error}`);
  419. });
  420. return new MyParcelable(4, 'openApp_success');
  421. } catch (error) {
  422. hilog.error(0x0000, 'Heanup2', `❌ openApp handler error: ${error}`);
  423. return new MyParcelable(-4, 'openApp_error');
  424. }
  425. });
  426. this.callee.on("toggleFavorite", (data: rpc.MessageSequence) => {
  427. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  428. hilog.info(0x0000, 'Heanup2', `toggleFavorite handler`);
  429. this.sendWidgetControlEvent('toggleFavorite', params).catch((error: Error) => {
  430. hilog.error(0x0000, 'Heanup2', `❌ toggleFavorite async error: ${error}`);
  431. });
  432. return new MyParcelable(-5, 'toggleFavorite');
  433. })
  434. hilog.info(0x0000, 'Heanup2', '🎵 Widget call listeners registered successfully (Enhanced)');
  435. } catch (err) {
  436. hilog.error(0x0000, 'Heanup2',
  437. `❌ Failed to register widget call listeners: ${JSON.stringify(err as BusinessError)}`);
  438. }
  439. }
  440. /**
  441. * 注销卡片call事件监听器
  442. */
  443. private unregisterWidgetCallListeners(): void {
  444. try {
  445. this.callee.off('playPause');
  446. this.callee.off('nextSong');
  447. this.callee.off('prevSong');
  448. this.callee.off('toggleFavorite');
  449. // this.callee.off('openApp');
  450. // this.callee.off('playByAction');
  451. // this.callee.off('collectAction');
  452. // this.callee.off('requestUpdatePlayCard');
  453. hilog.info(0x0000, 'Heanup2', 'Widget call listeners unregistered successfully');
  454. } catch (err) {
  455. hilog.error(0x0000, 'Heanup2',
  456. `Failed to unregister widget call listeners: ${JSON.stringify(err as BusinessError)}`);
  457. }
  458. }
  459. /**
  460. * 发送卡片控制事件到主应用(增强版本,确保服务已初始化)
  461. */
  462. private async sendWidgetControlEvent(command: string, params: Record<string, Object>): Promise<void> {
  463. try {
  464. // 获取UnifiedPlayerService实例
  465. const unifiedService = UnifiedPlayerService.getInstance();
  466. // 确保服务已经初始化
  467. if (!unifiedService) {
  468. hilog.error(0x0000, 'Heanup2', '❌ UnifiedPlayerService 未初始化');
  469. return;
  470. }
  471. // 获取详细的服务就绪状态信息
  472. const readinessInfo = unifiedService.getServiceReadinessInfo();
  473. hilog.info(0x0000, 'Heanup2', `🔍 Service readiness: ${JSON.stringify(readinessInfo.details)}`);
  474. // 智能等待服务完全就绪(多级检查)
  475. hilog.info(0x0000, 'Heanup2', '🔄 检查服务就绪状态...');
  476. let ready = await this.waitForServiceReady(unifiedService);
  477. if (ready) {
  478. hilog.info(0x0000, 'Heanup2', '✅ UnifiedPlayerService 已就绪');
  479. } else {
  480. hilog.warn(0x0000, 'Heanup2', '⚠️ UnifiedPlayerService 未完全就绪,尝试执行基础操作');
  481. // 检查是否至少可以进行基础操作
  482. if (!unifiedService.isAllServicesReady()) {
  483. const finalReadinessInfo = unifiedService.getServiceReadinessInfo();
  484. hilog.error(0x0000, 'Heanup2',
  485. `❌ 基础服务未就绪,无法执行操作: ${JSON.stringify(finalReadinessInfo.details)}`);
  486. return;
  487. }
  488. }
  489. // 检查初始化状态
  490. const currentState = unifiedService.getCurrentState();
  491. const playlist = unifiedService.getPlaylist();
  492. hilog.info(0x0000, 'Heanup2',
  493. `🎵 Widget command - Service state: isPlaying=${currentState.isPlaying}, currentIndex=${currentState.currentIndex}, playlistSize=${playlist.length}`);
  494. // 对于播放控制操作,检查是否有可播放内容
  495. if ((command === 'PLAY_PAUSE' || command === 'NEXT_SONG' || command === 'PREV_SONG') && playlist.length === 0) {
  496. hilog.warn(0x0000, 'Heanup2', '⚠️ 播放列表为空,尝试强制数据恢复');
  497. // 强制触发数据恢复
  498. await this.forceDataRestoration(unifiedService);
  499. // 重新检查播放列表
  500. const updatedPlaylist = unifiedService.getPlaylist();
  501. if (updatedPlaylist.length === 0) {
  502. hilog.warn(0x0000, 'Heanup2', '⚠️ 强制数据恢复后播放列表仍为空,无法执行播放控制操作');
  503. return;
  504. }
  505. hilog.info(0x0000, 'Heanup2', `✅ 强制数据恢复完成,播放列表大小: ${updatedPlaylist.length}`);
  506. }
  507. // 根据命令执行相应的播放控制
  508. switch (command) {
  509. case 'PLAY_PAUSE':
  510. // 获取卡片传递的当前显示状态
  511. const widgetIsPlaying = params['widgetIsPlaying'] as boolean;
  512. const hasWidgetState = widgetIsPlaying !== undefined;
  513. // 获取服务内部状态
  514. const latestState = unifiedService.getCurrentState();
  515. hilog.info(0x0000, 'Heanup2', `🎵 卡片显示状态: isPlaying=${widgetIsPlaying} (有效: ${hasWidgetState})`);
  516. hilog.info(0x0000, 'Heanup2', `🎵 服务内部状态: isPlaying=${latestState.isPlaying}`);
  517. hilog.info(0x0000, 'Heanup2', '⚠️ 使用向后兼容模式(基于服务状态)');
  518. if (latestState.isPlaying) {
  519. await unifiedService.pause();
  520. hilog.info(0x0000, 'Heanup2', '✅ 兼容模式:暂停播放');
  521. } else {
  522. hilog.info(0x0000, 'Heanup2', '🔄 兼容模式:开始播放');
  523. await unifiedService.startPlayOrResumePlay();
  524. hilog.info(0x0000, 'Heanup2', '✅ 兼容模式:开始播放');
  525. }
  526. break;
  527. case 'NEXT_SONG':
  528. await unifiedService.playNext();
  529. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Next song');
  530. break;
  531. case 'PREV_SONG':
  532. await unifiedService.playPrevious();
  533. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Previous song');
  534. break;
  535. case 'OPEN_APP':
  536. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Open app (handled by UI)');
  537. break;
  538. case 'OPEN_APP':
  539. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Open app (handled by UI)');
  540. break;
  541. case "toggleFavorite":
  542. await unifiedService.toggleFavorite();
  543. break;
  544. default:
  545. hilog.warn(0x0000, 'Heanup2', `❓ Unknown widget command: ${command}`);
  546. break;
  547. }
  548. // 操作完成后,广播最新状态给主应用UI和桌面卡片
  549. setTimeout(() => {
  550. hilog.info(0x0000, 'Heanup2', '📡 桌面卡片操作后广播状态更新');
  551. // 同时触发UnifiedPlayerService的状态广播,确保所有监听器都能收到更新
  552. try {
  553. unifiedService.broadcastCurrentState();
  554. } catch (error) {
  555. hilog.error(0x0000, 'Heanup2', `❌ 触发服务状态广播失败: ${error}`);
  556. }
  557. }, 100); // 短延迟,确保操作完全完成
  558. hilog.info(0x0000, 'Heanup2', `✅ Widget control command processed: ${command}`);
  559. } catch (error) {
  560. hilog.error(0x0000, 'Heanup2', `❌ Failed to process widget control command: ${error}`);
  561. }
  562. }
  563. /**
  564. * 强制数据恢复(用于桌面卡片冷启动场景)
  565. */
  566. private async forceDataRestoration(unifiedService: UnifiedPlayerService): Promise<void> {
  567. try {
  568. hilog.info(0x0000, 'Heanup2', '🔄 开始强制数据恢复...');
  569. // 检查是否已经有数据恢复完成
  570. if (unifiedService.isDataRestorationCompleted()) {
  571. hilog.info(0x0000, 'Heanup2', '✅ 数据已恢复,无需强制恢复');
  572. return;
  573. }
  574. // 调用UnifiedPlayerService的强制数据恢复方法
  575. const restored = await unifiedService.forceDataRestoration();
  576. if (restored) {
  577. hilog.info(0x0000, 'Heanup2', '✅ 强制数据恢复成功');
  578. } else {
  579. hilog.warn(0x0000, 'Heanup2', '⚠️ 强制数据恢复失败,尝试等待');
  580. // 如果强制恢复失败,再等待一段时间
  581. await unifiedService.waitForDataRestoration(2000);
  582. }
  583. // 额外等待一小段时间,确保播放列表数据完全加载
  584. await new Promise<void>(resolve => setTimeout(resolve, 300));
  585. } catch (error) {
  586. hilog.error(0x0000, 'Heanup2', `❌ 强制数据恢复失败: ${error}`);
  587. }
  588. }
  589. /**
  590. * 智能等待服务就绪(增强版本)
  591. */
  592. private async waitForServiceReady(unifiedService: UnifiedPlayerService): Promise<boolean> {
  593. try {
  594. hilog.info(0x0000, 'Heanup2', '🔄 开始等待UnifiedPlayerService完全就绪');
  595. // 检查是否是刚启动的应用(数据还没恢复)
  596. const currentPlaylist = unifiedService.getPlaylist();
  597. const isJustStarted = currentPlaylist.length === 0;
  598. const isDataRestored = unifiedService.isDataRestorationCompleted();
  599. if (isJustStarted || !isDataRestored) {
  600. hilog.info(0x0000, 'Heanup2',
  601. `🔄 检测到冷启动状态 - 播放列表为空: ${isJustStarted}, 数据未恢复: ${!isDataRestored}`);
  602. // 对于冷启动,给予更长的等待时间,并强制检查数据恢复
  603. const isReady = await unifiedService.waitForAllServicesReady(10000, true);
  604. if (isReady) {
  605. hilog.info(0x0000, 'Heanup2', '✅ UnifiedPlayerService完全就绪(冷启动)');
  606. return true;
  607. }
  608. // 如果仍未就绪,尝试强制触发数据恢复
  609. hilog.warn(0x0000, 'Heanup2', '⚠️ 冷启动等待超时,尝试强制数据恢复');
  610. await this.forceDataRestoration(unifiedService);
  611. // 再次检查服务状态
  612. const retryReady = await unifiedService.waitForAllServicesReady(3000, false);
  613. if (retryReady) {
  614. hilog.info(0x0000, 'Heanup2', '✅ 强制恢复后服务就绪');
  615. return true;
  616. }
  617. } else {
  618. // 对于已经有数据的情况,使用正常的等待时间
  619. const isReady = await unifiedService.waitForAllServicesReady(5000, true);
  620. if (isReady) {
  621. hilog.info(0x0000, 'Heanup2', '✅ UnifiedPlayerService完全就绪');
  622. return true;
  623. }
  624. }
  625. // 如果主要服务就绪但数据未恢复,再做一次宽松检查
  626. hilog.info(0x0000, 'Heanup2', '⚠️ 主要检查超时,进行备用检查');
  627. const basicReady = await unifiedService.waitForAllServicesReady(2000, false);
  628. if (basicReady) {
  629. // 检查是否至少有播放数据
  630. const currentState = unifiedService.getCurrentState();
  631. const playlist = unifiedService.getPlaylist();
  632. if (playlist.length > 0 || currentState.currentIndex >= 0) {
  633. hilog.info(0x0000, 'Heanup2', `✅ 基础服务就绪,播放列表: ${playlist.length} 首歌曲`);
  634. return true;
  635. }
  636. }
  637. hilog.warn(0x0000, 'Heanup2', '⚠️ Service readiness check timeout, proceeding with limited functionality');
  638. return false;
  639. } catch (error) {
  640. hilog.error(0x0000, 'Heanup2', `❌ Error waiting for service ready: ${error}`);
  641. return false;
  642. }
  643. }
  644. /**
  645. * 异步初始化播放器服务,避免阻塞生命周期
  646. */
  647. private initializePlayerServiceAsync(): void {
  648. // 使用 setTimeout 将初始化操作移到下一个事件循环
  649. setTimeout(async () => {
  650. try {
  651. hilog.info(0x0000, 'Heanup2', '🔄 开始异步初始化 UnifiedPlayerService');
  652. await UnifiedPlayerService.getInstance().initialize(this.context);
  653. hilog.info(0x0000, 'Heanup2', '✅ UnifiedPlayerService 异步初始化成功');
  654. // 对于冷启动场景,立即尝试数据恢复
  655. const unifiedService = UnifiedPlayerService.getInstance();
  656. if (!unifiedService.isDataRestorationCompleted()) {
  657. hilog.info(0x0000, 'Heanup2', '🔄 冷启动检测到数据未恢复,开始预恢复');
  658. await unifiedService.forceDataRestoration();
  659. }
  660. } catch (error) {
  661. hilog.error(0x0000, 'Heanup2', `❌ UnifiedPlayerService 异步初始化失败: ${error}`);
  662. }
  663. }, 50); // 缩短延迟,让初始化更快开始
  664. }
  665. /**
  666. * 异步处理后台逻辑
  667. */
  668. private handleBackgroundAsync(): void {
  669. try {
  670. hilog.info(0x0000, 'Heanup2', '🔄 处理后台逻辑');
  671. // 关键修复:应用进入后台时保存播放器状态
  672. try {
  673. const unifiedPlayerService = UnifiedPlayerService.getInstance();
  674. if (unifiedPlayerService && unifiedPlayerService.isAllServicesReady()) {
  675. // 保存当前状态,确保即使应用被强制杀死也能保存正确状态
  676. unifiedPlayerService.saveCurrentStateExternal();
  677. hilog.info(0x0000, 'Heanup2', '✅ 播放器状态已保存(后台)');
  678. }
  679. } catch (error) {
  680. hilog.error(0x0000, 'Heanup2', `❌ 保存播放器状态失败: ${error}`);
  681. }
  682. // 后台时可以执行一些清理或保存操作
  683. // 但要确保不会阻塞生命周期
  684. hilog.info(0x0000, 'Heanup2', '✅ 后台逻辑处理完成');
  685. } catch (error) {
  686. hilog.error(0x0000, 'Heanup2', `❌ 后台逻辑处理失败: ${error}`);
  687. }
  688. }
  689. /**
  690. * 异步处理Want参数,避免阻塞生命周期
  691. */
  692. private handleWantAsync(want: Want): void {
  693. setTimeout(async () => {
  694. try {
  695. this.loadDoWant(want);
  696. await this.handleParam(want);
  697. } catch (error) {
  698. hilog.error(0x0000, 'Heanup2', `❌ 处理Want参数失败: ${error}`);
  699. }
  700. }, 200);
  701. }
  702. /**
  703. * 清理所有Form ID
  704. * 应用销毁时调用,确保清理所有持久化的Form ID
  705. */
  706. private async clearAllFormIds(): Promise<void> {
  707. try {
  708. const preferencesUtil = PreferencesUtil.getInstance();
  709. const prefs = await preferencesUtil.getPreferences(this.context);
  710. // 获取所有Form ID
  711. const formIds = await preferencesUtil.getFormIds(prefs);
  712. if (formIds.length > 0) {
  713. // 清理所有Form ID
  714. await preferencesUtil.removeFormIds(prefs, formIds);
  715. hilog.info(0x0000, 'Heanup2', `Cleared ${formIds.length} form IDs on app destroy: ${formIds.join(', ')}`);
  716. } else {
  717. hilog.info(0x0000, 'Heanup2', 'No form IDs to clear on app destroy');
  718. }
  719. } catch (error) {
  720. hilog.error(0x0000, 'Heanup2', `Failed to clear form IDs: ${error}`);
  721. }
  722. }
  723. }