讨论广场 问答详情
内购订阅恢复与票据校验
bug菌¹ 2025-09-14 00:26:08
72 评论 分享
鸿蒙问答专区

描述:语音记录功能需要长时间录音;退到后台后若被系统回收会造成文件损坏。想要通过前台服务 + 常驻通知保活,并在页面前后台切换、来电打断等情况下正确暂停/恢复录音。有没有较稳妥的状态机与生命周期协同实践?

相关代码:

// 伪代码:下单成功后拿到 purchaseToken 传给服务端二次校验
const order = await iap.purchase('sub_premium_month');
await http.request('/verify', { method: 'POST', data: { token: order.purchaseToken } });
preferences.getPreferences({ name: 'iap' }).then(p => p.put('active', true));

 

 

72 评论 分享
写回答
全部评论(1)
1 楼

1. 整体架构
前台服务:使用Service Ability作为录音服务,通过keepBackgroundRunning()设置为前台服务,并显示常驻通知。

状态机:设计一个录音状态机来管理录音的生命周期(如空闲、录音中、暂停、停止),确保状态转换的一致性。

生命周期监听:监听应用的前后台切换和来电事件,及时暂停和恢复录音。

文件处理:使用临时文件录音,并在最终ize时确保文件完整,避免损坏。

2. 状态机设计
定义录音状态机,包含以下状态和转换:

状态:

IDLE:初始状态,录音未开始。

RECORDING:正在录音。

PAUSED:录音暂停。

STOPPED:录音停止,文件已保存。

转换方法:

startRecording():从IDLE或PAUSED切换到RECORDING。

pauseRecording():从RECORDING切换到PAUSED。

resumeRecording():从PAUSED切换到RECORDING。

stopRecording():从RECORDING或PAUSED切换到STOPPED。

3. 实现步骤
3.1 创建前台服务(Service Ability)
在ets目录下创建录音服务RecordingService.ts,实现前台服务和录音逻辑。

import featureAbility from '@ohos.ability.featureAbility';
import notification from '@ohos.notification';
import media from '@ohos.multimedia.media';
import fileio from '@ohos.fileio';

export default class RecordingService {
  private state: string = 'IDLE';
  private mediaRecorder: media.MediaRecorder | null = null;
  private outputPath: string = 'path/to/recording/file.mp3'; // 设置录音文件路径

  // 启动服务
  onStart(want, startId) {
    this.setupForegroundService();
    this.startRecording();
  }

  // 停止服务
  onStop() {
    this.stopRecording();
    this.cancelForegroundService();
  }

  // 设置前台服务
  private setupForegroundService() {
    let notificationRequest: notification.NotificationRequest = {
      content: {
        contentType: notification.ContentType.NOTIFICATION_CONTENT_TYPE_TEXT,
        normal: {
          title: '录音中',
          text: '正在后台录音',
          additionalText: '点击返回应用'
        }
      },
      id: 1,
      isOngoing: true // 常驻通知
    };

    featureAbility.keepBackgroundRunning(this.context, notificationRequest).then(() => {
      console.log('keepBackgroundRunning success');
    }).catch((error) => {
      console.error('keepBackgroundRunning failed: ' + error);
    });
  }

  // 取消前台服务
  private cancelForegroundService() {
    featureAbility.cancelBackgroundRunning().then(() => {
      console.log('cancelBackgroundRunning success');
    }).catch((error) => {
      console.error('cancelBackgroundRunning failed: ' + error);
    });
  }

  // 开始录音
  private startRecording() {
    if (this.state === 'IDLE' || this.state === 'PAUSED') {
      if (this.state === 'IDLE') {
        this.mediaRecorder = media.createMediaRecorder();
        let config: media.RecorderConfig = {
          audioSource: media.AudioSource.AUDIO_SOURCE_MIC,
          outputFormat: media.OutputFormat.OUTPUT_FORMAT_MPEG_4,
          audioEncoder: media.AudioEncoder.AUDIO_ENCODER_AAC,
          outputPath: this.outputPath
        };
        this.mediaRecorder.prepare(config);
      }
      this.mediaRecorder.start().then(() => {
        this.state = 'RECORDING';
        console.log('Recording started');
      }).catch((error) => {
        console.error('Recording start failed: ' + error);
      });
    }
  }

  // 暂停录音
  private pauseRecording() {
    if (this.state === 'RECORDING' && this.mediaRecorder) {
      this.mediaRecorder.pause().then(() => {
        this.state = 'PAUSED';
        console.log('Recording paused');
      }).catch((error) => {
        console.error('Recording pause failed: ' + error);
      });
    }
  }

  // 恢复录音
  private resumeRecording() {
    if (this.state === 'PAUSED' && this.mediaRecorder) {
      this.mediaRecorder.resume().then(() => {
        this.state = 'RECORDING';
        console.log('Recording resumed');
      }).catch((error) => {
        console.error('Recording resume failed: ' + error);
      });
    }
  }

  // 停止录音
  private stopRecording() {
    if (this.state === 'RECORDING' || this.state === 'PAUSED') {
      if (this.mediaRecorder) {
        this.mediaRecorder.stop().then(() => {
          this.mediaRecorder.release();
          this.mediaRecorder = null;
          this.state = 'STOPPED';
          console.log('Recording stopped');
        }).catch((error) => {
          console.error('Recording stop failed: ' + error);
        });
      }
    }
  }
}

3.2 监听应用生命周期事件

在应用主页面或全局环境中,监听应用前后台切换事件,并控制录音服务的暂停和恢复。

import app from '@app.app';
import { AppStateObserver } from '@app.app';

// 注册应用状态观察者
app.registerAppStateObserver({
  onAppStateChanged: (newState) => {
    if (newState === 'background') {
      // 应用进入后台,暂停录音
      this.pauseRecording();
    } else if (newState === 'foreground') {
      // 应用回到前台,恢复录音
      this.resumeRecording();
    }
  }
});

3.3 监听来电事件

使用电话状态监听器,在来电时暂停录音,挂断后恢复。

import observer from '@ohos.telephony.observer';

// 订阅电话状态变化
observer.on('callStateChange', (data) => {
  if (data.state === observer.CallState.CALL_STATE_OFFHOOK) {
    // 电话接通,暂停录音
    this.pauseRecording();
  } else if (data.state === observer.CallState.CALL_STATE_IDLE) {
    // 电话挂断,恢复录音
    this.resumeRecording();
  }
});

3.4 启动和停止服务

在需要开始录音的页面中,启动前台服务;在结束录音时停止服务。

import featureAbility from '@ohos.ability.featureAbility';

// 启动录音服务
let want = {
  bundleName: 'com.example.recordingapp',
  abilityName: 'RecordingService'
};
featureAbility.startAbility(want).then(() => {
  console.log('Service started');
}).catch((error) => {
  console.error('Service start failed: ' + error);
});

// 停止录音服务
featureAbility.stopAbility(want).then(() => {
  console.log('Service stopped');
}).catch((error) => {
  console.error('Service stop failed: ' + error);
});

 

2025-09-14 02:52:37