十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

安卓性能与稳定性相关问题

安卓性能与稳定性相关问题 最近老是被人问什么是anr怎么排查oom卡顿就卡顿闪退就闪退耗时就耗时内存泄漏就内存泄漏平时土到掉渣一听到很官方的名字就反应不过来是什么。好来捋一下。看看他们之间有什么关系。俺之前已经遇到过很多这些问题了记录一下下次再遇到就知道怎么解决了。目录影响体验的性能问题1.内存问题表现形式2.内存抖动3.内存泄露1内存泄漏的情况2表现3怎么避免?4内存泄漏原理5内存泄漏检测原理6怎么检测7LC原理和步骤8LC不能用于线上的原因9koom4.OOM1场景场景2排查方式3解决方法5.耗时操作有哪些1耗时操作2解决方法3之前遇到过4首帧耗时6.Fps7.卡顿问题排查及原理1表现2原因3原理4绘制渲染5排查方式8.anr是啥原理是啥1排查手段9.包大小1影响2怎么优化10.保活11.线上监控体系兼容性问题、多语言适配、RTL影响体验的性能问题内存泄露/内存抖动/OOM、启动首帧耗时、卡顿、anr、包体积、fps、兼容性问题、保活关联之前写过jvm内存分析文章做为内存部分的基础里面讲了jvm内存模型、gc算法、profile、MAT工具具体使用方法。JVM内存分析_jvm内存分析-CSDN博客文章浏览阅读1.3k次点赞27次收藏12次。在硬盘里面将java代码编译成机器能够识别的字节码JVM将这个字节码搂到内存里面在内存里面分配一块空间用堆来存储该对象在栈里面进行计算。_jvm内存分析https://blog.csdn.net/mix39/article/details/145408581?spm1001.2014.3001.55021.内存问题表现形式内存抖动锯齿状、GC频繁导致卡顿内存泄露可用内存逐渐减少、频繁GC内存溢出OOM、程序异常2.内存抖动内存抖动是由于短时间内有大量对象进出新生区导致的内存忽高忽低有短时间内快速上升和下滑的趋势分析图呈锯齿状。它伴随着频繁的GCGC会大量占用UI线程和CPU资源会导致APP整体卡顿甚至OOM的可能。所以说在耗时管控重的地方不能开个for循环频繁生成对象不然ui一卡一卡的3.内存泄露1内存泄漏的情况单例模式。静态变量持有的引用导致的内存泄露。非静态内部类包括匿名内部类默认就会持有外部类的引用当非静态内部类对象的生命周期比外部类对象生命周期长时就会内存泄露比如常见的HandlerThreadAsyncTask创建一个线程去作网络请求。未取消注册或回调导致的内存泄漏。集合中的对象未清理造成内存泄漏。资源未关闭或未释放导致内存泄漏比如流对象、流式RPC、WebView、地图等使用完后要及时关闭。Handler造成的内存泄漏线程造成的内存泄漏地图、视频播放器、图片加载、webview、流式数据停止使用或者退出页面的时候没有反注册。也不一定是页面如果一个列表每个item都去注册一次webview但是item不可见或者销毁的时候没有反注册也可能会导致后面的数据渲染不出来。2表现程序异常(case by case)程序越使用内存占用越大且内存占用增长较快频繁GC但是内存得不到有效的释放因为频繁的GC和生成对象和内存碎片化界面响应越来越慢界面渲染卡顿、滑动卡顿或者view组件渲染不出来又不报错、白屏最终OOM闪退3怎么避免?记得反注册合理使用缓冲策略handler使用匿名内部类关联view或者acitivty使用weakrefrence页面退出的时候remove所有消息及时释放资源如流式数据、webview避免静态集合无限制增长4内存泄漏原理创建一个对象也就是在堆区里申请多一块空间给它如果某个对象一直持有这块空间该空间无法得到释放。所以如果内存泄露的次数多最终很容易引起内存溢出。程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费。对象在引用链上但是已经不可用了。根可达但是内存已经不能再用了。5内存泄漏检测原理jvm判断对象应该被回收的方式引用计数法当对象没有其他对象在引用该对象时应该被回收。可达性分析GC roots静态变量、线程栈变量、常量池、JNI指针在GC roots的引用链上可以找到对象的时候就认为这个对象根可达GC来回收时发现该对象根可达该对象就不应该被回收。在引用链找不到该对象该对象不可达就应该被回收。6怎么检测自己可以用MAT俩Ativity跳来跳去比较heap堆栈过滤掉弱引用虚引用啥的就是泄漏的方法平时线下检测LeakCanary主要是hook页面生命周期hook生命周期这种操作也可以用来检测白屏。7LC原理和步骤原理可达性分析。根可达但无用。利用 WeakReference ReferenceQueue 来检测对象是否应被回收。8LC不能用于线上的原因LeakCanary 会主动GC、Dump内存快照造成卡顿和ANR只适合开发阶段不能用于线上。频繁gc造成掉帧卡顿dump内存快照耗时会造成anrhprof上传文件太大耗时耗流重复dump手机内存爆满9koom检测思路跟LC差不多线上可以使用这个监控内存泄漏。不同的是触发方式、gc时机不同、分析方式还要可以检测native泄漏和thread泄漏。koom不主动 GC不阻塞主进程。内存占用率 80% 连续检测超过3次时fork一个子进程去异步分析dump文件卡也只卡子进程且本地分析以后只把分析结果上传到服务器。4.OOMOOMout of memory。本质是系统给这块app分配的内存额度用完了申请新内存是gc回收后依然凑不够需要的连续空间。安卓给每个app设置了内存上限128mb、512mb-1gb。到达这个上限就是用完了。一个是真的用完了一个是可能腾不出来内存了内存被占了无法释放比如内存泄漏多了无法垃圾回收这块内存无法再使用如果新对象再进来就爆了1场景场景内存泄漏用完的对象忘了释放比如Activity被静态变量持有导致内存只增不减最终撑爆。这是线上OOM的头号元凶。大图/文件加载在低端机上直接加载一张几MB的原始高清大图瞬间占满可用内存。内存抖动在循环或高频方法如onDraw中大量new临时对象导致频繁GC当生成速度超过回收速度时爆仓。2排查方式内存泄漏见上面的内存泄漏排查方式。其他的可以看日志out of memory能直接看到报错可以查看java堆内存和native堆的占用情况看哪个大对象占着不动。内存抖动可以通过profile看到有没有频繁生成对象、gc。3解决方法内存泄漏各有各的解决办法内存抖动就是让它减少抖动比如减少对象生成复用线程线程池message、减少gc大图加载核心先问尺寸再算比例最后只加载缩略图。这样一张2000x2000的图采样率设为16后内存直接从~16MB2000×2000×4字节降到~0.06MB125×125×4字节。需要预处理裁剪和压缩安卓艺术开发探索书上有。一般是采样压缩即通过降低图片的分辨率来减少内存占用。核心步骤1.获取原始图片尺寸不占内存首次调用 BitmapFactory.decodeXxx 时设置 inJustDecodeBounds true此时只解析图片的宽高outWidth、outHeight并不真正加载像素数据到内存。2.根据图片容器宽高和图片素材宽高计算采样率 inSampleSize让容器装得下图片通过 calculateInSampleSize 计算一个 2 的幂次 的数值如 1、2、4、8…使得采样后的图片宽高恰好大于或等于目标宽高。例如原始图 2000×2000目标 100×100采样率最终为 16采样后图片变为 125×125。3.按采样率解码再次解码时将 inJustDecodeBounds false并设置 inSampleSize 16。系统只会加载原图每 16×16 个像素中的一个像素最终生成的 Bitmap 内存占用仅为原始内存的 1/256从而避免 OOM。特点只降低分辨率不改变图片格式适合将大图显示到尺寸固定的 ImageView。采样率必须是 2 的幂官方推荐算法采用循环翻倍直到满足条件保证解码效率。/** * 图片压缩功能 */ public class ImageResizer { private static final String TAG ImageResizer; public ImageResizer() { } /** * 高效加载图片将裁剪后的图片传给ImageView降低内存占用从而避免OOM提高Bitmap加载时的性能 */ public static Bitmap decodeSampledBitmapResource(Resources resources, int resourceId, int resourceWidth, int resourceHeight) { //1、将inJustDecodeBounds设为true并加载图片 final BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeResource(resources, resourceId, options); //3、根据采样规则结合目标view所需大小计算出采样率 options.inSampleSize calculateInSampleSize(options, resourceWidth, resourceHeight); //4、将inJustDecodeBounds参数设为false然后重新加载图片 options.inJustDecodeBounds false; return BitmapFactory.decodeResource(resources, resourceId, options); } /** * 根据采样规则结合目标view所需大小计算出采样率 */ private static int calculateInSampleSize(BitmapFactory.Options options, int resourceWidth, int resourceHeight) { //2、取出图片的原始宽高信息(outWidth、outHeight) final int height options.outHeight; final int width options.outWidth; int inSampleSize 1; if (height resourceHeight || width resourceWidth) { final int halfHeight height / 2; final int halfWidth width / 2; while ((halfHeight / inSampleSize resourceHeight) (halfWidth / inSampleSize resourceWidth)) { //inSampleSize按照2的指数倍递增直到图片的宽高小于ImageView inSampleSize * 2; } } Log.d(TAG, calculateInSampleSize: inSampleSize -- inSampleSize); return inSampleSize; } public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fileDescriptor, int resourceWidth, int resourceHeight) { //1、将inJustDecodeBounds设为true并加载图片 final BitmapFactory.Options options new BitmapFactory.Options(); options.inJustDecodeBounds true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); //3、根据采样规则结合目标view所需大小计算出采样率 options.inSampleSize calculateInSampleSize(options, resourceWidth, resourceHeight); //4、将inJustDecodeBounds参数设为false然后重新加载图片 options.inJustDecodeBounds false; return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } }decodeSampledBitmapFromFileDescriptor与decodeSampledBitmapResource的区别对比项decodeSampledBitmapFromFileDescriptordecodeSampledBitmapResource数据源文件资源外部存储应用资源res/内部资源密度处理不处理直接原始尺寸自动根据屏幕密度缩放采样基准图片真实物理尺寸经过密度缩放后的尺寸典型用途加载用户照片、大图加载内置大图如启动图一般用来加载本地大图可以使用这个。5.耗时操作有哪些影响会造成ui卡顿、启动耗时、首帧耗时掉帧1耗时操作1)rpc、网络请求、数据库操作、文件读写、IO、file、进程间通信、编解码2)sp(commit同步耗时apply异步大数据频繁读取耗时)、图片加载、嵌套布局、复杂布局、动态计算也有可能、线程切换、加锁卡顿、解析大型数据、AB开关、注册kmp、短时间生成大量对象2解决方法不同问题有不同的解决方式一般是放子线程/异步调用动画铺平框架解mkkv减少一次内存切换减少页面布局层级或者xml的布局写到java代码里面放首帧后有一点点耗时但是不是很耗时的更改调用时机放首帧后3之前遇到过主线程new file。低端车机煲机多媒体应用8小时视频播放卡顿严重音视频不同步。一个是性能带不起来一个是时间戳的差异越来越大网络请求弱网应用启动的时候切线程去用bitmapfactory加载了一次image导致冷启时用户肉眼可见的空白了几百毫秒还是高端机。其实这里放主线程比较好因为这里不是大图加载切线程对用户的影响可能更大一些。AB开关组件化的时候在类似于应用启动时高性能静态加载资源的地方使用了AB开关下游业务使用kmp注册下游业务的时候启动耗时又增加了拎到首帧后线上又出来anr因为kmp链接到了tecal给xml里面多加了一层布局首帧耗时增加几十ms。。。为了实现复杂动画动态计算css导致安卓大部分中/低端机动画掉帧非常严重。公司的跨端组件跑不起来复杂动画。为什么要动态计算而不是帧动画当然是动画太复杂只能根据业务规则来计算。。。4跟手滑的时候使用translateX整个页面掉帧。。。日志打印一些常调用遍历调用的地方加日志多了容易卡。4首帧耗时检测时机application.attachBaseContext -》 onGlobalLayoutapplication.attachBaseContextApplication 初始化起点此时还没加载 Application 代码这会儿可以插件化/热修复初始化、MultiDex 加载的起点。有的想跳过启动页直接进二级页面的可以在这里做操作。冷启动优化点冷启阶段对任务进行编排比如一些web进程、push进程不着急使用就可以到启动之后再拉起或者懒加载如果性能监控埋点啥的可以在首帧前业务侧非首页上的可见内容懒加载创建时分为首帧前后首帧前属于预加载必须要提前执行其他的放后面耗时的放子线程里面加个闪屏页提升用户体感打开厂商定制的冷启动加速黑科技6.Fps每秒生成的帧数。一般是60。帧耗时 16ms 就会掉帧掉帧率 (丢帧数 / 总帧数)。线上监控通常用帧耗时 P99 分位值而不是平均 FPS因为平均 FPS 会掩盖偶发长耗时。1一般是大列表有这个问题解决方法滑动时列表停止add 数据等列表滑动停止再add数据分页加载、懒加载、差分更新2背地里偷偷做微耗时操作或者每个item去做微耗时操作。微耗时参考耗时操作3频繁生成对象gc4频繁重绘ondraw这个会引起屏幕频繁刷新每次都是软件到硬件的一次刷新5内存泄露、越来越严重、gpu带不起来6view层级太多。滑动时超重如translateX啥的。view层级太多也会导致首帧耗时慢的问题。7动画掉帧改大fps7.卡顿问题排查及原理1表现滑动卡顿、动画卡顿、点击响应慢、拖拽响应慢。如果超级卡就anr闪退了2原因就是刚刚那些耗时操作做在了主线程或者后台线程把资源占完了导致前台无法及时绘制渲染。比如绘制次数太多点击事件就在MessageQueue 里排队导致“点击没反应”。3原理Android 系统每 16ms 需要刷新一帧60fps如果主线程的处理时间超过 16ms这一帧就会被丢弃用户就会感到“掉帧”。如果丢帧严重就成了“卡顿”。4绘制渲染performdraw-翻译成displaylist指令-renderthread调用skio/vuncal/opengl等GPU绘制-然后将renderbuffer给surfacefing然后Choreographer根据 VSync 信号调度 doTraversal保证绘制节奏配合屏幕刷新节奏。这个可以看我启动原理那篇文章里面的渲染部分。卡顿就是帧率 刷新率帧率 (FPS)每秒生成的帧数。刷新率 (Hz)每秒刷新的次数。5排查方式看代码里面有没有耗时操作、内存正不正常。Systrace / Perfetto、Choreographer#FrameCallback、BlockCanary。应用层代码逻辑BlockCanary能直接告诉哪一行代码如 DB操作、IO操作、复杂计算占用了主线程太久。渲染层绘图指令使用 Layout Inspector 或 Profile GPU Rendering (GPU 呈现模式分析)。看颜色 如果柱状图里的**红色Draw过高说明 onDraw 逻辑太重如果紫色Layout**过高说明布局嵌套太深需要用 include、merge 或者 ConstraintLayout 拍平布局。系统层资源竞争使用 Systrace / Perfetto。这是排查“为什么卡顿”的终极工具。你可以看到 Choreographer 的每一帧绘制信号如果看到 doFrame 后面紧跟着大量的 GC 或者 Binder 调用你就知道是 CPU 资源被抢占了。8.anr是啥原理是啥ANRapplication not responding当主线程在特定时间内没有处理完系统发送的事件就闪退activity等界面点击/滑动事件5秒没有响应BroadcastReceiver 前台 10 秒后台 60 秒内未处理完 onReceiveService 前台 20 秒后台 200 秒内未处理完 onCreate 等生命周期1排查手段看trace、看log、看cpu是否负载过高看 traces.txt 中的线程状态。一般是main主线程的线程状态RUNNABLE正在运行说明代码逻辑有问题可能在做耗时操作。大概率是 CPU 耗时计算/IOBLOCKED→ 在等锁找waiting to lock重点看是谁持有了锁查找 held by 关键词。WAITING /TIMED_WAITING/ BLOCKED被其他线程卡住了。在等某个操作完成比如Object.wait()或sleep查看 logcat 中的 ANR 信息搜索关键词 ANR in可以看到Reason 系统提示是因为什么原因例如keyDispatchingTimedOut。PID/TID 发生 ANR 的进程和线程 ID。看 CPU 是否负载过高如果是 CPU 满载说明代码在做极其复杂的计算如循环加密、大型 JSON 解析或者逻辑陷入死循环。如果是 CPU 空闲但主线程依然在 Wait说明主线程在等待锁Deadlock或者等待 IO 操作完成。9.包大小1影响互联网app新app是影响app上架需要短时间压缩包体积如果是很多年的老app包里面有很多功能包大小已经超了需要严格控制需求迭代的新增的包体积不然发不了版。车载、厂商内置app一般这类是将app作为内置应用包体积太大会影响内部功能使用流畅度啥的如果是低端机型那就有得搞了因为一般低端机性能非常差内存又非常小用一段时间很容易内存就满了。2怎么优化在不影响用户体验的情况下进行优化压缩。图片、资源压缩。图片可以使用ImageOptim压缩、安卓使用webp压缩、内置lottie资源找视觉压缩基本能压很多。代码绘制样式图片、动效。以便减少内置图片大小和减少图片加载时的空白时间。资源改成线上资源。画不出来再尝试线上资源图片、lottie、字体、视频压缩xml文件。删除无用/已下线功能代码优化日志打印代码裁剪。裁剪一些so库的代码只留有用到的资源比如:ffmpeg。一些app没那么多动态化需求就没有必要引入跨端组件或者小程序的一些库了。打开混淆10.保活双进程守护、service守护过时一般是这个前台服务的 Notification。11.线上监控体系线下LeakCanary BlockCanary Profile线上KOOM 自定义ANR监控WatchDog 卡顿监控FrameMetrics 日志上报兜底接入Bugly/火线等平台兜底崩溃和ANR兼容性问题、多语言适配、RTL一般是互联网app 机型兼容性问题。安卓原生就是多种安卓机型不同安卓os版本和不同厂商之间这种一般跟fwk和硬件有关。主要是分辨率和系统bug。字体粗细、分辨率适配(宽、窄、大屏、2折叠、3折叠、上下折叠)、繁体字逗号自动居中、view倾斜后有锯齿、不同机型屏幕展示的字体不一样、同一个数值但是view字体大小表现不一致、时不时带出个导航栏、白边如果是跨端就是在安卓兼容性问题上叠加不同类型的os之间比如安卓/ios/鸿蒙之间。iOS os 16以下识别链接时汉子识别出来是黑块、有的表情符安卓展示不出来显示一个方块。更多的是双端差异和跨端容器实现不一致。lottie不设置宽度的时候双端表现不一致一个填充所有一个按照lottie素材内部宽度来展示。两帧动画衔接处有差异。ios不保留第一帧的状态第2帧开始前会回到初始坐标需要marginTop来矫正。安卓保留最后一帧状态但是第2帧的初始坐标是第一帧的坐标安卓需要在动画里translateY来矫正
返回列表