12 2014
13

浅谈UI中的view复用
2014年12月13日

有比较丰富安卓开发经验的人都用过ListView、GridView等,iOS开发者也多用过类似的UI部件。 在使用这种东西的时候一定会接触到一个概念:复用。 这种模式有很显著的好处,但是好处到底在哪?如何真正的用对?

许多开发者及时经验丰富,也未必真的知道为何要复用,只是知道应该要复用。 对于缺少一定开发经验的程序员来说,没见复用带来多少好处,反倒是增加了不少Bug。 在这里,不针对具体实现,而是针对原理,来阐释一下复用的作用以及一些看起来奇怪的现象是如何出现的。

复用的好处

既然经验老道的开发者一定会告诉新手,能复用则复用,那么复用一定有它的好处。 新手会问为啥这么做,老手会不耐烦地说增加效率。

这一句增加效率实在太笼统了,也许是老手觉得对新手解释不清楚,也可能老手自己也不知道到底怎么增加效率了。

一个列表UI部件,从逻辑上来讲虽然可能承载了上百上千个条目,但是对于用户来说,一个屏幕上可见的部分却只有最多那么十几个。 如果什么都不想,有几个条目就创建几个对应的View,全部一次性在初始化的时候加载,那么如果有上百上千的条目时会发生什么?没错OOM!

不论安卓还是iOS,除非使用native C/C++,否则只给你30M左右的内存,更老的机型就更惨了,虽然新的旗舰机型会有所改善,但总不能指望人人都是土豪。 如果你每一个View都会占用200K内存(如果有个缩略图,轻松占用200K),那么不计算App其他的内存占用, 光这个列表如果有个150个条目,内存不负众望一定爆。

然而如果只为那可见的几个条目创建View,那么这内存占用量直接省去几个0,OOM也就不再是问题了。 后面的内容没有创建View,那么用户如果往下划了怎么办?这里就可以用复用了。 复用,无非就是不创建新的View,而把刚刚已经在视野范围外的View取出来,放置到新进入视野的位置,然后更新其内容。 这个复用的视野计算过程都由UI部件或框架实现了,开发者只要根据情况创建View或更新内容即可。

这样复用的第一个好处,也是最大的好处就出现了:大量节省内存

然而更有思想的人尤其是有写C/C++程序经历习惯于自己管理内存的开发者就会考虑另一件事, 只是节省内存,何必复用?只要及时释放,照样可以继续创建新的View。 从实现来看就是,每次ListView回调都返回个新的View,没必要管老的View,只要小心一点,老的View就会被JVM的GC或者OC的ARC释放掉, 这样内存占用一样不会爆,这样解决OOM问题,还逻辑简单不容易出Bug。

这种想法从某种层面上来讲是正确的,但是这是一般情况。在极端情况下,就会出现问题。 用户在一瞬间就把ListView从最上面拉到了最下面。这个过程当中,如果不复用,而是每次都创建一个新View那么会发生两种现象。

第一是卡顿,创建新View虽然计算量并不算大,但往往还是比只更新View要多不少,那么当在一瞬间发生了上百次创建, 其运行速度可能就会与上百次的更新有肉眼可见的差别。从用户体验上来讲,不复用,滚动可能就不顺畅。 这种现象在Android上比iOS更明显,因为Java创建对象比OC创建对象要付出更多计算与内存消耗。

第二个现象,虽然少见,但还是可能出现OOM了!不管是Java的GC,还是OC的ARC,其释放都是有延迟的,那么一瞬间几百甚至几千的对象创建, 可能会因为内存释放不及时而导致OOM再度出现了。 不过先进一些的内存回收机制,会在真的触发OOM之前尝试把能释放的内存释放了,不过这个过程也占时间,会造成视觉上的卡顿感。

那么复用的第二个好处就是:即使在极端情况也保持节省内存,并且节省计算

所以总结来说,复用最关键的目的就是节省内存。其实如果内容没那么多,最多也就十几二十个,复用也没啥用。

复用遇到的怪现象

复用虽有好处,但因其抽象的概念,能真正用对也不是那么容易,如果用错了,会产生各种Bug。

To Be Continued…