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

相关推荐
DB哥讲数据库1 小时前
【最新】MySQL9.7安装教程:图文详解(附MySQL安装包)
android·mysql·adb
IT乐手2 小时前
Android 写本地日志工具类
android·java
非典型Android程序员2 小时前
Launcher学习总结
android
Coffeeee3 小时前
Android功耗优化,哪些代码会让你的机子发热又耗电
android·架构·kotlin
xianrenli384 小时前
Android14自动时区更新
android·学习·framework·aosp14·自动时区
心中有国也有家4 小时前
AtomGit Flutter 鸿蒙客户端:零外部资源的应用打包策略
android·javascript·flutter·华为·harmonyos
恋猫de小郭5 小时前
Fluter 共享内存多线程正在落地,IsolateGroupBound 来了
android·前端·flutter
三少爷的鞋5 小时前
Kotlin 协程的七重境界:从 launch(IO) 到执行模型设计
android
-SOLO-15 小时前
Android Event 日志完全指南
android