Android性能专项篇
2023-06-22 10:09:15 0 举报
AI智能生成
移动端技术相关内容
作者其他创作
大纲/内容
Android性能优化之内存优化
避免内存泄漏
资源型对象使用完毕要关闭
注册对象需要解注册,如广播等
类的静态变量尽量不要持有大数据对象
容器中的对象未清理
非业务需要尽量避免把 Activity 等组件的 Context 上下文作为参数传递
非静态内部类和匿名内部类会持有外部 Activity 等的引用(使用静态内部类或独立类)
合理使用数据结构
尽量少用 + 来拼接字符串
能使用基础类型的(int)避免使用装箱类型(Integer)
内存复用:对象池等
使用合适的数据结构: HashMap vs ArrayMap vs SparseArray 等
使用 Dagger 2 实现依赖注入
谨慎使用外部库
针对序列化数据使用精简版 Protobuf
枚举会比静态常量多占用内存,也可以使用注解替代枚举:
合理使用 Bitmap 位图
Android 中的图片是以 Bitmap 形式存在的,Bitmap 所占用内存的多少对性能的影响很重要
Memory = width * height * 一个像素点占用的字节数
可以通过改变编码格式来改变 Bitmap 占用的内存
按照 Bitmap 占用内存的计算方式,可以减小其宽高达到减少内存占用的目的
内存管理(按优先级结束进程来释放RAM)
前台进程:用户目前执行操作所需的进程,除非内存过低,导致连这些进程都无法继续运行,才会在最后一步终止这些进程
可见进程:除非系统为了使所有前台进程保持运行而需要终止它们,否则不会这么做
服务进程:系统会始终使此类进程保持运行,除非没有足够的内存来保留所有前台和可见进程
缓存进程:目前不需要的进程,因此,如果其他地方需要内存,系统可以根据需要自由地终止该进程
Android性能优化之启动优化
关于APP启动
冷启动:耗时最长,从点击应用图标到创建进程再到 UI 显示且用户可操作的全部过程。
启动 App,加载空白的 StartingWindow 页面。
Zygote 创建 App 进程,接着启动主线程。
创建 Application 实例并执行相关回调方法。
创建 Activity 实例并执行相关回调方法。
加载布局,绘制视图。
热启动:耗时最短,直接从后台切入前台,只会走 Activity 的部分生命周期方法(onRestart, onResume)。
温启动:耗时介于上面两种启动方式之间,不会重走进程的创建,以及 Application 的创建和生命周期流程,只会重走 Activity 的生命周期。
优化思路
除去一些系统控制的步骤,我们可以优化的地方在于 Application 和 Activity 的生命周期阶段,避免做耗时任务
另外可以从绘制的角度考虑,提供的启动页 Activity 布局越简单则绘制越快,用户看到视图第一帧的耗时就越小
启动耗时检测
adb shell am start -W $pkg/$activity
// 输出
ThisTime: 1952
TotalTime: 1952
WaitTime: 2039
ThisTime: 启动一连串 Activity 的最后一个 Activity 的启动耗时,一般和 TotalTime 时间一样。
TotalTime: 创建进程 + Application 初始化 + Activity 初始化到界面显示的耗时。
WaitTime: 调用 context.startActivity 启动(包括 AMS 的工作) Activity 到第一帧完全显示的总耗时。
具体操作
懒加载
对于一些启动时不必要初始化的任务或第三方库,可以在用时才懒加载。
异步初始化
将一些不必要在主线程初始化的耗时任务放在子线程异步加载
延迟初始化
可以利用 Handle 机制中 IdleHandler 的特性,在主线程消息队列空闲时执行一些初始化任务
预加载类
在 Application 中提前异步加载一些加载耗时比较长的类,可以通过替换系统的 ClassLoader 来打印类加载的时间,选择一些加载耗时比较长的类去提前异步加载。
启动页面绘制优化
对启动闪屏页和主页的绘制进行优化,减少绘制时间
Android性能优化之包体积优化
一个APK实际上就是一个压缩文件,解压后可以看到通常包含如下几种类型的文件或文件夹
classes.dex源码;
编译生成的二进制资源文件resources.arsc;
res资源文件夹;
assets文件夹;
lib库文件夹;
AndroidManifest.xml清单文件;
依赖关系配置文件project.properties;
代码混淆配置文件proguard.cfg;
签名信息文件META-INF等。
classes.dex源码
代码混淆可以减小该文件的大小
删掉没有用到的代码
尝试去除Android Support库等系统库
重复引用依赖;
支持插件化:动态加载代码和动态加载资源;
Facebook的redex优化字节码
使用compileOnly配置依赖
减少java隐藏开销,比如一些自动生成的函数等(如enum)
res资源文件夹,瘦身过程中优化的大头
通过Android Studio→Inspect Code…对工程做静态代码检查,删掉没有用到的资源
一个APK尽量只用一套图片,从内存占用和适配的角度考虑,这一套图建议取720p的资源,放到xhdpi目录下
使用tinypng等图片压缩工具对图片进行压缩
使用不带alpha值的jpg图片以及同等质量下文件更小的webP图片格式
有损编码格式的音频文件代替无损格式的音频文件
用一张图片实现按钮按下和普通效果的样式
第三库里引用了一些大图但是实际上并不会被用到,就可以考虑用1x1的透明图片覆盖
使用矢量图:矢量图是由点与线组成,和位图不一样,它再放大也能保持清晰度,而且使用矢量图比位图设计方案能节约30~40%的空间
assets文件夹
使用字体压缩工具对字体文件进行压缩;
如果有web页面,可以考虑使用7zip压缩工具对该文件夹进行压缩,在正式使用的时候解压;
尽量不要在APK中打包预置数据,做到程序和数据分离,如果是不得不,可以考虑用7zip压缩工具对该文件进行压缩,在程序运行时解压;
将大资源文件放到服务端,启动后自动下载使用
删除无用的语言资源:大部分应用其实并不需要支持几十种语言的国际化支持,比如国内应用只支持中文
lib库文件夹
动态加载so库
只提供对主流架构的支持,比如arm,对于mips和x86架构可以考虑不支持,这样可以大大减小APK的体积;
删除armeabi-v7包下的so:基本上armeabi的so也是兼容armeabi-v7的,armeabi-v7a的库会对图形渲染方面有很大的改进,如果没有这方面的要求,可以精简。
这里不排除有极少数设备会Crash,可能和不同的so有一定的关系,请大家务必测试周全后再发布。
Android性能优化之布局与绘制优化
造成页面卡顿的根本原因可分为两种
绘制任务过于复杂,使得绘制一帧的时间过长。
主线程太忙碌,使得没有及时处理 Vsync 信号到来后的绘制任务。
针对性优化方案
布局优化
减少布局层级: merge
删除布局中无用的控件和层次,有选择地使用性能好的 ViewGroup, 合理使用 RelativeLayout 和 LinearLayout,善于利用 ConstraintLayout 布局减少层级嵌套。
合理使用 <merge> 标签。
懒加载布局: ViewStub
布局复用: include
使用低端机器进行测试,便于发现性能瓶颈。
使用 <Space> 标签添加间距。
尽可能少用 wrap_content 尺寸,因为它会增加布局 measure 的计算成本
避免过度绘制
产生过度绘制的主要原因是
XML 布局: 控件重叠或设置了无必要的背景。
View 绘制: View.OnDraw 中同一个区域被绘制多次。
XML 优化
减少重叠的元素,并可以将重叠的元素背景设置为空
移除 XML 中非必需的背景
移除非必要的 Window 背景
自定义 View 优化
可以使用 canvas.clipRect 方法指定一块矩形区域,在绘制一个元素之前先判断该元素是否在指定的区域内,若不在则直接返回,否则才绘制。比如说自定义重叠的图片控件时可以通过这个方法避免过度绘制。
可以使用 invalidate(Rect dirty) 指定脏区,避免整个 View 都重绘。
onDraw 方法中避免创建对象,避免做耗时操作。
提高动画性能
流畅度:控制每一帧动画在 16ms 内完成。
内存:避免内存泄漏,减小内存开销。
耗电:减小运算量,优化算法,减小 CPU 占用。
Android-App崩溃处理
异常崩溃原因
在 Zygote 进程 fork 出 App 进程后,会调用 zygoteInit 方法,其中会执行 ActivityThread.main() 方法,在此之前会通过 Thread.setDefaultUncaughtExceptionHandler 方法设置 App 的默认异常处理器(打印异常,并终止进程)。
主线程异常处理
loop方法捕获了主线程所有消息的异常,然后重新loop起来。
子线程异常
通过 Thread.setDefaultUncaughtExceptionHandler 来设置自定义处理拦截。
生命周期异常
替换 ActivityThread.mH.mCallback 来捕获生命周期异常,然后通过 token 来结束 Activity 或者直接杀死进程
Android-OOM分析
Android App 的每个进程有一个最大内存限制,如果申请的内存资源超过这个限制,系统就会抛出 OOM 错误
大内存对象,如 Bitmap 等。
内存泄漏。
OOM原因
Java堆内存溢出
堆内存分配失败,通常说明进程中大部分的内存已经被占用了,且不能被垃圾回收器回收,一般来说此时内存占用都存在一些问题,例如内存泄漏等
要想定位到问题所在,就需要知道进程中的内存都被哪些对象占用,以及这些对象的引用链路。而这些信息都可以在Java内存快照文件中得到,调用 Debug.dumpHprofData(String fileName) 函数就可以得到当前进程的Java内存快照文件(即HPROF文件)。
无足够连续的内存空间
FD数量超出限制
在后台启动一个线程,每隔1s读取一次当前进程创建的FD数量,当检测到FD数量达到阈值时(FD最大限制的95%),读取当前进程的所有FD信息归并后上报
在 /proc/pid/limits 描述着 Linux 系统对对应进程的限制,其中Max open files就代表可创建FD的最大数目。进程中创建的FD记录在 /proc/pid/fd 中,通过遍历它可以得到 FD 的信息。
线程数超出限制
通过 Thread.getAllStackTraces() 可以得到进程中的所有线程以及对应的堆栈信息
一般来说,当进程中线程数异常增多时,都是某一类线程被大量的重复创建
建议使用线程池
虚拟内存不足
Android-ANR分析
AMS 和 WMS 会检测App的响应时间,如果App在特定时间无法响应屏幕触摸或键盘输入时间,或者特定事件(生命周期等)没有处理完毕,就会出现 ANR(Application Not Responding)。
ANR 一般有以下几种类型:
KeyDispatchTimeout: input 事件在 5s 内没有处理完成, logcat 关键字: Input dispatching timed out。
BroadcastTimeout: 前台广播 onReceiver 在 10S 内没有处理完成,后台广播 onReceiver 在 60s 内没有处理完成, logcat 关键字: Timeout of broadcast BroadcastRecord。
ServiceTimeout: 前台服务 onCreate, onStart, onBind 等生命周期在20s内没有处理完成,后台服务 onCreate, onStart, onBind 等生命周期在 200s 内没有处理完成, logcat 关键字: Timeout executing service。
ContentProviderTimeout: ContentProvider 在 10s 内没有处理完成, logcat 关键字: timeout publishing content providers。
0 条评论
下一页
为你推荐
查看更多