Android性能相关--经验篇

##基础知识

  • 底层触发回收机制时机:

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 管道异常,这个信号通常在进程间通信时产生


磁盘性能问题

性能瓶颈:磁盘的顺序读写基本速度都非常快了,问题基本都在随机读写

  1. 随机读因为没有预读(read-ahead)的优化性能差很多
  2. 随机写会造成大量的失效页和写入放大问题
  3. 数据库的性能问题,本质上也是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的文件,记录耗时,两相对比。

工具推荐:

  1. Systrace:IO操作耗时过长问题
  2. Strict Mode:主线程IO问题
  3. IO Monitor:主线程IO问题、多余IO操作、Buffer过小问题
  4. SQL IO Monitor:主线程IO问题、数据库IO问题(全表扫描、不合理事务等造成)

优化建议:

  1. 通过缓存避免重复读写次数(变量保存、延迟写入等)
  2. IO操作、数据库操作尽量放入子线程
  3. ByteArrayOutputStream > ObjectOutputStream
  4. 合理设置Buffer大小
  5. 加解压 ZipFile + BufferedOutputStream;ZipInputStream适合网络数据解压、损坏压缩包解压、大量的小文件加解压
  6. 避免频繁开关数据库,数据库打开后等到真正不需要再关闭或应用退出再关闭
  7. Bitmap的加载,decodeStream + BufferedInputStream > decodeFile;decodeResource > decodeResourceStream
  8. SP的commit都是一次文件的打开读写关闭,尽量在必要时在进行;同时用apply代替commit
  9. 索引能加快查询速度,但同样能导致插入更新缓慢,需合理使用
  10. Buffer的读写每次的缓存数据使用8K,可以提供一定的效率

内存性能问题

性能瓶颈:

  1. OOM、Low Memory Killer会杀死进程,GC会导致App卡顿
  2. 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等工具查看具体分配的对象

工具推荐:

  1. top/procrank :内存占用过大、内存泄
  2. meminfo : Native内存泄漏、是否存在Activity/ApplicationContext泄漏、数据库缓存命中率低
  3. MAT/Finder/JHAT : Java层的重复内存、不合理图片解码、内存泄漏
  4. libc_malloc_deBug_leak.so : Ntive内存泄漏
  5. LeakCannary/StrictMode/LeakInspector : Activity内存泄漏
  6. 腾讯APT : 内存占用过大、内存泄漏
  7. GC Log from Logcat/GC Log 生成图表 : 人工触发GC for Explicit而导致的卡顿,Heap内存不足触发GC for Alloc 而导致的卡顿
  8. Systrace : GC导致的卡顿
  9. Allocation Tracer : 申请内存次数过多和过大,辅助定位GC Log发现的问题
  10. chrome devtool : HS的内存问题

优化建议:

  1. 使用轻量级的数据结构 ArrayMap、SparseArray
  2. 尽量避免使用Enum枚举(枚举最大优势是类型安全,但占用内存比普通定义常量大得多,可用注解来检查类型安全);尽量避免基本类型的拆装箱(Integer、Long 、Float…)
  3. String的拼接使用StringBuilder
  4. 内存对象的重复利用:对象池;利用系统自带的资源;StringBuilder、StringBuffer
  5. 尽量不要再循环中创建大量的临时变量–内存抖动
  6. 引入SDK库和调用新的系统API需要考虑成本
  7. dex文件有很多优化空间,如仔细统计并调整dex文件的顺序往往能节约1M以上的dex mmap内存

【内存泄露】

  1. this$0 间接引用 导致 Activity内存泄漏;
    解决方案:在Activity关闭时在onDestroy中,解除内部类和外部类的引用关系或使用弱引用
  2. Thread 直接引用 导致Activity内存泄漏
    解决方案:使用完解除Thread和Activity的引用关系,即Thread使用完移除Activity或使用弱引用
  3. mContext 间接引用 导致Activity内存泄漏
    解决方案:使用后解除mContext引用或使用弱引用
  4. this$0 间接引用 导致Activity内存泄漏
    解决方案:用static来截断匿名类引用,或使用弱引用
  5. 匿名内部类/非静态内部类(尤其是放在Activity中的):非静态内部类会持有外部类的引用,若非静态内部类的实例是静态的则其生命周期也是跟app一样长,导致外部类(尤其为Activity)无法释放
    解决方案:可将内部类置为静态类则不会持有外部类引用,或内部类设计成单例,传入资源用弱引用包裹,Context用ApplicationContext或用后置空
  6. 单例、Application的生命周期跟app一样长,要注意其所持有的对象无法释放。eg:单例对象持有Activity的mContext对象导致Activity无法释放。
  7. 非View类Context尽量使用ApplicationContext替代(startActivity、Layout.Inflation、Dialog必须使用Activity的Context)
  8. Timer、Handler、AsyncTask等异步操作,导致 Activity内存泄漏
    解决方案:onDestroy进行关闭,清除。handler为内部类会持有外部类(尤其Activity)的引用,而activity结束时若handler的消息还没处理完则导致内存泄漏【handler置为静态类,且内部若持有外部对象则置为弱引用WeakReference】
  9. 常驻内存问题,相同的图片保存一份,去重
  10. 避免静态变量持有大量数据引用。静态变量不会被回收,长期维持对象的引用阻止垃圾回收,如果持有对象是Bitmap等大数据对象就很容造成内存不足
  11. 注意资源/监听器/Cursor对象/Recerver对象/Sensor对象/File对象等的关闭、注销、移除,临时资源主动回收(加载后忘记释放的Bitmap、临时生成的Byte数组和文件缓冲区)。注意需要close后置空,并且注意try-catch中使用的资源在finally中进行关闭处理。

内部类的正确用法(Handler为例)

private static Class InnerHandler extends Handler{
	private final WeakReference<HandlerActivity> 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性能问题

性能瓶颈:

  1. CPU资源冗余使用,由于算法问题、重复查、排序、删除等浪费CPU资源
  2. CPU资源争抢,抢主线程的CPU资源,抢音视频的CPU资源,多线程平等互争资源
  3. CPU资源利用率低,主要在于磁盘IO、网络IO、锁操作、sleep操作等导致CPU无法得到有效利用,空跑。

常见问题:

  • 卡顿
    • 检查UI线程是否有可能有耗时的操作
    • overdraw 布局层数是否太多?
    • 动画是否层数太多?是否过于复杂?是否onDraw调用次数不合理频繁触发measure、layout、onDraw
  • ANR(Crash)
    • 查看CPU是否很忙,是否因为其他进程或子线程占用CPU时间片过多导致主线程时间片过少
    • 死锁、其他线程持有锁,导致主线程等待超时

工具推荐:

  1. adb命令

    adb shell top 列出进程的各种数据
    adb shell ps 处理进程的身份标识
    cat /proc/pid/stat 查看CPU信息
    adb shell dumpsys cpuinfo 获取PCU信息

  2. PerfBox : FPS、Activity启动速度
  3. Systrace:分析绘制时流程导致的卡顿,能大约定位是GC、IO、贴图太大,还是没用ViewHolder的问题
  4. Traceview:能深入定位分析各种流畅度与时延问题,但时只能初步定位XML布局和OpenGL绘制的性能问题
  5. Trepn
  6. Gfxinfo/Slickr : 定位硬件加速下的性能问题
  7. Hierarchy Viewer : 定位XML布局导致的性能问题
  8. Tracer for Open GL/Adreno/UXTune : 具体定位绘制性能问题
  9. Chrome DevTool : 定位具体H5卡顿问题

优化建议:

  1. CPU资源争抢,减少非核心需求的CPU消耗,或降低非核心线程的优先级
  2. 优化算法,避免重复浪费CPU资源
  3. 将CPU消耗转换为GPU消耗(使用RenderScript、OpenGL来进行复杂的绘制)
  4. 使用合适的数据类型(int、float、long),使用合适的容器(ArrayMap、SparseArray、ConcurrentHashMap)
  5. 使用缓存和预处理来提升算法效率,(缓存提升效率其实是内存空间换时间)
  6. 使用多线程提高效率的时候注意选择合适的线程数

【流畅性–布局优化】

  • 尽量使用RelativeLayout和LinearLayout布局,不用AbsoluteLayout。考虑用RelativeLayout代替LinearLayout减少布局层次,同层级则建议用LinearLayout(性能略高)
  • include 重用布局,include使用其他属性的时候需要设置width、height属性
  • ViewStub view的延迟加载,推迟布局加载。当布局需要显示的时候才会去加载布局(布局的显隐我们经常用setVisible来搞定,其实用ViewStub的inflation来控制显示性能会好一些,但不适合需要进行显隐切换的布局)
  • merge 减少不必要的层级,多用来替代FrameLayout
  • 去掉多余的背景颜色(包括Activity背景灯),减少过度绘制
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@null</item>
  • 图标+文字的布局可以考虑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快,降下来慢
这里写图片描述

性能瓶颈:

  1. 业务成功率
  2. 业务网络延迟。DNS部分可以采用IP直连、域名重用、HttpDNS等方式;TCP短连接会导致每次连接都发生三次握手,可利用长连接
  3. 业务带宽成本。减少用户使用app的带宽成本,不必要的网络请求放在wifi环境下等策略

工具推荐:

  1. Wireshark – 网络性能问题分析定位
  2. fiddler – 主要针对Http,能模拟Http的错误和延时返回
  3. SPDY/HTTP2.0/QUIC – 网络协议,利用FastTcpOpen减少握手次数,利用UDP更好地适应网络抖动
  4. WebPageTest.org – 做web应用的数据上报,提供LoadTime、StartRender、SpeedIndex、DOM Elements等耗时
  5. ATC – 最专业的弱网络模拟工具,能模拟窄带宽、延时、丢包、损坏包、包乱序等情况

优化建议:

  1. 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
  2. 大数据网络传输可先进行压缩下载后再进行解压使用
  3. 调整服务器RcvBuffer的MSS优化【分片策略】,缓解网络延迟
  4. 超时和重试,单个请求设置超时时间,适当考虑失败重试。分片下载,下载失败只重新获取失败部分
  5. 可考虑避免DNS解析直接用IP
  6. 根据业务合并网络请求或预先获取网络数据
  7. CDN的使用
  8. 尽量合并非必要实时请求,控制请求的频率。
  9. 数据压缩: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模式的五种状态:

  1. ACTIVE:手机处于激活状态
  2. INACTIVE:手机刚脱离激活状态
  3. IDLE_PENDING:IDLE预备态
  4. IDLE:空闲状态
  5. 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时间

优化建议:

  1. 做的初始化 工作尽量减轻,采用延迟加载、异步加载或懒加载(按需加载),attachBaseContext、onCreate中不做耗时操作
  2. Activity要进行 布局 优化;避免加载 或编码Bitmap,依赖Bitmap的View延迟更新 ;避免硬盘或网络操作 阻塞UI绘制;避免在UI线程 中进行资源初始化,可延迟或异步处理
  3. 为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/

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值