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)
}

相关推荐
叶辞树10 分钟前
死锁问题分析
android
TimeFine27 分钟前
让 Codex “用上”Android Studio:JetBrains MCP Server 接入与实践
android
__Witheart__2 小时前
3568 Android 设置首次烧录后 usb otg 口模式
android·adb·rockchip
AFinalStone3 小时前
Android 7系统编译与刷机(三)从编译产物到高通刷机包
android·系统编译
AFinalStone3 小时前
Android 7系统编译与刷机(四)QFIL刷机工具详解
android·系统编译
plainGeekDev3 小时前
JUnit 4 → JUnit 5 + Kotlin
android·java·kotlin
plainGeekDev3 小时前
Mockito → MockK
android·java·kotlin
4 小时前
使用ffmpeg将mp4视频转为m3u8格式
android·ffmpeg·音视频
00后程序员张4 小时前
使用Instruments工具深入分析iOS应用性能与启动时间优化
android·macos·ios·小程序·uni-app·cocoa·iphone
zbmwa5 小时前
jetpack compose 副作用 snapshotFlow
android