您的位置:首页 > 移动开发 > Android开发

内存优化和内存分析

2018-01-09 18:08 190 查看
        应用App内存的使用,也是评价一个应用性能高低的一个重要指标。虽然现在只能手机的内存越来越大,但是一个好的应用应当效率发挥到极致,精益求精。而现在有很多应用为了自己的利益,使用一些非常影响系统效率的方法,不仅败坏了Android的口碑,更极大地影响了系统的稳定性。例如某“X米”团购应用,在启动应用是会fork一个子线程,用于监听用户卸载应用。在KK下,该现场在卸载时不能被kill,而且每次启动都将fork新的进程,这就导致内存不断增高,极大地影响了低端机的使用体验。因此不管是什么应用,都应该把内存效率、用户体验放在首位,而不是为了满足自己的利益。

1.什么是内存

        由于Android应用的沙箱机制,每个应用所分配的内存大小是有限度的,内存太低就会出发LMK——Low Memory Killer机制。那么到底什么是内存呢?通常情况下我们所说的内存是指手机的RAM,它包括以下几个部分。

寄存器(Registers)
        速度最快的存储场所,因为寄存器位于处理器内部,在程序中无法控制。

栈(Stack)
        存放基本类型的数据和对象的引用,但本身不存放在栈中,而存放在堆中。

堆(Heap)
        堆内存用来存放new创建的对象和数组。在堆中分配的内存,由Java虚拟机的自动垃圾回收期(GC)来管理。

静态存储区(Static Field)
        静态存储区是指在固定位置存放应用程序运行时一直存在的数据,在Java内存中专门划分了一个静态存储区来管理一些特殊的数据变量如静态的数据变量。

常量池(Constant Pool)
        Java虚拟机必须为每个被卸载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集合,包括常量(基本类型,String)和对其他类型、字段、方法的符号引用。

        在这些概念中,最容易被搞错的就是堆和栈的划分。当定义一个变量,Java虚拟机就会在栈中为该变量分配内存空间,当该变量作用域结束后,这部分内存控件马上被用作新的控件进行分配。如果使用new方式创建一个变量,那么就会在堆中为这个对象分配内存控件,即使该对象作用域结束,这部分内存也不会立即被回收,而是等待系统GC进行回收。堆的大小随着手机的不断发展而不断变大。在程序中,可以如用如下代码来堆的大小,所谓的内存缝隙,正式分析Heap中的内存状态。

ActivityManager manager = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
int heapSize = manager.getLargeMemoryClass();


2. 获取Android系统内存信息

2.1Process Stats

        Process Stats是KK上新增的一个内存监视服务,可以通过“Setting-Developeroptions-Process Stats”来开启该功能界面,如图所示。

       


        同样,也可以使用Dumpsys命令来获取这些信息,命令如下所示。

adb shell dumpsys procstats

2.2 Meminfo

        Meminfo也是系统上一个非常重要的内存监视工具,可以通过Settings-Apps-Running中打开这一界面,如图所示。

        


        同样,也可以是用Dumpsys命令,命令如下所示。

adb shell dumpsys meminfo


3.内存回收

        Java对于C、C++这类语言最大的优势就是不用手动管理系统资源,Java创建了垃圾收集器现场来自动进行资源的管理。这样做的好处是大大降低了程序开发人员对内存管理的频繁工作。但这也带来了很多问题,例如Java的GC是系统自动进行的,何时进行确实开发者无法控制的,即使调用System.gc()方法,也只是建议系统进行GC。但系统是否采纳你的监狱,那就不一定了。JVM虚拟机虽然能够自动控制GC,但再强大的算法,也难免会存在部分对象忘记回收的现象发生,这就是造成内存泄漏的原因。

4.内存优化实例

        下面来看两个内存优化的实例,分别从Bitmap和代码两个角度来对内存进行优化。

4.1 Bitmap优化

        Bitmap是造成内存占用过高甚至是OOM(Out Of Memory)的最大威胁。下面给出一些使用Bitmap的小技巧。
使用适当分辨率和大小的图片
        由于Android系统再做资源适配的时候会对不同分辨率的文件夹下的图片进行缩放来适配相应的分辨率,如果图片分辨率与资源文件夹分辨率不匹配或者图片分辨率太高,就会导致系统消耗跟多的内存资源。同时,在适当的时候,应该显示合适大小的图片,例如在图片列表界面可以使用图片的缩略图thumbnails,而在显示详细图片的时候再显示原图;或者在对图像要求不高的地方,尽量降低图片的精度。
即时回收内存
        一旦使用完Bitmap后,一定要及时使用bitmap.recycle()方法释放内存资源。自Android3.0之后,由于Bitmap被放置到了堆中,其内存由GC管理,就不需要进行释放了。
使用图片缓存
        通过内存缓存(LruCache)和硬盘缓存(DiskLruCache)可以更好地使用Bitmap。

4.2 代码优化

        任何Java类,都将占用大约500字节地内存控件。创建一个类地实例会消耗大约15字节地内存。从代码地实现方式上也可以对内存进行优化,这里总结了一些小地技巧。
对常量使用static修饰符。
使用静态方法,静态方法会比普通方法提高15%左右地访问速度。
减少不必要的成员变量,这点在Android Lint工具上已经集成检测了,如果一个变量可以定义为局部变量,则会建议你不要定义为成员变量。
减少不必要的对象,使用基础类型会比使用对象更加节省资源,同时更应该避免频繁穿件短作用域的变量。
尽量不要使用美剧、少用迭代器。
对Cursor、Receiver、Sensor、File等对象,要非常注意对他们的创建、回收与注册、解注册。
避免使用IOC框架,IOC通常使用注解反射来进行实现,虽然现在Java反射的效率已经进行了很好的优化,但大量使用反射依然会带来性能的下降。
使用RenderScript、OpenGL来进行非常复杂的绘图操作。
使用SurfaceView来替代View进行大量、频繁的绘图操作。
尽量使用视图缓存,而不是每次都之下inflate()方法解析视图。

5. Lint工具

        AndroidLint工具时AndroidStudio中集成的一个Android代码提示工具,它可以给你的布局、代码提高非常强大的帮助。例如在前面的的那个例子,如果在布局文件中写了三层的冗余LinearLayout布局,在布局编辑器的右边可以看到这样的警告,如图所示。



         当然,这里只是一个很简单的例子,Lint的功能非常强大,大家应该养成在写完代码后检查Lint提示的习惯,这不仅可以让你及时发现代码中隐藏的一些问题,更能让你养成良好的代码风格,要知道,这些Lint提示可都是Google工程师们汗水和智慧的结晶。

6.使用Android Studio的Memory Monitor工具

        Memory Monitor工具是Android Studio自带的一个内存监视工具,他可以很好的帮助我们进行内存实时分析。通过点击Android Studio左下角的“Android Profiler”标签,打开Memory Monitor工具,如图所示。



        途中较浅的蓝色代表free的内存,而深色的部分代码使用的内存从内存变换的走势图变换,可以判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存突然减少时,可能发生了GC等。

7.使用TraceView工具优化App性能

     
        TraceView是一个Android下的可视化性能调查工具,它用来分析TraceView日志。

7.1生成TraceView日志的两种方法

        生成TraceView日志有两种方法,一个是利用debug类帮助我们生成日志文件,另一个是利用Android Device Monitor工具辅助生成日志文件。

7.1.1通过代码生成精确范围的TraceView日志

        视图Debug类的方法开启TraceView监听。通过调用Debug.startMethodTracing()方法开启监听没通过调用Debug.stopMethodTracking()方法结束监听,我们可以使用这两个方法来包围要监听的代码块,例如在onCreate()方法里调用startMethodTracing()方法来开始监听,在onDestory()方法里使用stopMethodTracking()方法来结束监听。TraceView的日志将会保存到"/sdcard/dmtrace.trace“目录下,因此,需要在Mainifest文件中增加如下所示的权限。

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>


        当然,除了使用默认的输出日志名,也可以自定义路径和日志名。
        当要监听的内容执行完毕后,通过ADB命令将日志文件导出到本地,命令如下所示。
adb pull /sdcard/dmtrace.trace trace.txt

7.1.2通过Android Device Monitor生成TraceView日志

        打开AndroidStudio的Android Device Monitor的工具,选择要调式的进程,点击工具栏中的"start method profiling”按钮,如图所示。



        点击后会弹出提示,选择监听的模式,如图所示。



        TraceView提供了以下两种监听方式。
整体监听
        跟踪每个方法执行的全部过程,这种方式资源消耗较大。
抽样监听
        按照指定的频率来进行抽样调查,这种方式需要执行较长时间来获取较长时间来获取较准确的样本数据。
        执行一段时间后,再次点击"start method profiling”按钮可结束监听。

7.2打开TraceView日志

        对于导出的TraceView日志文件,可以使用SDK中的"sdk\tools\traceview.bat”工具来打开。或者在AMD工具中,在“file”菜单下选择“open file..."选项打开TraceView日志文件。

7.3分析TraceView日志

        TraceView的分析界面分为两部分,上面是用于显示方法执行时间的时间轴区域,下面是显示详细信息的profile区域

7.3.1 时间轴区域

        时间轴区域如图所示。



        时间轴区域显示了不同线程在不同的时间段内的执行情况,在时间轴中,每一行都代表了一个独立的线程。

        使用鼠标滚轮可以放大时间轴,如图所示。



        不同的色块代表了不同的执行方法,色块的长度,代表了方法所执行的时间。

7.3.2 Profile区域

        Profile区域内显示了你选择的色块所代表的方法在该色块所处的时间段内的性能分析。如图所示。



        在Profile区域中主要显示以下的信息。

Incl CPU Time——某方法占用CPU的时间。
Excl CPU Time——某方法本身(包括子方法)占用CPU的时间。
Incl Real Time——某方法真正执行的时间。
Excl Real Time——某方法本身(不包括子方法)真正执行的时间。
Calls+RecurCalls——调用次数+递归回调的次数。
        每个时间都包含两列,一个是实际的时间,另一个是百分比。分析的时候,通常从Incl CPU Time和Calls+RecurCalls开始进行分析,对占用时间长的方法进行重点分析,如果占用时间长且Calls+RecurCalls次数少,那么就可以列为怀疑对象了。

8.使用MAT工具分析App内存状态

        MAT(Memory Analyzer Tool)工具是一个分析内存的强力助手。下载地址:MAT下载地址

8.1生成HPROF文件

        首先打开Android Device Monitor工具,选择要监听的线程,并点击菜单栏中的”Update Heap“按钮,如图所示。

       


        在Heap标签中,点击”Cause GC“按钮,就会显示出当前的内存状态,如图所示。



        这里有一个判断当前是否存在内存泄漏的小技巧:当我们不停地点击”Cause GC“按钮时,如果”data object“一栏中的”Total Size"有明显变化,就代表可能存在明显变化,就代表可能存在内存泄漏。

        上面时手动查看Heap状态,下面点击菜单栏的“Dump HPROF File”按钮,如图所示。

        


        等待几秒后系统就会生成一个.hprof文件,我们要分析的就是这个文件。将它保存到PC上,默认包名为.hprof。不过对这个文件我们还不能直接使用MAT工具进行分析,还需要进行格式转换。在命令行下,切换到SDK目录的platform-tools目录下,使用hprof-conv工具帮助我们进行转换,命令如下所示:

hprof-conv F:\com.qisi.orderingsystem.a920server.hprof heap.hprof

        命令格式为"hprof-conf infile outfile",使用生成的heap.hprof文件就可以利用MAT工具进行内存分析了。

8.2 分析HPROF

        打开MAT工具,选择“Open 啊 Heap Dump”选项,如图所示。



        等待文件导入后,显示分析结果如图所示。

 


        在上图中的报告里已经对整个内存状态进行了初始的分析,并给出了简要的分析结果。后面还可以进行更深入的分析,MAT功能强大,这里主要看以下几个功能。

Histogram
        Histogram直方图,用于显示内存中每个对象的数量、大小和名称。点击打开Histogram标签,如图所示。



        在最上方一行,可以通过搜索过滤相应的关键字,这点在分析内存中时非常有用的,比如可以过滤“Order”关键字,查询结果如图所示。



        在选择的对象上单击鼠标右键,在弹出的快捷菜单中选择“List objects-with incoming references"选项查看具体对象。

Dominator Tree
        Dominator Tree支配树会将内存中的对象按照大小进行排序,并显示对象之间的引用对象之间的引用结构。点击打开Dominator Tree标签,显示如图10.28所示。



        对象已经按照”Retained Heap“进行排序了,即按照对象及其所持有的引用的内存总和进行排序。通过分析内存占用大的对象来找出内存消耗的原因。

9.使用Dumpsys命令分析系统状态

        使用Dumpsys命令可以列出Android系统相关的信息和服务状态。Dumpsys命令的功能非常强大,可以使用参数配置也非常多哦。

        使用Dumpsys所支持的命令非常多,参数大致列举如下所示。

SurfaceFlinger
accessibility
account
activity
alarm
android.security.keystore
appops
appwidget
assetatlas
audio
backup
battery
batterypropreg
batterystats
bluetooth_manager
clipboard
commontime_management
connectivity
consumer_ir
content
country_detector
cpuinfo
dbinfo
device_policy
devicestoragemonitor
diskstats
display
display.qservice
dreams
drm.drmManager
dropbox
entropy
gfxinfo
hardware
input
input_method
iphonesubinfo
isms
location
lock_settings
media.audio_flinger
media.audio_policy
media.camera
media.player
media_router
meminfo
mount
netpolicy
netstats
network_management
nfc
notification
package
permission
phone
power
print
procstats
samplingprofiler
scheduling_policy
search
sensorservice
serial
servicediscovery
simphonebook
sip
statusbar
telephony.registry
textservices
uimode
updatelock
usagestats
usb
user
vibrator
wallpaper
wifi
wifip2p
window

        使用Dumpsys命令时,只需要输入"adb shell dumpsys + 以上参数”即可。例如使用如下所示命令来获取Activity栈的详细信息。

adb shell dumpsys activity


        下面的列表中,总结了一些常用的Dumpsys参数。



        配合Linux下的Shell命令,如 “grep”、“find”等,可以让Dumpsys命令发挥非常大的左右。这进行性能绣花、Bug分析时时非常有用的。

        性能优化时一个非常具有挑战性的工作,上面列举了很多分析内存、优化内存的方法,但真正的优化工作远不止这么简单。这里只是列举了一些入门的方法,而要进行完美的内存优化、内存分析绝非一日之功,需要开发者有身后的技术功底和耐心。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Android性能优化