性能优化
内存优化
内存泄漏是常客
什么是内存泄漏?
内存不在GC的掌控范围内
对一些对象有有限的周期声明,希望要做的事完成了,它会被垃圾回收器回收掉,但是有一些对这个对象的引用还存在,导致无法被回收,占用内存,持续累加,内存很快被耗尽OOM.
如一个activity的onDestroy被调用,但是如果有一个后台线程持有这个activity的引用,该activity占用的内存就不能被回收。
原因
- 错误使用单例-context(ApplicationContext)
- handler
在handler中设置ui元素
解决方案:
1. activity关闭的时候移除所有的消息和runable
2. 采用静态内部类+弱引用
- thread
非静态内部类或者匿名类创建线程会隐式引用activity,线程run方法不执行完,线程是不会销毁的,所引用的activity也不会销毁。
解决方案:
线程内部使用弱引用/加个tag,activity销毁时修改tag值,不继续执行线程中的操作
- AsyncTask
在AsyncTask中进行耗时操作,完成后,更新ui,有时会在耗时操作还没结束,activity就退出了,但是引用还被AsyncTask持有,无法释放导致内存泄漏。
解决方法:在ondestroy的时候,调用AsyncTask.cancel()
5. 资源未关闭BroadcastReceiver、注册后记得取消注册。
6. 动画资源未释放,无限循环动画
影响?
oom
工具-leakcanary
- java的GC内存回收机制是什么?
答:对象在没有任何引用的时候才会被回收 -
GC回收机制的原理是什么?/可以作为GC Root引用点是啥?
答:1.java栈中引用的对象2.方法静态引用的对象3.方法常量引用的对象4.Native中JNI引用的对象5.Thread-活着的线程。 -
内存泄漏原因?
答:代码存在泄漏,内存无法释放/图片加载消耗大量内存,系统给应用分配内存即为应用能占用内存的最大值。
Android Profiler 测量应用性能
代码优化
使用lint检查取出无效代码
- 检测未使用的资源
点击菜单栏 Analyze -> Run Inspection by Name -> unused resources -> Moudule ‘app’ -> OK
- 检测无效代码
步骤:点击菜单栏 Analyze -> Run Inspection by Name -> unused declaration -> Moudule ‘app’ -> OK
代码规范优化
- 避免创建不必要的对象,越少的对象意味着越少的GC操作
1. 字符串拼接优先使用stringBuilder或stringBuffer,而不是+号连接,+会创建多余的对象,拼接的字符串越长,+号连接符的性能越低
2. 当一个方法返回值为string通常要判断string的作用,如果明确知道该方法返回的string再进行拼接可以考虑返回一个stringBuuffer对象来替代,将一个对象的引用返回,而返回string则是创建了一个短生命周期的临时对象。
- 静态优于抽象
如果只是想调用来完成某项功能,可设置成静态的,不用专门调用这个方法而创建对象
- 对常量使用static final修饰
- 去除淡黄色警告
- 节制的使用service,可考虑使用intentServie代替service
- 不适用静态变量保存核心数据
android的进程并不是安全的,被kill掉后,app不会重新开始启动,android系统会创建一个新的Apllication对象然后启动上次用户离开时的activity,造成这个app从来没有退出的假象,静态变量数据由于进程被杀死而被初始化。
- 轮询操作优化
大概思路:当用户打开这个页面的时候初始化TimerTask对象,每个一分钟请求一次服务器拉取订单信息并更新UI,当用户离开页面的时候清除TimerTask对象,即取消轮训请求操作
UI优化(布局优化和绘制优化)
每秒60帧,相当于每帧需要在大概16毫秒内完成渲染,否则就会卡顿
- ui线程做耗时操作
- Layout过于复杂,无法在16ms内完成渲染
- 同一时间动画执行的次数过多,CPU GPU负荷过重
- View频繁的触发measure layout
- 内存频繁出发gc,stoptheword
- onDraw方法不需要创建新的对象
- onDraw方法不需要执行耗时操作循环
- 无用的资源和逻辑导致加载和执行缓慢)
- Analyze->Run Inspection by Name… –>unused resource
- Analyze->Inspect Code
- anr
通过瞬时测试和压力测试更容易出发上面问题
如何来定位?
1. 系统设置-开发者选项-调试GPU过度绘制,红色区域过度绘制,层级/relativelayout和linealayout
2.
布局优化
- include优化-重用公共布局
- viewstub优化-仅在需要时才加载布局性能比VISISBLE好
- merge优化-可以删减多余的层级,优化ui,但是无法预览
- 减少重叠,背景图,selector normal改成透明
速度优化(线程优化和网络优化)
ANR Activity 5s触摸或者键盘输入事件无响应,broadcastReceiver 10s, service 20s
线程优化
以上出现的anr多数由于线程阻塞引起的,可通过以下方法解决 * Asytask * HandlerThread * ThreadPool * intentService *
使用线程池
直接创建Thread实现Runable的弊端,大量线程的创建和销毁很容易导致gc频繁的执行,发生内存抖动从而造成界面卡顿
为什么使用线程池?
1. 重用线程
2. 控制最大并发数量
3. 对线程进行一定的管理
例子:
Rxjava,rxandroid
网络优化
图片:
- 图片优化使用webP格式,大幅节省流量,相对于jpg节省25~35%,对于png节省80%
- Bitmap优化
- 使用Android Matrix对图片缩放。
- 如何在不使用 缩略图,不压缩,优化,使用android bitmapRigionDecode显示指定区域。
- Glide加载优化
之前看到豆瓣的开源接口,思路很好,接口返回图片的数据有三种,高清大图、正常图片、缩略图,处于wifi用高清大图,4g或3g用正常,弱网条件下加载缩略图
- 网络请求处理:
- 对服务端返回的数据合理的缓存
- 下载采用断点续传
- 刷新数据尽可能采用局部刷新
加载Loading优化
- 一种就是从A页面跳转到B页面时的加载loading,loading的时候页面是纯白色,加载完数据才显示内容页面
- 在某个页面操作某种逻辑,局部loading(帧动画或者补间动画),建议在销毁弹窗时,销毁动画。
电量优化
重视程度低,音乐和视频耗电最大
- 网络请求前先判断网络的状态
- 网络请求最好进行批量处理,避免频繁间隔的网络请求
- 有wifi和移动网络优先使用wifi,请求wifi的耗电量远比移动耗电量低
启动优化
冷启动
耗时最长的,第一次启动app或者杀死进程第一次启动,主要包括application creat和activity的creat,因此在两个类的oncreat方法中做的事情越多,冷启动消耗的时间越长
暖启动
当应用中的Activity被销毁,但在内存中常驻时,启动方式就变成暖启动,减少了对象的初始化、布局加载等工作,启动时间更短,系统依然会展现闪屏页,直到第一个activity页面呈现
热启动
用户使用返回键退出应用,然后又马上重新启动应用
1. 白屏
2. 闪屏页倒计时进行一些预加载
3. 主线程中涉及到的sp操作尽量在非ui线程执行
4. application创建过程尽量少的进行耗时操作
5. 减少布局的层次
启动页优化
- 启动时间优化,从Application的onCreat()方法开始到MainActivity的onCreat()执行结束
- 延迟初始化
- 后台任务
- 启动界面预加载
* intentService分担部分初始化工作
bugly\推送,热修复等。。。
- 启动页白屏处理
为什么会出现?
zygote在启动一个APP时,会创建一个新的进程去运行这个app,创建进程是需要时间的,创建完成之前,界面是假死状态,系统根据你的manifest文件设置的主题颜色来展示一个白屏或者黑屏,称为preview window,即预览窗口
实际就是activity默认主题的android:windowBackground为白色或者黑色决定的
APP启动--previewWindow--启动页
解决办法有透明主题
添加一个有背景的style样式
,然后闪屏页引用该theme主题,在该页面将window的背景图片设置为空
分析性能的工具
- Systrace 进行埋点,使用门槛搞点,需要输入命令
原理:在一些关键链路中插入label,通过label的开始和结束时间来确定某个核心过程的执行时间,framwork层和java从都插入了label信息,亦可以自定义label
系统版本越高,Framwork中添加的系统可用label就越多
使用注意事项:
1. 手动缩小范围
2. sdk/plaform-tools/systrace目录下执行命令
3. 执行结束后会生成一个trace.html文件
自定义label
在开始和结束的地方加上自定义label,抓一遍trace,分析问题
如何分析非debug app
debug和非debug性能差别很大,为了支持debug,系统开启了一些列功能,并且关闭掉了某些重要优化 ,要想分析app真实的运行情况必须要在非debug的APP中启动systrace功能。
通过反射调用sdk中一个@hide的函数
- traceView
运行时开销很严重,trace会收集程序运行时所有方法的耗时情况
可以埋点
- Profiler
APK瘦身
APK构成初探
将apk包拖到android studio中就可以看到包结构了。
lib 主要存储各cpu架构的so库
classes.dex java文件编译生成的字节码文件
res 资源文件
AndroidManifest.xml 配置文件
META-INFO 相关签名信息
resource.arsc 资源文件映射表
该步主要目的是分析出哪些位置的文件大小占比较大,以及哪些位置还有优化的空间
如发现res文件夹有几十MB大小:就要考虑图片的优化,首先清除未引用图片,其次找到那些10K以上的图片进行转jpg或者webp的操作,然后进行无损压缩。
如发现lib文件大小特别大:考虑cpu架构相关优化,点开lib文件夹看下所适配的cpu架构是否可以精简,其次一次打开v7a和其它文件夹,找到大小特别大的s0库,调研所引用的so库是否是全家桶,以及如何精简。
如发现asset文件大小特别大:找到体积较大的进行分析优化
.dex文件属于java文件编译的字节码文件,删除未引用代码资源这些即可
优化方案
- 使用minifyEnable混淆代码
android {
buildTypes {
release {
minifyEnabled true
}
}
}
proguard-rules.pro中编写混淆规则
- 使用ShrinkResources移除无用资源
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
}
}
}
shrinkResource必须与minifyResource同时开启才有效。
注意:采用该方法其实无用的资源并没有被移除掉,而是被体积更小的占位符替代了
- 删除项目中未使用到的资源文件 通过androidstudio提供的lint工具,code-Analyze Code-Run inspection by name然后输入unused resource查找未使用的资源文件,确认无引用后删除。 一般需要执行多次上述步骤进行清理(xml中引用资源文件首次扫不出来),注意反射的情况。
经多个项目经验,该步骤优化效果不是太明显(因为是纯代码的优化,近百兆项目大概0.5M左右的空间),
但是实际意义是大于最终效果的,项目的维护性,规范和简洁性有很大提升。
-
删除项目中未使用的代码
code-Analyze Code-Run inspection by name然后输入unused declearation 找到无用代码对其删除通过反射引用的方法或者类,lint识别不了,也会给检查出来,删除的时候通过反射引用的方法或者类千万不能删除
-
对图片进行压缩
- 将Png图片换成体积更小的jpg格式。
- 使用tinypng或者upng对图片进行压缩
- 使用vector矢量图
- 将图片转化成webP格式(要求最低的api是18)转换后jpg节省35%。png 80%
- 使用shape代替图片
- 使用Resource Configs进行语言配置
Android应用本身是支持国际化的,但国内的项目好多都是在国内使用,不需要国际化,这时候可以对相关的String进行减法操作,使其只包含特定的语言(例如中文)
android {
defaultConfig {
...
//语言资源,只支持中文
resConfigs "zh-rCN"
}
}
- so库操作(慎重,建立在充分调研基础上),在引入第三方sdk难免要引入对应的so库,一般给到的so库分好多架构(armeabi, armeabi-v7a, x86等等) Android系统 目前支持以下七种不同的CPU架构:
ARMv5
ARMv7
x86
MIPS
ARMv8
MIPS64
x86_64
分析:
1. 如果只适配armeabi
基本上适配了所有得cpu架构,除了mips和mips64
2.如果只适配armeabi-v7a
漏掉一部分老设备,在性能性和兼容性中比较均衡
3.如果只适配arm64-v8a
性能最佳,但是只能运行在v8a的设备上
总结:考虑兼容性换性能就abi,均衡点就v7a,追求性能就v8a
所有的架构设备x86,x8664,armeabi-v7a,arm64-v8a都支持armeabi架构的.so文件,所以可以根据自己的业务需求选择使用armeabi或者armeabi-v7a
微信、qq、网易云音乐等都只保留了armeabi-v7a
android {
defaultConfig {
ndk {
abiFilters 'armeabi','armeabi-v7a'
}
}
}