##基础知识
GC_FOR_MALLOC 堆上分配对象时内存不足触发
GC_CONCURRENT 堆内存达到一定量(即快满了)时触发
GC_EXPLICIT 主动触发,System.gc、VMRuntime.gc或收到SIGUSR1信号
~
GC日志如下
D/dalvikvm( 7030) : GC_CONCURRENT feed 1049K,60% free 2341K/9351K ,external 3502K/6261K,paused 3ms 3ms
~
GC_CONCURRENT是GC类型,此外还有GC_FOR_MALLOC、GC_EXTERNAL_ALLOC、GC_HPROF_DUMO_HEAP、GC_EXPLICIT
feed 1049K 表示这次GC回收了多少内存
60% free 2341K/9351K 描述Heap内存的信息,本次回收后60%的Heap可用,存活对象大小为2341K,Heap大小为9351K
external 3502K/6261K 描述Native Momory信息(存放位图数据或堆外内存), 表示已经分配了3502K内存,当分配到6261K的时候会触发下一次GC
paused 3ms 3ms 表示GC暂停时间
-
UI卡顿:60fps=1000ms / 16ms
0~100 毫秒的延迟会让用户感知到瞬时的卡顿;
100~300 毫秒的延迟会让用户感觉迟缓;
300~1000 毫秒的延迟让用户感觉“手机卡死了”;
1000 毫秒以上的延迟会让用户想去干别的事情
-
硬件加速(GPU)的绘制效率远高于软件绘制(CPU),但存在以下缺陷:耗电高、某些接口不兼容、内存占用大(Open GL至少需要8M内存)
-
引用(强软弱虚)
软引用:垃圾收集器运行时可能会也可能不会释放软引用,但在保证虚拟机内存不足之时会清理它
弱引用:与软引用的区别是,弱引用生命周期更短,且无论虚拟机内存是否不足都会被清理
虚引用:只能用于跟踪即将对被引用对象进行的收集,在任何时候都可能被垃圾回收器回收
-
ANR:
1.主线程5s内没有处理完输入事件
2.主线程10s没处理完BroadCastReceiver.onReceive
3.主线程在Service各生命周期函数时20s没处理完
ANR IN :发生ANR的具体类
PID:发生ANR的进程,系统会在此时生成trace文件
Reason:当前ANR的类型以及导致ANR的原因
CPU usage:CPU的使用情况,在ANR发生前后都会产生一个CPU usage一共打印两次CPU使用情况
- OOM:试图申请的内存+已分配的内存>虚拟机允许的最大内存
内存泄漏:无用对象持续占用内存或得不到及时释放
案例:https://tech.meituan.com/oom_analysis.html
-
App闪退常见原因:NullPointer、OOM、数组越界、类型转换异常、数字转换异常、Activity/Service找不到、实体对象没有序列化等
-
Force Close常见原因:Error、OOM(内存溢出)、StackOverFlowError、RuntimeException空指针异常
-
Native 层Crash
SIGILL 执行了非法指令、可执行文件本身出错、堆栈溢出等情况
SIGABRT 调用abort函数生成
SIGBUS 访问非法地址,包括内存地址对齐出错
SIGFPE 算法运算错误,如溢出及除0等
SIGSEGV 访问不属于自己的存储空间或访问只读存储空间
SIGPIPE 管道异常,这个信号通常在进程间通信时产生
磁盘性能问题
性能瓶颈:磁盘的顺序读写基本速度都非常快了,问题基本都在随机读写
- 随机读因为没有预读(read-ahead)的优化性能差很多
- 随机写会造成大量的失效页和写入放大问题
- 数据库的性能问题,本质上也是IO性能问题
常见问题:
- 卡顿
- ANR
- IO Wait很高的时候,很可能主线程执行了耗时IO操作
- 大量的数据读写、数据库操作、硬件操作也可能造成ANR
Tips:写入放大情景:我们知道磁盘一块有512k,块中每页4k,当需要写入4k时系统恰好找到一个512k块中有一页4k的无效页。那么系统会将这512k块数据读出再擦除这512k块,之后再把读出的数据+要写入的4k数据写入这个512k的块中。这样原本的写入4k变成了【闪存读512k】-【缓存改4k】-【闪存擦除512k】-【闪存写入512k】。写入放大实验:1、正常写入1M文件,记录耗时。2、先用6k的小文件写满存储,再删除(系统标记为无效页,并未真正删除类似垃圾桶功能),这样保证存储中没有干净的存储块,最好写入1M的文件,记录耗时,两相对比。
工具推荐:
- Systrace:IO操作耗时过长问题
- Strict Mode:主线程IO问题
- IO Monitor:主线程IO问题、多余IO操作、Buffer过小问题
- SQL IO Monitor:主线程IO问题、数据库IO问题(全表扫描、不合理事务等造成)
优化建议:
- 通过缓存避免重复读写次数(变量保存、延迟写入等)
- IO操作、数据库操作尽量放入子线程
- ByteArrayOutputStream > ObjectOutputStream
- 合理设置Buffer大小
- 加解压 ZipFile + BufferedOutputStream;ZipInputStream适合网络数据解压、损坏压缩包解压、大量的小文件加解压
- 避免频繁开关数据库,数据库打开后等到真正不需要再关闭或应用退出再关闭
- Bitmap的加载,decodeStream + BufferedInputStream > decodeFile;decodeResource > decodeResourceStream
- SP的commit都是一次文件的打开读写关闭,尽量在必要时在进行;同时用apply代替commit
- 索引能加快查询速度,但同样能导致插入更新缓慢,需合理使用
- Buffer的读写每次的缓存数据使用8K,可以提供一定的效率
内存性能问题
性能瓶颈:
- OOM、Low Memory Killer会杀死进程,GC会导致App卡顿
- Activity泄露问题极为严重,通常有mContext间接引用造成、this$0间接引用造成、Activity直接引用造成
常见问题:
- 内存泄露 OOM(Crash)
- 随着功能的反复执行,Heap内存一直在持续增长。通常是内存泄露,适合用LeakCannary工具进行白盒测试分析
- 每次启动应用后,Heap内存相比以前版本稳定增长。通常出现在启动后待机或使用某功能后,可能是由新功能及代码改动引入固定内存增长。使用用Heap Dump进行多版本或功能使用前后的对比
- Heap Alloc变化不大,但进程的Dalvik Heap Pss内存明显增加。通常由于分配了大量小对象造成的内存碎片。
- GC引起的卡顿
- 大量的new临时变量或new Bitmap等大内存等操作需要注意
- 代码执行时出现频繁GC,Heap Alloc内存大幅度波动。即内存抖动,通常是分配了许多临时变量或数组还有AsyncTask,随后被迅速回收。适合使用Heap Viewer/Allocation Tracker等工具查看具体分配的对象
工具推荐:
- top/procrank :内存占用过大、内存泄
- meminfo : Native内存泄漏、是否存在Activity/ApplicationContext泄漏、数据库缓存命中率低
- MAT/Finder/JHAT : Java层的重复内存、不合理图片解码、内存泄漏
- libc_malloc_deBug_leak.so : Ntive内存泄漏
- LeakCannary/StrictMode/LeakInspector : Activity内存泄漏
- 腾讯APT : 内存占用过大、内存泄漏
- GC Log from Logcat/GC Log 生成图表 : 人工触发GC for Explicit而导致的卡顿,Heap内存不足触发GC for Alloc 而导致的卡顿
- Systrace : GC导致的卡顿
- Allocation Tracer : 申请内存次数过多和过大,辅助定位GC Log发现的问题
- chrome devtool : HS的内存问题
优化建议:
- 使用轻量级的数据结构 ArrayMap、SparseArray
- 尽量避免使用Enum枚举(枚举最大优势是类型安全,但占用内存比普通定义常量大得多,可用注解来检查类型安全);尽量避免基本类型的拆装箱(Integer、Long 、Float…)
- String的拼接使用StringBuilder
- 内存对象的重复利用:对象池;利用系统自带的资源;StringBuilder、StringBuffer
- 尽量不要再循环中创建大量的临时变量–内存抖动
- 引入SDK库和调用新的系统API需要考虑成本
- dex文件有很多优化空间,如仔细统计并调整dex文件的顺序往往能节约1M以上的dex mmap内存
【内存泄露】
- this$0 间接引用 导致 Activity内存泄漏;
解决方案:在Activity关闭时在onDestroy中,解除内部类和外部类的引用关系或使用弱引用
- Thread 直接引用 导致Activity内存泄漏
解决方案:使用完解除Thread和Activity的引用关系,即Thread使用完移除Activity或使用弱引用
- mContext 间接引用 导致Activity内存泄漏
解决方案:使用后解除mContext引用或使用弱引用
- this$0 间接引用 导致Activity内存泄漏
解决方案:用static来截断匿名类引用,或使用弱引用
- 匿名内部类/非静态内部类(尤其是放在Activity中的):非静态内部类会持有外部类的引用,若非静态内部类的实例是静态的则其生命周期也是跟app一样长,导致外部类(尤其为Activity)无法释放
解决方案:可将内部类置为静态类则不会持有外部类引用,或内部类设计成单例,传入资源用弱引用包裹,Context用ApplicationContext或用后置空
- 单例、Application的生命周期跟app一样长,要注意其所持有的对象无法释放。eg:单例对象持有Activity的mContext对象导致Activity无法释放。
- 非View类Context尽量使用ApplicationContext替代(startActivity、Layout.Inflation、Dialog必须使用Activity的Context)
- Timer、Handler、AsyncTask等异步操作,导致 Activity内存泄漏
解决方案:onDestroy进行关闭,清除。handler为内部类会持有外部类(尤其Activity)的引用,而activity结束时若handler的消息还没处理完则导致内存泄漏【handler置为静态类,且内部若持有外部对象则置为弱引用WeakReference】
- 常驻内存问题,相同的图片保存一份,去重
- 避免静态变量持有大量数据引用。静态变量不会被回收,长期维持对象的引用阻止垃圾回收,如果持有对象是Bitmap等大数据对象就很容造成内存不足
- 注意资源/监听器/Cursor对象/Recerver对象/Sensor对象/File对象等的关闭、注销、移除,临时资源主动回收(加载后忘记释放的Bitmap、临时生成的Byte数组和文件缓冲区)。注意需要close后置空,并且注意try-catch中使用的资源在finally中进行关闭处理。
内部类的正确用法(Handler为例)
private static Class InnerHandler extends Handler{
private final WeakReference mActivity;
public InnerHandler(HandlerActivity activity){
mActivity = activity;
}
@Override
public void handleMessage(){
HandlerActivity activity = mActivity.get();
if(activity != null){...}
}
}
单例中使用Context的正确方式
public class SingleInstance{
private Context mContext;
private static SingleInstance sInstance;
private SingleInstance(Context context){
mContext = context;
}
public static SingleInsatance getInstance(Context context){
if(sInstance == null){
sInstance = new SingleInstance(context.getApplicationContext());
}
return sInstance;
}
}
【图片优化】
- 格式:WebP(Android4.0+才支持) > JPEG(不支持透明) > PNG
- 图片压缩:ImageAlpha有损压缩、TinyPNG有损压缩、ImageOptim无损压缩
- ARGB565/ARGB4444/ALPHA_8的使用
- 改变图片大小:inSampleSize指数幂的缩放,inScaled、inDensity、inTargetDensity更为细致
BitmapFactory.Options options = new BitmapFractory.Options();
//inSample缩放
options.inSampleSize = 4;
//inScaled、inDensity、inTargetDensity组合
options.inScaled = true;
options.inDensity = srcWidth;
options.inTargetDensity = dstWidth;
BitmapFactory.decodeStream(is,null,optins);
- inBitmap复用内存
- 不用的使用recycle
- 三级缓存
【WebView内存问题】
WebView一经加载其内存不可释放,除非进程死亡;而且WebView有很多坑可能导致应用崩溃。可将WebView放在一个单独的进程中,通过AIDL与主进程进行通信。WebView进程关闭即可销毁内存,但不宜每次退出都进行关闭,因为进程的启动和销毁也是耗时操作,每次进出都进行新建销毁进程会导致卡顿问题,需要有一个策略来处理。
CPU性能问题
性能瓶颈:
- CPU资源冗余使用,由于算法问题、重复查、排序、删除等浪费CPU资源
- CPU资源争抢,抢主线程的CPU资源,抢音视频的CPU资源,多线程平等互争资源
- CPU资源利用率低,主要在于磁盘IO、网络IO、锁操作、sleep操作等导致CPU无法得到有效利用,空跑。
常见问题:
- 卡顿
- 检查UI线程是否有可能有耗时的操作
- overdraw 布局层数是否太多?
- 动画是否层数太多?是否过于复杂?是否onDraw调用次数不合理频繁触发measure、layout、onDraw
- ANR(Crash)
- 查看CPU是否很忙,是否因为其他进程或子线程占用CPU时间片过多导致主线程时间片过少
- 死锁、其他线程持有锁,导致主线程等待超时
工具推荐:
- adb命令
adb shell top 列出进程的各种数据
adb shell ps 处理进程的身份标识
cat /proc/pid/stat 查看CPU信息
adb shell dumpsys cpuinfo 获取PCU信息
- PerfBox : FPS、Activity启动速度
- Systrace:分析绘制时流程导致的卡顿,能大约定位是GC、IO、贴图太大,还是没用ViewHolder的问题
- Traceview:能深入定位分析各种流畅度与时延问题,但时只能初步定位XML布局和OpenGL绘制的性能问题
- Trepn
- Gfxinfo/Slickr : 定位硬件加速下的性能问题
- Hierarchy Viewer : 定位XML布局导致的性能问题
- Tracer for Open GL/Adreno/UXTune : 具体定位绘制性能问题
- Chrome DevTool : 定位具体H5卡顿问题
优化建议:
- CPU资源争抢,减少非核心需求的CPU消耗,或降低非核心线程的优先级
- 优化算法,避免重复浪费CPU资源
- 将CPU消耗转换为GPU消耗(使用RenderScript、OpenGL来进行复杂的绘制)
- 使用合适的数据类型(int、float、long),使用合适的容器(ArrayMap、SparseArray、ConcurrentHashMap)
- 使用缓存和预处理来提升算法效率,(缓存提升效率其实是内存空间换时间)
- 使用多线程提高效率的时候注意选择合适的线程数
【流畅性–布局优化】
- 尽量使用RelativeLayout和LinearLayout布局,不用AbsoluteLayout。考虑用RelativeLayout代替LinearLayout减少布局层次,同层级则建议用LinearLayout(性能略高)
- include 重用布局,include使用其他属性的时候需要设置width、height属性
- ViewStub view的延迟加载,推迟布局加载。当布局需要显示的时候才会去加载布局(布局的显隐我们经常用setVisible来搞定,其实用ViewStub的inflation来控制显示性能会好一些,但不适合需要进行显隐切换的布局)
- merge 减少不必要的层级,多用来替代FrameLayout
- 去掉多余的背景颜色(包括Activity背景灯),减少过度绘制
- true
- @null
- 图标+文字的布局可以考虑TextView的compound drawables来实现,会比TextView+ImageView更高效一些
- 使用layout_weight属性会导致LinearLayout绘制的时候需要被测量两次,影响性能,尤其在ListView/GridView的子Item中尤其明显。此外weight+weight_sum属性可以用来减少布局层数的情况下可以考虑使用。
- 文字有多种样式可以考虑Html.fromHtml()和Spannable来实现,而不是写多个TextView拼接
- 布局复杂导致层次多可以考虑用自定义ViewGroup来实现,可以考虑用LayoutAnimationController实现布局动画
【流畅性–ListView/RecyclerView优化】
- 复用convertView,即HoldView的使用
- 异步加载图片
- 快速滑动时SCROLL_STATE_FLING不显示/加载图片或消耗资源较大的view,停止后处于SCROLL_STATE_IDLE和SCROLL_STATE_TOUCH_SCROLL再进行加载显示
- item尽可能地减少使用控件和布局层次,尽可能地复用控件,这样可以减少ListView的内存使用,减少滑动时gc次数。ListView的背景色与cacheColorHint设置相同颜色可以提高滑动时的渲染性能
- getView不做复杂的逻辑计算
网络性能问题
网络有三种状态 FullPower全力运行、Low Power低状态、Standby等待状态,特点:升到Full快,降下来慢
性能瓶颈:
- 业务成功率
- 业务网络延迟。DNS部分可以采用IP直连、域名重用、HttpDNS等方式;TCP短连接会导致每次连接都发生三次握手,可利用长连接
- 业务带宽成本。减少用户使用app的带宽成本,不必要的网络请求放在wifi环境下等策略
工具推荐:
- Wireshark – 网络性能问题分析定位
- fiddler – 主要针对Http,能模拟Http的错误和延时返回
- SPDY/HTTP2.0/QUIC – 网络协议,利用FastTcpOpen减少握手次数,利用UDP更好地适应网络抖动
- WebPageTest.org – 做web应用的数据上报,提供LoadTime、StartRender、SpeedIndex、DOM Elements等耗时
- ATC – 最专业的弱网络模拟工具,能模拟窄带宽、延时、丢包、损坏包、包乱序等情况
优化建议:
- Webview或一些实时性不强的网络采用网络缓存;
解决方案:设置HTTP的Cache-control使得其保存资源文件;public/private/no-cache/no-store/max-age/max-stale
然后设置HTTP请求时的缓存模式;LOAD_CACHE_ONLY/LOAD_DEFAULT/LOAD_CACHE_NORMAL/LOAD_NO_CACHE/LOAD_CACHE_ELSE_NETWORK
- 大数据网络传输可先进行压缩下载后再进行解压使用
- 调整服务器RcvBuffer的MSS优化【分片策略】,缓解网络延迟
- 超时和重试,单个请求设置超时时间,适当考虑失败重试。分片下载,下载失败只重新获取失败部分
- 可考虑避免DNS解析直接用IP
- 根据业务合并网络请求或预先获取网络数据
- CDN的使用
- 尽量合并非必要实时请求,控制请求的频率。
- 数据压缩:POST的Body采用GZip;请求头压缩采用SPDY和HTTP2.0直接 压缩
电量
组成App电量消耗各部分定义:
● CPU的电量消耗
● wake lock的电量消耗
● 数据传输的电量消耗
● WIFI运行的电量消耗
● GPS的电量消耗
● other sensors的电量消耗
● 蜂窝通信电量消耗
● 屏幕电量消耗
● 信号电量消耗
● WIfi电量消耗
● CPU空闲时电量消耗
● 蓝牙模块电量消耗
推荐工具:
- adb shell dumpsys batteryinfo
- PowerStat2.0(腾讯)
- Battery Historian 2.0(Google 需要Android5.0以上系统)
获取方式:
adb shell dumpsys batterystats --enable full-wake-history
adb shell dumpsys batterystats --reset
adb bugreport > bugreport.txt
将txt转成html可视化,historian.py从https://github.com/google/battery-historian下载
python historian.py -a bugreport.txt > bugreport.html
优化建议:
1、网络方面
a、发起网络请求时机,把可以延迟执行的网络请求延后合并发起,减少网络Http建立次数
b、减少移动网络被激活时间和次数:发现返回数据相同时,延迟下次请求 时间。避免频繁间隔请求,采用批处理一次性请求。使用预存取技术提取获取一些数据避免后面频繁再次发起网络请求
c、数据处理:网络 传输进行压缩。进行大数据下载用GZIP下载。使用高效率的数据格式和解析方法,推荐JSON和Protobuf
d、慎用轮训方式去执行网络请求
e、减少推送消息的次数和频率
f、网络状态,处理具体业务前,养成判断当前网络 状态的习惯
2、界面相关
a、离开界面后停止对应的耗电活动
b、应用进入后台禁止异常耗电
3、定位相关
a、使用GPS后记得关闭
b、定位要求不高时用网络定位代替GPS定位
c、慎用持续定位
d、慎用被动定位
Android6.0之后的两种省电模式Doze 和 App Standby
Doze模式进入:未连接电源 + 屏幕关闭
Doze模式的五种状态:
- ACTIVE:手机处于激活状态
- INACTIVE:手机刚脱离激活状态
- IDLE_PENDING:IDLE预备态
- IDLE:空闲状态
- IDLE_MAINTENANCE:处理挂起任务
IDLE状态下的限制:
● 断开网络连接
● 忽略Wake Lock
● 标准闹钟AlarmManager定时任务延迟
● 系统不会扫描热点WIFI
● 禁止同步工作
● 停止JobScheduler任务调度
Apk瘦身
Analyze Apk:将APK拖入Android Studio即可观察当前包的结构及各部分占用的大小
瘦身建议:
- 资源压缩,字体压缩FontZip、解压库AndrodiUn7Zip
- 利用Proguard来做代码混淆、除去冗余代码、无用代码冗余资源
- 一般图片资源只做一套高清图,个别有问题再用9png解决,最后才会根据不同分辨率做不同的图的方案
- 图片格式的选择以及压缩
- redex 字节码优化,可以在优化包Size的同时提高字节码加载性能,提示App速度。主要优化项目有减少和压缩字符串、消除冗余代码、内联函数、Interdex(一种冷启动方法)
- 对最终的Apk包进行7Zip极限压缩
启动速度
冷启动:App进程还没有创建的情况下启动App
热启动:App启动时,后台已有App进程(后台挂起),eg:按Home键退出App
温启动:介于冷启动和温启动之间,eg:系统由于某种原因回收App,用户重新启动App等
启动时间度量:
adb shell am start -W pkg_name/activity 打印【TotalTime】 应用启动耗时 ,包括 新进程启动、Applicaton初始化、Activity启动时间;【ThisTime】一般和Total Time相同,除非应用启动时开了一个透明的Activity做预处理再加载主Activity;【WaitTime】总的耗时,包括启动耗时和前一个应用Activity pause时间
优化建议:
- 做的初始化 工作尽量减轻,采用延迟加载、异步加载或懒加载(按需加载),attachBaseContext、onCreate中不做耗时操作
- Activity要进行 布局 优化;避免加载 或编码Bitmap,依赖Bitmap的View延迟更新 ;避免硬盘或网络操作 阻塞UI绘制;避免在UI线程 中进行资源初始化,可延迟或异步处理
- 为Activity设置windowBackground,在View加载完成前可以显示这张 背景logo
Android性能优化典范
http://hukai.me/android-performance-patterns/
http://hukai.me/android-performance-patterns-season-2/
http://hukai.me/android-performance-patterns-season-3/
http://hukai.me/android-performance-patterns-season-4/
http://hukai.me/android-performance-patterns-season-5/
http://hukai.me/android-performance-patterns-season-6/
Android内存优化之OOM
http://hukai.me/android-performance-oom/
Android性能优化之电量篇
http://hukai.me/android-performance-battery/
Android性能优化之内存篇
http://hukai.me/android-performance-memory/
Android性能优化之运算篇
http://hukai.me/android-performance-compute/
Android性能优化之渲染篇
http://hukai.me/android-performance-render/