HarmonyOS鸿蒙Next中APP_INPUT_BLOCK响应超时问题

HarmonyOS鸿蒙Next中APP_INPUT_BLOCK响应超时问题 截屏后,快速点击我们的按钮多次,就会遇到这个错误,APP_INPUT_BLOCK,

看了下文档只说出了原因,有什么解决办法吗,或者有什么工具,能抓到具体的crash~

4 回复

进程崩溃日志是一种故障日志,与应用无响应日志、JS应用崩溃等都由FaultLogger模块管理,可通过以下方式获取:

  • 方式一:通过DevEco Studio获取日志:DevEco Studio会收集设备“/data/log/faultlog/faultlogger/”路径下的进程崩溃故障日志并归档在FaultLog下,获取日志的方法可参考DevEco Studio使用指南-FaultLog
  • 方式二:通过hiAppEvent接口订阅:hiAppEvent 提供了故障订阅接口,可以订阅各类故障打点,详见HiAppEvent介绍。
    参考文档:获取崩溃日志FaultLog

APP_INPUT_BLOCK响应超时可参考以下

  1. 可以查看AppFreeze日志,应用发生了APP_INPUT_BLOCK异常,搜索关键字TID:进程ID(PID),可以获取应用栈信息。
  2. 由于稳定性测试中会进行频繁的截屏操作,所以需要排查是否截屏事件过多,执行时间过长导致AppFreeze。
  3. 搜索关键字"catcher cmd: hilog -z 1000 -P ",查看最近1000条流水日志
  4. 从流水日志可以看出应用在卡死阶段在频繁执行业务,导致用户输入得不到响应

【修改建议】

频繁执行业务导致输入无响应可以降低业务执行频次,或将其放到子线程执行,参考文档使用多线程能力

优化耗时,当主线程中遇到一些难以避免的耗时操作时,例如同步网络请求、大文件读写、复杂计算、音频处理、数据传输等,可以从以下角度进行性能优化:

  • 避免使用耗时接口,同一接口的不同使用方式存在性能差异,选择耗时更少,性能更优的接口。
  • 使用多线程能力,可以使用系统自带的Taskpool多线程能力,将耗时任务交由子线程执行,避免主线程的长时间阻塞。

控制执行次数,当遇到用户高频事件输入时,根据业务场景,可以选择节流、防抖或限制次数的方式进行优化:

  • 限制执行次数,通过计数变量限制业务执行次数,例如限制弹窗仅弹出一个。
  • 节流,固定时间间隔内只允许触发一次操作,忽略中间频繁触发。
  • 防抖,事件停止触发后延迟一段时间再执行操作,仅响应最后一次触发。
import { window } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

/**
 * 防抖:在一段时间内函数被多次触发,防抖让函数在一段时间后最终只执行一次
 * 返回的函数不需要传参,直接使用this访问外部数据即可
 * @param fn 传入频繁触发的函数
 * @param delay 延迟时间
 * @returns
 */
export function debounce(fn: Function, delay: number = 300) {
  let timer: number = 0;
  return () => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      fn();
    }, delay);
  };
}

/**
 * 节流:在规定的时间内,只执行一次
 * @param callBack
 * @param delay
 * @param immediately 是否立即执行
 * @returns
 */
export function throttle(callBack: Function, delay: number = 300, immediately: boolean = true) {
  let timer = 0;
  if (immediately) {
    return () => {
      if (Date.now() - timer >= delay) {
        callBack();
        timer = Date.now();
      }
    };
  } else {
    return () => {
      if (timer) {
        return;
      }
      timer = setTimeout(() => {
        callBack();
        timer = 0;
      }, delay);
    };
  }
}

@Entry
@Component
export struct ScreenShotPage {
  @State remind: string = '未截图';
  private uiContext = this.getUIContext();
  private promptAction = this.uiContext.getPromptAction();
  private customDialogComponentId: number = 0;
  private isShowing = false;
  dfn = debounce(() => {this.simulateBlockingOperation();});
  tfn = debounce(() => {this.simulateBlockingOperation();});
  @State count: number = 0;
  @State consoleText: string = '';

  @Builder
  screenShotDialog() {
    Column({ space: 15 }) {
      Text('应用内截图事件弹窗')
        .fontSize(20)
      Button('关闭').onClick(() => {
        try {
          this.promptAction.closeCustomDialog(this.customDialogComponentId);
        } catch (error) {
          console.error(`closeCustomDialog error code is ${error.code}, message is ${error.message}`);
        }
      })
    }.padding(15)
  }

  aboutToAppear(): void {
    window.getLastWindow(this.getUIContext().getHostContext(), (err: BusinessError, data: window.Window) => {
      if (err) {
        console.error(`GetLastWindow failed, error code: ${err.code}, error message: ${err.message}`);
      }
      data.on('screenshot', () => {
        if (data.isFocused()) {
          this.remind = '应用内截图';
          this.screenShotEventFunc();
        } else {
          this.remind = '非应用内截图';
        }
      });
    });
  }

  screenShotEventFunc() {
    if (this.isShowing) {
      return;
    }
    this.promptAction.openCustomDialog({
      builder: () => this.screenShotDialog(),
      onDidDisappear: () => {
        this.isShowing = false;
        console.log('onDidDisappear: screenShotDialog');
      }
    }).then((dialogId: number) => {
      this.customDialogComponentId = dialogId;
      this.isShowing = true;
      console.info('openCustomDialog success');
    }).catch((error: BusinessError) => {
      console.error(`openCustomDialog error code is ${error.code}, message is ${error.message}`);
    });
  }

  // 模拟耗时操作的函数
  simulateBlockingOperation(duration: number = 1000) {
    this.count ++;
    this.consoleText += `\n${this.count}.开始执行`;
    const startTime = new Date().getTime();
    while (new Date().getTime() - startTime < duration) {
      // 空循环模拟耗时计算
    }
    this.consoleText += '-结束执行';
  }

  build() {
    Column({ space: 15 }) {
      Column() {
        Text('请多次截屏进行测试')
          .fontSize(20)
        Text(this.remind)
      }
      Button('长耗时任务1s').onClick(() => {
        this.simulateBlockingOperation();
      })
      Button('节流方式执行耗时任务').onClick(() => {
        this.tfn();
      })
      Button('防抖方式执行耗时任务').onClick(() => {
        this.dfn();
      })
      Text(`执行次数:${this.count}`)
      Text(this.consoleText)
    }
    .height('100%')
    .width('100%')
    .margin({ top: 30 })
  }
}

【背景知识】

  • 用户在使用应用时,如果出现点击无反应或应用无响应等情况,并且持续时间超过一定限制,就会被定义为应用无响应,详情参考AppFreeze(应用冻屏)检测
  • AppFreeze日志规格说明可以参考日志规格

更多关于HarmonyOS鸿蒙Next中APP_INPUT_BLOCK响应超时问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


加了个节流函数就好了,感谢!但是很神奇,只有在截屏之后,狂点某个按钮就会触发这个崩溃,这个按钮会进行网络请求,所以导致了无响应,然后崩溃,

在HarmonyOS Next中,APP_INPUT_BLOCK响应超时通常由于主线程阻塞导致。检查是否存在耗时操作在主线程执行,如复杂计算或同步I/O。使用异步任务或Worker线程处理此类任务。确保UI事件处理逻辑高效,避免长时间占用事件循环。监控应用性能,定位具体阻塞点进行优化。

在HarmonyOS Next中,APP_INPUT_BLOCK错误通常由主线程阻塞导致用户输入响应超时(默认5秒)。以下是排查和解决方案:

  1. 使用DevEco Studio的Profiler工具

    • 通过Time Profiler监控主线程执行情况,识别阻塞点(如耗时循环、同步I/O操作)。
    • 使用System Trace分析输入事件响应链路,定位延迟具体位置。
  2. 常见修复方法

    • 将耗时操作(如文件读写、网络请求、复杂计算)移至Worker线程。
    • 避免在主线程频繁执行大型循环或同步锁竞争。
    • 检查截屏后触发的回调逻辑,确保未包含阻塞性代码。
  3. 日志抓取

    • 通过hdc shell hilog捕获实时日志,过滤关键词APP_INPUT_BLOCK获取堆栈信息。
    • 在DevEco Studio中开启“Full Stack Trace”获取更详细阻塞调用链。

建议优先使用Profiler定位具体代码瓶颈,结合异步化改造解决阻塞问题。

回到顶部