文档管理中心

应用在后台时,一段时间后自动退出

问题现象

退出到后台后,应用自动退出。

背景知识

  • PROCESS_KILL是因为各种原因(如应用内存超限、运行锁异常等)导致应用被操作系统强制停止。
  • 当前支持的PROCESS_KILL问题种类可以参考分析PROCESS KILL问题
  • 长时任务(ArkTS):应用退至后台后,在后台需要长时间运行用户可感知的任务,如播放音乐、导航等。为防止应用进程被挂起,导致对应功能异常,可以申请长时任务,使应用在后台长时间运行。

问题定位

  1. 查看faultlogger目录,没有和应用相关的故障日志,应用不是因为崩溃而退出的。
  2. 排查应用是否被系统终止,在hilog日志中搜索关键字kill Reason,应用在20:51:29.264被系统终止,终止原因是在后台非法录音。
    收起
    自动换行
    深色代码主题
    复制
    1. 04-24 20:51:29.264 998 2232 W C01713/resource_schedule_service/SUSPEND_MANAGER: [(ReportKillAppEvent):277] hisysevent write result=0, send event [FRAMEWORK,PROCESS_KILL], pid=64424, processName=com.hm.example, msg=Kill Reason:ILLEGAL_AUDIO_CAPTURER_BY_SUSPEND, foreground=0
    2. 04-24 20:51:29.264 3674 3674 I A01B05/com.ohos.sceneboard/AOD: AodSettingsModule --> Check isInTimeRange, currentTime: 1745499089264, startTime: 1745449200000, endTime: 1745506800000
    3. 04-24 20:51:29.264 998 2232 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(IsKillAppWhenIllegalUseAudio):248] Application 20020222_com.hm.example_[64424] current has media and not apply background_task or Resource::AUDIO
  3. 搜索关键字TASK_DETECTION,查看应用正在执行的任务已经切换到后台的时间。应用在20:50:57.360存在player放音任务,在20:50:57.419存在recorder录音任务,在20:51:27.254切换到后台。
    收起
    自动换行
    深色代码主题
    复制
    1. 04-24 20:50:57.360 998 1306 I C01724/resource_schedule_service/TASK_DETECTION: [UpdateAudioRecord:89]Get Audio info uid: 20020222, sessionId: 100020, State: 2, type: player, streamType: 1
    2. 04-24 20:50:57.419 998 1307 I C01724/resource_schedule_service/TASK_DETECTION: [UpdateAudioRecord:89]Get Audio info uid: 20020222, sessionId: 100021, State: 2, type: recorder, streamType: 0
    3. 04-24 20:51:27.254 998 2230 I C01724/resource_schedule_service/TASK_DETECTION: [OnAppStateChanged:595]Application State change to background, uid: 20020222, pid: 64424,bundleName: com.hm.example
    4. // ...
    5. 04-24 20:51:29.309 998 2136 I C01724/resource_schedule_service/TASK_DETECTION: [UpdateAudioRecord:103]Get Audio info uid: 20020222, sessionId: 100020, State: 4, type: player, streamType: 1
    6. 04-24 20:51:29.309 998 2136 I C01724/resource_schedule_service/TASK_DETECTION: [UpdateAudioRecord:103]Get Audio info uid: 20020222, sessionId: 100021, State: 4, type: recorder, streamType: 0
  4. 搜索关键字Freeze pid,查看应用进程被冻结时间。应用进程id是64424,在20:51:29.258被系统冻结。
    收起
    自动换行
    深色代码主题
    复制
    1. 04-24 20:51:03.376 998 1173 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(FreezePids):246] Freeze pid: 59581 success.
    2. 04-24 20:51:09.392 998 1173 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(FreezePids):246] Freeze pid: 59581 success.
    3. 04-24 20:51:29.258 998 1173 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(FreezePids):246] Freeze pid: 64424 success.
    4. 04-24 20:51:40.381 998 1173 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(FreezePids):246] Freeze pid: 3847 success.
    5. 04-24 20:51:40.384 998 1173 I C01713/resource_schedule_service/SUSPEND_MANAGER: [(FreezePids):246] Freeze pid: 3653 success.
  5. 搜索关键字CONTINUOUS_TASK,查看应用是否申请长时任务。从日志可知,应用在20:51:29.267开始申请长时任务,但应用在20:51:29.264已经被系统终止,申请长时任务发生在系统终止之后。
    收起
    自动换行
    深色代码主题
    复制
    1. 04-24 20:51:29.264 64424 64424 I C01711/com.hm.example/CONTINUOUS_TASK: [GetModes:433] get bgModes arraylen: 2
    2. 04-24 20:51:29.264 64424 64424 I C01711/com.hm.example/CONTINUOUS_TASK: [GetModes:449] GetBackgroundMode audioRecording.
    3. 04-24 20:51:29.264 64424 64424 I C01711/com.hm.example/CONTINUOUS_TASK: [GetModes:449] GetBackgroundMode audioPlayback.
    4. 04-24 20:51:29.267 64424 65310 I C01711/com.hm.example/CONTINUOUS_TASK: [StartBackgroundRunningExecuteCB:240] RequestStartBackgroundRunning isBatch: 1, bgModeSize: 2

分析结论

切换到后台时,未及时开启长时任务,导致应用因为后台非法录音被系统终止。

修改建议

参考长时任务(ArkTS),在开始录音时开启长时任务,在停止录音时停止长时任务。

收起
自动换行
深色代码主题
复制
  1. import { media } from '@kit.MediaKit';
  2. import { BusinessError } from '@kit.BasicServicesKit';
  3. import { fileIo as fs } from '@kit.CoreFileKit';
  4. import { abilityAccessCtrl, WantAgent, wantAgent } from '@kit.AbilityKit';
  5. import { backgroundTaskManager } from '@kit.BackgroundTasksKit';
  6. @Entry
  7. @Component
  8. export struct AudioRecordPage {
  9. private avRecorder: media.AVRecorder | undefined = undefined;
  10. private avProfile: media.AVRecorderProfile = {
  11. audioBitrate: 112000, // 音频比特率。
  12. audioChannels: 2, // 音频声道数。
  13. audioCodec: media.CodecMimeType.AUDIO_AAC, // 音频编码格式,当前支持ACC,MP3,G711MU。
  14. audioSampleRate: 48000, // 音频采样率。
  15. fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // 封装格式,当前支持MP4,M4A,MP3,WAV,AMR,AAC。
  16. };
  17. private avConfig: media.AVRecorderConfig = {
  18. audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC, // 音频输入源,这里设置为麦克风。
  19. profile: this.avProfile,
  20. url: 'fd://35', // 参考应用文件访问与管理开发示例新建并读写一个文件。
  21. };
  22. private fileFd: number = 0;
  23. // 通过getUIContext().getHostContext()方法,来获取page所在的UIAbility上下文
  24. private context: Context | undefined = this.getUIContext().getHostContext();
  25. @State recordState: string = '未开始录制';
  26. @State longTaskState: string = '未申请';
  27. startContinuousTask() {
  28. let wantAgentInfo: wantAgent.WantAgentInfo = {
  29. // 点击通知后,将要执行的动作列表
  30. // 添加需要被拉起应用的bundleName和abilityName
  31. wants: [
  32. {
  33. bundleName: 'com.example.commondemofordfx',
  34. abilityName: 'EntryAbility'
  35. }
  36. ],
  37. // 指定点击通知栏消息后的动作是拉起ability
  38. actionType: wantAgent.OperationType.START_ABILITY,
  39. // 使用者自定义的一个私有值
  40. requestCode: 0,
  41. // 点击通知后,动作执行属性
  42. actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG],
  43. // 车钥匙长时任务子类型。只有申请bluetoothInteraction类型的长时任务,车钥匙子类型才能生效。
  44. };
  45. try {
  46. // 通过wantAgent模块下getWantAgent方法获取WantAgent对象
  47. wantAgent.getWantAgent(wantAgentInfo).then((wantAgentObj: WantAgent) => {
  48. try {
  49. let list: Array<string> = ['audioRecording'];
  50. backgroundTaskManager.startBackgroundRunning(this.context, list, wantAgentObj).then(() => {
  51. console.info('Operation startBackgroundRunning succeeded');
  52. // 此处执行具体的长时任务逻辑,如录音,录制等。
  53. this.longTaskState = '已申请';
  54. }).catch((error: BusinessError) => {
  55. console.error(`Failed to Operation startBackgroundRunning. code is ${error.code} message is ${error.message}`);
  56. });
  57. } catch (error) {
  58. console.error(`Failed to Operation startBackgroundRunning. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
  59. }
  60. });
  61. } catch (error) {
  62. console.error(`Failed to Operation getWantAgent. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
  63. }
  64. }
  65. stopContinuousTask() {
  66. backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
  67. console.info(`Succeeded in operationing stopBackgroundRunning.`);
  68. this.longTaskState = '已取消';
  69. }).catch((err: BusinessError) => {
  70. console.error(`Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
  71. });
  72. }
  73. // 创建文件以及设置avConfig.url
  74. async createAndSetFd(): Promise<void> {
  75. const context: Context = this.getUIContext().getHostContext()!; // 非空断言,Context类型且非空
  76. const path: string = context.filesDir + '/example.mp3'; // 文件沙箱路径,文件后缀名应与封装格式对应。
  77. const audioFile: fs.File | null = null;
  78. try {
  79. const audioFile: fs.File = fs.openSync(path, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
  80. this.avConfig.url = 'fd://' + audioFile.fd; // 更新url。
  81. this.fileFd = audioFile.fd;
  82. } catch (error) {
  83. console.error(`Failed to openSync. code is ${(error as BusinessError).code} message is ${(error as BusinessError).message}`);
  84. } finally {
  85. if (audioFile !== null) {
  86. fs.closeSync(audioFile);
  87. }
  88. }
  89. }
  90. // 注册audioRecorder回调函数。
  91. setAudioRecorderCallback() {
  92. if (this.avRecorder !== undefined) {
  93. // 状态机变化回调函数。
  94. this.avRecorder.on('stateChange', (state: media.AVRecorderState) => {
  95. console.info(`Aud ioRecorder current state is ${state.toString()}`);
  96. this.recordState = state;
  97. });
  98. // 错误上报回调函数。
  99. this.avRecorder.on('error', (err: BusinessError) => {
  100. console.error(`AudioRecorder failed, code is ${err.code}, message is ${err.message}`);
  101. });
  102. }
  103. }
  104. // 开始录制对应的流程。
  105. async startRecordingProcess() {
  106. if (this.avRecorder !== undefined) {
  107. await this.avRecorder.release();
  108. this.avRecorder = undefined;
  109. }
  110. // 1.创建录制实例。
  111. this.avRecorder = await media.createAVRecorder();
  112. this.setAudioRecorderCallback();
  113. // 2.获取录制文件fd赋予avConfig里的url;参考FilePicker文档。
  114. this.createAndSetFd();
  115. // 3.配置录制参数完成准备工作。
  116. await this.avRecorder.prepare(this.avConfig);
  117. // 4.开始录制。
  118. await this.avRecorder.start();
  119. }
  120. // 暂停录制对应的流程。
  121. async pauseRecordingProcess() {
  122. if (this.avRecorder !== undefined && this.avRecorder.state === 'started') { // 仅在started状态下调用pause为合理状态切换。
  123. await this.avRecorder.pause();
  124. }
  125. }
  126. // 恢复录制对应的流程。
  127. async resumeRecordingProcess() {
  128. if (this.avRecorder !== undefined && this.avRecorder.state === 'paused') { // 仅在paused状态下调用resume为合理状态切换。
  129. await this.avRecorder.resume();
  130. }
  131. }
  132. // 停止录制对应的流程。
  133. async stopRecordingProcess() {
  134. if (this.avRecorder !== undefined) {
  135. // 1. 停止录制。
  136. if (this.avRecorder.state === 'started' ||
  137. this.avRecorder.state === 'paused') { // 仅在started或者paused状态下调用stop为合理状态切换。
  138. await this.avRecorder.stop();
  139. }
  140. // 2.重置。
  141. await this.avRecorder.reset();
  142. // 3.释放录制实例。
  143. await this.avRecorder.release();
  144. this.avRecorder = undefined;
  145. // 4.关闭录制文件fd。
  146. await fs.close(this.fileFd);
  147. }
  148. }
  149. async checkPermissions(): Promise<void> {
  150. let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
  151. atManager.requestPermissionsFromUser(this.context, [
  152. 'ohos.permission.MICROPHONE',
  153. ], () => {
  154. console.info('request camera permission');
  155. });
  156. }
  157. aboutToAppear(): void {
  158. this.checkPermissions();
  159. if (this.avRecorder !== undefined) {
  160. if (this.avRecorder.state === 'paused') {
  161. this.resumeRecordingProcess();
  162. }
  163. }
  164. }
  165. aboutToDisappear(): void {
  166. if (this.avRecorder !== undefined) {
  167. if (this.avRecorder.state === 'started') {
  168. this.pauseRecordingProcess();
  169. }
  170. }
  171. }
  172. build() {
  173. Column({ space: 10 }) {
  174. Text(`录制状态:${this.recordState}`)
  175. .fontSize(20)
  176. Button('开始录音')
  177. .onClick(() => {
  178. this.startRecordingProcess();
  179. })
  180. Button('暂停录音')
  181. .onClick(() => {
  182. this.pauseRecordingProcess();
  183. })
  184. Button('恢复录音')
  185. .onClick(() => {
  186. this.resumeRecordingProcess();
  187. })
  188. Button('停止录音')
  189. .onClick(() => {
  190. this.stopRecordingProcess();
  191. })
  192. Text(`长时任务状态:${this.longTaskState}`)
  193. .fontSize(20)
  194. Button('申请长时任务')
  195. .onClick(() => {
  196. // 通过按钮申请长时任务
  197. this.startContinuousTask();
  198. })
  199. Button('取消长时任务')
  200. .onClick(() => {
  201. // 通过按钮取消长时任务
  202. this.stopContinuousTask();
  203. })
  204. }
  205. .width('100%')
  206. .height('100%')
  207. .margin({ top: 30 })
  208. }
  209. }
在 FAQ 中进行搜索
请输入您想要搜索的关键词