内存泄露--contentView缓存使用与ListView优化_android listview 内存泄漏-程序员宅基地

技术标签: Android  


引起Android内存泄露有很多种原因,下面罗列了一些问题,以后会一一解决

1、构造Adapter时没有使用缓存convertView(衍生出ListView优化问题)

2、查询数据库游标没有关闭

3、Activity中生命周期对象大于Activity生命周期(关于Application Context与Activity Context)

4、Bitmap对象不使用时没有recycle掉(这里还有其他解决方案)

 

今天说的是第一种:如何使用缓存来优化ListView

因为如果不使用缓存convertView的话,调用getView时每次都会重新创建View,这样之前的View可能还没有销毁,加之不断的新建View势必会造成内存泄露。

使用getView时有3方案:(1)没有使用convertView,(2)使用convertView,(3)使用convertView+静态类ViewHolder

 

我做了一个测试,代码在下面,创建2000个View,从0拉到最后,计算总共耗,同时显示GC释放内存的大小,三种测试的结果如下:

注:这里先说下 GC_EXTERNAL_ALLOC freed 7K, 18% free 11153K/13511K, external 1632K/1672K, paused 89ms 的意思

  在Dalvik中,为一个程序分配的内存要根据机型的不同而不同,一般为32M,而虚拟机会把这些内存分别分配给,JAVA使用的堆内存(heap)和Nativie使用的内存(external)(即虚拟机中通过JNI调用本地Nativie的类中malloc分配的内存,如Bitmap,java.nio.ByteBuffers)。不过两者不同共享,也就是说Native的内存不够用了,而JAVA内存够用时是不能向JAVA申请的,必须向虚拟机申请才行,当虚拟机无法分配的时候就会报OOM的错误

freed 7k:表示GC已经释放了7K的内存

18% free 11153K/13511K:表示JAVA使用的堆内存(对象存在于此),18% free表示当前剩余18%的堆内存(heap memory),11153K表示当前已用的堆内存,13511K表示堆内存总共大小(网上有些文章这部分弄错了,很多转载都是同一个)

external 1632K/1672K:1632K表示已用external memory,总共1672K external memory(注意:这个可能只存在于Android 3.0之前)

paused 89ms:这里其实包括了两部分,一个是在调用GC之前暂停的时间,一个是调用GC后基本完成时暂停的时间

详细可参考:http://stackoverflow.com/questions/4550757/android-logs-gc-external-alloc-gc-for-malloc

 

(1)没有使用convertView

  没有任何处理,不建议这样写。如果数据量少可以,但是如果列表项数据量很大的时候,会每次都重新创建View,设置资源,严重影响性能,所以从一开始就不要用这种方式

复制代码
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();
            View item = mInflater.inflate(R.layout.list_item, null);
            ImageView img = (ImageView)item.findViewById(R.id.img);
            TextView title = (TextView)item.findViewById(R.id.title);
            TextView info = (TextView)item.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");
            
            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return item;
        }
复制代码

测试结果:

目前VM只为他们分配了5767K+518k的内存,而内存峰值是32M

刚开始时,而且heap memory只申请了5767K,已用内存3353K,注意数据大小的变化:耗时:167633055ns = 0.167633055秒

当拉到1000的时候,堆内存总计已经申请了9607K,已用内存7245K,明显已经比刚开始时要大了 ,耗时:3435241667ns=3.435241667秒

当拉到2000的时候,堆内存总计13511K,已用内存11153K,耗时:6660369835ns = 6.660369835秒

---------------------------我又创建了10000个ListView,测试后直到内存泄露,证明峰值却是是32M,而不使用convertView导致的内存泄露,当内存泄露时手机会提示force close,并将错误写入/data/anr/traces.txt中,你可以adb pull下来查看具体信息

(2)使用convertView后的测试数据(优化后)

  通过缓存convertView,convertView可以缓存可视范围内的convertView,当再次向下滑动时又开始更新View,这种利用缓存convertView的方式可以判断如果缓存中不存在View才创建View,如果已经存在可以利用缓存中的View,这样会减少很多View的创建,提升了性能

 

复制代码
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            if(convertView == null)
            {
                convertView = mInflater.inflate(R.layout.list_item, null);
            }
            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();
            
            ImageView img = (ImageView)convertView.findViewById(R.id.img);
            TextView title = (TextView)convertView.findViewById(R.id.title);
            TextView info = (TextView)convertView.findViewById(R.id.info);
            img.setImageResource(R.drawable.ic_launcher);
            title.setText("loulijun");
            info.setText("www.cnblogs.com/loulijun");
            
            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }
复制代码

 

测试数据我还是用2000吧,10000太大了(一万年太久,只争朝夕)

测试结果:

这次一直拉到最后明显比刚才流畅多了,而且GC释放内存的次数也明显少了很多,最后用的时间和当前使用的内存也小很多,优化后的确好多了

当position为1000的时候,附近没怎么调用GC,用时:213653551ns=0.213653551秒,额,差距有点大,上面到达1000时用时达到3.43秒之多。

当position为2000的时候,已用内存只有3068K,堆总共内存6215K,而且external memory是0K,用时:378326396ns = 0.378326396秒,性能差距如此之大,都有点不敢相信。也不知道这种方式对不对,如有不妥的地方,还希望大牛能给出正确回答

(3)使用contentView+静态类ViewHolder类

  通过convertView+ViewHolder来实现,ViewHolder就是一个静态类,使用 ViewHolder 的关键好处是缓存了显示数据的视图(View),加快了 UI 的响应速度。

当我们判断 convertView == null  的时候,如果为空,就会根据设计好的List的Item布局(XML),来为convertView赋值,并生成一个viewHolder来绑定converView里面的各个View控件(XML布局里面的那些控件)。再用convertView的setTag将viewHolder设置到Tag中,以便系统第二次绘制ListView时从Tag中取出。(看下面代码中)

如果convertView不为空的时候,就会直接用convertView的getTag(),来获得一个ViewHolder。

静态类ViewHolder

复制代码
//定义静态类ViewHolder
    static class ViewHolder
    {
        public ImageView img;
        public TextView title;
        public TextView info;
    }
复制代码
复制代码
@Override
        public View getView(int position, View convertView, ViewGroup parent) {
            //Get a View that displays the data at the specified position in the data set.
            
            //开始计时,性能测试用nanoTime会更精确,因为它是纳秒级的
            long startTime = System.nanoTime();
            ViewHolder holder;
            
            if(convertView == null)
            {
                holder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item, null);
                holder.img = (ImageView)convertView.findViewById(R.id.img);
                holder.title = (TextView)convertView.findViewById(R.id.title);
                holder.info = (TextView)convertView.findViewById(R.id.info);
                convertView.setTag(holder);
            }else
            {
                holder = (ViewHolder)convertView.getTag();
                holder.img.setImageResource(R.drawable.ic_launcher);
                holder.title.setText("loulijun");
                holder.info.setText("www.cnblogs.com/loulijun");
            }
                
            //停止计时
            long endTime = System.nanoTime();
            //耗时
            long spendTime = (endTime - startTime);
            
            sumTime += spendTime;
            Log.d("GoogleIO", "position at:"+position+"--sumTime:"+String.valueOf(sumTime));
            return convertView;
        }
复制代码

到这里,可能会有人问ViewHolder静态类结合缓存convertView与直接使用convertView有什么区别吗,是否重复了

在这里,官方给出了解释

提升Adapter的两种方法

To work efficiently the adapter implemented here uses two techniques:
-It reuses the convertView passed to getView() to avoid inflating View when it is not necessary

(译:重用缓存convertView传递给getView()方法来避免填充不必要的视图)
-It uses the ViewHolder pattern to avoid calling findViewById() when it is not necessary

(译:使用ViewHolder模式来避免没有必要的调用findViewById():因为太多的findViewById也会影响性能)
ViewHolder类的作用
-The ViewHolder pattern consists in storing a data structure in the tag of the view
returned by getView().This data structures contains references to the views we want to bind data to,
thus avoiding calling to findViewById() every time getView() is invoked

(译:ViewHolder模式通过getView()方法返回的视图的标签(Tag)中存储一个数据结构,这个数据结构包含了指向我们

要绑定数据的视图的引用,从而避免每次调用getView()的时候调用findViewById())

 

测试数据:(跟直接使用convertView数据相差不多)

当position为1000时,用时:199188216ns = 0.199188216秒,堆内存的时候也没比没有使用convertView理想的多

当position为2000时,用时:336669887ns = 0.336669887秒,比直接使用convertView的方式稍微好一点点,不过性能相差不多

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/CrazyChickOne/article/details/8132927

智能推荐

常见文件格式的文件头数值-程序员宅基地

文章浏览阅读390次。常见文件格式的文件头数值JPEG (jpg),文件头:FFD8FFPNG (png),文件头:89504E47GIF (gif),文件头:47494638TIFF (tif),文件头:49492A00Windows Bitmap (bmp),文件头:424DCAD (dwg),文件头:41433130Adobe Photoshop (psd),文件头:38425053..._常见文件格式的文件头数值

安装leach-234,./configure出现‘Installation of tcl seems incomplete or can't be found automaticall'怎么办_configure: error: installation of tcl seems incomp-程序员宅基地

文章浏览阅读1.5k次。# ./configurechecking for gcc... gccchecking whether the C compiler works... yeschecking for C compiler default output file name... a.outchecking for suffix of executables... checking whether we are c..._configure: error: installation of tcl seems incomplete or can't be found aut

解决Could not resolve com.github.tbruyelle:rxpermissions:0.10.2.-程序员宅基地

文章浏览阅读3.5k次。android studio 运行项目报 Could not resolve com.github.tbruyelle:rxpermissions:0.10.2.如图:提示说是没有这个版本库的缓存可以用于离线模式,搞了半天,最后发现原来是gradle设置那里把离线模式打开了,在Android Studio ---Tool Windows ---Gradle,打开如图,设置Gradle:Toggle Offline Mode, 点击这个按钮为非选中状态,即非离线状态,然后再运行项._could not resolve com.github.tbruyelle:rxpermissions:0.10.2.

利用libwebsockets写ws、wss服务端和客户端_libwsclient_send-程序员宅基地

文章浏览阅读3.2k次。服务端:server.c#include "libwebsockets.h"#include <signal.h>#include <string.h>static volatile int exit_sig = 0;#define MAX_PAYLOAD_SIZE 10 * 1024void sighdl( int sig ) { lwsl_notice( "%d traped", sig ); exit_sig = 1;}/** _libwsclient_send

Linux系统安装与配置_装linux系统配置-程序员宅基地

文章浏览阅读856次,点赞18次,收藏30次。Linux系统的安装过程需要进行准备工作,并按照一定的步骤进行。用户需要选择适合自己的发行版、下载ISO镜像文件、制作启动盘或启动U盘、备份重要数据和检查硬件要求。安装步骤包括选择启动设备、进入安装程序、分区、安装基本系统、设置用户账户、安装引导程序和完成安装。在安装过程中,用户可能会遇到各种问题,例如无法启动计算机、无法识别硬盘、安装程序卡住、安装程序出现错误等。用户可以根据问题的具体情况,采取相应的解决方法。_装linux系统配置

4.19最新携程酒店价格爬虫_携程的调价助手是爬虫吗-程序员宅基地

文章浏览阅读2.2k次。在最近的4月,携程的反爬又升级了,加入了canvas指纹识别,反selenium操作,更多的cookies加密,不过思路还是以往的思路,通过正确的参数请求获得加密脚本,从而获得正确的eleven参数,请求得到ComplexHtml,解密正确的html.目前js代码已经扒出来了,可实现脱机操作,稳定的解密eleven参数,稳定获取酒店价格相关信息,以下是截图:有需要的可..._携程的调价助手是爬虫吗

随便推点

Java定时任务-ScheduledExecutorService_scheduledexecutortask-程序员宅基地

文章浏览阅读1.1k次。1.ScheduledExecutorService的接口介绍package java.util.concurrent;public interface ScheduledExecutorService extends ExecutorService { //单次执行,在指定延时delay后运行command任务 public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);_scheduledexecutortask

处理Oracle中杀不掉的锁-程序员宅基地

文章浏览阅读147次。一些ORACLE中的进程被杀掉后,状态被置为"killed",但是锁定的资源很长时间不释放,有时实在没办法,只好重启数据库。现在提供一种方法解决这种问题,那1.就是在ORACLE中杀不掉的,2.在OS一级再杀。 1.下面的语句用来查询哪些对象被锁: select a.object_name,a.object_type,s.osuser,s.username,s.status,machine,s.s..._locked mode 为6杀不掉

基于用户乘车行为大数据的客流分析及预测模型_轨道交通大数据分析模型有哪些-程序员宅基地

文章浏览阅读1.1k次。目录 一.项目说明 1项目背景 1问题说明 1任务要求 1二.包含技术 2本项目用到的技术 2三.数据格式 2客流数据 2天气数据 2构建数据 2四.设计过程 3环境配置 3 1.相应库的导入 3 2.数据的读取 4 3.数据探索 4 4.数据合并 5 5.数据预处理 5 5.1查看数据分布 5 5.2异常数据处理 5 5.3构造特征值 7 6.特征相关性分析 7 7.目标相关性分析 8 8.LSTM模型搭建 8 9.预测及结果可视化 9五.个人总结 10_轨道交通大数据分析模型有哪些

java转双层pdf文件_双层ofd转pdf时报错,带图片带坐标的那种格式-程序员宅基地

文章浏览阅读1.9k次。双层ofd转pdf时报错,带图片带坐标的那种格式org.ofdrw.reader.BadOFDException: OFD解析失败,原因:无法在目录: C:\Users\ADMINI~1\AppData\Local\Temp\ofd-tmp-3503787938540531089\Doc_0中找到,文件 [ Annotations.xml ]at org.ofdrw.reader.OFDReade..._无法在目录: /ofd-tmp-72300205115873

Kotlin与Java互操作-程序员宅基地

文章浏览阅读463次。互操作就是在Kotlin中可以调用其他编程语言的接口,只要它们开放了接口,Kotlin就可以调用其成员属性和成员方法,这是其他编程语言所无法比拟的。同时,在进行Java编程时也可以调用Kotlin中的API接口。Kotlin调用JavaKotlin在设计时就考虑了与Java的互操作性。可以从Kotlin中自然地调用现有的Java代码,在Java代码中也可以很

单片机设计资料,仿真、程序、原理图收集好资料分享,stm32、8086、单片机方案_多个完整设计的单片机、8086、stm32制作教程和资料-转发分享-程序员宅基地

文章浏览阅读3.8k次,点赞9次,收藏112次。偶然网上保存的资料,单片机设计资料,仿真、程序、原理图收集的资料转发分享,有需要的拿走。资源下载地址百度网盘基于单片机红外自动触发计时器protues仿真基于单片机汇编语言抢答器系统设计基于单片机火灾声光报警仿真系统设计基于单片机货车重量监测系统基于单片机霍尔传感器电机测速系统基于单片机计分器控制系统设计(含AD)基于单片机继电器及双向可控硅控制照明设备基于单片机加热器电饭煲仿真设计基于单片机家具窗帘控制系统设计基于单片机家用厨房天然气火灾报警器仿真基于单片机简易报警器控制系统._多个完整设计的单片机、8086、stm32制作教程和资料-转发分享