7. Android <卡顿七>优化动画导致卡顿:一套基于 Matrix 监控、Systrace/Perfetto 标准化排查流程(卡顿实战)

前面讲了卡顿的原理,原因,监控手段和分析手段,各种工具

Android <卡顿一> 深入理解Android 卡顿Choreographer:从VSYNC到掉帧(卡顿原理)

Android <卡顿二> 突破性性能监控方案Matrix---揭开微信亿级用户背后的流畅秘密 (卡顿监控工具集成)

Android <卡顿三> 卡顿性能分析工具 SystemTrace 精准定位 Android 性能瓶颈 (工具使用)

Android <卡顿四> 卡顿性能第二代工具 Perfetto 精准定位 Android 性能瓶颈 (工具使用)

1.过渡绘制动画导致卡顿案例

做360度的旋转后空翻的效果动画

ini 复制代码
package com.evenbus.myapplication.trace;

import android.animation.ValueAnimator;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.LinearInterpolator;

import androidx.appcompat.app.AppCompatActivity;

import com.evenbus.myapplication.R;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * 极端卡顿动画演示Activity - 演示大量视图和复杂变换导致的严重性能问题
 * 注意:此代码专门设计为制造严重卡顿,用于性能测试目的
 */
public class LaggyAnimationActivity extends AppCompatActivity {

    private View animationView;
    private List<View> views = new ArrayList<>();
    private ValueAnimator animator;
    private Random random = new Random();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_laggy_animation);
        setTitle("复杂变换导致的严重卡顿");

        animationView = findViewById(R.id.animation_view);

        ViewGroup container = findViewById(R.id.container);

        // 创建大量视图 - 10000个视图
        for (int i = 0; i < 10000; i++) {
            View view = new View(this);
            view.setLayoutParams(new ViewGroup.LayoutParams(20, 20));

            // 使用随机颜色
            int color = Color.argb(200,
                    random.nextInt(256),
                    random.nextInt(256),
                    random.nextInt(256));
            view.setBackgroundColor(color);

            container.addView(view);
            views.add(view);
        }

        // 启动极端卡顿动画
        startExtremeLaggyAnimation();
    }

    private void startExtremeLaggyAnimation() {
        // 创建复杂的运动路径
        Path path = new Path();
        path.moveTo(0, 0);
        for (int i = 1; i <= 50; i++) {
            path.lineTo(i * 20, (float) (Math.sin(i * 0.2) * 200 + Math.cos(i * 0.1) * 100));
        }

        final PathMeasure pathMeasure = new PathMeasure(path, false);
        final float pathLength = pathMeasure.getLength();

        animator = ValueAnimator.ofFloat(0, 1);
        animator.setDuration(4000);
        animator.setRepeatCount(ValueAnimator.INFINITE);
        animator.setInterpolator(new LinearInterpolator());

        animator.addUpdateListener(animation -> {
            updateExtremeAnimation(animation, pathLength, pathMeasure);
        });

        animator.start();
    }

    private void updateExtremeAnimation(ValueAnimator animation, float pathLength, PathMeasure pathMeasure) {
        float fraction = (float) animation.getAnimatedValue();
        float distance = fraction * pathLength;
        float[] pos = new float[2];
        pathMeasure.getPosTan(distance, pos, null);

        // 更新主动画视图
        animationView.setTranslationX(pos[0]);
        animationView.setTranslationY(pos[1]);

        // 极端复杂的视图变换 - 每帧更新10000个视图的多个属性
        for (int i = 0; i < views.size(); i++) {
            View view = views.get(i);

            // 1. 复杂的波浪形移动
            float waveOffsetX = (float) Math.sin(i * 0.01f + fraction * Math.PI * 4) * 60;
            float waveOffsetY = (float) Math.cos(i * 0.008f + fraction * Math.PI * 3) * 40;
            float circularX = (float) Math.cos(i * 0.005f + fraction * Math.PI * 2) * 50;
            float circularY = (float) Math.sin(i * 0.005f + fraction * Math.PI * 2) * 30;

            view.setTranslationX(pos[0] + i * 4 + waveOffsetX + circularX);
            view.setTranslationY(pos[1] + i * 3 + waveOffsetY + circularY);

            // 2. 复杂的多层旋转
            float baseRotation = fraction * 360 * 6; // 基础快速旋转
            float waveRotation = (float) Math.sin(i * 0.015f + fraction * Math.PI * 5) * 120; // 正弦波动
            float indexRotation = i * 0.3f; // 基于索引的旋转

            view.setRotation(baseRotation + waveRotation + indexRotation);

            // 3. 复杂的脉冲缩放效果
            float pulse = (float) (0.4f + Math.sin(fraction * Math.PI * 8 + i * 0.012f) * 0.3f);
            float scaleVariationX = (float) Math.cos(i * 0.004f + fraction * Math.PI) * 0.15f;
            float scaleVariationY = (float) Math.sin(i * 0.004f + fraction * Math.PI) * 0.15f;

            view.setScaleX(pulse + scaleVariationX);
            view.setScaleY(pulse + scaleVariationY);

            // 4. 复杂的波浪式透明度变化
            float alphaWave1 = (float) Math.sin(fraction * Math.PI * 3 + i * 0.01f) * 0.4f;
            float alphaWave2 = (float) Math.cos(fraction * Math.PI * 2 + i * 0.008f) * 0.3f;
            float baseAlpha = 0.3f;

            float alpha = baseAlpha + alphaWave1 + alphaWave2;
            view.setAlpha(Math.max(0.1f, Math.min(1.0f, alpha)));

            // 5. 动态颜色变化(每50个视图更新一次)
            if (i % 50 == 0) {
                int red = (int) ((Math.sin(fraction * Math.PI * 2 + i * 0.001f) * 0.5f + 0.5f) * 255);
                int green = (int) ((Math.cos(fraction * Math.PI * 3 + i * 0.002f) * 0.5f + 0.5f) * 255);
                int blue = (int) ((Math.sin(fraction * Math.PI * 4 + i * 0.003f) * 0.5f + 0.5f) * 255);

                int color = Color.argb(200, red, green, blue);
                view.setBackgroundColor(color);
            }
        }

        // 每帧执行重型计算来加剧卡顿
        performHeavyFrameCalculations();
    }

    /**
     * 每帧执行的重型数学计算 - 阻塞UI线程
     */
    private void performHeavyFrameCalculations() {
        double result = 0;
        // 复杂的数学计算
        for (int j = 0; j < 15000; j++) {
            // 多种三角函数和数学运算组合
            double angle = j * 0.1;
            result += Math.sin(angle) * Math.cos(angle * 2)
                    * Math.tan(angle * 0.5 + 0.1)
                    * Math.log(j + 2);

            // 添加条件判断增加计算复杂度
            if (j % 500 == 0) {
                result *= Math.sqrt(j + 1);
            }
        }

        // 创建临时对象触发GC
        if (System.currentTimeMillis() % 1000 < 16) { // 大约每帧一次
            List<String> tempObjects = new ArrayList<>();
            for (int k = 0; k < 50; k++) {
                tempObjects.add("calculation_temp_" + k + "_" + result);
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (animator != null) {
            animator.cancel();
        }
        if (views != null) {
            views.clear();
        }
    }

    /**
     * 获取当前视图数量
     */
    public int getViewCount() {
        return views != null ? views.size() : 0;
    }

    /**
     * 停止动画
     */
    public void stopAnimation() {
        if (animator != null) {
            animator.cancel();
        }
    }
}

动画计算复杂度爆炸

arduino 复制代码
for (int i = 0; i < views.size(); i++) { // 10,000次循环
    // 每个视图需要计算:
    // - 4个三角函数计算(移动)
    // - 3个三角函数计算(旋转)
    // - 4个三角函数计算(缩放)
    // - 3个三角函数计算(透明度)
    // - 条件性的颜色计算
}

每帧总共需要执行约 100,000+ 次三角函数计算,这是极其昂贵的操作。

2.用Matrix分析上面案例

2.1 log日志

2.2 json报告

json 复制代码
{
    "machine": "BEST",  // 设备性能等级:BEST表示高性能设备
    "cpu_app": 0,       // 应用CPU使用率:0%表示CPU使用数据未正确采集或应用未占用CPU
    "mem": 11901149184, // 设备总内存:11.9GB,高端设备配置
    "mem_free": 4352848,// 空闲内存:4.25MB,内存使用接近饱和,压力极大
    "scene": "com.evenbus.myapplication.trace.LaggyAnimationActivity",  // 当前活动场景
    "dropLevel": {      // 丢帧等级分布
        "DROPPED_BEST": 0,      // 最佳等级丢帧数:0
        "DROPPED_NORMAL": 0,    // 正常等级丢帧数:0  
        "DROPPED_MIDDLE": 113,  // 中等等级丢帧数:113次 - 严重丢帧
        "DROPPED_HIGH": 0,      // 高等级丢帧数:0
        "DROPPED_FROZEN": 0     // 冻结等级丢帧数:0
    },
    "dropSum": {        // 累计丢帧总数
        "DROPPED_BEST": 0,      // 最佳等级累计丢帧:0
        "DROPPED_NORMAL": 0,    // 正常等级累计丢帧:0
        "DROPPED_MIDDLE": 1089, // 中等等级累计丢帧:1089帧 - 极端严重
        "DROPPED_HIGH": 0,      // 高等级累计丢帧:0
        "DROPPED_FROZEN": 0     // 冻结等级累计丢帧:0
    },
    "fps": 5.60915470123291,    // 帧率:5.6 FPS,严重卡顿(正常应为60FPS)
    "UNKNOWN_DELAY_DURATION": 81198792,  // 未知延迟时长:81.20ms(主要是GC垃圾回收)
    "INPUT_HANDLING_DURATION": 3001,     // 输入处理时长:3ms(正常)
    "ANIMATION_DURATION": 67112243,      // 动画计算时长:67.11ms - 主要瓶颈
    "LAYOUT_MEASURE_DURATION": 146379,   // 布局测量时长:0.15ms(正常)
    "DRAW_DURATION": 2576376,            // 绘制时长:2.58ms(正常)
    "SYNC_DURATION": 16843537,           // 同步时长:16.84ms(较高,属性同步开销)
    "COMMAND_ISSUE_DURATION": 8579170,   // 命令提交时长:8.58ms(较高)
    "SWAP_BUFFERS_DURATION": 344337,     // 缓冲区交换时长:0.34ms(正常)
    "TOTAL_DURATION": 178279885,         // 总帧时长:178.28ms(严重超标,正常应16.67ms)
    "GPU_DURATION": 1327788,             // GPU处理时长:1.33ms(正常)
    "DROP_COUNT": 10,                    // 丢帧事件次数:10次
    "REFRESH_RATE": 60,                  // 屏幕刷新率:60Hz
    "tag": "Trace_FPS",                  // 数据标签:帧率追踪
    "process": "com.evenbus.myapplication",  // 进程名称
    "time": 1756525459898                // 时间戳:2025年1月1日左右
}

2.3 极端严重卡顿 - FPS仅5.6

根本原因分析:

  1. ANIMATION_DURATION: 67.11ms (37.7%) - 动画计算严重超时

    • 在UI线程执行重型数学计算
    • 10000个视图的复杂变换计算
  2. UNKNOWN_DELAY: 81.20ms (45.6%) - 垃圾回收阻塞

    • 频繁创建临时对象触发GC
    • 内存压力极大(空闲仅4.25MB)
  3. SYNC_DURATION: 16.84ms (9.5%) - 属性同步开销

    • 每帧40000+次属性设置

性能指标解读:

指标 实际值 正常值 超标倍数 问题严重度
FPS 5.6 60 10.7倍 🔴 极端严重
总帧时长 178ms 16.7ms 10.7倍 🔴 极端严重
丢帧数 1089帧 0帧 - 🔴 极端严重
空闲内存 4.25MB >100MB 严重不足 🔴 极端严重

2.4 动画导致的卡顿

关键证据:ANIMATION_DURATION异常高

json 复制代码
"ANIMATION_DURATION":67112243  // 67.11ms - 这是决定性证据!

1. ANIMATION_DURATION的含义

  • 这个指标专门表示动画计算和更新的耗时
  • 包括:属性动画、视图变换、数学计算等
  • 正常值应该小于5ms

3.用Systrace分析上面案例

3.1 看帧:Jank frames

3.2 主线程:提示是动画执行中

3.3 火焰图:

4.用Pefetto分析上面案例

4.1 卡顿的原因

4.2 卡顿时间:看绘制的时间和实际花费的时间: Expected Timeline 和 Actual Timeline

4.3 主线程

4.4 火焰图

cpu调度:

5. 总结

Matrix 监控、Systrace/Perfetto 分析动画卡顿的特点

针对动画卡顿的分析对比

5.1 Matrix的表现:

json 复制代码
// 量化指标:
"ANIMATION_DURATION":67112243  // 动画计算耗时67.11ms
"fps":5.6                     // 帧率极低
"DROP_COUNT":10               // 10次丢帧事件

// 但无法显示:
- 具体是哪个动画导致问题
- 动画的属性变化过程
- 渲染管道的具体瓶颈

5.2 Systrace/Perfetto的表现:

diff 复制代码
// 可视化分析:
- 显示Choreographer的VSync信号
- 显示UI线程的onAnimationUpdate调用
- 显示RenderThread的绘制命令

// 帧分析:
- 可以查看每一帧的动画计算时间
- 可以看到属性更新的具体耗时
- 显示GPU渲染动画帧的时间

trace文件和项目案例:

RecyclerView卡顿案例地址: github.com/pengcaihua1...

相关推荐
emma羊羊1 天前
【CSRF】防御
前端·网络安全·csrf
Paddy哥1 天前
html调起exe程序
前端·html
emma羊羊1 天前
【CSRF】跨站请求伪造
前端·网络安全·csrf
折翼的恶魔1 天前
HTML基本标签二:
前端·html
我是日安1 天前
零到一打造 Vue3 响应式系统 Day 16 - 性能处理:LinkPool
前端·vue.js
一树山茶1 天前
uniapp的双token
前端·javascript
正义的大古1 天前
OpenLayers地图交互 -- 章节六:范围交互详解
前端·javascript·vue.js·openlayers
訾博ZiBo1 天前
【文本朗读小工具】- 快速、免费的智能语音合成工具
前端
天蓝色的鱼鱼1 天前
低代码是“未来”还是“骗局”?前端开发者有话说
前端
答案answer1 天前
three.js着色器(Shader)实现数字孪生项目中常见的特效
前端·three.js