综述:

本篇首先介绍了ListView的实现细节。然后介绍了Gallery,ListView,ViewPager的效率对比分析。以及效率低下的原因。最后给出了一些解决方案。

1.在上一篇讨论了requestLayout的效率问题。对如何避免这个问题也进行了深入探讨。本篇就内存问题进行讨论。一般情况下,安卓的ListView实现方式上,就存在要移动childView位置的需求。

如果对childView进行了回收并且回收的childView必须仍然在原来的位置上,那么childView的位置可能要出现在两个位置上。这非常有挑战性,因为安卓的很多东西都是基于一个View一个位置的这样的思想。现在突然出现两个位置。那么也就是说,回收的View不能再呆在原来的位置了。必须被remove掉。remove掉之后呢,其他的View必须挤过去。总之所有的children都得改变位置。

也就是说必须改变布局。但是改变布局是否就一定要reqestLayout,也是个问题。刚才说了不需要。只要调用一下onLayout就行了。onLayout调用的时候,必须知道childView的位置吧。、那还得改变child的位置,那又调用什么呢?

那么,仅仅改变childView位置的函数是什么呢?这个函数就是offset系列函数。在Gallery那边是offsetChildrenLeftAndRight另外还有个setX和setY。这些都是改变位置的函数。

ListView的实现时非常高效的。既保证了回收childiew,有能不进行layout。非常高效。

第一。View的回收机制。

第二。View不进行requestLayout

2.Gallery的实现方式

Gallery在实现的时候没有采用回收机制。经过测试的Adapter.getView方法参数,View都是null。也就是说不对View进行复用。其实上Gallery中的View是越来越多。而且每一个View都不会进行回收。这跟一个

ScrollView+ViewPager的实现方式是一样的。唯一的区别是Gallery采用了Adapter机制,并且使用了ListView的滚动原理。但是Gallery没有对View进行回收,全部保存了起来。在内存不够用的时候,尽量不要使用这个。下面拿Gallery和ListView的回收机制进行了对比,

先看trackMotionScroll方法:

Gallery:

    /**
* Tracks a motion scroll. In reality, this is used to do just about any
* movement to items (touch scroll, arrow-key scroll, set an item as selected).
*
* @param deltaX Change in X from the previous event.
*/
void trackMotionScroll(int deltaX) {
if (getChildCount() == 0) {
return;
}
boolean toLeft = deltaX < 0;
int limitedDeltaX = getLimitedMotionScrollAmount(toLeft, deltaX);
if (limitedDeltaX != deltaX) {
// The above call returned a limited amount, so stop any scrolls/flings
mFlingRunnable.endFling(false);
onFinishedMovement();
}
offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
if (toLeft) {
// If moved left, there will be empty space on the right
fillToGalleryRight();
} else {
// Similarly, empty space on the left
fillToGalleryLeft();
}
// Clear unused views
mRecycler.clear();
setSelectionToCenterChild();
onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
invalidate();
}

ListView

   /**
* Track a motion scroll
*
* @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion
*        began. Positive numbers mean the user's finger is moving down the screen.
* @param incrementalDeltaY Change in deltaY from the previous event.
* @return true if we're already at the beginning/end of the list and have nothing to do.
*/
boolean trackMotionScroll(int deltaY, int incrementalDeltaY) {
final int childCount = getChildCount();
if (childCount == 0) {
return true;
}
final int firstTop = getChildAt(0).getTop();
final int lastBottom = getChildAt(childCount - 1).getBottom();
final Rect listPadding = mListPadding;
// "effective padding" In this case is the amount of padding that affects
// how much space should not be filled by items. If we don't clip to padding
// there is no effective padding.
int effectivePaddingTop = 0;
int effectivePaddingBottom = 0;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
effectivePaddingTop = listPadding.top;
effectivePaddingBottom = listPadding.bottom;
}
// FIXME account for grid vertical spacing too?
final int spaceAbove = effectivePaddingTop - firstTop;
final int end = getHeight() - effectivePaddingBottom;
final int spaceBelow = lastBottom - end;
final int height = getHeight() - mPaddingBottom - mPaddingTop;
if (deltaY < 0) {
deltaY = Math.max(-(height - 1), deltaY);
} else {
deltaY = Math.min(height - 1, deltaY);
}
if (incrementalDeltaY < 0) {
incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY);
} else {
= Math.min(height - 1, incrementalDeltaY);
}
final int firstPosition = mFirstPosition;
// Update our guesses for where the first and last views are
if (firstPosition == 0) {
mFirstPositionDistanceGuess = firstTop - listPadding.top;
} else {
mFirstPositionDistanceGuess += incrementalDeltaY;
}
if (firstPosition + childCount == mItemCount) {
mLastPositionDistanceGuess = lastBottom + listPadding.bottom;
} else {
mLastPositionDistanceGuess += incrementalDeltaY;
}
final boolean cannotScrollDown = (firstPosition == 0 &&
firstTop >= listPadding.top && incrementalDeltaY >= 0);
final boolean cannotScrollUp = (firstPosition + childCount == mItemCount &&
lastBottom <= getHeight() - listPadding.bottom && incrementalDeltaY <= 0);
if (cannotScrollDown || cannotScrollUp) {
return incrementalDeltaY != 0;
}
final boolean down = incrementalDeltaY < 0;
final boolean inTouchMode = isInTouchMode();
if (inTouchMode) {
hideSelector();
}
final int headerViewsCount = getHeaderViewsCount();
final int footerViewsStart = mItemCount - getFooterViewsCount();
int start = 0;
int count = 0;
if (down) {
int top = -incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
top += listPadding.top;
}
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.getBottom() >= top) {
break;
} else {
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
} else {
int bottom = getHeight() - incrementalDeltaY;
if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
bottom -= listPadding.bottom;
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = getChildAt(i);
if (child.getTop() <= bottom) {
break;
} else {
start = i;
count++;
int position = firstPosition + i;
if (position >= headerViewsCount && position < footerViewsStart) {
mRecycler.addScrapView(child, position);
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child,
ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP,
firstPosition + i, -1);
}
}
}
}
}
mMotionViewNewTop = mMotionViewOriginalTop + deltaY;
mBlockLayoutRequests = true;
if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);
if (down) {
mFirstPosition += count;
}
invalidate();
final int absIncrementalDeltaY = Math.abs(incrementalDeltaY);
if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) {
fillGap(down);
}
if (!inTouchMode && mSelectedPosition != INVALID_POSITION) {
final int childIndex = mSelectedPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(mSelectedPosition, getChildAt(childIndex));
}
} else if (mSelectorPosition != INVALID_POSITION) {
final int childIndex = mSelectorPosition - mFirstPosition;
if (childIndex >= 0 && childIndex < getChildCount()) {
positionSelector(INVALID_POSITION, getChildAt(childIndex));
}
} else {
mSelectorRect.setEmpty();
}
mBlockLayoutRequests = false;
invokeOnItemScrollListener();
awakenScrollBars();
return false;
}

可以看到Gallery处理View的关键代码:

      offsetChildrenLeftAndRight(limitedDeltaX);
detachOffScreenChildren(toLeft);
。。。。
mRecycler.clear();

而ListView中为:

        if (count > 0) {
detachViewsFromParent(start, count);
}
offsetChildrenTopAndBottom(incrementalDeltaY);

detachOffScreenChildren和detachViewsFromParent跟ListView功能一样。但是Gallery多出一个 mRecycler.clear()。等待重复利用的mRecycler被回收了,这就造成了再Gallery中无法复用之前的View的情况,由此可见Gallery在内存的使用上存在很大的设计缺陷。

再看Gallery与ListView的makeAndAddView方法

Gallery

    private View makeAndAddView(int position, int offset, int x, boolean fromLeft) {
View child;
if (!mDataChanged) {
child = mRecycler.get(position);
if (child != null) {
// Can reuse an existing view
int childLeft = child.getLeft();
// Remember left and right edges of where views have been placed
mRightMost = Math.max(mRightMost, childLeft
+ child.getMeasuredWidth());
mLeftMost = Math.min(mLeftMost, childLeft);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}
}
// Nothing found in the recycler -- ask the adapter for a view
child = mAdapter.getView(position, null, this);
// Position the view
setUpChild(child, offset, x, fromLeft);
return child;
}

ListView:

    private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
boolean selected) {
View child;
if (!mDataChanged) {
// Try to use an existing view for this position
child = mRecycler.getActiveView(position);
if (child != null) {
if (ViewDebug.TRACE_RECYCLER) {
ViewDebug.trace(child, ViewDebug.RecyclerTraceType.RECYCLE_FROM_ACTIVE_HEAP,
position, getChildCount());
}
// Found it -- we're using an existing child
// This just needs to be positioned
setupChild(child, position, y, flow, childrenLeft, selected, true);
return child;
}
}
// Make a new view for this position, or convert an unused view if possible
child = obtainView(position, mIsScrap);
// This needs to be positioned and measured
setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
return child;
}

由此可见Gallery每次都将不可见的View进行了清理。对比ListView来说,在滚动过程中多了new一个View的开销。那么就存在一个避免此类问题的方法,自己对View建一个recyler从而弥补这个缺点。

另外安卓的ViewPager+ScrollView 采用了传统的整个容器进行滚动算法,而不是调整childView的位置,但是也存在卡顿问题。

ViewPager的用法见下文:

http://blog.csdn.net/wangjinyu501/article/details/816

ViewPager的卡顿见下文:

http://marspring.mobi/viewpager-majorization/

里面google提供了一个接口解决卡顿问题。至少目前看来,只是五十步笑百步。不能根本解决问题。默认当前正中位置前后缓存一个View。改后可以缓存多个View。当缓存的多个View被用完的时候,仍然是卡顿。

另外,

有人说用异步加载,异步加载解决的是数据加载问题,跟这个new View造成的卡顿问题是两码事儿。

如何解决?

自己做类似ListView的回收机制,对View进行复用,从而根本上解决这个问题。

另外,安卓没有采用享元模式。。。。很遗憾。。。也许google大牛们会想到,希望如此。希望google能体谅我们,再提供一个类似View的轻量级显示控件来直接支持享元模式。这在Brew平台是有的。。。。。。。。。。。。。。。。。。。。。。郁闷。

安卓高手之路之图形系统(6)ListView继续相关推荐

  1. 安卓高手之路之图形系统【5】安卓ListView和EditText配合使用时的注意事项。

    ListView实现滚动和布局时的注意实现: 1.如何实现自动定位到底部: android:stackFromBottom="true" 这样ListView就可以实现从底向上增长 ...

  2. 安卓高手之路之 图形系统之 图形框架(1)

    安卓图形系统理解起来并不容易.那是因为系统对于数据的封装非常多,图形模块与输入输出,应用程序管理等模块参杂在一起.让开发者很难摸清其中的脉络.下面先给出最简单的一张图.             这张图 ...

  3. 安卓高手之路之图形系统(6)requestLayout的流程

    当一个View调用requestLayout的时候,会给当前的View设置一个FORCE_LAYOUT标记.由此向ViewParent请求布局.这样从这个View开始向上一直requestLayout ...

  4. 安卓高手之路 图形系统(4 Measure的算法)

    安卓高手之路 图形系统(4 Measure的算法) - 修补C++ - ITeye技术网站 Java代码   /** * Does the hard part of measureChildren:  ...

  5. 安卓高手之路 图形系统(2)----------------基于Binder的架构思路)

    在学习安卓的时候最迷惑的就是Binder.图形框架的理解与Binder的理解分不开.前面一篇 [ 安卓高手之路之java层Binder 从代码角度分析了Java层Binder的实现原理.在C++层,这 ...

  6. 安卓高手之路之 ClassLoader

    我不喜欢那些泛泛而谈的去讲那些形而上学的道理,更不喜欢记那些既定的东西.靠记忆去弥补思考的人,容易陷入人云亦云的境地,最后必定被记忆所围困,而最终消亡的是创造力.希望这个高手之路系列能够记录我学习安卓 ...

  7. 安卓高手之路 图形系统(3 底层SurfceFlinger系统)

    底层SurfaceFLinger系统主要通过SurfaceTextureLayer进行绘图信息的传递.整个图花了几天时间,终于有所眉目.希望能给致力于研究SurfaceFlinger架构的人一些参考. ...

  8. [置顶] 安卓高手之路之ClassLoader(二)

    因为ClassLoader一定与虚拟机的启动有关系,那么必须从Zygote的启动开始看代码.下面就分析一下这些代码,行数不多: int main(int argc, const char* const ...

  9. [置顶] 安卓高手之路之 WindowManager

    安卓中的画面不是纯粹由window组成.而是改成了window+view的组织模式.window是一个顶层窗口的概念.view就相当于在window内的控件.而subwindow则是依附于window ...

最新文章

  1. redis支持的数据类型有哪些?
  2. cocos2dx 大地图分块加载的研究(初)
  3. python PyQt5 QLabel()(可以用来显示文字、图片或作为放置一些控件提示信息的容器)
  4. [云炬创业学笔记]第一章创业是什么测试14
  5. maven打包pom.xml备忘
  6. 关系型数据库全表扫描分片详解
  7. LeetCode:Longest Consecutive Sequence
  8. mysql事件定时_MySQL事件(定时任务)
  9. 机器学习之决策树与随机森林
  10. Mycat分库路由规则
  11. php如何每天自调用不同的ccs,Python-ccs高级选择器 盒模型
  12. Hive 1.1.0 某些字段的查询结果为NULL
  13. Knockout事件传递参数的几种方式
  14. HTML--day02
  15. Windows API 学习记录1
  16. Julia : 如何一行实现99乘法表?
  17. 电商系统数据库设计原则
  18. 企业服务总线ESB是什么
  19. 海尔电商峰值系统架构设计最佳实践
  20. 微软认证(MTA)的java,js以及数据库考过了

热门文章

  1. mysql 多个unique key_[MySQL]MySQL 中通过使用UNIQUE KEY 来控制字段值不重复的问题.
  2. mfc 固定编辑框输入上限和下限_餐饮行业案例:固定工资、底薪提成改成这样,员工为自己拼命干...
  3. debug控制台内容消失 idea_IDEA 乱码问题(解决方案篇)
  4. mybatis 如何判断重复插入_MyBatis常见面试题3:数据库插入重复如何处理
  5. python以读写方式打开文件_python读写文件操作详细介绍【传智播客】
  6. PPIO 商业化架构解析
  7. Oracle-04:DDL语言数据表的操作
  8. Linux系统介绍(二)文件系统结构
  9. 微软考虑将 Python 作为 Excel 官方脚本语言
  10. iOS10 推送通知 UserNotifications