ListView的高效率使用

ListView作为android上最常用的控件之一,其原理与效率十分值得我们去探究,特别是当与Button、ImageView一起时,许多问题便纷至沓来。今天我就说说这老生常谈的话题,也是由此做一个总结。

一,ListView的原理

在Android中有一个Recycler的构件,滑出屏幕的的Item就会被缓存在里面。但并不是缓存全部,只是会根据item的类型数来缓存相应的个数。举个例子,一般情况下都没有必要去重写ListView的getViewTypeCount())函数,那么type值也就为1,也就是说,Recycler中只会缓存一个item View。如下图(图片来自网络):

当Item1滑出屏幕的时候,Recycler构件中就缓存了item1,再往上滑,item2滑出屏幕。这时有趣的事情就发生了,Recycler构件只会缓存一个item,这时毫无争议会缓存Item2,那么item1就被复用,显示为item8(实际上当item1完全滑出界面时就会被显示作Item8了)。

二,需要注意的地方

1,数据与显示分离

由于ListView的item view是复用的,这就要求我们将数据与显示分离,否则将造成显示的结果错乱。比如,当ListView的Item中含有单选按钮时,假如只是简单地记录按钮的点击状态,那么当滑动ListView的时候,“已点击”与“未点击”就显示混乱。再举个例子,ListView的item中含有图片,选择异步加载。如果做法是当下载完毕后就设置ImageView的显示内容,那么也会造成混乱。

比较明智的做法是自己单独维护一个数据结构来缓存数据,上文的点击状态就时候使用ArrayList,异步加载的图片就适合使用HashMap

2,数据更新

ListView的数据更新只能在UI主线程中进行,否则会报:

1
“java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread.”

另外还需要注意的是,不能更改原来的数据指向的对象。比如,原来的数据List 指向的是 ArrayList1。在数据更新后,为了方便,我们直接 List = ArrayList2。这样就使List指向新的ArrayList,也是会出错的。总而言之,唯一的做法就是在原理的ArrayList1中增加或删除数据。

在github上参与开源项目

近日用到一个不错的开源库AndroidCharts使用方便,易于扩展和自定制。在使用的过程中发现了一个小bug,于是果断发起了pull request。第一次使用GitHub参与开源项目还是十分手生的,需要各种查资料,学习Git的用法。

参考了许多资料,这里记录一下在GitHub上参与开源项目的过程。

1,寻找项目

可以是你使用到的项目,你觉得想参与的,或者在这里找:

2,查看社区和文档

一般项目中都有的文件。

  • Readme:几乎所有的Github项目都包含一个README.md文件。readme提供了该项目的一个概览及关于如何使用、构建甚至如何贡献于一个项目的相关细节。

  • Contributing:项目和项目维护者不同,所以每个项目所期望的作贡献的最佳方法也会有所不同。一定要注意一个标注为CONTRIBUTING的文档,Contributing文档详细描述了一个项目的维护者希望看到贡献的补丁或功能应该符合怎样的规格。这可能包含要写什么测试,代码语法规范或补丁集中的区域。

  • License:一个 LICENSE 文件当然就是该项目的许可证了。一个开源项目的 license 会告诉用户他们能做和不能做的(例如使用、修改、重新发布),及告诉贡献者他们允许其他人做的。有许多的办法对开源项目加上许可证,你可以在 choosealicense.com 读到更多的关于每个许可证的含义。

  • Documentation and Wikis:许多大型项目有的不只有一个readme来指导人么如何使用他们的项目。在这种情况下你通常能够发现一个指向库中名为“docs”的另一个文件或文件夹的链接。

3,发起一个issue

简记git与svn

svn与git是目前比较主流的版本控制系统,在这里简单记录一下它们之间的异同。

一,从架构来说,SVN是集中式,git是分布式。

集中式的优点是代码高度统一,缺点也很明显就是非常依赖于中央服务器。如果服务器宕机或者本地无法联网,那么你将无法提交更新、还原、对比等,也就无法协同工作。同时SVN上的提交也并非是每次都成功,如果有一个人在你提交的时候先提交,那么就会提示你先更新再提交。SVN原理上只关心文件内容的具体差异,每次记录有哪些文件做了更新并且更新了哪些内容。

而分布式的git会在本地克隆一个自己的版本库,在无法联网的时候,依然能够提交文件,查看历史版本记录等。Git中每个版本库都是平等的。与SVN不同,Git只关心文件数据的整体是否发生变化,Git并不保存文件内容前后变化的差异数据,更像是把变化的文件作快照后记录在一个微型的文件系统中(为提高性能,没有变化的文件不会再次保存)。

二,版本库与工作区的差异

SVN的版本库与工作区是分离的。工作区在你本地检出的那个目录,它是一份版本库在某个历史状态下的快照。SVN的工作区中每个目录下都包含一个名为.svn的控制目录(隐藏目录)。

git的版本库与工作区在同一个目录下,工作区的根目录有一个.git的子目录。git的工作区必须要有.git目录,它是用来保存元数据和对象数据库的地方,并且Git的工作区下只有一个.git目录,此外再无任何控制目录,如果删除它,则破环了整个历史,版本库也永远失去了。(可以在工作区之外克隆一个版本库,并且经常把提交push到里面,这样就实现了数据备份。)

Android中保持Service的存活

如何让Service keep alive是一个很常见的问题。

在APP开发过程中,需要Service持续提供服务的应用场景太多了,比如闹钟需要作出及时提醒,那么比如得有一个Service不断去比较当前时间和设置时间;QQ要能流畅的聊天,必然也需要及时接收消息等。

但是Android并没有保证Service有这样功能,毕竟一个系统面对的是用户,必然以对用户友好为先。

关于如何让Service keep alive,我在上篇博客给出的解决方案是:方案一,让服务器端发一个推送,检查Service是否还存活;方案二,将Service独立出来,运行在另一个进程中。

这两个方案有些地方需要说明和改进,然后还会有其他方案补充进来。

方案一:利用推送来确保Service存活。

方案一的做法有点“偷懒”。因为相当于把这个难题转移给推送服务提供者来处理,或者说,只需要依靠推送,就不需要自己去考虑存活问题。

推送一直是移动客户端开发的热门话题(实际上也是传统软件开发的热门话题)。

一般情况下,C/S结构(B/S是特殊的C/S结构)中的业务流程是这样的:客户端主动向服务器端发出请求,服务器端响应请求,建立二者之间的连接。通过建立的链接,双方可以发送/接收数据。最后客户端和服务器端断开连接。也就是说,客户端是主动方,是请求连接的发起者;服务器端保持对某个端口的监听(0-1023是系统端口号,比如80端口被指派给HTTP),而被动地等待客户端的连接请求。

那么推送指的是,服务器端在没有收到请求的情况下主动向客户端发送信息,比如服务器端收到一封邮件后主动发往邮件的客户端。

推送是如何实现的呢?首先,服务器端的功能还是不变,监听某个端口等待请求。然后我们可以看到,服务器端能主动发送信息的唯一时间段就是在建立了服务器端到客户端的连接之后、二者断开连接之前。因此,推送的实现就是基于保持服务器端和客户端的连接一直存在,方式可以是持续连接或者轮询。

回到Service这个问题上来,推送服务的提供者必然会保证推送技术的稳定性,依靠推送,可以唤醒我们的APP,那么保证Service的存活也就不在话下。

方案二:将Service运行在另一个进程中。

将Service放在另一个进程中,可以避免APP所在的进程因资源紧张或者被用户手动结束的时候,Service也被结束。

这个做法的另一个优点是,可以让Service独立出来,为同一个公司的不同APP提供相同的服务。比如当大部分APP都需要同一个信息的时候,为每个APP都写一个Service显然不好,这时完全可以写一个独立进程中的Service,为所有APP提供信息。

当然,这个方案有个缺点是,当某些手机助手无脑式结束掉全部的非系统进程时,Service无法存活。

Android 4.X界面开发那点事

前言:

为什么要使用dp/dip?导航栏的宽度/高度应该设为多少dp合适?一般情况下应该使每个控件的高度显示为多少?本文就是要回答这些问题。

一,Android中的各种单位

在android开发过程中,我们无法忽略的一个问题就是设备适配问题。从我们刚开始学android的时候,各类教程上就不厌其烦的说,“android碎片化严重,适配很麻烦”、“控件的长度用dip作单位,字体用sp”等等。我们有必要去详细了解一下这些东西,在开发过程中才能游刃有余。

下面是android中的常见单位:

  • px(像素)可以理解为一个小颜色块,是设备显示时的单位;
  • dip是设备独立像素,不同设备有不同的显示效果,和具体硬件有关;备注:dip == dp
  • dpi是屏幕像素密度,每英寸像素数;
  • sp是像素缩放。google建议用于字体显示。

dpi等于对角线的像素值(=$\sqrt{长^2+宽^2}$)除于英寸数。比如,分辨率480 x 800,屏幕尺寸4.3英寸的DPI为:216.

为了方便,google将dpi分为多种模式LDPI, MDPI, HDPI, XHDPI, XXHDPI, and XXXHDPI,常见四个如下,分别对应资源文件中的四个后缀:

也就是说,216会被划分进hdpi。

Fork me on GitHub