0


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

【性能优化】安卓性能优化之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 日志privateFileObserver fileObserver =null;voidinitialize(....){// 实例化FileObserver ,监控路径"/data/anr/",监听文件被写入
            fileObserver =newFileObserver("/data/anr/", CLOSE_WRITE){publicvoidonEvent(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);}}privatevoidhandleAnr(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获取日志publicclassANRMoniterimplementsRunnable{privatefinalString TAG ="ANRMoniter";privateHandlerThread handlerThread =newHandlerThread("WatchMainHandler");privateILog logImpl;privateApplication app;privateHandler watchHandler;privateHandler mainHandler;privateScheduleCheckTask scheduleCheckTask;privateint CHECK_INTERVAL =5_000;publicANRMoniter(Application app,ILog logImpl){this.app = app;this.logImpl = logImpl;init();}privatevoidinit(){
        handlerThread.start();Looper looper = handlerThread.getLooper();
        watchHandler =newHandler(looper);
        mainHandler =newHandler(Looper.getMainLooper());
        scheduleCheckTask =newScheduleCheckTask();}publicvoidstart(){
        watchHandler.post(this);}@Overridepublicvoidrun(){
        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);}privatevoidcheckRealANR(StackTraceElement[] stack){ThreadPool.getInstance().execute(newRunnable(){@Overridepublicvoidrun(){ActivityManager.ProcessErrorStateInfo processErrorStateInfo =getANRInfo(app);if(processErrorStateInfo !=null){logImpl.Loge(TAG,"ANR action");//real ANRRuntimeException e =newRuntimeException(processErrorStateInfo.shortMsg);
                    e.setStackTrace(stack);
                    e.printStackTrace();logImpl.Loge(TAG,e.getMessage());}}});}privateActivityManager.ProcessErrorStateInfogetANRInfo(Application app){try{finallong sleepTime =500L;finallong 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();}returnnull;}privateclassScheduleCheckTaskimplementsRunnable{privateboolean isBlocking;ScheduleCheckTask(){
            isBlocking =true;}@Overridepublicvoidrun(){
            isBlocking =false;}publicbooleanisBlocking(){return isBlocking;}publicvoidreset(){
            isBlocking =true;}}}
  • 自定义线程WatchDog参考
packagecom.aispeech.util;importandroid.os.Handler;importandroid.os.HandlerThread;importandroid.os.Looper;importandroid.os.Message;importandroid.util.Log;importcom.aispeech.common.ThreadNameUtil;importcom.aispeech.lite.BaseKernel;importjava.util.Vector;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.atomic.AtomicInteger;/**
* Description: 检测Kernel层 是否阻塞的工具类
* Author: junlong.huang
* CreateTime: 2023/8/21
*/publicclassKernelWatchDog{privatestaticfinalString TAG ="KernelWatchDog";HandlerThread innerThread;Handler innerHandler;long timeoutMillis =2000;staticfinalint MSG_INCREMENT =0x01;privatestaticvolatileKernelWatchDog mInstance;privateConcurrentHashMap<BaseKernel,AtomicInteger> monitorMap;privateVector<BaseKernel> removeList;publicstaticKernelWatchDoggetInstance(){if(mInstance ==null){synchronized(KernelWatchDog.class){if(mInstance ==null){
                    mInstance =newKernelWatchDog();}}}return mInstance;}privateKernelWatchDog(){init();}privatevoidinit(){
        monitorMap =newConcurrentHashMap<>();
        removeList =newVector<>();
        innerThread =newHandlerThread(ThreadNameUtil.getSimpleThreadName("watchdog-k"));
        innerThread.start();
        innerHandler =newInnerHandler(innerThread.getLooper());
        innerHandler.sendMessage(innerHandler.obtainMessage(MSG_INCREMENT));}publicvoidaddChecker(BaseKernel baseKernel){Log.i(TAG,"addChecker:"+ baseKernel.getInnerThreadName());
        monitorMap.put(baseKernel,newAtomicInteger(baseKernel.getTick()));}publicvoidremoveChecker(BaseKernel baseKernel){if(monitorMap.containsKey(baseKernel)){Log.i(TAG,"removeChecker: "+ baseKernel.getInnerThreadName());
            monitorMap.remove(baseKernel);}}classInnerHandlerextendsHandler{publicInnerHandler(Looper looper){super(looper);}@OverridepublicvoidhandleMessage(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 =newAtomicInteger(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;}}}publicvoidrelease(){
                        innerHandler.removeMessages(MSG_INCREMENT);
                        innerThread.quit();
                        monitorMap.clear();
                        removeList.clear();}privateStringgetThreadStack(StackTraceElement[] elements){StringBuilder stackTraceString =newStringBuilder();for(StackTraceElement element : elements){
                        stackTraceString.append(element.toString()).append("\n");}return stackTraceString.toString();}}
卡顿检测
  • matrix 字节码插桩,慢函数检测
  • 采样率法,通过一个外置的工作线程Handler,按一段时间采样,如果大部分都是某个方法,则这个方法可能存在风险点
/**
* 按照一定频率采样
* 目标是找到卡顿时刻前后的堆栈,做大致定位,无法做到精准定位
* 原则上采样越高,定位越精准
* 还有,目前只采样了java层的堆栈,c层的需要另外实现,这个后续补充
*/publicclassCallstackSampler{privatestaticfinalString TAG ="CallstackSampler";privatefinalThread thread;privatefinalHandler mHandler;privatefinallong sThreshold =1000;privatefinalRunnable mRunnable =newRunnable(){@Overridepublicvoidrun(){doSample();
            mHandler.postDelayed(this, sThreshold);}};publicCallstackSampler(Thread thread){this.thread = thread;HandlerThread mWorkThread =newHandlerThread("StackSampler"+ thread.getName());
        mWorkThread.start();
        mHandler =newHandler(mWorkThread.getLooper());}privatevoiddoSample(){// 采集指定线程当前堆栈信息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);}}publicvoidstartSampling(){
        mHandler.postDelayed(mRunnable, sThreshold);}publicvoidstopSampling(){
        mHandler.removeCallbacks(mRunnable);}}
  • 主线程耗时检测:设置一个printer

CPU 优化案例

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

本文转载自: https://blog.csdn.net/weixin_41802023/article/details/143195025
版权归原作者 疯狂的皮卡 所有, 如有侵权,请联系我们删除。

“【性能优化】安卓性能优化之CPU优化”的评论:

还没有评论