告别应用卡死焦虑:鸿蒙系统冻屏回调能力重磅上线

0 评论 817 浏览 1 收藏 13 分钟

应用使用过程中,点击屏幕后毫无反应是件让人抓狂的事。手指点在屏幕上,界面却像凝固了一样,既不知道应用是在认真处理,还是已经彻底卡死。这种情况有个专门的名字:AppFreeze(应用冻屏)。
对开发者来说,冻屏问题一直是个麻烦事。系统检测到冻屏后生成的日志,常常缺少足够的上下文。看到日志只知道"线程卡住了",却不知道"为什么卡住",定位问题像在雾里摸索。

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

告别应用卡死焦虑:鸿蒙系统冻屏回调能力重磅上线-华为开发者话题 | 华为开发者联盟

引言

       应用使用过程中,点击屏幕后毫无反应是件让人抓狂的事。手指点在屏幕上,界面却像凝固了一样,既不知道应用是在认真处理,还是已经彻底卡死。这种情况有个专门的名字:AppFreeze(应用冻屏)

       对开发者来说,冻屏问题一直是个麻烦事。系统检测到冻屏后生成的日志,常常缺少足够的上下文。看到日志只知道”线程卡住了”,却不知道”为什么卡住”,定位问题像在雾里摸索。

       现在,这个情况有了改善。HarmonyOS SDK 提供了 OH_HiCollie_SetFreezeCallback 冻屏回调接口,开发者可以在系统冻屏时主动写入自定义日志,这些日志会被迁移到 APP_FREEZE 或 APP_HICOLLIE 事件中。配合系统日志一起看,问题定位会清晰很多。

什么是应用冻屏

       先从基础说起。应用冻屏指的是应用持续一段时间不响应用户操作的现象。从系统角度看,当主线程长时间阻塞,无法处理输入事件或完成生命周期回调时,就会触发冻屏检测。

       根据触发场景不同,冻屏分为以下几类:

冻屏类型 说明
主线程卡死(THREAD_BLOCK) 主线程执行超时,分3秒和6秒两个级别
生命周期超时(LIFECYCLE_TIMEOUT) Ability 生命周期回调执行超时
输入事件超时(INPUT_BLOCK) 点击、滑动等输入事件在主线程排队处理超时

       系统会自动检测这些场景并生成故障日志。

冻屏回调:主动注入业务上下文

       传统维测是”事后分析”模式——应用卡死了,日志生成了,开发者再根据日志反推原因。问题在于,系统日志不感知业务语义。同样是主线程卡6秒,可能是网络请求卡住、数据库操作慢、还是计算任务阻塞消息队列,系统日志看不出区别。

       OH_HiCollie_SetFreezeCallback 就是来解决这个问题的。系统在冻屏事件触发时,会回调开发者注册的函数。开发者可以在回调里写入当前业务状态的自定义日志,这些内容最终会出现在 APP_FREEZE 或 APP_HICOLLIE 事件中。

接口声明

void* OH_HiCollie_SetFreezeCallback(OH_HiCollie_FreezeCallback callback)

       系统发生冻屏事件时,会自动调用开发者注册的自定义函数。开发者可以在回调中写入业务状态日志、记录关键变量值、输出链路追踪信息。这些内容会被迁移到 APP_FREEZE 或 APP_HICOLLIE 事件,结合系统日志形成完整的故障信息。

回调函数签名

typedef size_t (*OH_HiCollie_FreezeCallback)(OH_HiCollie_Freeze_Type type, void* buffer, size_t size)

       回调接收三个参数:

参数 类型 说明
type OH_HiCollie_Freeze_Type 冻屏事件类型
buffer void* 系统提供的日志缓冲区,最大64KB
size size_t 缓冲区大小

       返回值表示写入的字节数。

支持的冻屏类型

       API version 24 起支持以下冻屏类型:

类型 说明
OH_THREAD_BLOCK_3S 主线程超时3秒
OH_THREAD_BLOCK_6S 主线程超时6秒
OH_LIFECYCLE_HALF_TIMEOUT Ability生命周期超时一个周期
OH_LIFECYCLE_TIMEOUT Ability生命周期超时两个周期
OH_APP_INPUT_BLOCK 输入事件超时
OH_BUSINESS_THREAD_BLOCK_3S 业务线程卡死3秒
OH_BUSINESS_THREAD_BLOCK_6S 业务线程卡死6秒
OH_BUSINESS_INPUT_BLOCK 业务输入阻塞

快速上手

       来看怎么使用这个接口。

示例代码

#include "hicollie/hicollie.h"

static size_t UserFreezeCallback(OH_HiCollie_Freeze_Type typevoid* buffer, size_t size)
{
    std::string logInfo = "Business state: ";

    switch (type) {
        case OH_THREAD_BLOCK_3S:
            logInfo += "Main thread blocked for 3s";
            break;
        case OH_THREAD_BLOCK_6S:
            logInfo += "Main thread blocked for 6s";
            break;
        case OH_LIFECYCLE_TIMEOUT:
            logInfo += "Ability lifecycle timeout";
            break;
        case OH_APP_INPUT_BLOCK:
            logInfo += "Input event blocked";
            break;
        default:
            logInfo += "Other freeze type";
            break;
    }

    logInfo += ", Current screen: HomePage, User action: button_click";

    char* bufferPtr = static_cast<char*>(buffer);
    int written = snprintf(bufferPtr, size, "%s", logInfo.c_str());

    return written;
}

void InitFreezeCallback()
{
    OH_HiCollie_SetFreezeCallback(UserFreezeCallback);
}

使用步骤

1. 引入头文件:在 C++ 代码中包含 #include “hicollie/hicollie.h”

2. 实现回调函数:根据业务需求,在回调中写入有助于定位问题的日志信息

3. 注册回调:在应用启动阶段调用 OH_HiCollie_SetFreezeCallback 注册回调函数

       4. 订阅事件:通过 HiAppEvent 接口订阅 APP_FREEZE 或 APP_HICOLLIE 事件,获取包含自定义日志的完整故障信息

查看日志

冻屏事件发生后,订阅的事件中会包含 external_callback_log 字段,这个字段就是在回调里写入的自定义日志。结合系统的线程堆栈、Binder调用链、内存信息等,可以更快定位问题根因。

应用场景举例

场景一:网络请求超时追踪

static size_t NetworkFreezeCallback(OH_HiCollie_Freeze_Type typevoid* buffer, size_t size)
{
    std::string log;
    log += "Network request info: ";
    log += "URL=" + g_currentRequestUrl + ", ";
    log += "Timeout=" + std::to_string(g_requestTimeout) + "ms, ";
    log += "Retry count=" + std::to_string(g_retryCount);

    char* buf = static_cast<char*>(buffer);
    return snprintf(buf, size, "%s", log.c_str());
}

场景二:数据库操作日志

static size_t DatabaseFreezeCallback(OH_HiCollie_Freeze_Type typevoid* buffer, size_t size)
{
    std::string log;
    log += "Database operation: ";
    log += "Table=" + g_currentTable + ", ";
    log += "Operation=" + g_currentOperation + ", ";
    log += "Records affected=" + std::to_string(g_recordsAffected);

    char* buf = static_cast<char*>(buffer);
    return snprintf(buf, size, "%s", log.c_str());
}

场景三:页面状态记录

static size_t PageFreezeCallback(OH_HiCollie_Freeze_Type typevoid* buffer, size_t size)
{
    std::string log;
    log += "Page context: ";
    log += "Current page=" + g_currentPageName + ", ";
    log += "Navigation stack depth=" + std::to_string(g_navStackSize) + ", ";
    log += "Last user interaction=" + g_lastInteraction;

    char* buf = static_cast<char*>(buffer);
    return snprintf(buf, size, "%s", log.c_str());
}

最佳实践

       使用冻屏回调时有几点建议:

1. 写入关键业务上下文

       避免写入无意义的固定字符串,尽量记录与当前业务状态相关的信息:

   • 正在处理的业务类型

   • 关键变量的当前值

   • 用户最近的操作行为

2. 控制日志长度

       缓冲区最大支持64KB,但建议日志控制在1KB以内,超过之后大概率会导致崩库。内容要简洁明确。

3. 避免在回调中执行耗时操作

       回调执行时系统状态可能已不稳定,避免在回调中进行I/O操作或获取锁,以免加重系统负担。

4. 结合多种维测手段

       冻屏回调是维测工具箱的一员,建议与线程卡死检测(OH_HiCollie_Init_StuckDetection)、卡顿检测(OH_HiCollie_Init_JankDetection)等能力结合使用。

总结

       OH_HiCollie_SetFreezeCallback 冻屏回调接口让开发者可以主动注入业务语义,不再被动等待系统日志。配合原有的维测能力使用,能大幅提升故障定位效率。

       如果应用经常出现卡死问题,或者需要快速响应线上故障,这个接口值得关注。

—————————————————————————————————–

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

🔗 社区DFX专题文章:  https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001

【扫码加入 HarmonyOS DFX 技术交流群】

本文由 @华为开发者联盟 授权发布于人人都是产品经理。未经作者许可,禁止转载

题图来自Unsplash,基于CC0协议

该文观点仅代表作者本人,人人都是产品经理平台仅提供信息存储空间服务

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!