如下是我工作中一个项目的一次经历(我将代码回退特意抓取的),出现这个问题的场景是一次压力测试导致整个系统卡顿,瞬间杀掉应用就OK了,究其原因最终查到是一个API的调运位置写错了方式,导致一直被狂调,当普通使用时不会有问题,压力测试必现卡顿。具体内存参考图如下:

与此抖动图对应的LogCat抓取如下:

//截取其中比较密集一段LogCat,与上图Memory检测到的抖动图对应,其中xxx为应用包名

10-06 00:59:45.619 xxx I/art: Explicit concurrent mark sweep GC freed 72515(3MB) AllocSpace objects, 65(2028KB) LOS objects, 80% free, 17MB/89MB, paused 3.505ms total 60.958ms

10-06 00:59:45.749 xxx I/art: Explicit concurrent mark sweep GC freed 5396(193KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.079ms total 100.522ms

10-06 00:59:48.059 xxx I/art: Explicit concurrent mark sweep GC freed 4693(172KB) AllocSpace objects, 0(0B) LOS objects, 75% free, 23MB/95MB, paused 2.227ms total 101.692ms

我们知道,类似上面logcat打印一样,触发垃圾回收的主要原因有以下几种:

  • GC_MALLOC——内存分配失败时触发;

  • GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;

  • GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;

  • GC_EXTERNAL_ALLOC——外部内存分配失败时触发;

可以看见,这种不停的大面积打印GC导致所有线程暂停的操作必定会导致UI视觉的卡顿,所以我们要避免此类问题的出现,具体的常见优化方式如下:

  • 检查代码,尽量避免有些频繁触发的逻辑方法中存在大量对象分配;

  • 尽量避免在多次for循环中频繁分配对象;

  • 避免在自定义View的onDraw()方法中执行复杂的操作及创建对象(譬如Paint的实例化操作不要写在onDraw()方法中等);

  • 对于并发下载等类似逻辑的实现尽量避免多次创建线程对象,而是交给线程池处理。

当然了,有了上面说明GC导致的性能后我们就该定位分析问题了,可以通过运行DDMS->Allocation Tracker标签打开一个新窗口,然后点击Start Tracing按钮,接着运行你想分析的代码,运行完毕后点击Get Allocations按钮就能够看见一个已分配对象的列表,如下:

点击上面第一个表格中的任何一项就能够在第二个表格中看见导致该内存分配的栈信息,通过这个工具我们可以很方便的知道代码分配了哪类对象、在哪个线程、哪个类、哪个文件的哪一行。譬如我们可以通过Allocation Tracker分别做一次Paint对象实例化在onDraw与构造方法的一个自定义View的内存跟踪,然后你就明白这个工具的强大了。

PS一句,Android Studio新版本除过DDMS以外在Memory视图的左侧已经集成了Allocation Tracker功能,只是用起来还是没有DDMS的方便实用,如下图:

2-3-6 使用Traceview和dmtracedump进行分析优化

关于UI卡顿问题我们还可以通过运行Traceview工具进行分析,他是一个分析器,记录了应用程序中每个函数的执行时间;我们可以打开DDMS然后选择一个进程,接着点击上面的“Start Method Profiling”按钮(红色小点变为黑色即开始运行),然后操作我们的卡顿UI(小范围测试,所以操作最好不要超过5s),完事再点一下刚才按的那个按钮,稍等片刻即可出现下图,如下:

花花绿绿的一幅图我们怎么分析呢?下面我们解释下如何通过该工具定位问题:

整个界面包括上下两部分,上面是你测试的进程中每个线程运行的时间线,下面是每个方法(包含parent及child)执行的各个指标的值。通过上图的时间面板可以直观发现,整个trace时间段main线程做的事情特别多,其他的做的相对较少。当我们选择上面的一个线程后可以发现下面的性能面板很复杂,其实这才是TraceView的核心图表,它主要展示了线程中各个方法的调用信息(CPU使用时间、调用次数等),这些信息就是我们分析UI性能卡顿的核心关注点,所以我们先看几个重要的属性说明,如下:

| 属性名 | 含义 |

| — | — |

| name | 线程中调运的方法名; |

| Incl CPU Time | 当前方法(包含内部调运的子方法)执行占用的CPU时间; |

| Excl CPU Time | 当前方法(不包含内部调运的子方法)执行占用的CPU时间; |

| Incl Real Time | 当前方法(包含内部调运的子方法)执行的真实时间,ms单位; |

| Excl Real Time | 当前方法(不包含内部调运的子方法)执行的真实时间,ms单位; |

| Calls+Recur Calls/Total | 当前方法被调运的次数及递归调运占总调运次数百分比; |

| CPU Time/Call | 当前方法调运CPU时间与调运次数比,即当前方法平均执行CPU耗时时间; |

| Real Time/Call | 当前方法调运真实时间与调运次数比,即当前方法平均执行真实耗时时间;(重点关注) |

有了对上面Traceview图表的一个认识之后我们就来看看具体导致UI性能后该如何切入分析,一般Traceview可以定位两类性能问题:

  • 方法调运一次需要耗费很长时间导致卡顿;

  • 方法调运一次耗时不长,但被频繁调运导致累计时长卡顿。

譬如我们来举个实例,有时候我们写完App在使用时不觉得有啥大的影响,但是当我们启动完App后静止在那却十分费电或者导致设备发热,这种情况我们就可以打开Traceview然后按照Cpu Time/Call或者Real Time/Call进行降序排列,然后打开可疑的方法及其child进行分析查看,然后再回到代码定位检查逻辑优化即可;当然了,我们也可以通过该工具来trace我们自定义View的一些方法来权衡性能问题,这里不再一一列举喽。

可以看见,Traceview能够帮助我们分析程序性能,已经很方便了,然而Traceview家族还有一个更加直观强大的小工具,那就是可以通过dmtracedump生成方法调用图。具体做法如下:

dmtracedump -g result.png target.trace //结果png文件 目标trace文件

通过这个生成的方法调运图我们可以更加直观的发现一些方法的调运异常现象。不过本人优化到现在还没怎么用到它,每次用到Traceview分析就已经搞定问题了,所以说dmtracedump自己酌情使用吧。

PS一句,Android Studio新版本除过DDMS以外在CPU视图的左侧已经集成了Traceview(start Method Tracing)功能,只是用起来还是没有DDMS的方便实用(这里有一篇AS MT个人觉得不错的分析文章(引用自网络,链接属于原作者功劳)),如下图:

2-3-7 使用Systrace进行分析优化

Systrace其实有些类似Traceview,它是对整个系统进行分析(同一时间轴包含应用及SurfaceFlinger、WindowManagerService等模块、服务运行信息),不过这个工具需要你的设备内核支持trace(命令行检查/sys/kernel/debug/tracing)且设备是eng或userdebug版本才可以,所以使用前麻烦自己确认一下。

我们在分析UI性能时一般只关注图形性能(所以必须选择Graphics和View,其他随意),同时一般对于卡顿的抓取都是5s,最多10s。启动Systrace进行数据抓取可以通过两种方式,命令行方式如下:

python systrace.py --time=10 -o mynewtrace.html sched gfx view wm

图形模式:

打开DDMS->Capture system wide trace using Android systrace->设置时间与选项点击OK就开始了抓取,接着操作APP,完事生成一个trace.html文件,用Chrome打开即可如下图:

在Chrome中浏览分析该文件我们可以通过键盘的W-A-S-D键来搞定,由于上面我们在进行trace时选择了一些选项,所以上图生成了左上方相关的CPU频率、负载、状态等信息,其中的CPU N代表了CPU核数,每个CPU行的柱状图表代表了当前时间段当前核上的运行信息;下面我们再来看看SurfaceFlinger的解释,如下:

可以看见上面左边栏的SurfaceFlinger其实就是负责绘制Android程序UI的服务,所以SurfaceFlinger能反应出整体绘制情况,可以关注上图VSYNC-app一行可以发现前5s多基本都能够达到16ms刷新间隔,5s多开始到7s多大于了15ms,说明此时存在绘制丢帧卡顿;同时可以发现surfaceflinger一行明显存在类似不规律间隔,这是因为有的地方是不需要重新渲染UI,所以有大范围不规律,有的是因为阻塞导致不规律,明显可以发现0到4s间大多是不需要渲染,而5s以后大多是阻塞导致;对应这个时间点我们放大可以看到每个部分所使用的时间和正在执行的任务,具体如下:

可以发现具体的执行明显存在超时性能卡顿(原点不是绿色的基本都代表存在一定问题,下面和右侧都会提示你选择的帧相关详细信息或者alert信息),但是遗憾的是通过Systrace只能大体上发现是否存在性能问题,具体问题还需要通过Traceview或者代码中嵌入Trace工具类等去继续详细分析,总之很蛋疼。

PS:如果你想使用Systrace很轻松的分析定位所有问题,看明白所有的行含义,你还需要具备非常扎实的Android系统框架的原理才可以将该工具使用的得心应手。

2-3-8 使用traces.txt文件进行ANR分析优化

ANR(Application Not Responding)是Android中AMS与WMS监测应用响应超时的表现;之所以把臭名昭著的ANR单独作为UI性能卡顿的分析来说明是因为ANR是直接卡死UI不动且必须要解掉的Bug,我们必须尽量在开发时避免他的出现,当然了,万一出现了那就用下面介绍的方法来分析吧。

我们应用开发中常见的ANR主要有如下几类:

  • 按键触摸事件派发超时ANR,一般阈值为5s(设置中开启ANR弹窗,默认有事件派发才会触发弹框ANR);

  • 广播阻塞ANR,一般阈值为10s(设置中开启ANR弹窗,默认不弹框,只有log提示);

  • 服务超时ANR,一般阈值为20s(设置中开启ANR弹窗,默认不弹框,只有log提示);

当ANR发生时除过logcat可以看见的log以外我们还可以在系统指定目录下找到traces文件或dropbox文件进行分析,发生ANR后我们可以通过如下命令得到ANR trace文件:

adb pull /data/anr/traces.txt ./

然后我们用txt编辑器打开可以发现如下结构分析:

//显示进程id、ANR发生时间点、ANR发生进程包名

----- pid 19073 at 2015-10-08 17:24:38 -----

Cmd line: com.example.yanbo.myapplication

//一些GC等object信息,通常可以忽略

//ANR方法堆栈打印信息!重点!

DALVIK THREADS (18):

“main” prio=5 tid=1 Sleeping

| group=“main” sCount=1 dsCount=0 obj=0x7497dfb8 self=0x7f9d09a000

| sysTid=19073 nice=0 cgrp=default sched=0/0 handle=0x7fa106c0a8

| state=S schedstat=( 125271779 68162762 280 ) utm=11 stm=1 core=0 HZ=100

| stack=0x7fe90d3000-0x7fe90d5000 stackSize=8MB

| held mutexes=

at java.lang.Thread.sleep!(Native method)

  • sleeping on <0x0a2ae345> (a java.lang.Object)

at java.lang.Thread.sleep(Thread.java:1031)

  • locked <0x0a2ae345> (a java.lang.Object)

//真正导致ANR的问题点,可以发现是onClick中有sleep导致。我们平时可以类比分析即可,这里不详细说明。

at java.lang.Thread.sleep(Thread.java:985)

at com.example.yanbo.myapplication.MainActivity$1.onClick(MainActivity.java:21)

at android.view.View.performClick(View.java:4908)

at android.view.View$PerformClick.run(View.java:20389)

at android.os.Handler.handleCallback(Handler.java:815)

at android.os.Handler.dispatchMessage(Handler.java:104)

at android.os.Looper.loop(Looper.java:194)

at android.app.ActivityThread.main(ActivityThread.java:5743)

at java.lang.reflect.Method.invoke!(Native method)

at java.lang.reflect.Method.invoke(Method.java:372)

at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)

at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)

//省略一些不常关注堆栈打印

至此常见的应用开发中ANR分析定位就可以解决了。

2-4 应用UI性能分析解决总结


可以看见,关于Android UI卡顿的性能分析还是有很多工具的,上面只是介绍了应用开发中我们经常使用的一些而已,还有一些其他的,譬如Oprofile等工具不怎么常用,这里就不再详细介绍。

通过上面UI性能的原理、原因、工具分析总结可以发现,我们在开发应用时一定要时刻重视性能问题,如若真的没留意出现了性能问题,不妨使用上面的一些案例方式进行分析。但是那终归是补救措施,在我们知道上面UI卡顿原理之后我们应该尽量从项目代码架构搭建及编写时就避免一些UI性能问题,具体项目中常见的注意事项如下:

  • 布局优化;尽量使用include、merge、ViewStub标签,尽量不存在冗余嵌套及过于复杂布局(譬如10层就会直接异常),尽量使用GONE替换INVISIBLE,使用weight后尽量将width和heigh设置为0dp减少运算,Item存在非常复杂的嵌套时考虑使用自定义Item View来取代,减少measure与layout次数等。

  • 列表及Adapter优化;尽量复用getView方法中的相关View,不重复获取实例导致卡顿,列表尽量在滑动过程中不进行UI元素刷新等。

  • 背景和图片等内存分配优化;尽量减少不必要的背景设置,图片尽量压缩处理显示,尽量避免频繁内存抖动等问题出现。

  • 自定义View等绘图与布局优化;尽量避免在draw、measure、layout中做过于耗时及耗内存操作,尤其是draw方法中,尽量减少draw、measure、layout等执行次数。

  • 避免ANR,不要在UI线程中做耗时操作,遵守ANR规避守则,譬如多次数据库操作等。

当然了,上面只是列出了我们项目中常见的一些UI性能注意事项而已,相信还有很多其他的情况这里没有说到,欢迎补充。还有一点就是我们上面所谓的UI性能优化分析总结等都是建议性的,因为性能这个问题是一个涉及面很广很泛的问题,有些优化不是必需的,有些优化是必需的,有些优化掉以后又是得不偿失的,所以我们一般着手解决那些必须的就可以了。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

3 应用开发Memory内存性能分析优化

========================

说完了应用开发中的UI性能问题后我们就该来关注应用开发中的另一个重要、严重、非常重要的性能问题了,那就是内存性能优化分析。Android其实就是嵌入式设备,嵌入式设备核心关注点之一就是内存资源;有人说现在的设备都在堆硬件配置(譬如国产某米的某兔跑分手机、盒子等),所以内存不会再像以前那么紧张了,其实这句话听着没错,但为啥再牛逼配置的Android设备上有些应用还是越用系统越卡呢?这里面的原因有很多,不过相信有了这一章下面的内容分析,作为一个移动开发者的你就有能力打理好自己应用的那一亩三分地内存了,能做到这样就足以了。关于Android内存优化,这里有一篇Google的官方指导文档,但是本文为自己项目摸索,会有很多不一样的地方。

3-1 Android内存管理原理


系统级内存管理:

Android系统内核是基于Linux,所以说Android的内存管理其实也是Linux的升级版而已。Linux在进程停止后就结束该进程,而Android把这些停止的进程都保留在内存中,直到系统需要更多内存时才选择性的释放一些,保留在内存中的进程默认(不包含后台service与Thread等单独UI线程的进程)不会影响整体系统的性能(速度与电量等)且当再次启动这些保留在内存的进程时可以明显提高启动速度,不需要再去加载。

再直白点就是说Android系统级内存管理机制其实类似于Java的垃圾回收机制,这下明白了吧;在Android系统中框架会定义如下几类进程、在系统内存达到规定的不同level阈值时触发清空不同level的进程类型。

可以看见,所谓的我们的Service在后台跑着跑着挂了,或者盒子上有些大型游戏启动起来就挂(之前我在上家公司做盒子时遇见过),有一个直接的原因就是这个阈值定义的太大,导致系统一直认为已经达到阈值,所以进行优先清除了符合类型的进程。所以说,该阈值的设定是有一些讲究的,额,扯多了,我们主要是针对应用层内存分析的,系统级内存回收了解这些就基本够解释我们应用在设备上的一些表现特征了。

应用级内存管理:

在说应用级别内存管理原理时大家先想一个问题,假设有一个内存为1G的Android设备,上面运行了一个非常非常吃内存的应用,如果没有任何机制的情况下是不是用着用着整个设备会因为我们这个应用把1G内存吃光然后整个系统运行瘫痪呢?

哈哈,其实Google的工程师才不会这么傻的把系统设计这么差劲。为了使系统不存在我们上面假想情况且能安全快速的运行,Android的框架使得每个应用程序都运行在单独的进程中(这些应用进程都是由Zygote进程孵化出来的,每个应用进程都对应自己唯一的虚拟机实例);如果应用在运行时再存在上面假想的情况,那么瘫痪的只会是自己的进程,不会直接影响系统运行及其他进程运行。

既然每个Android应用程序都执行在自己的虚拟机中,那了解Java的一定明白,每个虚拟机必定会有堆内存阈值限制(值得一提的是这个阈值一般都由厂商依据硬件配置及设备特性自己设定,没有统一标准,可以为64M,也可以为128M等;它的配置是在Android的属性系统的/system/build.prop中配置dalvik.vm.heapsize=128m即可,若存在dalvik.vm.heapstartsize则表示初始申请大小),也即一个应用进程同时存在的对象必须小于阈值规定的内存大小才可以正常运行。

接着我们运行的App在自己的虚拟机中内存管理基本就是遵循Java的内存管理机制了,系统在特定的情况下主动进行垃圾回收。但是要注意的一点就是在Android系统中执行垃圾回收(GC)操作时所有线程(包含UI线程)都必须暂停,等垃圾回收操作完成之后其他线程才能继续运行。这些GC垃圾回收一般都会有明显的log打印出回收类型,常见的如下:

  • GC_MALLOC——内存分配失败时触发;

  • GC_CONCURRENT——当分配的对象大小超过一个限定值(不同系统)时触发;

  • GC_EXPLICIT——对垃圾收集的显式调用(System.gc()) ;

  • GC_EXTERNAL_ALLOC——外部内存分配失败时触发;

通过上面这几点的分析可以发现,应用的内存管理其实就是一个萝卜一个坑,坑都一般大,你在开发应用时要保证的是内存使用同一时刻不能超过坑的大小,否则就装不下了。

3-2 Android内存泄露性能分析


有了关于Android的一些内存认识,接着我们来看看关于Android应用开发中常出现的一种内存问题—-内存泄露。

3-2-1 Android应用内存泄露概念

众所周知,在Java中有些对象的生命周期是有限的,当它们完成了特定的逻辑后将会被垃圾回收;但是,如果在对象的生命周期本来该被垃圾回收时这个对象还被别的对象所持有引用,那就会导致内存泄漏;这样的后果就是随着我们的应用被长时间使用,他所占用的内存越来越大。如下就是一个最常见简单的泄露例子(其它的泄露不再一一列举了):

public final class MainActivity extends Activity {

private DbManager mDbManager;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//DbManager是一个单例模式类,这样就持有了MainActivity引用,导致泄露

mDbManager = DbManager.getInstance(this);

}

}

可以看见,上面例子中我们让一个单例模式的对象持有了当前Activity的强引用,那在当前Acvitivy执行完onDestroy()后,这个Activity就无法得到垃圾回收,也就造成了内存泄露。

内存泄露可以引发很多的问题,常见的内存泄露导致问题如下:

  • 应用卡顿,响应速度慢(内存占用高时JVM虚拟机会频繁触发GC);

  • 应用被从后台进程干为空进程(上面系统内存原理有介绍,也就是超过了阈值);

  • 应用莫名的崩溃(上面应用内存原理有介绍,也就是超过了阈值OOM);

造成内存泄露泄露的最核心原理就是一个对象持有了超过自己生命周期以外的对象强引用导致该对象无法被正常垃圾回收;可以发现,应用内存泄露是个相当棘手重要的问题,我们必须重视。

3-2-2 Android应用内存泄露察觉手段

知道了内存泄露的概念之后肯定就是想办法来确认自己的项目是否存在内存泄露了,那该如何察觉自己项目是否存在内存泄露呢?如下提供了几种常用的方式:

| 察觉方式 | 场景 |

| — | — |

| AS的Memory窗口 | 平时用来直观了解自己应用的全局内存情况,大的泄露才能有感知。 |

| DDMS-Heap内存监测工具 | 同上,大的泄露才能有感知。 |

| dumpsys meminfo命令 | 常用方式,可以很直观的察觉一些泄露,但不全面且常规足够用。 |

| leakcanary神器 | 比较强大,可以感知泄露且定位泄露;实质是MAT原理,只是更加自动化了,当现有代码量已经庞大成型,且无法很快察觉掌控全局代码时极力推荐;或者是偶现泄露的情况下极力推荐。 |

AS的Memory窗口如下,详细的说明这里就不解释了,很简单很直观(使用频率高):

DDMS-Heap内存监测工具窗口如下,详细的说明这里就不解释了,很简单(使用频率不高):

dumpsys meminfo命令如下(使用频率非常高,非常高效,我的最爱之一,平时一般关注几个重要的Object个数即可判断一般的泄露;当然了,adb shell dumpsys meminfo不跟参数直接展示系统所有内存状态):

leakcanary神器使用这里先不说,下文会专题介绍,你会震撼的一B。有了这些工具的定位我们就能很方便的察觉我们App的内存泄露问题,察觉到以后该怎么定位分析呢,继续往下看。

3-2-3 Android应用内存泄露leakcanary工具定位分析

leakcanary是一个开源项目,一个内存泄露自动检测工具,是著名的GitHub开源组织Square贡献的,它的主要优势就在于自动化过早的发觉内存泄露、配置简单、抓取贴心,缺点在于还存在一些bug,不过正常使用百分之九十情况是OK的,其核心原理与MAT工具类似。

关于leakcanary工具的配置使用方式这里不再详细介绍,因为真的很简单,详情点我参考官方教程学习使用即可。

PS:之前在优化性能时发现我们有一个应用有两个界面退出后Activity没有被回收(dumpsys meminfo发现一直在加),所以就怀疑可能存在内存泄露。但是问题来了,这两个Activity的逻辑十分复杂,代码也不是我写的,相关联的代码量也十分庞大,更加郁闷的是很难判断是哪个版本修改导致的,这时候只知道有泄露,却无法定位具体原因,使用MAT分析解决掉了一个可疑泄露后发现泄露又变成了概率性的。可以发现,对于这种概率性的泄露用MAT去主动抓取肯定是很耗时耗力的,所以决定直接引入leakcanary神器来检测项目,后来很快就彻底解决了项目中所有必现的、偶现的内存泄露。

总之一点,工具再强大也只是帮我们定位可能的泄露点,而最核心的GC ROOT泄露信息推导出泄露问题及如何解决还是需要你把住代码逻辑及泄露核心概念去推理解决。

3-2-4 Android应用内存泄露MAT工具定位分析

Eclipse Memory Analysis Tools(点我下载)是一个专门分析Java堆数据内存引用的工具,我们可以使用它方便的定位内存泄露原因,核心任务就是找到GC ROOT位置即可,哎呀,关于这个工具的使用我是真的不想说了,自己搜索吧,实在简单、传统的不行了。

PS:这是开发中使用频率非常高的一个工具之一,麻烦务必掌握其核心使用技巧,虽然Android Studio已经实现了部分功能,但是真的很难用,遇到问题目前还是使用Eclipse Memory Analysis Tools吧。

原谅我该小节的放荡不羁!!!!(其实我是困了,呜呜!)

3-2-5 Android应用开发规避内存泄露建议

有了上面的原理及案例处理其实还不够,因为上面这些处理办法是补救的措施,我们正确的做法应该是在开发过程中就养成良好的习惯和敏锐的嗅觉才对,所以下面给出一些应用开发中常见的规避内存泄露建议:

  • Context使用不当造成内存泄露;不要对一个Activity Context保持长生命周期的引用(譬如上面概念部分给出的示例)。尽量在一切可以使用应用ApplicationContext代替Context的地方进行替换(原理我前面有一篇关于Context的文章有解释)。

  • 非静态内部类的静态实例容易造成内存泄漏;即一个类中如果你不能够控制它其中内部类的生命周期(譬如Activity中的一些特殊Handler等),则尽量使用静态类和弱引用来处理(譬如ViewRoot的实现)。

  • 警惕线程未终止造成的内存泄露;譬如在Activity中关联了一个生命周期超过Activity的Thread,在退出Activity时切记结束线程。一个典型的例子就是HandlerThread的run方法是一个死循环,它不会自己结束,线程的生命周期超过了Activity生命周期,我们必须手动在Activity的销毁方法中中调运thread.getLooper().quit();才不会泄露。

  • 对象的注册与反注册没有成对出现造成的内存泄露;譬如注册广播接收器、注册观察者(典型的譬如数据库的监听)等。

  • 创建与关闭没有成对出现造成的泄露;譬如Cursor资源必须手动关闭,WebView必须手动销毁,流等对象必须手动关闭等。

  • 不要在执行频率很高的方法或者循环中创建对象,可以使用HashTable等创建一组对象容器从容器中取那些对象,而不用每次new与释放。

  • 避免代码设计模式的错误造成内存泄露。

关于规避内存泄露上面我只是列出了我在项目中经常遇见的一些情况而已,肯定不全面,欢迎拍砖!当然了,只有我们做到好的规避加上强有力的判断嗅觉泄露才能让我们的应用驾驭好自己的一亩三分地。

3-3 Android内存溢出OOM性能分析


上面谈论了Android应用开发的内存泄露,下面谈谈内存溢出(OOM);其实可以认为内存溢出与内存泄露是交集关系,具体如下图:

下面我们就来看看内存溢出(OOM)相关的东东吧。

3-3-1 Android应用内存溢出OOM概念

上面我们探讨了Android内存管理和应用开发中的内存泄露问题,可以知道内存泄露一般影响就是导致应用卡顿,但是极端的影响是使应用挂掉。前面也提到过应用的内存分配是有一个阈值的,超过阈值就会出问题,这里我们就来看看这个问题—–内存溢出(OOM–OutOfMemoryError)。

内存溢出的主要导致原因有如下几类:

  • 应用代码存在内存泄露,长时间积累无法释放导致OOM;

  • 应用的某些逻辑操作疯狂的消耗掉大量内存(譬如加载一张不经过处理的超大超高清图片等)导致超过阈值OOM;

可以发现,无论哪种类型,导致内存溢出(OutOfMemoryError)的核心原因就是应用的内存超过阈值了。

3-3-2 Android应用内存溢出OOM性能分析

通过上面的OOM概念和那幅交集图可以发现,要想分析OOM原因和避免OOM需要分两种情况考虑,泄露导致的OOM,申请过大导致的OOM。

内存泄露导致的OOM分析:

这种OOM一旦发生后会在logcat中打印相关OutOfMemoryError的异常栈信息,不过你别高兴太早,这种情况下导致的OOM打印异常信息是没有太大作用,因为这种OOM的导致一般都如下图情况(图示为了说明问题数据和场景有夸张,请忽略):

从图片可以看见,这种OOM我们有时也遇到,第一反应是去分析OOM异常打印栈,可是后来发现打印栈打印的地方没有啥问题,没有可优化的余地了,于是就郁闷了。其实这时候你留心观察几个现象即可,如下:

  • 留意你执行触发OOM操作前的界面是否有卡顿或者比较密集的GC打印;

  • 使用命令查看下当前应用占用内存情况;

确认了以上这些现象你基本可以断定该OOM的log真的没用,真正导致问题的原因是内存泄露,所以我们应该按照上节介绍的方式去着手排查内存泄露问题,解决掉内存泄露后红色空间都能得到释放,再去显示一张0.8M的优化图片就不会再报OOM异常了。

不珍惜内存导致的OOM分析:

上面说了内存泄露导致的OOM异常,下面我们再来看一幅图(数据和场景描述有夸张,请忽略),如下:

可见,这种类型的OOM就很好定位原因了,一般都可以从OOM后的log中得出分析定位。

如下例子,我们在Activity中的ImageView放置一张未优化的特大的(30多M)高清图片,运行直接崩溃如下:

//抛出OOM异常

10-10 09:01:04.873 11703-11703/? E/art: Throwing OutOfMemoryError “Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM”

10-10 09:01:04.940 11703-11703/? E/art: Throwing OutOfMemoryError “Failed to allocate a 743620620 byte allocation with 4194208 free bytes and 239MB until OOM”

//堆栈打印

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: FATAL EXCEPTION: main

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: Process: com.example.application, PID: 11703

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.application/com.example.myapplication.MainActivity}: android.view.InflateException: Binary XML file line #21: Error inflating class

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2610)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2684)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.app.ActivityThread.access$800(ActivityThread.java:177)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1542)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:111)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.os.Looper.loop(Looper.java:194)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:5743)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at java.lang.reflect.Method.invoke(Method.java:372)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:988)

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:783)

//出错地点,原因是21行的ImageView设置的src是一张未优化的31M的高清图片

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: Caused by: android.view.InflateException: Binary XML file line #21: Error inflating class

10-10 09:01:04.958 11703-11703/? E/AndroidRuntime: at android.view.LayoutInflater.createView(LayoutInflater.java:633)

通过上面的log可以很方便的看出来问题原因所在地,那接下来的做法就是优化呗,降低图片的相关规格即可(譬如使用BitmapFactory的Option类操作等)。

PS:提醒一句的是记得应用所属的内存是区分Java堆和native堆的!

3-3-3 Android应用规避内存溢出OOM建议

还是那句话,等待OOM发生是为时已晚的事,我们应该将其扼杀于萌芽之中,至于如何在开发中规避OOM,如下给出一些我们应用开发中的常用的策略建议:

  • 时刻记得不要加载过大的Bitmap对象;譬如对于类似图片加载我们要通过BitmapFactory.Options设置图片的一些采样比率和复用等,具体做法点我参考官方文档,不过过我们一般都用fresco或Glide开源库进行加载。

  • 优化界面交互过程中频繁的内存使用;譬如在列表等操作中只加载可见区域的Bitmap、滑动时不加载、停止滑动后再开始加载。

  • 有些地方避免使用强引用,替换为弱引用等操作。

  • 避免各种内存泄露的存在导致OOM。

  • 对批量加载等操作进行缓存设计,譬如列表图片显示,Adapter的convertView缓存等。

  • 尽可能的复用资源;譬如系统本身有很多字符串、颜色、图片、动画、样式以及简单布局等资源可供我们直接使用,我们自己也要尽量复用style等资源达到节约内存。

  • 对于有缓存等存在的应用尽量实现onLowMemory()和onTrimMemory()方法。

  • 尽量使用线程池替代多线程操作,这样可以节约内存及CPU占用率。

  • 尽量管理好自己的Service、Thread等后台的生命周期,不要浪费内存占用。

  • 尽可能的不要使用依赖注入,中看不中用。

  • 尽量在做一些大内存分配等可疑内存操作时进行try catch操作,避免不必要的应用闪退。

  • 尽量的优化自己的代码,减少冗余,进行编译打包等优化对齐处理,避免类加载时浪费内存。

可以发现,上面只是列出了我们开发中常见的导致OOM异常的一些规避原则,还有很多相信还没有列出来,大家可以自行追加参考即可。

3-4 Android内存性能优化总结


无论是什么电子设备的开发,内存问题永远都是一个很深奥、无底洞的话题,上面的这些内存分析建议也单单只是Android应用开发中一些常见的场景而已,真正的达到合理的优化还是需要很多知识和功底的。

合理的应用架构设计、设计风格选择、开源Lib选择、代码逻辑规范等都会决定到应用的内存性能,我们必须时刻头脑清醒的意识到这些问题潜在的风险与优劣,因为内存优化必须要有一个度,不能一味的优化,亦不能置之不理。

【工匠若水 http://blog.csdn.net/yanbober 转载请注明出处。点我开始Android技术交流】

4 Android应用API使用及代码逻辑性能分析

=============================

在我们开发中除过常规的那些经典UI、内存性能问题外其实还存在很多潜在的性能优化、这种优化不是十分明显,但是在某些场景下却是非常有必要的,所以我们简单列举一些常见的其他潜在性能优化技巧,具体如下探讨。

4-1 Android应用String/StringBuilder/StringBuffer优化建议


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


字符串操作在Android应用开发中是十分常见的操作,也就是这个最简单的字符串操作却也暗藏很多潜在的性能问题,下面我们实例来说说。

先看下面这个关于String和StringBuffer的对比例子:

//性能差的实现

String str1 = “Name:”;

String str2 = “GJRS”;

String Str = str1 + str2;

//性能好的实现

String str1 = “Name:”;

String str2 = “GJRS”;

StringBuffer str = new StringBuilder().append(str1).append(str2);

Android应用开发性能优化完全分析,移动应用开发课程报告相关推荐

  1. 转:Android应用开发性能优化完全分析

    转自:http://blog.csdn.net/yanbober/article/details/48394201 1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜 ...

  2. Android应用开发性能优化完全分析

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  3. 【安卓开发系列 -- APP 】APP 性能优化 -- 崩溃分析

    [安卓开发系列 -- APP ]APP 性能优化 -- 崩溃分析 [1]Native Crash 分析示例 [1.1]Linux 编译 breadpad 下载 breadpad 源码 git clon ...

  4. 抖音Android岗面试性能优化篇之Rhea(新一代全能型性能分析工具)【速看】

    写在前面的话 性能优化这个知识点是很多大厂面试中都会问到的问题,尤其是想要面试抖音的Android岗的朋友. 用户交互响应的耗时,作为 Android 用户日常感知最深的一项性能指标,在日常开发中有着 ...

  5. Android应用程序性能优化

    1 背景 其实有点不想写这篇文章的,但是又想写,有些矛盾.不想写的原因是随便上网一搜一堆关于性能的建议,感觉大家你一总结.我一总结的都说到了很多优化注意事项,但是看过这些文章后大多数存在一个问题就是只 ...

  6. Android面试汇总-Android内存和性能优化面试

    一.app优化 app优化:(工具:Hierarchy Viewer 分析布局 工具:TraceView 测试分析耗时的) App启动优化 布局优化 响应优化 内存优化 电池使用优化 网络优化 App ...

  7. android 内存和性能优化汇总

    1.即时编译(Just-in-time Compilation,JIT),又称动态转译(Dynamic Translation),是一种通过在运行时将字节码翻译为机器码,从而改善字节码编译语言性能的技 ...

  8. Android进阶:性能优化篇 Android进阶:性能优化篇

    Android进阶:性能优化篇 分类:Android 性能优化2011-08-09 17:06585人阅读评论(0)收藏举报 一.在使用Gallery控件时,如果载入的图片过多,过大,就很容易出现Ou ...

  9. ym——Android之ListView性能优化

    转载请注明本文出自Cym的博客(http://blog.csdn.net/cym492224103),谢谢支持! Android之ListView性能优化 假设有看过我写过的15k面试题的朋友们一定知 ...

最新文章

  1. 如何设计一个复杂的业务系统?从对领域设计、云原生、微服务、中台的理解开始...
  2. java swing 总结_java实验之swing图形用户界面程序设计及总结
  3. [密码学基础][每个信息安全博士生应该知道的52件事][Bristol Cryptography][第25篇]使用特殊的素数定义GF(p)和GF(2^n)的方法
  4. Mini C# Set class
  5. 苹果启动“突围”计划 建立自营金融服务
  6. java引用数据类型可以更改类型_Java改变引用数据类型的值
  7. 20.1 shell脚本介绍 20.2 shell脚本结构和执行 20.3 date命令用法 20.4 shell脚本中的变量...
  8. “敏捷开发”怎么就“敏捷”了
  9. linux搭建雷霆传奇h5源码,【雷霆传奇H5服务端】2020.07最新超好看大羽翼传奇网页游戏程序[附超详尽构建实例教程]...
  10. 个人投资——基本原则
  11. Week 3: 边下边播完整性校验作业
  12. 如何制作Android.9图片
  13. 借力函数计算 FC,HEROZ 打造专业级 AI 日本将棋服务
  14. 企业微信2020年翻倍增长背后:更新27个版本、迭代1000多次
  15. 北京峰会系列二|基于SPDK的UDisk全栈优化
  16. Keras下载数据失败,本地导入
  17. 常用手机uc浏览器兼容问题记录(陆续补充)
  18. 量子力学 or 线性代数(四)?
  19. 信息安全从业人员认证证书一览表
  20. 游戏充值订单金额修改思路与实践

热门文章

  1. 关于人机智能的几点思考
  2. 是不是程序员总会遇到一个瓶颈期,觉得自己没有进步了,该怎么解决?
  3. 2020寒假【gmoj2417】【Loan Repayment】【二分】
  4. 苹果iPhone白屏死机?如何修复?
  5. 【硬核】肝了一个月,Cisco网络工程师知识点总结
  6. 2021-12-13 云计算平台基础架构 swift
  7. 16*32点阵c语言,16*32点阵的原理
  8. 华宇软件华为鸿蒙,舒华体育携手华为打造:全球首款搭载鸿蒙操作系统跑步机面世...
  9. 【AI视野·今日NLP 自然语言处理论文速览 第十三期】Wed, 23 Jun 2021
  10. Unity 鼠标拖动UI