【性能优化】安卓性能优化之CPU优化

【性能优化】安卓性能优化之CPU优化

      • CPU优化及常用工具
        • 原理与文章参考
        • 常用ADB
        • 常用原理、监控手段
          • 原理
          • 监控手段
          • 多线程并发解决耗时
          • UI相关
        • 常见场景
        • 排查CPU占用过高
        • 常用系统/开源分析工具
          • AndroidStudio Profiler
          • Systrace
          • Btrace
          • Perfetto
          • TraceView和 Profile
      • ANR相关
      • CPU 优化案例

CPU优化及常用工具

原理与文章参考
  • 编舞者、looper、JankStats方法
常用ADB
含义命令备注
查看CPU状态adb shell top -H -d 1 -p pid -O cpu-O cpu 查看对应在那个核心 ;修改采样间隔为1s
导出当前进程所有线程状态到tombstonedadb shell run-as kill -3 实际上是以一个异常状态导出了,利用了墓碑机制
查看进程的所有线程adb shell "ps -Tgrep pid"
查看进程占用cpu情况adb shell dumpsys cpuinfogrep [进程名]
查看进程内线程占用cpu的情况adb shell top -n 1 -d 0.5grep proc_ id
获取设备cpu信息adb shell cat /proc/cpuinfo或者查看 /sys/devices/system/cpu 目录下的文件夹
常用原理、监控手段
原理
  1. 普通手机默认60帧刷新率,相当于每帧16.6ms
  2. 利用系统预留接口 对每个帧率/handler消息等 进行统计
监控手段
  1. 设置looperPrinter
  2. 字节码插桩检测慢函数(martix dokit)
  3. 编舞者获取frame帧率
  4. jetpack JankStats,获取丢帧信息
多线程并发解决耗时

线程池/数量参考

  1. CPU密集:线程数设置为CPU核心数 + 1
  2. IO密集:线程数设置为CPU核心数 * 2
UI相关
  • 利用<font style="color:rgb(77, 77, 77);">IdelHandler</font>对一些常用view进行预绘制
  • 通过排查布局,减少过度绘制
常见场景
  • 过度绘制
  • 频繁IO
  • 主线程耗时任务
排查CPU占用过高
  • 规范线程命名,定位线程
  • 抓取top数据,查看具体哪个线程占用高
  • cpu指标含义解释
  • 线程各参数详解
常用系统/开源分析工具
AndroidStudio Profiler
  • 抓取CPU火焰图,卡顿/ANR 主要监测主线程,是否会出现耗时操作
Systrace
  • 官方指令参考
  • 官方推荐指令 $ python systrace.py -o mynewtrace.html sched freq idle am wm gfx view binder_driver hal dalvik camera input res
  • 要求环境

python2.7 安装
python six 模块,命令 : pip install six
“No module named win32con” 问题,安装相关: pip install pypiwin32

  • 拉取到信息后用perfetto 打开即可 但是这个主要是针对系统的 对应用开发帮助不大,分析自己应用可以用btrace
Btrace
  • 官方链接
Perfetto
  • 官方-快速开始
  • 工具界面
  • 入门使用
  • 线程状态

TraceView和 Profile
  • traceview官方参考
  • traceview使用
  • 导出的日志分析
  • 使用DDMS查看

新版路径:Sdk\tools\monitor.bat

  • Incl Cpu Time:方法在CPU中执行所有时间(包含其调用的方法所消耗的时间)
  • Excl Cpu Time: 方法在CPU中执行的时间(不包含其调用的方法所消耗的时间)
  • Incl Real Time:方法运行消耗的所有时间(包含子方法)
  • Excl Real Time:方法运行消耗的时间(不包含子方法)
  • Calls + Recur Calls/Total :方法调用、递归次数(重要指标,防止死循环)
  • Cpu Time/Call :该方法平均占用 CPU 的时间(重要指标,可以看出单个方法占用CPU的平均时间,但是要防止在个别调用处出现长时间占用,然后被平均了)
  • Real Time/Call :平均执行时间,包括切换、阻塞的时间(重要指标,可以看出单个方法执行的平均时间值,但是要防止在个别调用处出现长时间调用,然后被平均了)
  • TraceView优势

可以精确埋点

Debug.startMethodTracing("sample");
   ...
Debug.stopMethodTracing();

ANR相关

ANR原理及常见场景
  • 原理

ANR(Application Not Responding)的监测原理本质上是消息机制,设定一个delay消息,超时未被移除则触发ANR。具体逻辑处理都在system server端,包括发送超时消息,移除超时消息,处理超时消息以及ANR弹框展示等;对于app而言,触发ANR的条件是主线程阻塞。

  • 常见场景
  1. Service ANR:前台20s,后台200s;startForeground超时10s
  2. Broadcast ANR:前台10s,后台60s
  3. Input ANR:按键或触摸事件在5s内无响应
  4. ContentProvider ANR:10s,少见
ANR/卡顿检测
  • 通过设置Looper的printer可以检测耗时
  • WatchDog机制,子线程发送消息自增,休眠后检查
  • 参考
  • ANR日志导出
// 安卓21以下有权限可以获取到 anr 日志
private FileObserver fileObserver = null;
void initialize(....){
			// 实例化FileObserver ,监控路径"/data/anr/",监听文件被写入
			fileObserver = new FileObserver("/data/anr/", CLOSE_WRITE) {
            public void onEvent(int event, String path) {
                try {
                    if (path != null) {
                        String filepath = "/data/anr/" + path;
                        // 写入的文件是否有关键字 “trace”
                        if (filepath.contains("trace")) {
                        	// 处理anr异常
                            handleAnr(filepath);
                        }
                    }
                } catch (Exception e) {
                    XCrash.getLogger().e(Util.TAG, "AnrHandler fileObserver onEvent failed", e);
                }
            }
        };

        try {
        	// 启动FileObserver 监控
            fileObserver.startWatching();
        } catch (Exception e) {
            fileObserver = null;
            XCrash.getLogger().e(Util.TAG, "AnrHandler fileObserver startWatching failed", e);
        }
}

private void handleAnr(String filepath) {
	...
	// 读取anr文件 /data/anr/trace*.txt。返回文件内容
	String trace = getTrace(filepath, anrTime.getTime());
	//删除其他的anr异常日志文件
        if (!FileManager.getInstance().maintainAnr()) {
            return;
        }
        //获取  tombstone 的文件头
        String emergency = null;
        try {
            emergency = getEmergency(anrTime, trace);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "AnrHandler getEmergency failed", e);
        }
        // 创建anr异常日志保存文件
         File logFile = null;
        try {
            String logPath = String.format(Locale.US, "%s/%s_%020d_%s__%s%s", logDir, Util.logPrefix, anrTime.getTime() * 1000, appVersion, processName, Util.anrLogSuffix);
            logFile = FileManager.getInstance().createLogFile(logPath);
        } catch (Exception e) {
            XCrash.getLogger().e(Util.TAG, "AnrHandler createLogFile failed", e);
        }
        if (logFile != null){
        	// 根据配置将日志文件头,traces,logcat日志保存在文件中。
        }
}


// 高版本通过AMS获取日志
public class ANRMoniter implements Runnable {

    private final String TAG = "ANRMoniter";

    private HandlerThread handlerThread = new HandlerThread("WatchMainHandler");
    private ILog logImpl;
    private Application app;
    private Handler watchHandler;
    private Handler mainHandler;
    private ScheduleCheckTask scheduleCheckTask;
    private int CHECK_INTERVAL = 5_000;

    public ANRMoniter(Application app, ILog logImpl) {
        this.app = app;
        this.logImpl = logImpl;
        init();
    }

    private void init() {
        handlerThread.start();
        Looper looper = handlerThread.getLooper();
        watchHandler = new Handler(looper);
        mainHandler = new Handler(Looper.getMainLooper());
        scheduleCheckTask = new ScheduleCheckTask();
    }

    public void start() {
        watchHandler.post(this);
    }

    @Override
    public void run() {
        mainHandler.post(scheduleCheckTask);
        long endTime = System.currentTimeMillis() + CHECK_INTERVAL;
        long sleepTime = endTime - System.currentTimeMillis();
        while (sleepTime > 0) {
            try {
                Thread.sleep(sleepTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sleepTime = endTime - System.currentTimeMillis();
        }
        if (scheduleCheckTask.isBlocking()) {
            logImpl.Loge(TAG,"main handler blocking");
            checkRealANR(mainHandler.getLooper().getThread().getStackTrace());
        }
        scheduleCheckTask.reset();
        watchHandler.post(this);
    }

    private void checkRealANR(StackTraceElement[] stack) {
        ThreadPool.getInstance().execute(new Runnable() {
            @Override
            public void run() {
                ActivityManager.ProcessErrorStateInfo processErrorStateInfo = getANRInfo(app);
                if (processErrorStateInfo != null) {
                    logImpl.Loge(TAG,"ANR action");
                    //real ANR
                    RuntimeException e = new RuntimeException(processErrorStateInfo.shortMsg);
                    e.setStackTrace(stack);
                    e.printStackTrace();
                    logImpl.Loge(TAG,e.getMessage());
                }
            }
        });
    }


    private ActivityManager.ProcessErrorStateInfo getANRInfo(Application app) {
        try {
            final long sleepTime = 500L;
            final long loop = 20;
            long times = 0;
            do {
                ActivityManager activityManager = (ActivityManager) app.getSystemService(Context.ACTIVITY_SERVICE);
                List<ActivityManager.ProcessErrorStateInfo> processesInErrorState = activityManager.getProcessesInErrorState();
                if (processesInErrorState != null) {
                    for (ActivityManager.ProcessErrorStateInfo proc : processesInErrorState) {
                        if (proc.condition == ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING) {
                            return proc;
                        }
                    }
                }
                Thread.sleep(sleepTime);
            } while (times++ < loop);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private class ScheduleCheckTask implements Runnable {

        private boolean isBlocking;

        ScheduleCheckTask() {
            isBlocking = true;
        }

        @Override
        public void run() {
            isBlocking = false;
        }

        public boolean isBlocking() {
            return isBlocking;
        }

        public void reset() {
            isBlocking = true;
        }
    }
}


  • 自定义线程WatchDog参考
package com.aispeech.util;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.aispeech.common.ThreadNameUtil;
import com.aispeech.lite.BaseKernel;

import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
* Description: 检测Kernel层 是否阻塞的工具类
* Author: junlong.huang
* CreateTime: 2023/8/21
*/
public class KernelWatchDog {

    private static final String TAG = "KernelWatchDog";
    HandlerThread innerThread;
    Handler innerHandler;
    long timeoutMillis = 2000;

    static final int MSG_INCREMENT = 0x01;

    private static volatile KernelWatchDog mInstance;
    private ConcurrentHashMap<BaseKernel, AtomicInteger> monitorMap;
    private Vector<BaseKernel> removeList;

    public static KernelWatchDog getInstance() {

        if (mInstance == null) {
            synchronized (KernelWatchDog.class) {
                if (mInstance == null) {
                    mInstance = new KernelWatchDog();
                }
            }
        }
        return mInstance;
    }

    private KernelWatchDog() {
        init();
    }

    private void init() {
        monitorMap = new ConcurrentHashMap<>();
        removeList = new Vector<>();
        innerThread = new HandlerThread(ThreadNameUtil.getSimpleThreadName("watchdog-k"));
        innerThread.start();
        innerHandler = new InnerHandler(innerThread.getLooper());
        innerHandler.sendMessage(innerHandler.obtainMessage(MSG_INCREMENT));
    }

    public void addChecker(BaseKernel baseKernel) {
        Log.i(TAG, "addChecker:" + baseKernel.getInnerThreadName());
        monitorMap.put(baseKernel, new AtomicInteger(baseKernel.getTick()));
    }

    public void removeChecker(BaseKernel baseKernel) {
        if (monitorMap.containsKey(baseKernel)) {
            Log.i(TAG, "removeChecker: " + baseKernel.getInnerThreadName());
            monitorMap.remove(baseKernel);
        }
    }

    class InnerHandler extends Handler {

        public InnerHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);

            if (innerHandler == null) return;

            switch (msg.what) {
                case MSG_INCREMENT:
                    if (monitorMap == null || monitorMap.size() == 0) {
                        innerHandler.sendMessageDelayed(innerHandler.obtainMessage(MSG_INCREMENT), timeoutMillis);
                        break;
                    }

                    for (BaseKernel baseKernel : monitorMap.keySet()) {
                        if (baseKernel.getInnerThread() != null &&
                            !baseKernel.getInnerThread().isAlive()) {
                            Log.i(TAG, "Detected thread quit,Add to list to be removed");
                            removeList.add(baseKernel);
                            continue;
                    }
                        AtomicInteger lastTick = monitorMap.get(baseKernel);
                        if (lastTick == null) lastTick = new AtomicInteger(baseKernel.getTick());

                        if (lastTick.get() != baseKernel.getTick()) {
                        Log.w(TAG, "Detected target thread may blocked,export thread stack");
                        Thread innerThread = baseKernel.getInnerThread();
                        if (innerThread != null) {
                        Log.w(TAG, getThreadStack(innerThread.getStackTrace()));
                    }
                    }

                        lastTick.incrementAndGet();
                        baseKernel.tick();
                    }

                        for (BaseKernel baseKernel : removeList) {
                        monitorMap.remove(baseKernel);
                    }
                        removeList.clear();
                        innerHandler.sendMessageDelayed(innerHandler.obtainMessage(MSG_INCREMENT), timeoutMillis);
                        break;
                    }
                    }
                    }


                        public void release() {
                        innerHandler.removeMessages(MSG_INCREMENT);
                        innerThread.quit();
                        monitorMap.clear();
                        removeList.clear();
                    }

                        private String getThreadStack(StackTraceElement[] elements) {
                        StringBuilder stackTraceString = new StringBuilder();
                        for (StackTraceElement element : elements) {
                        stackTraceString.append(element.toString()).append("\n");
                    }

                        return stackTraceString.toString();
                    }
                    }
卡顿检测
  • matrix 字节码插桩,慢函数检测
  • 采样率法,通过一个外置的工作线程Handler,按一段时间采样,如果大部分都是某个方法,则这个方法可能存在风险点
/**
* 按照一定频率采样
* 目标是找到卡顿时刻前后的堆栈,做大致定位,无法做到精准定位
* 原则上采样越高,定位越精准
* 还有,目前只采样了java层的堆栈,c层的需要另外实现,这个后续补充
*/
public class CallstackSampler {
    private static final String TAG = "CallstackSampler";
    private final Thread thread;
    private final Handler mHandler;
    private final long sThreshold = 1000;

    private final Runnable mRunnable = new Runnable() {
        @Override
        public void run() {
            doSample();
            mHandler.postDelayed(this, sThreshold);
        }
    };

    public CallstackSampler(Thread thread) {
        this.thread = thread;
        HandlerThread mWorkThread = new HandlerThread("StackSampler" + thread.getName());
        mWorkThread.start();
        mHandler = new Handler(mWorkThread.getLooper());
    }

    private void doSample() {
        // 采集指定线程当前堆栈信息
        StackTraceElement[] stackTrace = thread.getStackTrace();
        String stackTraceString = Arrays.toString(stackTrace);
        if (!stackTraceString.contains("nativePollOnce")) {
            Log.d(TAG, thread.getName() + " Callstack sample taken at time: " + System.currentTimeMillis() + " " + stackTraceString);
        }
    }

    public void startSampling() {
        mHandler.postDelayed(mRunnable, sThreshold);
    }

    public void stopSampling() {
        mHandler.removeCallbacks(mRunnable);
    }
}
  • 主线程耗时检测:设置一个printer

CPU 优化案例

  1. 线程池复用,减少CPU调度开销
  2. 资源拷贝优化,减少读取IO时间
  3. 线程命名,方便定位问题
  4. 非必要内容,延迟初始化
  5. 初始化任务优先级分配,削峰填谷

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/899648.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Android 图片相识度比较(pHash)

概述 在 Android 中&#xff0c;要比对两张 Bitmap 图片的相似度&#xff0c;常见的方法有基于像素差异、直方图比较、或者使用一些更高级的算法如 SSIM&#xff08;结构相似性&#xff09;和感知哈希&#xff08;pHash&#xff09;。 1. 基于像素的差异比较 可以逐像素比较…

Java基础-注解机制详解

文章目录 注解基础Java内置注解内置注解- Override内置注解 - Deprecated内置注解 - SuppressWarnings 元注解元注解 - Target元注解 - Retention & RetentionTarget元注解 - Documented元注解 - Inherited 注解与反射接口自定义注解 深入理解注解Java8提供了哪些新的注解&…

【React系列三】—React学习历程的分享

一、组件实例核心—Refs 通过定义 ref 属性可以给标签添加标识 字符串形式的Refs 这种形式已经不再推荐使用&#xff0c;官方不建议使用 https://zh-hans.legacy.reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs 回调形式的Refs <script type"te…

nginx精讲

&#x1f939;‍♀️潜意识起点&#xff1a;个人主页 &#x1f399;座右铭&#xff1a;得之坦然&#xff0c;失之淡然。 &#x1f48e;擅长领域&#xff1a;前端 是的&#xff0c;我需要您的&#xff1a; &#x1f9e1;点赞❤️关注&#x1f499;收藏&#x1f49b; 是我持…

计算广告第三版pdf

需要该书pdf版本的同学点赞&#xff0c;私信我&#xff1a;

给哔哩哔哩bilibili电脑版做个手机遥控器

前言 bilibili电脑版可以在电脑屏幕上观看bilibili视频。然而&#xff0c;电脑版的bilibili不能通过手机控制视频翻页和调节音量&#xff0c;这意味着观看视频时需要一直坐在电脑旁边。那么&#xff0c;有没有办法制作一个手机遥控器来控制bilibili电脑版呢&#xff1f; 首先…

WPF MVVM模式实现DataGrid编辑

本文是一个MVVM模式开发的基础教程&#xff0c;完全手写实现&#xff0c;未借助三方框架&#xff0c;适用于初学者 要实现DataGrid的编辑&#xff0c;步骤如下&#xff1a; 1、创建两个窗口&#xff0c;第一个窗口用于显示DataGrid&#xff0c; 布局如下&#xff1a; 这个界…

(3) c++基本代码

main函数 main函数只有可执行程序才需要&#xff0c;如果是动态库等则无需main函数。 main函数标准的写法是 #include <iostream> using namspace std; int main(void) {// 业务代码return 0; } 当然以上代码只是最简单的案例&#xff0c;其中代表main函数值是int&#…

TypeScript中 元组、枚举enum、type

元组&#xff1a; let arr : [string, number] [hello, 3]; let arr2 : [number, boolean?] [44];//问号可选的let arr3 : [number, ...string[]] [34, a, b, c];//任意多个字符串&#xff0c;也可以没有 let arr4 : [number, ...string[]] [34]; 枚举&#xff1a; //e…

【C++进阶】之C++11的简单介绍(一)

&#x1f4c3;博客主页&#xff1a; 小镇敲码人 &#x1f49a;代码仓库&#xff0c;欢迎访问 &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞 &#x1f442;&#x1f3fd;留言 &#x1f60d;收藏 &#x1f30f; 任尔江湖满血骨&#xff0c;我自踏雪寻梅香。 万千浮云遮碧…

【CSS、JS】监听transitionend多次触发的原因

现有代码如下&#xff0c;移入红色内容区域触发动画&#xff0c;监听动画触发&#xff0c;但是每次触发控制台会打印4次 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content…

低功耗4G模组采集温湿度传感器数据~ 超全教程!不会的小伙伴看这篇!

在物联网&#xff08;IoT&#xff09;快速发展的今天&#xff0c;温湿度传感器作为环境监测的重要设备&#xff0c;被广泛应用于农业、工业、智慧城市等领域。本文将详细介绍如何使用低功耗4G模组Air780E采集温湿度传感器数据并实现网页查看&#xff0c;帮助初学者快速上手。 一…

springboot汉妆养生会馆网站-计算机毕业设计源码96229

目录 摘要 Abstract 1 绪论 1.1选题背景 1.2研究意义 1.3系统开发目标 2相关技术介绍 2.1 Java编程语言 2.2 B/S模式 2.3 MySQL简介 2.4 SpringBoot框架 3.汉妆养生会馆网站的设计与实现系统分析 3.1 可行性分析 3.1.1技术可行性分析 3.1.2经济可行性分析 3.1.3…

canvas小蜘蛛

一. 效果 二. 代码 <!--* Author: error: error: git config user.name & please set dead value or install git && error: git config user.email & please set dead value or install git & please set dead value or install git* Date: 2024-10-2…

【视频混剪Demo】FFmpeg的使用【Windows】

#1024程序员节 | 征文# 目录 一、简介 二、音频素材页 2.1 功能描述 &#x1f449; 搜索 &#x1f449; 添加 &#x1f449; 删除 2.2 效果展示 2.3 代码实现 &#x1f449; 前端 &#x1f449; 后端 三、视频素材页 3.1 功能描述 &#x1f449; 搜索 &#x1…

【2024CANN训练营第二季】使用华为云体验AscendC_Sample仓算子运行

环境介绍 NPU&#xff1a;Ascend910B2 环境准备 创建Notebook 华为云选择&#xff1a;【控制台】-【ModelArts】 ModelArts主页选择【开发生产】-【开发空间】-【Notebook】 页面右上角选择【创建Notebook】 选择资源 主要参数 规格&#xff1a;Ascend: 1*ascend-snt…

微搭低代码学习1:不同页面传递值

这个系列逐渐学习低代码平台&#xff0c;补足因为技术栈不足带来的问题&#xff0c;同时借助低代码平台快速搭建成型的系统。 这个博客用来记录一个非常常见的操作&#xff0c;在两个页面/多个页面之间传递值 文章目录 1. 创建页面2. 添加逻辑主动跳转页逻辑设置数据接收页逻辑…

【数据结构与算法】之栈详解

栈&#xff08;Stack&#xff09;是一种基本的线性数据结构&#xff0c;遵循后进先出、先进后出的原则。本文将更详细地介绍栈的概念、特点、Java 实现以及应用场景。 1. 栈概念概述 想象一摞叠放的盘子&#xff0c;你只能从最上面取盘子&#xff0c;放盘子也只能放在最上面。…

html和css实现页面

任务4 html文件 任务5 htm文件 css文件 任务6 html文件 css文件 任务7 html文件 css文件

工业交换机的电源类型

工业交换机的电源通常有以下几种类型和注意事项&#xff1a; 1. 电源类型&#xff1a; 交流电源&#xff08;AC&#xff09;&#xff1a;一些工业交换机使用标准的AC电源&#xff0c;通常是110V或220V。适用于有稳定电源环境的场合。 直流电源&#xff08;DC&#xff09;&#…