当前位置: 首页 > article >正文

【性能优化】安卓性能优化之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. 初始化任务优先级分配,削峰填谷

http://www.kler.cn/news/363131.html

相关文章:

  • 硬件驱动应用
  • 【C++】动态探索:在C++中实现一个简单的反射系统
  • 监控-08-skywalking监控告警
  • shell脚本-函数
  • 内核参数优化记录
  • docker环境安装mongoDB实现平滑迁移实战
  • 【设计模式系列】观察者模式
  • 3D虚拟服装试穿技术:迈向元宇宙与AR电商的新时代
  • 鼠标移入盒子,盒子跟随鼠标移动
  • word,exl,txt转pdf
  • HttpOnly Cookie
  • 产品经理应掌握的 API 接口技术知识
  • 暴雨基于NVIDIA Blackwell的AI服务器开始交付给客户
  • React与TypeScript
  • 分布式环境的分布式锁 - Redlock方案和fencing token方案
  • 手撕数据结构 —— 堆(C语言讲解)
  • Spring Boot:植物健康的智能守护者
  • 【建议收藏】2024年最新Windows系统重装教程:轻松学会,小白必看,赶紧收藏!
  • Unity3D VisionPro 环境扫描 空间理解 网格扫描 AR Mesh
  • Linux - 文件描述符 | 文件系统 | 软硬链接
  • 【实战场景】java.util.LinkedHashMap cannot be cast to XXXX 问题
  • 私域小程序怎么运营,如何引流?(二)
  • 基于MATLAB车道检测与跟踪
  • 《计算机视觉》—— 换脸
  • 背景音乐自动播放createjs
  • keepalived(高可用)+nginx(负载均衡)+web