前言: APP卡顿对用户体验有很大的影响,而我们如何成为一个Android开发的性能优化高手,今天我们来讲一下性能优化卡顿优化。开启你的优化大师之路。学习资料地址,请私信我发送核心笔记或手册,直接获取哟!卡顿现象:如何定义发生了卡顿现象:如果App的FPS平均值小于30,最小值小于24,即表明应用发生了卡顿。复制代码线下很难复现,与发生场景强相关(所以需要我们去做卡顿监控,收集现场信息)CPU相关知识现在最新的主流机型都使用了多级能效的CPU架构(即多核分层架构)从CPU到GPU再到AI芯片NPU,随着手机CPU整体性能的飞跃,我们可以充分利用移动端的计算能力来降低高昂的服务器成本;评价一个CPU的性能,需要看主频、核心数、缓存等参数,具体表现出来的是计算能力和指令执行能力,也就是每秒执行的浮点计算数和每秒执行的指令数;造成卡顿的原因很多(涉及到代码、内存、绘制、IO、CPU等),最终都反映到CPU时间上,CPU时间可以分为用户时间和系统时间;用户时间:执行用户态应用程序代码所消耗的时间;系统时间:执行内核态系统调用所消耗的时间,包括IO、锁、中断以及其他系统调用的时间;常用命令:adbshell获取CPU核心数catsysdevicessystemcpupossible获取第一个CPU的最大频率catsysdevicessystemcpucpu0cpufreqcpuinfomaxfreq获取第二个CPU的最小频率catsysdevicessystemcpucpu1cpufreqcpuinfominfreq整个系统的CPU使用情况catproc〔pid〕stattop命令可以帮助我们查看哪个进程是CPU的消耗大户;vmstat命令可以实时动态监视操作系统的虚拟内存和CPU活动;strace命令可以跟踪某个进程中所有的系统调用vmstat命令或者proc〔pid〕schedstat文件来查看CPU上下文切换次数proc〔pid〕stat进程CPU使用情况proc〔pid〕task〔tid〕stat进程下面各个线程的CPU使用情况proc〔pid〕sched进程CPU调度相关procloadavg系统平均负载,uptime命令对应文件复制代码否存在高优先级的线程空等低优先级线程,例如主线程等待某个后台线程的锁CPU相关的三类问题:CPU资源冗余使用:算法效率低没使用缓存计算时使用的基本类型不对(如int足够却用long,运算压力多出4倍)CPU资源争抢:抢主线程的CPU资源抢音视频的CPU资源,音视频编解码本身会消耗大量的CPU资源,并且其对于解码的速度是有硬性要求的,如果达不到就可能产生播放流畅度的问题;采取两种方式去优化:尽量排除非核心业务的消耗。优化自身的性能消耗,把CPU负载转化为GPU负载,如使用renderscript来处理视频中的影像信息。大家平等,互相抢(三个和尚没水喝)CPU资源利用率低:有磁盘和网络IO,还有锁操作、sleep等等,对于锁的优化,通常是尽可能地缩减锁的范围;卡顿排查工具Traceview和systrace都是我们比较熟悉的排查卡顿的工具,从实现上这些工具分为两个流派instrument:获取一段时间内所有函数的调用过程,可以通过分析这段时间内的函数调用流程,再进一步分析待优化的点;sample:有选择性或者采用抽样的方式观察某些函数调用过程,可以通过这些有限的信息推测出流程中的可疑点,然后再继续细化分析;Traceview类型:instrument;原理:利用AndroidRuntime函数调用的event事件,将函数运行的耗时和调用关系写入trace文件中;特点:用来查看整个过程有哪些函数调用,但工具本身带来的性能开销过大,有时无法反映真实的情况;Android5。0之后,新增了startMethodTracingSampling方法,可以使用基于样本的方式进行分析,以减少分析对运行时的性能影响。新增了sample类型后,就需要我们在开销和信息丰富度之间做好权衡。无论是哪种的Traceview对release包支持的都不太好,例如无法反混淆Nanoscope类型:instrument原理:直接修改Android虚拟机源码,在ArtMethod执行入口和执行结束位置增加埋点代码,将所有的信息先写到内存,等到trace结束后才统一生成结果文件;特点:性能损耗较小,适合做启动耗时的自动化分析,但是trace结束生成结果文件这一步需要的时间比较长。另一方面它可以支持分析任意一个应用,可用于做竞品分析。但是它也有不少限制:需要自己刷ROM,并且当前只支持Nexus6P,或者采用其提供的x86架构的模拟器;默认只支持主线程采集,其他线程需要〔代码手动设置〕考虑到内存大小的限制,每个线程的内存数组只能支持大约20秒左右的时间段。我们可以每天定期去跑自动化启动测试,查看是否存在新增的耗时点systrace类型:sampleAndroid4。1新增的性能分析工具。我通常使用systrace跟踪系统的IO操作、CPU负载、Surface渲染、GC等事件。特点:只能监控特定系统调用的耗时情况,性能开销低,但不支持应用程序代码的耗时分析;但是系统预留了Trace。beginSection接口来监听应用程序的调用耗时,我们可以通过编译时给每个函数插桩的方式来实现在systrace基础上增加应用程序耗时的监控Simpleperf类型:sample如果我们想分析Native函数的调用,上面的三个工具都不能满足这个需求,Android5。0新增了Simpleperf性能分析工具利用CPU的性能监控单元(PMU)提供的硬件perf事件,可以看到所有的Native代码的耗时,同时封装了systrace的监控功能AndroidStudio3。2也在Profiler中直接支持Simpleperf汇总一下如果需要分析Native代码的耗时,可以选择Simpleperf;如果想分析系统调用,可以选择systrace;如果想分析整个程序执行流程的耗时,可以选择Traceview或者插桩版本的systrace。可视化方法AndroidStudio3。2的Profiler中直接集成了几种性能分析工具:SampleJavaMethods的功能类似于Traceview的sample类型TraceJavaMethods的功能类似于Traceview的instrument类型TraceSystemCalls的功能类似于systraceSampleNative(APILevel26)的功能类似于Simpleperf虽然不够全面和强大,但大大降低了开发者的使用门槛分析结果的展示方式:这些分析工具都支持了CallChart和FlameChart两种展示方式CallChart是Traceview和systrace默认使用的展示方式,按照应用程序的函数执行顺序来展示,适合用于分析整个流程的调用FlameChart也就是大名鼎鼎的火焰图,以一个全局的视野来看待一段时间的调用分布,时间和空间两个维度上的信息融合在一张图上StrictMode是Android2。3引入的一个工具类,它被称为严苛模式,是Android提供的一种运行时检测机制,可以用来帮助开发人员用来检测代码中一些不规范的问题。主要用来检测两大问题:线程策略:检测内容是一些自定义的耗时调用、磁盘读取操作以及网络请求等;虚拟机策略:检测内容包括Activity泄漏,SqLite对象泄漏,检测实例数量;使用:在Application的onCreate方法中对StrictMode进行统一配置,在日志输出栏中注意使用StrictMode关键字过滤出对应的log即可privatevoidinitStrictMode(){ 1、设置Debug标志位,仅仅在线下环境才使用StrictMode if(BuildConfig。isDebug){ 2、设置线程策略 StrictMode。setThreadPolicy(newStrictMode。ThreadPolicy。Builder() 。detectCustomSlowCalls()API等级11,使用StrictMode。noteSlowCode 。detectDiskReads() 。detectDiskWrites() 。detectNetwork()or。detectAll()foralldetectableproblems 。penaltyLog()在Logcat中打印违规异常信息 。penaltyDialog()也可以直接跳出警报dialog 。penaltyDeath()或者直接崩溃 。build()); 3、设置虚拟机策略 StrictMode。setVmPolicy(newStrictMode。VmPolicy。Builder() 。detectLeakedSqlLiteObjects() 给Person对象的实例数量限制为1 。setClassInstanceLimit(Person。class,1) 。detectLeakedClosableObjects()API等级11 。penaltyLog() 。build()); } } 复制代码卡顿监控消息队列方式1:通过替换Looper的Printer实现;首先,我们需要使用Looper。getMainLooper()。setMessageLogging()去设置我们自己的Printer实现类去打印输出logging。这样,在每个message执行的之前和之后都会调用我们设置的这个Printer实现类。如果我们匹配到Dispatchingto之后,我们就可以执行一行代码:也就是在指定的时间阈值之后,我们在子线程去执行一个任务,这个任务就是去获取当前主线程的堆栈信息以及当前的一些场景信息,比如:内存大小、电脑、网络状态等。如果在指定的阈值之内匹配到了Finishedto,那么说明message就被执行完成了,则表明此时没有产生我们认为的卡顿效果,那我们就可以将这个子线程任务取消掉方式2:通过一个监控线程,每隔1秒向主线程消息队列的头部插入一条空消息;如果我们需要监控3秒卡顿,那在第4次轮询中头部消息依然没有被消费的话,就可以确定主线程出现了一次3秒以上的卡顿;插装基于消息队列的卡顿监控并不准确,正在运行的函数有可能并不是真正耗时的函数;假设一个消息循环里面顺序执行了A、B、C三个函数,当整个消息执行超过3秒时,因为函数A和B已经执行完毕, 我们只能得到的正在执行的函数C的堆栈,事实上它可能并不耗时,不过对于线上大数据来说,因为函数A和B相对 比较耗时,所以抓取到它们的概率会更大一些,通过后台聚合后捕获到函数A和B的卡顿日志会更多一些; 如果跟Traceview一样,可以拿到整个卡顿过程所有运行函数的耗时,就可以明确知道其实函数A和B才是造成卡顿的主要原因; 那能否利用AndroidRuntime函数调用的回调事件,做一个自定义的Traceview呢? 复制代码需要使用InlineHook技术。我们可以实现类似Nanoscope先写内存的方案;需要注意两点:避免方法数暴增过滤简单的函数实现参考:微信的Matrix虽然插桩方案对性能的影响总体还可以接受,但只会在灰度包使用;短板:只能监控应用内自身的函数耗时,无法监控系统的函数调用,整个堆栈看起来好像缺失了一部分Profilo参考了JVM的AsyncGetCallTrace思路,然后适配AndroidRuntime的实现Facebook开源库,它收集了各大方案的优点集成atrace功能ftrace所有性能埋点数据都会通过tracemarker文件写入内核缓冲区,Profilo通过 PLTHook拦截了写入操作,选择部分关心的事件做分析。这样所有systrace的探针我们 都可以拿到,例如四大组件生命周期、锁等待时间、类校验、GC时间等。 复制代码快速获取Java堆栈获取堆栈的代价是巨大的,它要暂停主线程的运行,Profilo的实现非常精妙,它实现类似Native崩溃捕捉的方式快速获取Java堆栈,通过间隔发送SIGPROF信号;AndroidPerformanceMonitor一个非侵入式的性能监控组件,可以通过通知的形式弹出卡顿信息。优势:非侵入式,方便精准,能够定位到代码的某一行代码。使用:1。build。gradle下配置它的依赖 apicom。github。markzhai:blockcanaryandroid:1。5。0 仅在debug包启用BlockCanary进行卡顿监控和提示的话,可以这么用 debugApicom。github。markzhai:blockcanaryandroid:1。5。0 2。Application的onCreate方法中开启卡顿监控 BlockCanary。install(this,newAppBlockCanaryContext())。start(); 3。继承BlockCanaryContext类去实现自己的监控配置上下文类 Author:LiuJinYang CreateDate:2020129 publicclassAppBlockCanaryContextextendsBlockCanaryContext{ 实现各种上下文,包括应用标识符,用户uid,网络类型,卡顿判断阙值,Log保存位置等等 提供应用的标识符 return标识符能够在安装的时候被指定,建议为versionflavor。 Override publicStringprovideQualifier(){ returnunknown; } 提供用户uid,以便在上报时能够将对应的 用户信息上报至服务器 returnuserid Override publicStringprovideUid(){ returnuid; } 提供当前的网络类型 return{linkString}like2G,3G,4G,wifi,etc。 Override publicStringprovideNetworkType(){ returnunknown; } 配置监控的时间区间,超过这个时间区间,BlockCanary将会停止,use with{codeBlockCanary}sisMonitorDurationEnd returnmonitorlastduration(inhour) Override publicintprovideMonitorDuration(){ return1; } 指定判定为卡顿的阈值threshold(inmillis), 你可以根据不同设备的性能去指定不同的阈值 returnthresholdinmills Override publicintprovideBlockThreshold(){ return1000; } 设置线程堆栈dump的间隔,当阻塞发生的时候使用,BlockCanary将会根据 当前的循环周期在主线程去dump堆栈信息 由于依赖于Looper的实现机制,真实的dump周期 将会比设定的dump间隔要长(尤其是当CPU很繁忙的时候)。 returndumpinterval(inmillis) Override publicintprovideDumpInterval(){ returnprovideBlockThreshold(); } 保存log的路径,比如blockcanary,如果权限允许的话, 会保存在本地sd卡中 returnpathoflogfiles Override publicStringprovidePath(){ returnblockcanary; } 是否需要通知去通知用户发生阻塞 returntrueifneed,elseifnotneed。 Override publicbooleandisplayNotification(){ returntrue; } 用于将多个文件压缩为一个。zip文件 paramsrcfilesbeforecompress paramdestfilescompressed returntrueifcompressionissuccessful Override publicbooleanzip(File〔〕src,Filedest){ returnfalse; } 用于将已经被压缩好的。ziplog文件上传至 APM后台 paramzippedFilezippedfile Override publicvoidupload(FilezippedFile){ thrownewUnsupportedOperationException(); } 用于设定包名,默认使用进程名, returnnullifsimplyconcernonlypackagewithprocessname。 Override publicListconcernPackages(){ returnnull; } 使用{codeconcernPackages}方法指定过滤的堆栈信息 returntrueiffilter,falseitnot。 Override publicbooleanfilterNonConcernStack(){ returnfalse; } 指定一个白名单,在白名单的条目将不会出现在展示阻塞信息的UI中 returnreturnnullifyoudontneedwhitelistfilter。 Override publicListprovideWhiteList(){ LinkedListwhiteListnewLinkedList(); whiteList。add(org。chromium); returnwhiteList; } 使用白名单的时候,是否去删除堆栈在白名单中的文件 returntrueifdelete,falseitnot。 Override publicbooleandeleteFilesInWhiteList(){ returntrue; } 阻塞拦截器,我们可以指定发生阻塞时应该做的工作 Override publicvoidonBlock(Contextcontext,BlockInfoblockInfo){ } } 复制代码其他监控除了主线程的耗时过长之外,我们还有哪些卡顿问题需要关注呢?AndroidVitals是GooglePlay官方的性能监控服务,涉及卡顿相关的监控有ANR、启动、帧率三个帧率业界都使用Choreographer来监控应用的帧率;需要排除掉页面没有操作的情况,应该只在界面存在绘制的时候才做统计;监听界面是否存在绘制行为 getWindow()。getDecorView()。getViewTreeObserver()。addOnDrawListener 复制代码平均帧率:衡量界面流畅度;冻帧率:计算发生冻帧时间在所有时间的占比;冻帧:AndroidVitals将连续丢帧超过700毫秒定义为冻帧,也就是连续丢帧42帧以上;出现丢帧的时候,我们可以获取当前的页面信息、View信息和操作路径上报后台,降低二次排查的难度生命周期监控Activity、Service、Receiver组件生命周期的耗时和调用次数也是我们重点关注的性能问题;如:Activity的onCreate()不应该超过1秒,不然会影响用户看到页面的时间对于组件生命周期应采用更严格地监控,全量上报,在后台查看各个组件各个生命周期的启动时间和启动次数;除了四大组件的生命周期,我们还需要监控各个进程生命周期的启动次数和耗时;生命周期监控推荐使用编译时插桩的方式,如Aspect、ASM和ReDex三种插桩技术;线程监控Java线程管理是很多应用非常头痛的事情,应用启动过程就已经创建了几十上百个线程。而且大部分的线程都没有经过线程池管理,都在自由自在地狂奔着;另外一方面某些线程优先级或者活跃度比较高,占用了过多的CPU。这会降低主线程UI响应能力,我们需要特别针对这些线程做重点的优化。对于线程需要监控两点线程数量,以及创建线程的方式:可以通过gothook线程的nativeCreate()函数,主要用于进行线程收敛,也就是减少线程数量。监控线程的用户时间utime、系统时间stime和优先级导致卡顿的原因会有很多,比如函数非常耗时、IO非常慢、线程间的竞争或者锁等。其实很多时候卡顿问题并不难解决,相较解决来说,更困难的是如何快速发现这些卡顿点,以及通过更多的辅助信息找到真正的卡顿原因。卡顿现场以AssetManager。openNonAsset函数耗时为例进行分析方案一:java实现:通过源码可以发现,AssetManager内部有大量的synchronized锁;步骤1:获得Java线程状态:通过Thread。getState获取,证实当时主线程是BLOCKED状态;WAITING、TIMEWAITING和BLOCKED都是需要特别注意的状态; BLOCKED是指线程正在等待获取锁,对应的是下面代码中的情况一; WAITING是指线程正在等待其他线程的唤醒动作,对应的是代码中的情况二; synchronized(object){情况一:在这里卡住BLOCKED doSomething(); object。wait();情况二:在这里卡住WAITING } 不过当一个线程进入WAITING状态时,它不仅会释放CPU资源,还会将持有的object锁也同时释放。 复制代码步骤2:获得所有线程堆栈:通过Thread。getAllStackTraces()获得注意:在Android7。0,getAllStackTraces是不会返回主线程的堆栈的通过分析收集上来的卡顿日志,发现跟AssetManager相关的线程是BackgroundHandlerBackgroundHandlerRUNNABLE atandroid。content。res。AssetManager。list atcom。sample。business。init。listZipFiles 通过查看AssetManager。list的确发现是使用了同一个synchronized锁,而list函数需要遍历整个目录,耗时会比较久 publicString〔〕list(Stringpath)throwsIOException{ synchronized(this){ ensureValidLocked(); returnnativeList(mObject,path); } } 另外一方面,BackgroundHandler线程属于低优先级后台线程,这也是我们前面文章提到的不良现象,也就是主线程等待低优先级的后台线程 复制代码方案2:ANR日志实现(SIGQUIT信号)上面java实现方案还不错,不过貌似ANR日志的信息更加丰富,如果直接用ANR日志呢?线程名称;优先级;线程id;线程状态 mainprio5tid1Suspended 线程组;线程suspend计数;线程debugsuspend计数; groupmainsCount1dsCount0obj0x74746000self0xf4827400 线程nativeid;进程优先级;调度者优先级; sysTid28661nice4cgrpdefaultsched00handle0xf72cbbec native线程状态;调度者状态;用户时间utime;系统时间stime;调度的CPU stateDschedstat(3137222937944272285819)utm218stm95core2HZ100 stack相关信息 stack0xff7170000xff719000stackSize8MB 复制代码Native线程状态上面的ANR日志中main线程的状态是Suspended,Java线程中的6种状态中并不存在Suspended状态啊? 事实上,Suspended代表的是Native线程状态。怎么理解呢?在Android里面Java线程的运行都委托于一个 Linux标准线程pthread来运行,而Android里运行的线程可以分成两种,一种是Attach到虚拟机的,一种是 没有Attach到虚拟机的,在虚拟机管理的线程都是托管的线程,所以本质上Java线程的状态其实是Native线程 的一种映射。不同的Android版本Native线程的状态不太一样,例如Android9。0就定义了27种线程状态, 它能更加明确地区分线程当前所处的情况。 复制代码如何拿到卡顿时的ANR日志?第一步:当监控到主线程卡顿时,主动向系统发送SIGQUIT信号。第二步:等待dataanrtraces。txt文件生成。第三步:文件生成以后进行上报通过ANR日志,我们可以直接看到主线程的锁是由BackgroundHandler线程持有。相比之下通过getAllStackTraces方法,我们只能通过一个一个线程进行猜测。堆栈相关信息 atandroid。content。res。AssetManager。open(AssetManager。java:311) waitingtolock(android。content。res。AssetManager)heldbytid66(BackgroundHandler) atandroid。content。res。AssetManager。open(AssetManager。java:289) 复制代码存在的问题:可行性:很多高版本系统已经没有权限读取dataanrtraces。txt文件,需要刷ROM;性能:获取所有线程堆栈以及各种信息非常耗时,对于卡顿场景不一定合适,它可能会进一步加剧用户的卡顿;方案3:Hook实现通过Hook方式我们实现了一套无损获取所有Java线程堆栈与详细信息的方法:通过fork子进程方式实现,这样即使子进程崩溃了也不会影响我们主进程的运行,而且获取所有线程堆栈这个过程可以做到完全不卡我们主进程;通过libart。so、dlsym调用ThreadList::ForEach方法,拿到所有的Native线程对象。遍历线程对象列表,调用Thread::DumpState方法;线上ANR监控方式:ANR的几种常见的类型:KeyDispatchTimeout:按键事件在5s的时间内没有处理完成;BroadcastTimeout:广播接收器在前台10s,后台60s的时间内没有响应完成;ServiceTimeout:服务在前台20s,后台200s的时间内没有处理完成;之前的崩溃优化中说了怎么去发现应用中的ANR异常,那么,有没有更好的实现方式呢?ANRWatchDog:一种非侵入式的ANR监控组件,可以用于线上ANR的监控1。build。gradle下配置它的依赖 implementationcom。github。anrwatchdog:anrwatchdog:1。4。0 2。Application的onCreate方法中初始化ANRWatchDog newANRWatchDog()。start(); 3。源码:ANRWatchDog实际上是继承了Thread类,也就是它是一个线程,对于线程来说,最重要的就是其run方法 privatestaticfinalintDEFAULTANRTIMEOUT5000; privatevolatilelongtick0; privatevolatilebooleanreportedfalse; privatefinalRunnabletickernewRunnable(){ Overridepublicvoidrun(){ tick0; reportedfalse; } }; Override publicvoidrun(){ 1、首先,将线程命名为ANRWatchDog。 setName(ANRWatchDog); 2、接着,声明了一个默认的超时间隔时间,默认的值为5000ms。 longintervaltimeoutInterval; 3、然后,在while循环中通过uiHandler去post一个tickerRunnable。 while(!isInterrupted()){ 3。1这里的tick默认是0,所以needPost即为true。 booleanneedPosttick0; 这里的tick加上了默认的5000ms tickinterval; if(needPost){ uiHandler。post(ticker); } 接下来,线程会sleep一段时间,默认值为5000ms。 try{ Thread。sleep(interval); }catch(InterruptedExceptione){ interruptionListener。onInterrupted(e); return; } 4、如果主线程没有处理Runnable,即tick的值没有被赋值为0,则说明发生了ANR,第二个reported标志位是为了避免重复报道已经处理过的ANR。 if(tick!0!reported){ noinspectionConstantConditions if(!ignoreDebugger(Debug。isDebuggerConnected()Debug。waitingForDebugger())){ Log。w(ANRWatchdog,AnANRwasdetectedbutignoredbecausethedebuggerisconnected(youcanpreventthiswithsetIgnoreDebugger(true))); reportedtrue; continue; } intervalanrInterceptor。intercept(tick); if(interval0){ continue; } finalANRErrorerror; if(namePrefix!null){ errorANRError。New(tick,namePrefix,logThreadsWithoutStackTrace); }else{ 5、如果没有主动给ANRWatchdog设置线程名,则会默认会使用ANRError的NewMainOnly方法去处理ANR。 errorANRError。NewMainOnly(tick); } 6、最后会通过ANRListener调用它的onAppNotResponding方法,其默认的处理会直接抛出当前的ANRError,导致程序崩溃。anrListener。onAppNotResponding(error); intervaltimeoutInterval; reportedtrue; } } } 但是在Java层去获取所有线程堆栈以及各种信息非常耗时,对于卡顿场景不一定合适,它可能会进一步加剧用户的卡顿。 如果是对性能要求比较高的应用,可以通过HookNative层的方式去获得所有线程的堆栈信息,参考上面方案3:Hook实现 复制代码现场信息:能不能进一步让卡顿的现场信息的比系统ANR日志更加丰富?我们可以进一步增加这些信息:CPU使用率和调度信息:参考下面的课后作业1;内存相关信息:可以添加系统总内存、可用内存以及应用各个进程的内存等信息。如果开启了Debug。startAllocCounting或者atrace,还可以增加GC相关的信息。IO和网络相关:还可以把卡顿期间所有的IO和网络操作的详细信息也一并收集Android8。0后,Android虚拟机终于支持了JVM的JVMTI机制。Profiler中内存采集等很多模块也切换到这个机制中实现,卡顿单点问题检测方案常见的单点问题有主线程IPC(进程间通信)、DB操作等等IPC单点问题检测方案:在IPC的前后加上埋点。但是,这种方式不够优雅线下可以通过adb命令监测1、对IPC操作开始监控 adbshellamtraceipcstart 2、结束IPC操作的监控,同时,将监控到的信息存放到指定的文件 adbshellamtraceipcstopdumpfiledatalocaltmpipctrace。txt 3、将监控到的ipctrace导出到电脑查看 adbpulldatalocaltmpipctrace。txt 复制代码ARTHookAspectJ只能针对于那些非系统方法,也就是我们App自己的源码,或者是我们所引用到的一些jar、aar包;ARTHook可以用来Hook系统的一些方法,因为对于系统代码来说,我们无法对它进行更改,但是我们可以Hook住它的一个方法,在它的方法体里面去加上自己的一些代码;通过PackageManager去拿到我们应用的一些信息,或者去拿到设备的DeviceId这样的信息以及AMS相关的信息等,最终会调用到android。os。BinderProxy 在项目中的Application的onCreate方法中使用ARTHook对android。os。BinderProxy类的transact方法进行Hook try{ DexposedBridge。findAndHookMethod(Class。forName(android。os。BinderProxy),transact, int。class,Parcel。class,Parcel。class,int。class,newXCMethodHook(){ Override protectedvoidbeforeHookedMethod(MethodHookParamparam)throwsThrowable{ LogHelper。i(BinderProxybeforeHookedMethodparam。thisObject。getClass()。getSimpleName() Log。getStackTraceString(newThrowable())); super。beforeHookedMethod(param); } }); }catch(ClassNotFoundExceptione){ e。printStackTrace(); } 复制代码除了IPC调用的问题之外,还有IO、DB、View绘制等一系列单点问题需要去建立与之对应的检测方案对于卡顿问题检测方案的建设,主要是利用ARTHook去完善线下的检测工具,尽可能地去Hook相对应的操作,以暴露、分析问题。使用Lancet统计界面耗时Lancet是一个轻量级AndroidAOP框架,编译速度快,并且支持增量编译。使用Demo如下1。在根目录的build。gradle添加:dependencies{classpathme。ele:lancetplugin:1。0。6}2。在app目录的build。gradle添加applyplugin:me。ele。lancetdependencies{providedme。ele:lancetbase:1。0。6}3。基础API使用Author:LiuJinYangCreateDate:20201210publicclassLancetUtil{Proxy指定了将要被织入代码目标方法i,织入方式为Proxy(将使用新的方法替换代码里存在的原有的目标方法)Proxy(i)TargetClass指定了将要被织入代码目标类android。util。LogTargetClass(android。util。Log)publicstaticintanyName(Stringtag,Stringmsg){msgLJYLOG:msg;Origin。call()代表了Log。i()这个目标方法return(int)Origin。call();}}4。统计界面耗时Author:LiuJinYangCreateDate:20201210publicclassLancetUtil{publicstaticActivityRecordsActivityRecord;static{sActivityRecordnewActivityRecord();}Insert(valueonCreate,mayCreateSupertrue)TargetClass(valueandroid。support。v7。app。AppCompatActivity,scopeScope。ALL)protectedvoidonCreate(BundlesavedInstanceState){sActivityRecord。mOnCreateTimeSystem。currentTimeMillis();调用当前Hook类方法中原先的逻辑Origin。callVoid();}Insert(valueonWindowFocusChanged,mayCreateSupertrue)TargetClass(valueandroid。support。v7。app。AppCompatActivity,scopeScope。ALL)publicvoidonWindowFocusChanged(booleanhasFocus){sActivityRecord。mOnWindowsFocusChangedTimeSystem。currentTimeMillis();LjyLogUtil。i(getClass()。getCanonicalName()onWindowFocusChangedcost(sActivityRecord。mOnWindowsFocusChangedTimesActivityRecord。mOnCreateTime));Origin。callVoid();}publicstaticclassActivityRecord{避免没有仅执行onResume就去统计界面打开速度的情况,如息屏、亮屏等等publicbooleanisNewCreate;publiclongmOnCreateTime;publiclongmOnWindowsFocusChangedTime;}}复制代码卡顿分析在客户端捕获卡顿之后,最后数据需要上传到后台统一分析卡顿率评估卡顿的影响面:UV卡顿率发生过卡顿UV开启卡顿采集UV,一个用户如果命中采集,那么在一天内都会持续的采集数据评估卡顿的严重度:PV卡顿率发生过卡顿PV启动采集PV,对于命中采集PV卡顿率的用户,每次启动都需要上报作为分母结语: 卡顿优化是Android开发高手之路,这篇文章可能有些地方讲的不够详细的,或关于更多的性能优化(启动优化、崩溃优化、卡顿优化、弱网优化、内存优化)等一系列优化。资料获取:请私信我发送暗号核心笔记或手册直接领取哟! 希望共同进步,能帮助到各位Android的小伙伴。