app性能日志工具

kotlin 复制代码
xxFragment.kt:

  PerformancePointUtil.getInstance().startUiLog(currentPageName)
  
  ....
  
 
  PerformancePointUtil.getInstance().endUiLog(currentPageName)
  
  
  
  
xxUtils.kt:

   private Logger mLogger;
   .....
    private String getUiMsg(boolean isStart, String pageName) {
        final String flag = isStart ? START : END;
        final StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(mProjectName)
                .append(UNDER_LINE)
                .append(UI)
                .append(UNDER_LINE)
                .append(pageName)
                .append(UNDER_LINE)
                .append(flag)
                .append(COLON)
                .append(System.currentTimeMillis());
        return stringBuilder.toString();
    }
	
	
    /***
     * when UI on create start output log
     * @param pageName UI page name,now is class name.
     */
    public void startUiLog(@NonNull String pageName) {
        logMsg(getUiMsg(true, pageName));
    }

    /***
     * when UI data set finished and draw completed output log.
     * @param pageName current UI page name,now is class simple name.
     */
    public void endUiLog(@NonNull String pageName) {
        logMsg(getUiMsg(false, pageName));
    }
	
	    public void logMsg(String msg) {
        if (mLogger == null){
            return;
        }

        if (mLogSwitch) {
            mLogger.log(LOG_LEVEL, msg);
        } else {
            mLogger.log(LOG_LEVEL, LOG_FAILED_MSG);
        }
    }
	

分析

1. endUiLog 目前具体应该写在哪个回调方法里?

在您的 xxFragment.kt 架构中,endUiLog 必须精准挂载在 LoadState.LoadSuccess 分支触发的 UI 渲染完成时。

然而,仅仅写在 updateUI(state.data) 后面是不够精准的。因为 RecyclerView.Adapter.notifyDataSetChanged() 或 setNewList() 只是把数据提交给了内存,此时 CPU 还在计算布局,GPU 还没有完成首帧的绘制。

💻 完美对齐"首帧渲染完成"的最佳写法

利用 View.post 机制,将 endUiLog 放入下一帧的队列中,确保日志输出时 RecyclerView 已经完成了在屏幕上的第一帧绘制:

kotlin 复制代码
private fun handleUiState(state: ExploreUiState) {
    when (state.loadState) {
        is LoadState.Loading -> {
            exploreAdapter?.setNewList(emptyList())
            showLoading()
            // 页面生命周期起点已经由外层触发,此处可执行通用动作
            onUiStart(javaClass.simpleName) 
        }

        is LoadState.LoadSuccess -> {
            setGridLayoutManager(state.bannerWidgetStyle)
            updateUI(state.data)
            
            // 🎯 核心:在视图队列最后执行,确保 RecyclerView 渲染绘制完毕
            binding?.exploreRvList?.post {
                PerformancePointUtil.getInstance().endUiLog(javaClass.simpleName)
            }
        }
        
        // ... 其他分支
    }
}

2. 如果接口请求失败(LoadError),是否也需要调用 endUiLog?

结论:不应该调用 endUiLog,而应该调用代表异常或中断的独立埋点函数。

❌ 为什么不能在 LoadError 中调用 endUiLog?

如果请求失败时也调用 endUiLog,后台系统通过 END时间戳 - START时间戳 计算出的耗时,就会把"接口报错卡顿的时间"误认为"页面成功加载的时间"。这会导致线上真实的页面渲染耗时数据被严重污染,无法筛选出真正因为 UI 卡顿导致的性能问题。

🛠️ 推荐的异常处理策略(3种方案)

根据车机系统(Volvo 级别)的严谨监控规范,面对 LoadError 应该采用以下策略:

方案 具体做法 适用场景
方案 A:单独定义取消/错误标签(最推荐) 在 PerformancePointUtil 中扩展一个 cancelUiLog 或 errorUiLog 方法,输出 _ERROR 或 _CANCEL 标记。 需要统计接口失败率和异常等待耗时。
方案 B:直接舍弃当前耗时 在 LoadError 中只处理异常 UI 展示(showExceptionLayout),不调用任何 end 埋点。 后台分析工具会自动过滤掉只有 START 没有 END 的死链数据。
方案 C:上报错误码(Error Code) 在日志拼装时追加错误码:项目_UI_页面_ERROR:时间戳:错误码。 便于自动化脚本一眼看出是因为网络超时的白屏,还是数据解析失败的白屏。

💻 方案 A 在 handleUiState 中的代码演进示例

kotlin 复制代码
is LoadState.LoadError -> {
    showExceptionLayout(state.loadState.exception)
    onUiReady()
    
    // ⚠️ 报错时不调 endUiLog,而是调专门的异常埋点
    PerformancePointUtil.getInstance().errorUiLog(javaClass.simpleName)
}

相关推荐
LinXunFeng5 小时前
拿竞品 App 一扫,技术栈全暴露了
android·flutter·开源
Gracker7 小时前
AIW:从系统机制到性能排查,一本可以对照源码的 Android 电子书
android
三少爷的鞋8 小时前
代码越来越便宜,判断力越来越贵:AI 时代的 Android 架构与协程避坑指南
android
Gracker9 小时前
SmartPerfetto 1.19.0:换过分析引擎之后,这次更新了什么
android
HQL_seven13 小时前
【Android】BroadcastReceiver 详解
android
_祝你今天愉快14 小时前
Paint Cavans高级绘制:[二] Paint-滤镜,XFERMODE
android
小乖写代码14 小时前
字段获取代码(Android) · Android Input Examples
android·whatsapp·账号注册
booboosui15 小时前
停一个活只有杀进程:界面上少了「说点要求」
android
传奇开心果编程17 小时前
【Flutter踩坑排雷】第28课:平台目录缺失与修复坑|`flutter create` 的“重建”与“覆盖”边界
android·flutter·ios·移动开发·跨平台·dart·声明式ui
longxibo1 天前
【AIBIZ低代码平台:能长出业务系统的平台】
android·低代码·rxjava