标签:
本章以实际案例分析在android开发中,性能方面的优化和处理。设计到知识点有弱引用,memory monitor,Allocation Tracker和leakcanary插件。
下载bug项目:https://github.com/lzyzsd/MemoryBugs,请注意配合使用MemoryMonitor, AllocationTracker以及HeapDump,LeakCanary等工具来查找潜在的内存问题,并尝试解决。
AndroidStudio提供了Memory Monitor来实时显示应用运行时内存占用情况,下边蓝色部分是现在占用的内存,上面灰色的部分显示是已回收的内存。如果在图上看到尖峰,也就是快速分配内存又被回收,也就是发生了内存抖动,这里就是需要优化的地方。在模拟器或者真机中Debug项目,可实时监看Memory,CPU,NetWork等的资源占用情况。
单击Allocation tracker标签,就会打开一个新的窗口,单击“Start Tracing”按钮;然后,让应用运行你想分析的代码。运行完毕后,单击“Get Allocations”按钮,一个已分配对象的列表就会出现第一个表格中。
单击第一个表格中的任何一项,在表格二中就会出现导致该内存分配的栈跟踪信息。通过allocation tracker,不仅知道分配了哪类对象,还可以知道在哪个线程、哪个类、哪个文件的哪一行。查看方式如下:
轮胎图是以圆心为起点,最外层是其内存实际分配的对象,每一个同心圆可能被分割成多个部分,代表了其不同的子孙,每一个同心圆代表他的一个后代,每个分割的部分代表了某一带人有多人,双击某个同心圆中某个分割的部分,会变成以你点击的那一代为圆心再向外展开。如果想回到原始状态,双击圆心就可以了。
圆心是起点处,如果你把鼠标放到我图中标注的区域,会在右边显示当前指示的是什么线程(Thread1)以及具体信息(分配了8821次,分配了 564.18k的内存),但是红框标注的区域并不代表Thread1,而是第一个同心圆中占比最大的那个线程,所以现在把鼠标放到第一个同心圆上,可 以看出来,划过同心圆的轨迹时可以看到右边的树枝的变化情况。
内存波动指不合理的设计,在一瞬间创建了多个对象,有及时释放时产生影响内存性能的现象。案例中的问题代码如下:
执行代码引起内存波动的分析图如下:
不应该在for语句里面频繁的New对象。可先在外面创建好一个对象,在for里面直接调用对象。具体代码修改,请参考最后上传性能改良后的代码。改良后内存监控如同所示:
Android中使用Handler造成内存泄露的例子代码如下:
1 Handler mHandler = new Handler() { 2 @Override 3 public void handleMessage(Message msg) { 4 mImageView.setImageBitmap(mBitmap); 5 } 6 }
上面是一段简单的Handler的使用。当使用内部类(包括匿名类)来创建 Handler的时候,Handler对象会隐式地持有一个外部类对象(通常是一个Activity)的引用(不然就不可能通过Handler来操作 Activity中的View)。而Handler通常会伴随着一个耗时的后台线程(例如从网络拉取图片)一起出现,这个后台线程在任务执行完毕(例如 图片下载完毕)之后,通过消息机制通知Handler,然后Handler把图片更新到界面。然而,如果用户在网络请求过程中关闭了Activity,正常情况下,Activity不再被使用,它就有可能在GC检查时被回收掉,但由于这时线程尚未执行完,而该线程持有Handler的引用,这个Handler又持有Activity的引用,就导致该Activity无法被回收(即内存泄露),直到网络请求结束(例如图片下载完毕)。另外,如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条 Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
内存泄露的危害
内存泄露会出现虚拟机占用内存过高的危害,导致OOM(内存溢出),程序出错。对于Android应用来说,用户打开一个Activity,使用完之后关闭它,内存泄露;又打开,又关闭,又泄露;几次之后,程序占用内存超过系统限制,致使程序崩溃。使用Handler导致内存泄露的解决方法如下:
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
2.如果Handler是被delay的Message持有了引用,使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
静态类不持有外部类的对象,所以Activity可以随意被回收。代码如下:
1 static class MyHandler extends Handler { 2 @Override 3 public void handleMessage(Message msg) { 4 mImageView.setImageBitmap(mBitmap); 5 } 6 }
声明静态类后,由于Handler不再持有外部类对象的引用,导致程序不允许该Handler去操作Activity中的对象了。解决方法是在Handler中增加一个对Activity的弱引用(WeakReference)。参考代码如下:
1 static class MyHandler extends Handler { 2 WeakReference<Activity > mActivityReference; 3 MyHandler(Activity activity) { 4 mActivityReference= new WeakReference<Activity>(activity); 5 } 6 7 @Override 8 public void handleMessage(Message msg) { 9 final Activity activity = mActivityReference.get(); 10 if (activity != null) { 11 mImageView.setImageBitmap(mBitmap); 12 } 13 } 14 15 }
WeakReference简介
WeakReference 弱引用,与强引用(即我们常说的引用)相对,它的特点是,GC在回收时会忽略掉弱引用,即就算有弱引用指向某对象,但只要该对象没有被强引用指向(实际上多数时候还要求没有软引用,但此处软引用的概念可以忽略),该对象就会在被GC检查到时回收掉。对于上面的代码,用户在关闭Activity之后,就算后台线程还没结束,但由于仅有一条来自Handler的弱引用指向Activity,所以GC仍然会在检查的时候把Activity回收掉。这样,内存泄露的问题就不避免了。
案例中存在hander内存泄露的代码截图如下:
通过上一章介绍的leakcanary插件,可检查到内存泄露相关的代码,leakcanary检查运行截图如下:
本项目源代码在360云盘上,开发环境为 Android Studio 2.0 。
https://yunpan.cn/cPSA8uyeL2CwY 访问密码 dc98。文件名称:android性能改良代码。
标签:
原文地址:http://www.cnblogs.com/wlandwl/p/android_13.html