首页 文章 精选 留言 我的

精选列表

搜索[Android],共10000篇文章
优秀的个人博客,低调大师

Android项目:模仿ConvertView原理(ListView的getView方法)对View对象进行回收和复用

在项目优化过程中,通过MAT监控发现存在一处内存泄露,反复进入某个页面,内存占用越来越大。后分析找到了泄露原因,原来是在自定义列表中,将行布局的layout文件inflate成view对象的时候,每加载一次列表就要new出一组新的view对象。因为没有对这些布局一致的view进行复用,又没法及时释放,导致了列表的行布局对象越积越多,造成内存泄露。 解决这个oom问题,首先想到了listview加载中对convertview的回收和复用的方法。于是模仿convertview的原理写了个对view对象进行回收和复用的类,此处类名为ViewRecycler,使用后有效的解决了view对象的复用,远离这个棘手的oom问题。由于项目中需要复用的view对象布局都是一样的,此方法只考虑了复用同一布局的情况。同时,项目中的列表已有获取显示行与隐藏行的相应接口,此方法仅主要从回收与复用的逻辑层面加以实现,并未涉及任何底层代码部分。 一、以下是具体的实现过程: 1.首先ViewRecycler类需要两个容器分别用来保存活动的View对象和回收可复用的View对象。一个map用来保存回收的View(无序添加),一个数组用来记录当前显示的行号以及对应的View(有序添加)。 如下: 1 2 private SparseArray<View> recycleViews; // 废弃的view private View[] activeViews = new View[ 0 ]; //正在使用的view 注:key为int类型的HashMap用SparseArray代替,会有更好的性能. 2.每次列表刷新或变化,就更新一次activeViews的大小。即activeViews的数组长度与当前列表的总行数一致。在刷新列表或者加载更多时,调用getCount()方法更新activeViews数组长度。 如下: 1 2 3 4 5 6 // 加载完毕 private void loadComplete() { //..... mViewRecycler.getCount(mDataList.size()); } ViewRecycler类里的getCount方法: 1 2 3 4 5 6 7 8 9 10 11 12 /** 获取活动view的总数 */ public void getCount( int count) { final int length = this .activeViews.length; if (count > length) { final View[] activeViews = this .activeViews; this .activeViews = Arrays.copyOf(activeViews, count); // Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]); } } 3.列表每新增显示一行,就先获取是否有可复用的View对象。先判断recycleViews是否已存有该行号对应的View,没有则获取最新回收的View。再结合setTag与getTag便可实现对回收View对象的复用了。如果recycleViews没有可复用的View,则inflate生成新的View。 如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 public void convertFromView( final ListJson listJson, final int n) { ViewHolder holder = null ; // 判断是否有可重复利用的view View resycleView = mViewRecycler.getRecycleView(n); if (resycleView == null ) { resycleView = LayoutInflater.from(getActivity()).inflate(R.layout.list_item, null ); holder = new ViewHolder(); //..... holder.iv_main = (ImageView) resycleView.findViewById(R.id.item_iv_main); resycleView.setTag(holder); } else { holder = (ViewHolder) resycleView.getTag(); } //保存新增活动的View对象 mViewRecycler.addActiveView(n, resycleView); // 加载行布局数据 holder.tv_title.setText(lison.getName()); holder.tv_price.setText( "待定" ); //..... // 下载图片 ImageLoadingListener listener = new ImageLoadingListener() { @Override public void onLoadingStarted(String arg0, View arg1) { } @Override public void onLoadingComplete(String arg0, View arg1, Bitmap bitmap) { //获取view对象 View bitmapView = mViewRecycler.getActiveView(n); if (bitmapView != null ) { ViewHolder holder = (ViewHolder) bitmapView.getTag(); if (holder != null ) { // 加载图片 holder.iv_main.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.bg_item)); } } } @Override public void onLoadingFailed(String arg0, View arg1, FailReason arg2) { } @Override public void onLoadingCancelled(String arg0, View arg1) { } }; imageLoader.loadImage(listJson.getStatusPic(), listener); } ViewRecycler类里的对应方法: (1)将行号与对应的View填充到activeViews数组里保存。添加前对行号与activeViews的长度进行校正,避免越界。 1 2 3 4 5 6 7 8 9 10 11 12 /** 添加记录当前活动的view */ public void addActiveView( int position, View view) { final int length = this .activeViews.length; if (position > length - 1 ) { getCount(position + 1 ); } this .activeViews[position] = view; // Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews)); } (2)根据行号获取对应的View对象。 1 2 3 4 5 6 7 8 9 10 /** 获取某个活动view */ public View getActiveView( int position) { final int length = this .activeViews.length; if (position > length - 1 ) { getCount(position + 1 ); } return activeViews[position]; } (3)获取已回收的View对象。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /** 获取回收的view */ View getRecycleView( int position) { return retrieveFromRecycle(recycleViews, position); } /** 检索回收的view */ static View retrieveFromRecycle(SparseArray<View> recycleViews, int position) { int size = recycleViews.size(); if (size > 0 ) { // See if we still have a view for this position. for ( int i = 0 ; i < size; i++) { int fromPosition = recycleViews.keyAt(i); View view = recycleViews.get(fromPosition); if (fromPosition == position) { recycleViews.remove(fromPosition); return view; } } int index = size - 1 ; View r = recycleViews.valueAt(index); recycleViews.remove(recycleViews.keyAt(index)); return r; } else { return null ; } } 4.列表每隐藏一行,将消失的行布局对应的View对象回收,添加到recycleViews容器里,同时移除activeViews里的这个View。然后再进行比较并清除recycleViews容器里的回收对象,保证回收对象总数不多于活动view容器的总长度。 如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 隐藏 @Override public void onInvalidateItem( int id) { super .onInvalidateItem(id); // ..... // 回收view对象 View recycleView = mViewRecycler.getActiveView(id); if (recycleView != null ) { mViewRecycler.addRecycleView(id, recycleView); } } ViewRecycler类里的对应方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 /** 添加废弃的view,无序添加 */ void addRecycleView( int position, View scrap) { recycleViews.put(position, scrap); final int length = this .activeViews.length; if (position < length) { this .activeViews[position] = null ; } pruneRecycleViews(); // Log.e("Recycle", "Recycle.size() = " +recycleViews.size()); } 1 2 3 4 5 6 7 8 9 10 11 12 /** 确保废弃的view总数不多于活动的view容器的长度(此方法可再改进为不多于当前活动的View对象数量) */ private void pruneRecycleViews() { final int maxViews = activeViews.length; int size = recycleViews.size(); final int extras = size - maxViews; size--; for ( int j = 0 ; j < extras; j++) { recycleViews.remove(recycleViews.keyAt(size--)); } } 5.退出时,清除所有View对象及其引用。 以下方法则是根据项目需求,将所有的活动view移到recycleViews里,再整理recycleViews。 如下: 1 2 3 4 5 6 7 @Override public void onDestroy() { super .onDestroy(); //销毁所有view对象 mViewRecycler.recycleAllActiveViews(); } ViewRecycler类里的对应方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 /** 将所有剩余的活动view移到废弃view里 */ void recycleAllActiveViews() { final View[] activeViews = this .activeViews; SparseArray<View> recycleViews = this .recycleViews; final int count = activeViews.length; for ( int i = count - 1 ; i >= 0 ; i--) { final View victim = activeViews[i]; if (victim != null ) { activeViews[i] = null ; recycleViews.put(i, victim); } } pruneRecycleViews(); } 二、调试结果 滑动时,请求加载行数据。每次添加一个行布局对象,当达到10行后开始执行View对象回收。每次将栈底的View回收并复用到栈顶的行布局里。如下图: 三、最后附上完整的ViewRecycler类 如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 public class ViewRecycler { private SparseArray<View> recycleViews; // 废弃的view private View[] activeViews = new View[ 0 ]; //正在使用的view public ViewRecycler() { recycleViews = new SparseArray<View>(); } /** 获取活动view的总数 */ public void getCount( int count) { final int length = this .activeViews.length; if (count > length) { final View[] activeViews = this .activeViews; this .activeViews = Arrays.copyOf(activeViews, count); // Log.e("getCount", "activeViews[" + (count - 1) + "]=" + this.activeViews[count-1]); } } /** 添加记录当前活动的view */ public void addActiveView( int position, View view) { final int length = this .activeViews.length; if (position > length - 1 ) { getCount(position + 1 ); } this .activeViews[position] = view; // Log.e("addActiveView", "activeViews.size() = " + Arrays.toString(activeViews)); } /** 获取某个活动view */ public View getActiveView( int position) { final int length = this .activeViews.length; if (position > length - 1 ) { getCount(position + 1 ); } return activeViews[position]; } /** 获取废弃的view */ View getRecycleView( int position) { return retrieveFromRecycle(recycleViews, position); } /** 检索废弃的view */ static View retrieveFromRecycle(SparseArray<View> recycleViews, int position) { int size = recycleViews.size(); if (size > 0 ) { // See if we still have a view for this position. for ( int i = 0 ; i < size; i++) { int fromPosition = recycleViews.keyAt(i); View view = recycleViews.get(fromPosition); if (fromPosition == position) { recycleViews.remove(fromPosition); return view; } } int index = size - 1 ; View r = recycleViews.valueAt(index); recycleViews.remove(recycleViews.keyAt(index)); return r; } else { return null ; } } /** 添加废弃的view,无序添加 */ void addRecycleView( int position, View scrap) { recycleViews.put(position, scrap); final int length = this .activeViews.length; if (position < length) { this .activeViews[position] = null ; } pruneRecycleViews(); // Log.e("Recycle", "Recycle.size() = " +recycleViews.size()); } /** 将所有剩余的活动view移到废弃view里 */ void recycleAllActiveViews() { final View[] activeViews = this .activeViews; SparseArray<View> recycleViews = this .recycleViews; final int count = activeViews.length; for ( int i = count - 1 ; i >= 0 ; i--) { final View victim = activeViews[i]; if (victim != null ) { activeViews[i] = null ; recycleViews.put(i, victim); } } pruneRecycleViews(); } /** 确保废弃的view不多于活动的view容器的总数量 */ private void pruneRecycleViews() { final int maxViews = activeViews.length; int size = recycleViews.size(); final int extras = size - maxViews; size--; for ( int j = 0 ; j < extras; j++) { recycleViews.remove(recycleViews.keyAt(size--)); } } } 方法仅供参考,具体实现过程还需根据项目实际需求进行修改或优化。欢迎各路高手不吝赐教,多多交流! 本文转自 glblong 51CTO博客,原文链接:http://blog.51cto.com/glblong/1366319,如需转载请自行联系原作者

优秀的个人博客,低调大师

Android 针对继承BaseAdapter的自定义适配器应注意的几个地方

通常我们写一个继承BaseAdapter的自定义适配器的时候,主要想做的大概有两件事: 1、在适配的组件里(如ListView,GridView)定义自己想要的布局(Layout)。 2、为布局里的组件(如Button)添加触发响应的事件(如Click)。 当我们写自定义Adapter的时候需要override以下几个方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Overridepublic int getCount(){ return 0 ; } @Overridepublic ObjectgetItem( int position){ return null ; } @Overridepublic long getItemId( int position){ return 0 ; } @Overridepublic ViewgetView( int position,ViewconvertView,ViewGroupparent){ return null ; } 这里我们需要了解getView方法不是在页面生成的时候只被调用一次,而是根据你当前页面能显示的List里的条目数,被调用数次。什么意思呢?说白了,我们生成的这个自定义的页面(通常是放在ListView里的)看起来好像是一个整体,实际上是由许多个convertView拼装成的,每个convertView就相当于一个Item,然后这些Item被塞到ListView中去,理解不了,可以看下图: 这是一个activity的layout布局。 这是一个Item的layout布局。 convertView里我们就可以随意的加些小组件,比如按钮啊,然后为按钮添加一些事件什么的。 getView里的具体实现我就不缀述了,我想强调的是返回值,一定要return最新生成的convertView,否则功亏一篑。这里我栽过小2次。正确的就是这样: 1 2 3 4 @Overridepublic ViewgetView( int position,ViewconvertView,ViewGroupparent){ //这里就是给获取组件,进行一些赋值操作或者添加事件 return convertView; } 然后,这里还有另外一个坑,就是getCount方法。默认情况下就如同上面写的一样,看似好像可以不用管它,直接return 0;就好,不过经过我这个试坑专员的验证,不管它是绝对不行的。当我们用 listview去setAdapter的时候,程序先走的是你自定义Adapter的构造函数,然后就会走getCount方法,之后才会进入getView方法,如果getCount返回0了,那么后面的事情就不用多想了,不会调用。这个坑我记得是把我整的挺惨,希望大家别掉下去。 本文转自 我不会抽烟 51CTO博客,原文链接:http://blog.51cto.com/zhouhongyu1989/1417226,如需转载请自行联系原作者

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册