说说Android瀑布流的内存管理

来源:互联网 发布:自己没货如何开淘宝店 编辑:程序博客网 时间:2024/05/01 05:55

Title:这是我在CSDN上的第一篇技术博客

项目进入到三期优化阶段,想着也该积累些东西了。所以就在CSDN上开了个技术博客。

首先声明——我不是什么技术大牛,小菜一个。Post上来的东西肯定有很多BUG,不敢炫耀,只盼和同道中人共同探讨,进步。

(这话官方得我自己都起鸡皮疙瘩~-_-|||)

项目中有用到图片墙的展示方式,而且貌似这种套路目前很受欢迎,所以摘几个自认为比较有分量的点和大家分享一下。

先上页面效果图


图片墙的布局模式我也是参考了其他同学分享的图片墙,父控件是一个horizontal的LinearLayout,里面包含了三个vertical的LinearLayout。我们需要做的就是往这三个vertical的LinearLayout中添加图片布局就可以了。

//记录每列的高度private int[] mColumHeight;

我认为让很多同学(包括我在内)最为头痛的就是图片的内存管理了,一不小心OOM就和你say hello。所以先和大家分享我是如何管理图片内存的。

我是用FinalBitmap来管理图片的,个人认为FinalBitmap还是比较好用的,而且开源的特性可以修改源码以满足各式各样的需求。

BUT!FinalBitmap不是万能的,即使它有自己内存管理的方式,但在我使用过程中还是会OOM,所以还是得需要自己手动管理图片内存。

我的设计思路是——“只显示手机屏幕中显示的图片,那些被遮挡的图片全部回收”。这样在不影响用户视觉体验的情况下尽可能的减小内存使用。

如果有同学调研过图片墙的内存管理会发现现在网上流行在ScrollView回调onAutoLoad(int l, int t, int oldl, int oldt),然后根据用户滑动来动态回收内存的方法,我的思路参考了这个方法,并做了改进。

好了!进入正题。

在Post思路之前先给大家解释会用到的一些变量。(比较绕,我尽量说清楚~-_-|||)

//记录每列的高度private int[] mColumHeight;//每列图片的数量private int[] lineCellCount;//存没列数组 存放每列每张图的总高度 //比如第一列第一张图高90  总高度为90、第一列第2张图高120 总高度为210... 以此类推private HashMap<Integer, Integer>[] cellTotalHeight = null;//表示当前瀑布流最底部的三张图的索引 比如第一列有21张图 索引为20 第二列有19张 索引为18 第三列有22张 索引为21private int[] screenTop;//每列在屏幕上最顶部的图片索引private int[] screenBottom;//每列在屏幕最底部的图片索引

这几个变量是这样用的

WaterFallCell cell = (WaterFallCell)msg.obj;int minColum = getMinHeightLL();cell.setmColumn(minColum);LinearLayout ll = (LinearLayout)mContainer.getChildAt(minColum);ll.addView(cell.getmContentView());TextView tv = (TextView)cell.getmContentView().findViewById(R.id.water_fall_item_tv);tv.setText(String.valueOf(cell.getmItem().click));AutoMarqueeTextView nameTV = (AutoM);mColumHeight[minColum] += cell.getmHeight()//minColum列的图片个数++lineCellCount[minColum]+=1;if(mColumHeight[minColum] <= mScrollHeight){screenTop[minColum] = 1;}else if(screenBottom[minColum] == 0){screenBottom[minColum] = lineCellCount[minColum];}//记录加上该张图片后,该张图片在该列中的总高度cellTotalHeight[minColum].put(lineCellCount[minColum], mColumHeight[minColum]);mFinalBitmap.configLoadingImage(null);mFinalBitmap.configLoadfailImage(null);mFinalBitmap.display(cell, cell.getmItem().img,false);

我复写了ScrollView并设置了回调监听器

@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {if(mListener != null){mListener.onAutoLoad(l, t, oldl, oldt);}}

复写的ScrollView一旦调用onScrollChanged便回调onAutoLoad(l, t, oldl, oldt)。接下来就是在我的Activity中回调的onAutoLoad做文章了。

用户滑动就会调用onScrollChanged。

@Overridepublic void onAutoLoad(int l, int t, int oldl, int oldt) {//获取最外层ScrollView的高度scroll_height = mScrollView.getMeasuredHeight();//下面一段代码起到垃圾回收的作用if (t > oldt) {// 向下滚动for (int k = 0; k < COLUMN_COUNT; k++) {//mContainer是个LinearLayout,那三列LinearLayout就是他的三个子控件LinearLayout localLinearLayout = (LinearLayout)mContainer.getChildAt(k);//当用户滑动的Y轴大于每列最顶部图片所在的总高度(也就是每列的最顶部图片被隐藏)if(t>cellTotalHeight[k].get(screenTop[k])){//为了更好的用户视觉,我决定将每列最顶部图片的上一张图片回收掉if(screenTop[k]-2>0){View view = localLinearLayout.getChildAt(screenTop[k]-2);WaterFallCell cell = (WaterFallCell)view.findViewById(R.id.water_fall_item_cell);cell.recycle();}//既然顶部图片被隐藏了那么自然而然现在屏幕上的最顶部图片+1screenTop[k]+=1;}//计算完屏幕上最顶部图片后再计算屏幕上每列最底部图片的索引值if(t+mScrollHeight>cellTotalHeight[k].get(screenBottom[k])){if(screenBottom[k]+1<=lineCellCount[k]){screenBottom[k] += 1;}}}} else {// 向上滚动//向下滚动回收的是顶部的图片,以此推理,向上滑动当然要回收底部图片了//理解了向下滑动的思路,向上滑动就不做累述了for(int k=0;k<COLUMN_COUNT;k++){LinearLayout localLinearLayout = (LinearLayout)mContainer.getChildAt(k);if(screenTop[k]>1){if(cellTotalHeight[k].get(screenTop[k]-1)>t){screenTop[k] -= 1;}}if(screenBottom[k] != 0){if(cellTotalHeight[k].get(screenBottom[k]-1)>t+scroll_height){View view = localLinearLayout.getChildAt(screenBottom[k]-1);WaterFallCell cell = (WaterFallCell)view.findViewById(R.id.water_fall_item_cell);cell.recycle();screenBottom[k] -= 1;}}}}if(t == 0){for(int k=0;k<COLUMN_COUNT;k++){LinearLayout localLinearLayout = (LinearLayout)mContainer.getChildAt(k);for(int i=0;i<10;i++){LinearLayout rl = (LinearLayout)localLinearLayout.getChildAt(i);if(rl != null){WaterFallCell cell = (WaterFallCell)rl.findViewById(R.id.water_fall_item_cell);cell.reload();}}}}}


上面这段代码中用到了WaterFallCell ,其实这个是我自定义的控件,说直白了就是瀑布流的每一个格子,它的recycle()方法就是回收图片。

有的同学该问了,图片都回收了那用户岂不是看到的是大白板?!别急,还有下面这个方法。

@Overridepublic void stopScroll(int nowY) {Message msg = mHandler.obtainMessage();msg.what = WaterFallCell.LOAD_PIC;msg.arg1 = nowY;mHandler.sendMessage(msg);}
当ScrollView停止滑动后会回调这个方法,然后我在Hanlder中reload现在屏幕上显示的格子中的图片

Handler中的代码:

for(int i=0;i<COLUMN_COUNT;i++){LinearLayout localLinearLayout = (LinearLayout)mContainer.getChildAt(i);for(int k=screenTop[i]-1;k<screenBottom[i]+1;k++){if(k-1>0){View view = localLinearLayout.getChildAt(k-1);WaterFallCell cell = (WaterFallCell)view.findViewById(R.id.water_fall_item_cell);cell.reload();}}}
因为在用户滑动过程中screenTop和screenBottom实时记录和更新目前屏幕上显示的每列的屏幕顶部的格子索引和屏幕底部的格子索引,所以当ScrollView停止滑动后我只需遍历screenTop和screenBottom之间的格子并reload就可以了。
最后总结一下:
我的图片墙的特性是用户停止滑动后只显示目前手机屏幕中显示的格子的图片,其他不显示的统统回收,目前最多翻过30页,没有OOM。再极限的测试没有做。

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

本想把项目中的代码摘出来并生成安卓工程当做附件Post上来,但无奈现在所有的数据都走我们自己的服务器,一不小心把目前还没做任何防护的服务器的地址暴露,那我可就吃不了兜着走了。所以最终决定将个人认为比较有“点”的代码Post上来。

这篇博客断断续续写了三天(无奈每天苦X加班到很晚),所以可能有些地方逻辑衔接不紧密,请同学们谅解。

最后!灰常欢迎同学们一起来找茬,改进。

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

应eoe上广大同学的要求,周末抽时间做了个Demo,Demo去掉了瀑布流的其他布局,只剩下图片。

Demo见eoe帖子附件。

eoe帖子传送门——http://www.eoeandroid.com/thread-265353-1-1.html