你好,我之前对我爱编程网这一类的不是特别了解,就想着学好之后能找一个稳

可以跟着文章,就着代码一起看:&br&比如下面的文章: &a href=&///?target=http%3A///blogs/detail/54cfab086c42& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Volley 源码解析 @codeKK 开源站&i class=&icon-external&&&/i&&/a&
可以跟着文章,就着代码一起看: 比如下面的文章:
&img src=&/v2-cf57ffee138bc76a6df6c_b.jpg& data-rawwidth=&550& data-rawheight=&310& class=&origin_image zh-lightbox-thumb& width=&550& data-original=&/v2-cf57ffee138bc76a6df6c_r.jpg&&&p&农场中一头奶牛悠闲的吃着草,一个卡通小人儿在和消费者聊着天。 &/p&&p&出现这一切,都源于一个简单的牛奶瓶。消费者通过手机或平板扫描牛奶瓶身上的图案,呈现一个虚拟农场,可以选择各种小动物,并和它们进行互动。&/p&&p&&a href=&/?target=http%3A///x/page/v.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&点击观看视频:荷兰飞天农场的 AR 营销活动&i class=&icon-external&&&/i&&/a&&/p&&p&这种深受孩子喜爱的宣传方式是怎么实现的呢?其实这个过程只需要六步: &/p&&p&1. 通过摄像头采集实时视频; &/p&&p&2. 将采集到的视频流数字化成图像,然后通过上文的特征点检测、特征点描述子生成、特征点匹配一系列处理,找到识别标识物; &/p&&p&3. 识别出标识物后,以标识物作为参考,确定要生成的动画在 AR 环境中的位置和方向,并确定提前存入手机中的标识图的位置; &/p&&p&4. 标识物中的标识符号与预设的目标图进行匹配; &/p&&p&5. 程序根据标识物位置调整动画(虚拟信息)位置; &/p&&p&6. 动画(虚拟信息)被渲染进视频流。 &/p&&p&这就是 AR 的实现过程,今天将为大家解释六步中的「精华」——识别并跟踪标识物。 &/p&&ul&&li&&b&牛奶瓶上的特点&/b&&/li&&/ul&&p&当消费者拿起手机扫描牛奶瓶时,手机是怎么判断这幅图是不是自己要找的目标呢? &/p&&br&&p&&img src=&/v2-71d3d76df675fad8cb3a_b.jpg& data-rawwidth=&600& data-rawheight=&267& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-71d3d76df675fad8cb3a_r.jpg&& 目标图是在开发这款 AR 应用时提前读入 APP 的。启动 APP 后,它就开始查找与标识图相同的帧。 &/p&&p&这就像警察在抓逃犯时,会在各交通要道设立排查点,将过往行人与逃犯照片进行对比,进行排查。警察在比对照片时,不会每根头发,每个毛孔都要进行比较。他们会关注嫌疑人的一些显著外貌特征,与这些特征相似度极高的人视为犯罪嫌疑人。&img src=&/v2-bd540c6cbb27cc_b.jpg& data-rawwidth=&600& data-rawheight=&372& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-bd540c6cbb27cc_r.jpg&&&/p&&p&同理,图像识别中的目标图也具有特征,我们将这些特征称为「特征点」。 &/p&&ul&&li&&b&那么问题来了,如何找到这些点? &/b&&/li&&/ul&&p&图像在电子设备中的状态是一堆数字。特征点就是那些和周围数字值相差较大的点,检测方法一般有:FAST、Harris、SIFT、SURF、MSER 等。 &img src=&/v2-46ab7b6ad78c58b2ed39ddd8a10bb2b4_b.png& data-rawwidth=&600& data-rawheight=&266& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-46ab7b6ad78c58b2ed39ddd8a10bb2b4_r.png&&&/p&&p&FAST 特征点检测法因检测速度快而深受研究者喜爱,具体遵循以下步骤: &/p&&p&首先,从图像中选取一个像素,记作 P。如果我们想判断它是否是一个特征点,首先把 P 点的亮度值记作 I。再设定一个合适的阈值 t,用来控制特征点的选取。以 P 为圆心,3 个像素为半径做圆,这个圆的边界上有 16 个像素,如下图所示。 &img src=&/v2-484c8d4d92c5cfd5c4ec_b.jpg& data-rawwidth=&600& data-rawheight=&295& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-484c8d4d92c5cfd5c4ec_r.jpg&&&/p&&p&如果这
16 个像素的像素值要么都比 I+t 大,要么都比 I-t 小,那么 P 就是一个特征点。一般在实际检测中,这 16 个像素不用全部与 P
的亮度值进行比较,只要有 3/4 个以上的像素的亮度值比 P+t 大或比 P-t 小,就可以认为 P 点为特征点。 &/p&&p&简单来说,特征点就是与周围反差较大的点。就像下图所示。&img src=&/v2-edec7dec7e0a512b5b3f11ee816f5f02_b.jpg& data-rawwidth=&600& data-rawheight=&547& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-edec7dec7e0a512b5b3f11ee816f5f02_r.jpg&&&/p&&ul&&li&&b&找到特征点就可以识别了么? &/b&&/li&&/ul&&p&只知道检测到特征点还不行,必须知道两张图像中的特征是否一致。如何判断特征点是否一致,就需要借助特征点描述子了。 &img src=&/v2-8f7e82eeb01f1909facbd_b.jpg& data-rawwidth=&600& data-rawheight=&544& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-8f7e82eeb01f1909facbd_r.jpg&&&/p&&p&特征点描述子是一种描述特征点的方法。比如,我们在挑西瓜时,会选根蒂蜷缩、敲起来声音浊响的青绿西瓜。这个挑瓜的过程就是描述的过程,挑瓜的依据就相当于生成的描述子。 &/p&&p&如果一个瓜和我们的挑瓜条件相似或相同,那么我们就判断它是一个好瓜。同样的如果一幅图像中有一个点和我们的描述子相似或相同,那么就可以判断它们为同一特征。&/p&&p&找到了特征点描述子,接下来就是要在目标图和识别图之间进行匹配。&img src=&/v2-5c9f8debd7d3e07a614a_b.jpg& data-rawwidth=&600& data-rawheight=&290& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-5c9f8debd7d3e07a614a_r.jpg&&&/p&&p&判断两幅图是否是同一幅图的标准是人为控制的。我们既可以设置匹配率为10%以上的两幅图像是同一图像,也可以设置匹配率为90%以上。具体标准根据应用所需而定。 &/p&&p&目前,特征点匹配技术大概分为两类:一类是分别获取两幅图像的特征点及特征点描述子,根据一定的搜索策略对这些特征点和描述子进行计算,最终获得最优极值点即为匹配结果;另一类是只获取参考帧图像的特征点信息,并利用它在当前帧上寻找最优匹配,这种匹配的最大优点是只需提取参考帧的特征点,节约了一半的特征点提取时间。 &/p&&ul&&li&&b&发现目标要盯紧 &/b&&/li&&/ul&&p&从文章开头的视频中可以看到,当小孩拿着 iPad 转动时,生成的动画会保持不动。给我们一种「动画和牛奶是一个整体」的错觉。要想达到这种效果,需要对视频流中的图像进行特征点跟踪。 &/p&&p&特征跟踪有两种方式,一种是对视频流中的每一帧图像,进行特征点匹配。另一种则在第一幅图像中,寻找可能的特征位置,然后在后续的图像中搜索它们的对应位置。显然,这类「先检测后跟踪」的方法适合用在视频跟踪应用中。 &img src=&/v2-b24aaa9c6ed987b74e086f8a07403dc7_b.jpg& data-rawwidth=&600& data-rawheight=&465& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-b24aaa9c6ed987b74e086f8a07403dc7_r.jpg&&&/p&&p&在跟踪过程中,相邻两帧经过匹配后,得到当前帧的目标特征点。由于得到的目标特征点中存在不稳定特征点,因此需要将这些不稳定的点剔除。 &/p&&p&但又出现了一个新的问题,即随着跟踪的进行,有效特征点会逐渐减少。尤其是在跟踪过程中当目标姿态发生了大幅度变化时,经常会出现目标的特征点急剧减少甚至消失的情况,这样会导致跟踪效果不好甚至跟踪失败。 &/p&&p&这就像卖水果的小商贩经常会挑出好的水果摆放在最前面,随着客人的购买,再继续往上添加。 &/p&&p&解决特征点减少问题的方法和卖水果是一样的。只需扩大范围搜索重新提取特征点,即当前时刻目标物中得到的特征点的个数小于一定的阈值时,扩大跟踪面积重新提取特征点,将重新提取到的特征点用在接下来的跟踪过程中。 &img src=&/v2-c30e5fc1d3_b.jpg& data-rawwidth=&600& data-rawheight=&488& class=&origin_image zh-lightbox-thumb& width=&600& data-original=&/v2-c30e5fc1d3_r.jpg&&&/p&&p&在跟踪的同时还需要完成一件事情,那就是渲染动画。动画必须和识别图位置保持一致才能达到融合效果。 &/p&&p&目前手机端 AR 应用开发多数是采用这种图像法,这种方法已经被模式化。开发者不用考虑上述这些底层实现,可以使用一些插件来实现。常用插件国外的有 Vuforia、Metaio 等,国内有 HiAR、EasyAR、VoidAR 等。 &/p&
农场中一头奶牛悠闲的吃着草,一个卡通小人儿在和消费者聊着天。 出现这一切,都源于一个简单的牛奶瓶。消费者通过手机或平板扫描牛奶瓶身上的图案,呈现一个虚拟农场,可以选择各种小动物,并和它们进行互动。这…
MVP + RxJava + Retrofit + Dagger2 + Realm + Glide &br&我们的专栏里有关于MVP和Retrofit的详细解读,可以了解了解&br&基于RxJava的一种MVP实现 &a href=&/p/& class=&internal&&知乎专栏&/a&&br&&a href=&///?target=http%3A//mp./s%3F__biz%3DMzA3NTYzODYzMg%3D%3D%26mid%3D%26idx%3D1%26sn%3D1a5f6369faeb22b4b68ea39frd& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&深入浅出 Retrofit,这么牛逼的框架你们还不来看看?&i class=&icon-external&&&/i&&/a&
MVP + RxJava + Retrofit + Dagger2 + Realm + Glide 我们的专栏里有关于MVP和Retrofit的详细解读,可以了解了解 基于RxJava的一种MVP实现
&img src=&/v2-bab4b3dafaaade4cdfc1b04_b.png& data-rawwidth=&661& data-rawheight=&349& class=&origin_image zh-lightbox-thumb& width=&661& data-original=&/v2-bab4b3dafaaade4cdfc1b04_r.png&&对一名安卓开发者来说,最棒的地方就在于富有活力的社区。安卓的生态系统充满了多样的生机,许多开发者乐于在网络上和现实中(黑客马拉松、会议、集会等)分享技巧和教程。&br&&br&在你朝着一名专业的安卓开发者而磨练技能的道路上,不要忘记抽出一点时间来看看下面这 30 个在线资源。这些资源涵盖了从跟进业界动态到提高自身技能等方面,总有一款适合你!&br&&br&&h2&这些资讯站,让你时刻紧跟行业最新动态!&/h2&&ul&&li&Android Weekly &a href=&/?target=http%3A//androidweekly.net/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://androidweekly.net&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&这份免费的周报里面应有尽有,包括文章、教程、视频和 GitHub 上的优质项目。不论是什么级别的安卓开发者都应该订阅这份周报。&br&&br&&ul&&li&AndroidHive
&a href=&/?target=http%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&由印度人 Ravi Tamada 创建,分享关于安卓的最新消息。&br&&br&&ul&&li&Google 开发者官方推特 &a href=&/?target=https%3A///googledevs& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/googledevs&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&还有比最初消息源更好的信息渠道吗?&br&&br&&ul&&li&Medium 上的安卓文章存档 &a href=&/?target=https%3A///tag/android& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/tag/android&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&在 Medium 上你可以遇到各种写安卓相关内容的博主和水友。用 “Android”、“Android App Development” 和 “Android Apps” 这样的标签搜索,或是直接从 Google 专家级开发者文章存档中寻找促人思考和有帮助的新闻和文章。&br&&br&&h2&权威教程,提升开发技能的好帮手!&/h2&&ul&&li&Vogella &a href=&/?target=http%3A///tutorials& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/tutorials&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&这是安卓开发者必去的教程网站。你可以用它深入理解安卓开发的各个方面,像是如何调试应用,或是如何使用谷歌地图 API。&br&&br&&ul&&li&Android developer hub &a href=&/?target=http%3A///index.html& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/i&/span&&span class=&invisible&&ndex.html&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&这是安卓官方的开发者网站。你能找到无数的工具、教程、资源下载、代码样例、第三方库等等。&br&&br&&ul&&li&Android Open Source &a href=&/?target=http%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&选择安卓开发的另一个好处就是安卓系统是完全开源的。这个网站为你提供了关于自定义安卓栈以及移植到不同设备和附件的所有信息。同时你也能找到一堆指南,包括如何运行安卓模拟器,以及如何将安卓系统移植到一个新设备等等。&br&&ul&&li&优达学城 (Udacity)- Android
&a href=&/?target=http%3A///courses/android& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/courses/&/span&&span class=&invisible&&android&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&p&Google 官方和优达学城共同开发的在线课程,从零基础到进阶开发的课程视频应有尽有,看视频不够瘾?还有Google与滴滴出行联合制作的项目等你挑战。&br&&/p&&br&&br&&ul&&li&Tuts+ &a href=&/?target=http%3A///categories/android& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/categ&/span&&span class=&invisible&&ories/android&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&这个网站上的视频课程和文字教程涵盖了从如何给你的应用添加拼写检查功能,到如何使用推特的移动开发平台 Fabric 的一切内容。&br&&br&&ul&&li&Codementor &a href=&/?target=https%3A//www.codementor.io/android& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&codementor.io/android&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&在这个学习网站上,资深安卓开发者会分享一些教程、视频、指南以及小贴士。你可以注册并通过邮件订阅更新。&br&&br&&ul&&li&The Android Arsenal
&a href=&/?target=http%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&这个分类的免费安卓第三方库和工具列表对开发者来说非常有用。&br&&br&&h2&没有灵感?这些网站能够快速激发你的头脑!&/h2&&ul&&li&Android Niceties
&a href=&/?target=http%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&这个博客收集了许多视觉体验极佳的安卓应用截图。光是浏览一遍就能获得许多灵感。&br&&br&&ul&&li&YouTube 安卓开发者频道 &a href=&/?target=https%3A///user/androiddevelopers& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/user/androi&/span&&span class=&invisible&&ddevelopers&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&这个谷歌官方设立的频道会上传发布会、产品演示和教程等等相关的视频。最近这个频道还发布了一系列优质而又有启发性的记录视频,讲述像 The Hunt 和 Haystack TV 这样的公司是如何使用安卓开发来创造炫酷和有冲击力的产品的。&br&&br&&ul&&li&App Design Served &a href=&/?target=http%3A//www.appdesignserved.co/search%3Fsearch%3Dandroid& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&appdesignserved.co/sear&/span&&span class=&invisible&&ch?search=android&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&由 Behance 维护的这份列表包括许多手机和平板应用的截图,你或许能在这上面找到一些你同事的作品。&br&&br&&ul&&li&Fragmented Podcast
&a href=&/?target=http%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&这是一个由 Donn Felker 和 Kaushik Gopal 制作的安卓开发者播客。他们每两周都为我们讲述如何创造伟大的软件产品和成为更好的安卓开发者。播客的内容涉及工具链、设计模式和最佳实践,以及对工业界顶尖人物的采访。&br&&br&&h2&Android 开发者社区,你不是一个人在战斗!&/h2&&ul&&li&Quora &a href=&/?target=https%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&安卓社区成员都很乐意回答互相之间提出的问题。像是 “为什么安卓应用是用 Java 写的,而操作系统本身是用 C++ 写的?”;又或者是 “哪门在线课程是学习安卓应用开发的最佳选择?”。光是浏览历史问题你就能花上好几个小时,当然你也可以自己提出新问题。&br&&br&&ul&&li&Stack Overflow &a href=&/?target=http%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&这是一个开发者之间充满高度互动的网站。职业和业余的程序员都喜欢提问和回答,或是评论他人的答案。通过这么做,他们达成了在开发者社区内结交朋友和促进交流的目的。当你写代码卡壳,或是想要了解实现一个功能的正确方法,又或许只是想认识跟你在做类似工作的人,Stack Overflow 就是你最好的去处。&br&&br&&ul&&li&GitHub &a href=&/?target=https%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&GitHub 是当前最大的代码托管网站,拥有超过 2270 万个代码仓库。去 GitHub 上与其他的开发者协作,或是寻找有趣的项目并为在全世界创造炫酷的软件产品而贡献代码。&br&&br&&ul&&li&AndroidDev subreddit
&a href=&/?target=https%3A///r/androiddev& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/r/androiddev&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&这上面有安卓新闻、工具链、问答以及大量对话。这是你进入安卓社区的一个宝贵的起始点。放松地加入到他们的对话当中,当然你也可以做一个默默无闻的观察者和学习者。&br&&br&&ul&&li&Google+
&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&Google 社区上也有不少活跃的安卓社区,比如 GitHub 优质安卓项目、官方安卓开发者社区和安卓开发者工具社区。&br&&br&&h2&这些网站,帮你找到高质量的工作!&/h2&&ul&&li&LinkedIn
&a href=&/?target=https%3A///& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://www.&/span&&span class=&visible&&/&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&&/li&&/ul&&br&不论在什么行业,想找工作就先上 LinkedIn,这是不会错的。搜索你向往的公司,查看是否有空缺的职位,通过群组或是私信与其他的开发者和猎头建立联系。同时不要忘记及时更新你的资料来吸引猎头的关注。&br&&br&&ul&&li&Guru
&a href=&/?target=http%3A///d/jobs/c/web-software-it/skill/android-672& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&/d/jobs/c/web-s&/span&&span class=&invisible&&oftware-it/skill/android-672&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&想找自由兼职?你可以在 Guru 的 “Web, Software & IT” 版块寻找带有安卓标签的工作。你能发现各种不同的公司以不同的薪酬标准提供兼职机会。&br&&br&&ul&&li&Stack Overflow Careers &a href=&/?target=http%3A///jobs/tag/android& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&&/span&&span class=&invisible&&/jobs/tag/android&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&你可以在 Stack Overflow 的求职版块找到许多开放的安卓开发岗位,也可以根据岗位、公司和所在城市进行搜索。更棒的是你可以直接把 LinkedIn 资料导入进来,在瞬间完善自己的档案。&ul&&li&GitHub Jobs &a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&&i class=&icon-external&&&/i&&/a&&br&&/li&&/ul&&br&又一个方便找安卓开发岗位的资源。你可以根据职位、专长、公司、地点、甚至薪酬来搜索不同岗位。&br&&br&&h2&科学上网福利:最值得关注的 Android 开发相关 Twitter 账号&/h2&Juhani Lehtimaeki&br&他是《Smashing Android UI》一书的作者以及一家名为 Fat Robot 的安卓软件开发公司的 CTO。关注他,获取一切关于安卓的新闻和思考。&br&&br&Cyril Mottier&br&作为一位手机应用开发大师,他被谷歌官方认定为专家级安卓开发者。&br&&br&&p&Richard Hyndman&br&你可能已经在 YouTube 上看到过这位来自谷歌的安卓开发倡导者。他拥有 15000 多名粉丝,每天推送新闻、简介和一些有趣的图片。&/p&&p&&br&Andy Rubin&br&作为安卓系统的创造者本人,虽然频率并不高,但他发布推文时,所有人都会去关注他说了什么。&br&&br&JR Raphael&br&资深安卓记者。他开创了《计算机世界》杂志旗下的《安卓力量》专栏。他推送关于安卓的新闻、炫酷的发现以及有趣的思考,你一定不能错过。&br&&br&Jake Wharton&br&他是来自 Square 公司的一位安卓工程师,经常在各种会议和集会上做演讲。他为推特带来许多妙趣横生的想法和有价值的编程见解。&/p&&p&&strong&▲ 本文由优达学城(Udacity)原创。&/strong&&/p&&p&如果想获得更多实战经验,&a href=&/?target=http%3A///course/android-developer-nanodegree--nd801& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 开发者纳米学位&i class=&icon-external&&&/i&&/a& 是一个很好的途径。完成所有四个学期后,你将成为被 Google 认可的顶尖 Android 工程师,直接获得滴滴出行等合作企业面试机会!&/p&
对一名安卓开发者来说,最棒的地方就在于富有活力的社区。安卓的生态系统充满了多样的生机,许多开发者乐于在网络上和现实中(黑客马拉松、会议、集会等)分享技巧和教程。 在你朝着一名专业的安卓开发者而磨练技能的道路上,不要忘记抽出一点时间来看看下…
&b&当然是自己手动实现一下 Content Provider。&/b&&br&&br&&p&现在 ORM 满天飞,但是 Content Provider 毕竟是 Google 的亲儿子,和 CursorLoader,SyncAdapter 配合起来更加是天衣无缝。为其他应用提供数据你需要content provider,为系统中的 widget 提供数据,也需要 content provider 接口。 &/p&&img src=&/v2-b661a358a02aa3519946_b.png& data-rawwidth=&1710& data-rawheight=&958& class=&origin_image zh-lightbox-thumb& width=&1710& data-original=&/v2-b661a358a02aa3519946_r.png&&&br&&p&很多人可能没有从来没有写过Content Provider,更加视 SQLite 语句如洪水猛兽。 其实也没什么难的,只要10分钟阅读这篇文章,加上一个周末时间自己手动实践,你就能打开了一个新的大门! 直接上例子,例如我们要做一个天气应用。先来个俯瞰图,我们会完成三个大部分,WeatherContract,WeatherDBHelper,WeatherProvider 和他们对应的测试类,测试类很重要! &br&&/p&&img src=&/v2-abd2c5b5c0b_b.png& data-rawwidth=&1736& data-rawheight=&970& class=&origin_image zh-lightbox-thumb& width=&1736& data-original=&/v2-abd2c5b5c0b_r.png&&&br&&b&Contract 设计表&/b&&p&Contract 字面上是合同的意思。在这里就是指制定规则,数据库有哪些表,每个表有哪些列,如何访问数据。&/p&&p&因为我们要现实多个地方的天气,每个地方又有多日的天气,因此我们需要两个表Weather 和 Location,并且在Weather表中有一个Location_id作为外键指向Location表的主键 &br&&/p&&img src=&/v2-7a3abfb6bd2f68f97d816d6c7c50bf4f_b.png& data-rawwidth=&1752& data-rawheight=&970& class=&origin_image zh-lightbox-thumb& width=&1752& data-original=&/v2-7a3abfb6bd2f68f97d816d6c7c50bf4f_r.png&&&p&然后天气表当然还要包含很多我们需要显示的信息,例如日期,最高温度,最低温度,天气状况等等。具体代码可以定义如下:&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
/* Inner class that defines the contents of the weather table */
public static final class WeatherEntry implements BaseColumns {
public static final String TABLE_NAME = &weather&;
// Column with the foreign key into the location table.
public static final String COLUMN_LOC_KEY = &location_id&;
// Date, stored as long in milliseconds since the epoch
public static final String COLUMN_DATE = &date&;
// Weather id as returned by API, to identify the icon to be used
public static final String COLUMN_WEATHER_ID = &weather_id&;
// Short description and long description of the weather, as provided by API.
// e.g &clear& vs &sky is clear&.
public static final String COLUMN_SHORT_DESC = &short_desc&;
// Min and max temperatures for the day (stored as floats)
public static final String COLUMN_MIN_TEMP = &min&;
public static final String COLUMN_MAX_TEMP = &max&;
// Humidity is stored as a float representing percentage
public static final String COLUMN_HUMIDITY = &humidity&;
// Humidity is stored as a float representing percentage
public static final String COLUMN_PRESSURE = &pressure&;
// Windspeed is stored as a float representing windspeed
public static final String COLUMN_WIND_SPEED = &wind&;
// Degrees are meteorological degrees (e.g, 0 is north, 180 is south).
Stored as floats.
public static final String COLUMN_DEGREES = &degrees&;
Inner class that defines the contents of the location table
public static final class LocationEntry implements BaseColumns {
public static final String TABLE_NAME = &location&;
// The location setting string is what will be sent to openweathermap
// as the location query.
public static final String COLUMN_LOCATION_SETTING = &location_setting&;
// Human readable location string, provided by the API.
Because for styling,
// &Mountain View& is more recognizable than 94043.
public static final String COLUMN_CITY_NAME = &city_name&;
// In order to uniquely pinpoint the location on the map when we launch the
// map intent, we store the latitude and longitude as returned by openweathermap.
public static final String COLUMN_COORD_LAT = &coord_lat&;
public static final String COLUMN_COORD_LONG = &coord_long&;
&/code&&/pre&&/div&&br&&b&WeatherDBHelper 实现&/b&&p&完成了表的设计以后,我们要用SQLiteDbHelper来真正创建数据库。其实就是写两个创建表的SQL语句,然后执行SQL语句。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
public void onCreate(SQLiteDatabase sqLiteDatabase) {
final String SQL_CREATE_LOCATION_TABLE = &CREATE TABLE & + LocationEntry.TABLE_NAME + & (& +
LocationEntry._ID + & INTEGER PRIMARY KEY,& +
LocationEntry.COLUMN_LOCATION_SETTING + & TEXT UNIQUE NOT NULL, & +
LocationEntry.COLUMN_CITY_NAME + & TEXT NOT NULL, & +
LocationEntry.COLUMN_COORD_LAT + & REAL NOT NULL, & +
LocationEntry.COLUMN_COORD_LONG + & REAL NOT NULL & +
final String SQL_CREATE_WEATHER_TABLE = &CREATE TABLE & + WeatherEntry.TABLE_NAME + & (& +
WeatherEntry._ID + & INTEGER PRIMARY KEY AUTOINCREMENT,& +
// the ID of the location entry associated with this weather data
WeatherEntry.COLUMN_LOC_KEY + & INTEGER NOT NULL, & +
WeatherEntry.COLUMN_DATE + & INTEGER NOT NULL, & +
WeatherEntry.COLUMN_SHORT_DESC + & TEXT NOT NULL, & +
WeatherEntry.COLUMN_WEATHER_ID + & INTEGER NOT NULL,& +
WeatherEntry.COLUMN_MIN_TEMP + & REAL NOT NULL, & +
WeatherEntry.COLUMN_MAX_TEMP + & REAL NOT NULL, & +
WeatherEntry.COLUMN_HUMIDITY + & REAL NOT NULL, & +
WeatherEntry.COLUMN_PRESSURE + & REAL NOT NULL, & +
WeatherEntry.COLUMN_WIND_SPEED + & REAL NOT NULL, & +
WeatherEntry.COLUMN_DEGREES + & REAL NOT NULL, & +
// Set up the location column as a foreign key to location table.
& FOREIGN KEY (& + WeatherEntry.COLUMN_LOC_KEY + &) REFERENCES & +
LocationEntry.TABLE_NAME + & (& + LocationEntry._ID + &), & +
// To assure the application have just one weather entry per day
// per location, it's created a UNIQUE constraint with REPLACE strategy
& UNIQUE (& + WeatherEntry.COLUMN_DATE + &, & +
WeatherEntry.COLUMN_LOC_KEY + &) ON CONFLICT REPLACE);&;
sqLiteDatabase.execSQL(SQL_CREATE_LOCATION_TABLE);
sqLiteDatabase.execSQL(SQL_CREATE_WEATHER_TABLE);
&/code&&/pre&&/div&&br&&b&TestDb实现&/b&&p&到这里我们已经可以设计一些测试样例来测试两个表是否都成功建立,编写 ContentValues,测试插入成功,读取成功,主键,副键和约束条件是否生效等等。&/p&&p&这里代码就省略了。可以在文章最后找到课程地址,查看一步步建立content provider的课程视频。&/p&&br&&b&Contract 设计URI&/b&&p&设计URI就是设计如何和表中的信息交互,包括读和写。作为一个天气应用,当然我们要获取&/p&&ul&&li&所有天气,&/li&&li&某个位置的所有天气,&/li&&li&以及某个位置某个日期的天气&/li&&li&所有位置,&/li&&/ul&&p&四种访问数据的方式。其中第三种返回一个,其他都返回多个。根据这些需求,我们可以定义以下四个 URI 和他们对应的类型。 &br&&/p&&img src=&/v2-4c5fa62cd792c3d17c9a12f95e9fe425_b.png& data-rawwidth=&1263& data-rawheight=&854& class=&origin_image zh-lightbox-thumb& width=&1263& data-original=&/v2-4c5fa62cd792c3d17c9a12f95e9fe425_r.png&&&p&这里写出contract里面所有代码太长了,就用Weather Path 下面的三个URI作为例子, 省略了Location Path下面的 URI,和一些辅助函数。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
public static final String CONTENT_AUTHORITY = &com.example.android.sunshine.app&;
public static final Uri BASE_CONTENT_URI = Uri.parse(&content://& + CONTENT_AUTHORITY);
public static final String PATH_WEATHER = &weather&;
public static final Uri CONTENT_URI =
BASE_CONTENT_URI.buildUpon().appendPath(PATH_WEATHER).build();
public static final String CONTENT_TYPE =
ContentResolver.CURSOR_DIR_BASE_TYPE + &/& + CONTENT_AUTHORITY + &/& + PATH_WEATHER;
public static final String CONTENT_ITEM_TYPE =
ContentResolver.CURSOR_ITEM_BASE_TYPE + &/& + CONTENT_AUTHORITY + &/& + PATH_WEATHER;
public static Uri buildWeatherUri(long id) {
return ContentUris.withAppendedId(CONTENT_URI, id);
public static Uri buildWeatherLocation(String locationSetting) {
return CONTENT_URI.buildUpon()
.appendPath(locationSetting)
public static Uri buildWeatherLocationWithDate(String locationSetting, long date) {
return CONTENT_URI.buildUpon()
.appendPath(locationSetting)
.appendPath(Long.toString(normalizeDate(date)))
&/code&&/pre&&/div&&br&&b&Content Provider 重载&/b&&p&万事俱备,下面开始开始真正重载 ContentProvider,完成我们自己的WeatherProvider了. 我们需要重载下面几个核心函数。onCreate比较简单,只需要在里面创建并获取数据库就可以了。但是在开始完成其他五个函数之前,我们先要实现UriMatcher, 因为这五个函数都依赖于UriMatcher 来为每个URI匹配正确的行为。 &br&&/p&&img src=&/v2-a6b68fc834550faf02a0f2d_b.png& data-rawwidth=&1264& data-rawheight=&594& class=&origin_image zh-lightbox-thumb& width=&1264& data-original=&/v2-a6b68fc834550faf02a0f2d_r.png&&&br&&b&Content Provider UriMatcher&/b&&p&我们需要用 UriMatcher 来确认进入provider的 URI是哪里个,从而完成相对应的工作。上面的设计有四个URI,因此我们需要匹配所有四种 URI&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
static final int WEATHER = 100;
static final int WEATHER_WITH_LOCATION = 101;
static final int WEATHER_WITH_LOCATION_AND_DATE = 102;
static final int LOCATION = 300;
static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
final String authority = WeatherContract.CONTENT_AUTHORITY;
// For each type of URI you want to add, create a corresponding code.
matcher.addURI(authority, WeatherContract.PATH_WEATHER, WEATHER);
matcher.addURI(authority, WeatherContract.PATH_WEATHER + &/*&, WEATHER_WITH_LOCATION);
matcher.addURI(authority, WeatherContract.PATH_WEATHER + &/*/#&, WEATHER_WITH_LOCATION_AND_DATE);
matcher.addURI(authority, WeatherContract.PATH_LOCATION, LOCATION);
&/code&&/pre&&/div&&p&这里也是个绝佳的时间添加一些 URIMatcher 的测试代码来确认是否每个uri 都已经被正确得匹配。 这会节省你很多很多的调试的时间。&/p&&br&&b&Content Provider getType()&/b&&p&getType是剩余五个函数里面最简单的,匹配uri以后,只要返回对应的type就可以了。对于其他的query,insert,update,delete 我们也采取同样的 switch case语句,只不过每个情况都稍微复杂一点罢了。&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
public String getType(Uri uri) {
// Use the Uri Matcher to determine what kind of URI this is.
final int match = sUriMatcher.match(uri);
switch (match) {
// Student: Uncomment and fill out these two cases
case WEATHER_WITH_LOCATION_AND_DATE:
return WeatherContract.WeatherEntry.CONTENT_ITEM_TYPE;
case WEATHER_WITH_LOCATION:
return WeatherContract.WeatherEntry.CONTENT_TYPE;
case WEATHER:
return WeatherContract.WeatherEntry.CONTENT_TYPE;
case LOCATION:
return WeatherContract.LocationEntry.CONTENT_TYPE;
throw new UnsupportedOperationException(&Unknown uri: & + uri);
&/code&&/pre&&/div&&br&&b&Content Provider query()&/b&&p&query 是最复杂的,基本结构当然和 getType一样,但是就像之前说的,每个情况都略微复杂。如果要获得某个日期,某个地点的天气,我们就需要把两个表join起来才可以。因此我们定义一个比较复杂的from语句,和一个比较复杂的where语句。我们就以最复杂的 WEATHER_WITH_LOCATION_AND_DATE 为例&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&case WEATHER_WITH_LOCATION_AND_DATE:
sWeatherByLocationSettingQueryBuilder = new SQLiteQueryBuilder();
//FROM clause
//This is an inner join which looks like
//weather INNER JOIN location ON weather.location_id = location._id
sWeatherByLocationSettingQueryBuilder.setTables(
WeatherContract.WeatherEntry.TABLE_NAME + & INNER JOIN & +
WeatherContract.LocationEntry.TABLE_NAME +
& ON & + WeatherContract.WeatherEntry.TABLE_NAME +
&.& + WeatherContract.WeatherEntry.COLUMN_LOC_KEY +
& = & + WeatherContract.LocationEntry.TABLE_NAME +
&.& + WeatherContract.LocationEntry._ID);
//WHERE clause
String sLocationSettingAndDaySelection =
WeatherContract.LocationEntry.COLUMN_LOCATION_SETTING + & = ?&
+ & AND & + WeatherContract.WeatherEntry.COLUMN_DATE + & = ?&;
//WHER clause argument
String locationSetting = WeatherContract.WeatherEntry.getLocationSettingFromUri(uri);
long date = WeatherContract.WeatherEntry.getDateFromUri(uri);
//actual SQL query statement
retCursor = sWeatherByLocationSettingQueryBuilder.query(mOpenHelper.getReadableDatabase(),
projection,
sLocationSettingAndDaySelection,
new String[]{locationSetting, Long.toString(date)},
retCursor.setNotificationUri(getContext().getContentResolver(), uri);
return retCursor
&/code&&/pre&&/div&&br&&b&Content Provider update(), insert(), delete()&/b&&p&update insert delete 相对比较简单,因为我们只需要针对 WEATHER 和 LOCATION 两种URI进行操作就可以了。需要注意的是,在对数据库进行修改了以后,应该调用&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&getContext().getContentResolver().notifyChange(uri, null);
&/code&&/pre&&/div&&p&来通知所有的observer这个URI内容变化了,loader的功效在这里也就体现出来了。这里就只给出insert的代码,其他两个的类似&/p&&br&&div class=&highlight&&&pre&&code class=&language-text&&
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sUriMatcher.match(uri);
Uri returnU
switch (match) {
case WEATHER: {
normalizeDate(values);
long _id = db.insert(WeatherContract.WeatherEntry.TABLE_NAME, null, values);
if ( _id & 0 )
returnUri = WeatherContract.WeatherEntry.buildWeatherUri(_id);
throw new android.database.SQLException(&Failed to insert row into & + uri);
case LOCATION: {
long _id = db.insert(WeatherContract.LocationEntry.TABLE_NAME, null, values);
if ( _id & 0 )
returnUri = WeatherContract.LocationEntry.buildLocationUri(_id);
throw new android.database.SQLException(&Failed to insert row into & + uri);
throw new UnsupportedOperationException(&Unknown uri: & + uri);
getContext().getContentResolver().notifyChange(uri, null);
return returnU
&/code&&/pre&&/div&&br&&p&至此整个content provider也就实现完了。当然每完成一个函数,我们都应该添加一些单元测试代码来验证这个函数是否正常工作。这属于一个比较复杂的content provider,但其实真的写出来就没多少嘛 (-_-\)花这点时间,绝对是一个有价值的投资!&/p&&br&&p&以上内容都来自于我们和Google共同开发的免费课程 &a href=&///?target=https%3A///courses/ud853/lessons//concepts/23%23& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android应用开发&i class=&icon-external&&&/i&&/a&。&/p&
当然是自己手动实现一下 Content Provider。 现在 ORM 满天飞,但是 Content Provider 毕竟是 Google 的亲儿子,和 CursorLoader,SyncAdapter 配合起来更加是天衣无缝。为其他应用提供数据你需要content provider,为系统中的 widget 提供数据,也需要 con…
你别说,还真有。&br&&br&&br&&em&前几天,GitHub 用户 ryanjay0 开源了一个可以用来识别色情视频中特定类型的场景的人工智能项目 Miles Deep。该算法可以将你想看的类型的片段从完整视频中截取出来并生成一个集合了这些片段的新视频,让你可以不再为那些多余的片段烦恼。&/em&&br&&br&&a href=&///?target=http%3A//mp./s/b0vn1DQl2_F1-lbdlKVLtA& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&mp./s/b0vn&/span&&span class=&invisible&&1DQl2_F1-lbdlKVLtA&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&br&Github项目地址&a href=&///?target=https%3A///ryanjay0/miles-deep& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/ryanjay0/mil&/span&&span class=&invisible&&es-deep&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&br&&br&这样大家以后就可以高速开车了,实现“无人驾驶” ,不过这个项目下面大家最关心的是让作者公开测试集 。
你别说,还真有。 前几天,GitHub 用户 ryanjay0 开源了一个可以用来识别色情视频中特定类型的场景的人工智能项目 Miles Deep。该算法可以将你想看的类型的片段从完整视频中截取出来并生成一个集合了这些片段的新视频,让你可以不再为那些多余的片段烦恼。
&h1&概述&/h1&&p&说到Android MVVM,相信大家都会想到Google 2015年推出的DataBinding框架。然而两者的概念是不一样的,不能混为一谈。MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。&/p&&p&之前看过很多关于Android MVVM的博客,但大多数提到的都是DataBinding的基本用法,很少有文章仔细讲解在Android中是如何通过DataBinding去构建MVVM的应用框架的。View、ViewModel、Model每一层的职责如何?它们之间联系怎样、分工如何、代码应该如何设计?这是我写这篇文章的初衷。&/p&&p&接下来,我们先来看看什么是MVVM,然后再一步一步来设计整个MVVM框架。&/p&&h1&MVC、MVP、MVVM&/h1&&p&首先,我们先大致了解下Android开发中常见的模式。&/p&&h2&MVC&/h2&&blockquote&&p&&strong&View:&/strong&XML布局文件。&br&&strong&Model:&/strong&实体模型(数据的获取、存储、数据状态变化)。&br&&strong&Controller:&/strong&对应于Activity,处理数据、业务和UI。&/p&&/blockquote&&p&从上面这个结构来看,Android本身的设计还是符合MVC架构的,但是Android中纯粹作为View的XML视图功能太弱,我们大量处理View的逻辑只能写在Activity中,这样Activity就充当了View和Controller两个角色,直接导致Activity中的代码大爆炸。相信大多数Android开发者都遇到过一个Acitivty数以千行的代码情况吧!所以,更贴切的说法是,这个MVC结构最终其实只是一个Model-View(Activity:View&Controller)的结构。&/p&&h2&MVP&/h2&&blockquote&&p&&strong&View: &/strong&对应于Activity和XML,负责View的绘制以及与用户的交互。&br&&strong&Model: &/strong&依然是实体模型。&br&&strong&Presenter: &/strong&负责完成View与Model间的交互和业务逻辑。&/p&&/blockquote&&p&前面我们说,Activity充当了View和Controller两个角色,MVP就能很好地解决这个问题,其核心理念是通过一个抽象的View接口(不是真正的View层)将Presenter与真正的View层进行解耦。Persenter持有该View接口,对该接口进行操作,而不是直接操作View层。这样就可以把视图操作和业务逻辑解耦,从而让Activity成为真正的View层。&/p&&p&但MVP也存在一些弊端:&/p&&ul&&li&Presenter(以下简称P)层与View(以下简称V)层是通过接口进行交互的,接口粒度不好控制。粒度太小,就会存在大量接口的情况,使代码太过碎版化;粒度太大,解耦效果不好。同时对于UI的输入和数据的变化,需要手动调用V层或者P层相关的接口,相对来说缺乏自动性、监听性。如果数据的变化能自动响应到UI、UI的输入能自动更新到数据,那该多好!&/li&&li&MVP是以UI为驱动的模型,更新UI都需要保证能获取到控件的引用,同时更新UI的时候要考虑当前是否是UI线程,也要考虑Activity的生命周期(是否已经销毁等)。&/li&&li&MVP是以UI和事件为驱动的传统模型,数据都是被动地通过UI控件做展示,但是由于数据的时变性,我们更希望数据能转被动为主动,希望数据能更有活性,由数据来驱动UI。&/li&&li&V层与P层还是有一定的耦合度。一旦V层某个UI元素更改,那么对应的接口就必须得改,数据如何映射到UI上、事件监听接口这些都需要转变,牵一发而动全身。如果这一层也能解耦就更好了。&/li&&li&复杂的业务同时也可能会导致P层太大,代码臃肿的问题依然不能解决。&/li&&/ul&&h2&MVVM&/h2&&blockquote&&p&&strong&View: &/strong&对应于Activity和XML,负责View的绘制以及与用户交互。&br&&strong&Model: &/strong&实体模型。&br&&strong&ViewModel: &/strong&负责完成View与Model间的交互,负责业务逻辑。&/p&&/blockquote&&p&MVVM的目标和思想与MVP类似,利用数据绑定(Data Binding)、依赖属性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一个更加灵活高效的架构。&/p&&h3&数据驱动&/h3&&p&在常规的开发模式中,数据变化需要更新UI的时候,需要先获取UI控件的引用,然后再更新UI。获取用户的输入和操作也需要通过UI控件的引用。在MVVM中,这些都是通过数据驱动来自动完成的,数据变化后会自动更新UI,UI的改变也能自动反馈到数据层,数据成为主导因素。这样MVVM层在业务逻辑处理中只要关心数据,不需要直接和UI打交道,在业务处理过程中简单方便很多。&/p&&h3&低耦合度&/h3&&p&MVVM模式中,数据是独立于UI的。&/p&&p&数据和业务逻辑处于一个独立的ViewModel中,ViewModel只需要关注数据和业务逻辑,不需要和UI或者控件打交道。UI想怎么处理数据都由UI自己决定,ViewModel不涉及任何和UI相关的事,也不持有UI控件的引用。即便是控件改变了(比如:TextView换成EditText),ViewModel也几乎不需要更改任何代码。它非常完美的解耦了View层和ViewModel,解决了上面我们所说的MVP的痛点。&/p&&h3&更新UI&/h3&&p&在MVVM中,数据发生变化后,我们在工作线程直接修改(在数据是线程安全的情况下)ViewModel的数据即可,不用再考虑要切到主线程更新UI了,这些事情相关框架都帮我们做了。&/p&&h3&团队协作&/h3&&p&MVVM的分工是非常明显的,由于View和ViewModel之间是松散耦合的:一个是处理业务和数据、一个是专门的UI处理。所以,完全由两个人分工来做,一个做UI(XML和Activity)一个写ViewModel,效率更高。&/p&&h3&可复用性&/h3&&p&一个ViewModel可以复用到多个View中。同样的一份数据,可以提供给不同的UI去做展示。对于版本迭代中频繁的UI改动,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二选择。&/p&&h3&单元测试&/h3&&p&有些同学一看到单元测试,可能脑袋都大。是啊,写成一团浆糊的代码怎么可能做单元测试?如果你们以代码太烂无法写单元测试而逃避,那可真是不好的消息了。这时候,你需要MVVM来拯救。&/p&&p&我们前面说过了,ViewModel层做的事是数据处理和业务逻辑,View层中关注的是UI,两者完全没有依赖。不管是UI的单元测试还是业务逻辑的单元测试,都是低耦合的。在MVVM中数据是直接绑定到UI控件上的(部分数据是可以直接反映出UI上的内容),那么我们就可以直接通过修改绑定的数据源来间接做一些Android UI上的测试。&/p&&p&通过上面的简述以及模式的对比,我们可以发现MVVM的优势还是非常明显的。虽然目前Android开发中可能真正在使用MVVM的很少,但是值得我们去做一些探讨和调研。&/p&&h1&如何构建MVVM应用框架&/h1&&h2&如何分工&/h2&&p&构建MVVM框架首先要具体了解各个模块的分工。接下来我们来讲解View、ViewModel、Model它们各自的职责所在。&/p&&h3&View&/h3&&p&View层做的就是和UI相关的工作,我们只在XML、Activity和Fragment写View层的代码,View层不做和业务相关的事,也就是我们在Activity不写业务逻辑和业务数据相关的代码,更新UI通过数据绑定实现,尽量在ViewModel里面做(更新绑定的数据源即可),Activity要做的事就是初始化一些控件(如控件的颜色,添加RecyclerView的分割线),View层可以提供更新UI的接口(但是我们更倾向所有的UI元素都是通过数据来驱动更改UI),View层可以处理事件(但是我们更希望UI事件通过Command来绑定)。&strong&简单地说:View层不做任何业务逻辑、不涉及操作数据、不处理数据,UI和数据严格的分开。&/strong&&/p&&h3&ViewModel&/h3&&p&ViewModel层做的事情刚好和View层相反,ViewModel只做和业务逻辑和业务数据相关的事,不做任何和UI相关的事情,ViewModel 层不会持有任何控件的引用,更不会在ViewModel中通过UI控件的引用去做更新UI的事情。ViewModel就是专注于业务的逻辑处理,做的事情也都只是对数据的操作(这些数据绑定在相应的控件上会自动去更改UI)。同时DataBinding框架已经支持双向绑定,让我们可以通过双向绑定获取View层反馈给ViewModel层的数据,并对这些数据上进行操作。关于对UI控件事件的处理,我们也希望能把这些事件处理绑定到控件上,并把这些事件的处理统一化,为此我们通过BindingAdapter对一些常用的事件做了封装,把一个个事件封装成一个个Command,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把你可能需要的数据带给你,这使得我们在ViewModel层处理事件的时候只需要关心处理数据就行了,具体见&strong&&a href=&/?target=http%3A///p/43ea7a531700& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVVM Light Toolkit 使用指南&i class=&icon-external&&&/i&&/a&&/strong&的Command部分。再强调一遍:ViewModel 不做和UI相关的事。&/p&&h3&Model&/h3&&p&Model层最大的特点是被赋予了数据获取的职责,与我们平常Model层只定义实体对象的行为截然不同。实例中,数据的获取、存储、数据状态变化都是Model层的任务。Model包括实体模型(Bean)、Retrofit的Service ,获取网络数据接口,本地存储(增删改查)接口,数据变化监听等。Model提供数据获取接口供ViewModel调用,经数据转换和操作并最终映射绑定到View层某个UI元素的属性上。&/p&&h2&如何协作&/h2&&p&关于协作,我们先来看下面的一张图:&/p&&img src=&/v2-c9ab4b4def6d47c786ceeaadf2224957_b.png& data-rawwidth=&803& data-rawheight=&607& class=&origin_image zh-lightbox-thumb& width=&803& data-original=&/v2-c9ab4b4def6d47c786ceeaadf2224957_r.png&&&p&上图反映了MVVM框架中各个模块的联系和数据流的走向,我们从每个模块一一拆分来看。那么我们重点就是下面的三个协作。&/p&&blockquote&&ul&&li&&strong&ViewModel与View的协作&/strong&。&/li&&li&&strong&ViewModel与Model的协作&/strong&。&/li&&li&&strong&ViewModel与ViewModel的协作&/strong&。&/li&&/ul&&/blockquote&&h3&ViewModel与View的协作&/h3&&img src=&/v2-e5de67e2f7_b.png& data-rawwidth=&500& data-rawheight=&306& class=&origin_image zh-lightbox-thumb& width=&500& data-original=&/v2-e5de67e2f7_r.png&&&p&图2中ViewModel和View是通过绑定的方式连接在一起的,绑定分成两种:一种是数据绑定,一种是命令绑定。数据的绑定DataBinding已经提供好了,简单地定义一些ObservableField就能把数据和控件绑定在一起了(如TextView的text属性),但是DataBinding框架提供的不够全面,比如说如何让一个URL绑定到一个ImageView,让这个ImageView能自动去加载url指定的图片,如何把数据源和布局模板绑定到一个ListView,让ListView可以不需要去写Adapter和ViewHolder相关的东西?这些就需要我们做一些工作和简单的封装。MVVM Light Toolkit 已经帮我们做了一部分的工作,详情可以查看&strong&&a href=&/?target=http%3A///p/43ea7a531700& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVVM Light Toolkit 使用指南&i class=&icon-external&&&/i&&/a&&/strong&。关于事件绑定也是一样,MVVM Light Toolkit 做了简单的封装,对于每个事件我们用一个ReplyCommand去处理就行了,ReplyCommand会把可能需要的数据带给你,这样我们处理事件的时候也只关心处理数据就行了。&/p&&p&由 &strong&图 1&/strong& 中ViewModel的模块中我们可以看出ViewModel类下面一般包含下面5个部分:&/p&&blockquote&&ul&&li&Context (上下文)&/li&&li&Model (数据源 Java Bean)&/li&&li&Data Field (数据绑定)&/li&&li&Command (命令绑定)&/li&&li&Child ViewModel (子ViewModel)&/li&&/ul&&/blockquote&&p&我们先来看下示例代码,然后再一一讲解5个部分是干嘛用的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//context
//model(数据源 Java Bean)
private NewsService.N
private TopNewsService.News topN
//数据绑定,绑定到UI的字段(data field)
public final ObservableField&String& imageUrl = new ObservableField&&();
public final ObservableField&String& html = new ObservableField&&();
public final ObservableField&String& title = new ObservableField&&();
// 一个变量包含了所有关于View Style 相关的字段
public final ViewStyle viewStyle = new ViewStyle();
//命令绑定(command)
public final ReplyCommand onRefreshCommand = new ReplyCommand&&(() -& {
public final ReplyCommand&Integer& onLoadMoreCommand = new ReplyCommand&&((itemCount) -& {
//Child ViewModel
public final ObservableList&NewItemViewModel& itemViewModel = new ObservableArrayList&&();
/** * ViewStyle 关于控件的一些属性和业务数据无关的Style 可以做一个包裹,这样代码比较美观,
ViewModel 页面也不会有太多太杂的字段。 **/
public static class ViewStyle {
public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
&/code&&/pre&&/div&&h3&Context&/h3&&p&Context是干嘛用的呢,为什么每个ViewModel都最好需要持了一个Context的引用呢?ViewModel不处理和UI相关的事也不操作控件,更不更新UI,那为什么要有Context呢?原因主要有以下两点:&/p&&ol&&li&&p&通过图1中,然后得到一个Observable,其实这就是网络请求部分。其实这就是网络请求部分,做网络请求我们必须把Retrofit Service返回的Observable绑定到Context的生命周期上,防止在请求回来时Activity已经销毁等异常,其实这个Context的目的就是把网络请求绑定到当前页面的生命周期中。&/p&&/li&&li&&p&在图1中,我们可以看到两个ViewModel之间的联系是通过Messenger来做,这个Messenger是需要用到Context,这个我们后续会讲解。&/p&&/li&&/ol&&p&当然,除此以外,调用工具类、帮助类有时候需要Context做为参数等也是原因之一。&/p&&h3&Model (数据源)&/h3&&p&Model是什么呢?其实就是数据源,可以简单理解是我们用JSON转过来的Bean。ViewModel要把数据映射到UI中可能需要大量对Model的数据拷贝和操作,拿Model的字段去生成对应的ObservableField然后绑定到UI(我们不会直接拿Model的数据去做绑定展示),这里是有必要在一个ViewModel保留原始的Model引用,这对于我们是非常有用的,因为可能用户的某些操作和输入需要我们去改变数据源,可能我们需要把一个Bean在列表页点击后传给详情页,可能我们需要把这个Model当做表单提交到服务器。这些都需要我们的ViewModel持有相应的Model(数据源)。&/p&&h3&Data Field(数据绑定)&/h3&&p&Data Field就是需要绑定到控件上的ObservableField字段,这是ViewModel的必需品,这个没有什么好说。但是这边有一个建议:&br&这些字段是可以稍微做一下分类和包裹的。比如说可能一些字段是绑定到控件的一些Style属性上(如长度、颜色、大小),对于这类针对View Style的的字段可以声明一个ViewStyle类包裹起来,这样整个代码逻辑会更清晰一些,不然ViewModel里面可能字段泛滥,不易管理和阅读性较差。而对于其他一些字段,比如说title、imageUrl、name这些属于数据源类型的字段,这些字段也叫数据字段,是和业务数据和逻辑息息相关的,这些字段可以放在一块。&/p&&h3&Command(命令绑定)&/h3&&p&Command(命令绑定)简言之就是对事件的处理(下拉刷新、加载更多、点击、滑动等事件处理)。我们之前处理事件是拿到UI控件的引用,然后设置Listener,这些Listener其实就是Command。但是考虑到在一个ViewModel写各种Listener并不美观,可能实现一个Listener就需要实现多个方法,但是我们可能只想要其中一个有用的方法实现就好了。更重要一点是实现一个Listener可能需要写一些UI逻辑才能最终获取我们想要的。简单举个例子,比如你想要监听ListView滑到最底部然后触发加载更多的事件,这时候就要在ViewModel里面写一个OnScrollListener,然后在里面的onScroll方法中做计算,计算什么时候ListView滑动底部了。其实ViewModel的工作并不想去处理这些事件,它专注做的应该是业务逻辑和数据处理,如果有一个东西不需要你自己去计算是否滑到底部,而是在滑动底部自动触发一个Command,同时把当前列表的总共的item数量返回给你,方便你通过 page=itemCount/LIMIT+1去计算出应该请求服务器哪一页的数据那该多好啊。MVVM Light Toolkit 帮你实现了这一点:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& public final ReplyCommand&Integer& onLoadMoreCommand =
new ReplyCommand&&((itemCount) -& {
int page=itemCount/LIMIT+1;
loadData(page.LIMIT)
&/code&&/pre&&/div&&p&接着在XML布局文件中通过bind:onLoadMoreCommand绑定上去就行了。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& &android.support.v7.widget.RecyclerView
android:layout_width=&match_parent&
android:layout_height=&match_parent&
bind:onLoadMoreCommand=&@{viewModel.loadMoreCommand}&/&
&/code&&/pre&&/div&&p&具体想了解更多请查看 &strong&&a href=&/?target=http%3A///p/43ea7a531700& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVVM Light Toolkit 使用指南&i class=&icon-external&&&/i&&/a&&/strong&,里面有比较详细地讲解Command的使用。当然Command并不是必须的,你完全可以依照自己的习惯和喜好在ViewModel写Listener,不过使用Command可以使ViewModel更简洁易读。你也可以自己定义更多的、其他功能的Command,那么ViewModel的事件处理都是托管ReplyCommand来处理,这样的代码看起来会比较美观和清晰。Command只是对UI事件的一层隔离UI层的封装,在事件触发时把ViewModel层可能需要的数据传给ViewModel层,对事件的处理做了统一化,是否使用的话,还是看你个人喜好了。&/p&&h3&Child ViewModel(子ViewModel)&/h3&&p&子ViewModel的概念就是在ViewModel里面嵌套其他的ViewModel,这种场景还是很常见的。比如说你一个Activity里面有两个Fragment,ViewModel是以业务划分的,两个Fragment做的业务不一样,自然是由两个ViewModel来处理,这时候Activity对应的ViewModel里面可能包含了两个Fragment各自的ViewModel,这就是嵌套的子ViewModel。还有另外一种就是对于AdapterView,如ListView RecyclerView、ViewPager等。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
//Child ViewModelpublic final
ObservableList&ItemViewModel& itemViewModel = new ObservableArrayList&&();
&/code&&/pre&&/div&&p&它们的每个Item其实就对应于一个ViewModel,然后在当前的ViewModel通过ObservableList持有引用(如上述代码),这也是很常见的嵌套的子ViewModel。我们其实还建议,如果一个页面业务非常复杂,不要把所有逻辑都写在一个ViewModel,可以把页面做业务划分,把不同的业务放到不同的ViewModel,然后整合到一个总的ViewModel,这样做起来可以使我们的代码业务清晰、简短意赅,也方便后人的维护。&/p&&p&总的来说,ViewModel和View之前仅仅只有绑定的关系,View层需要的属性和事件处理都是在XML里面绑定好了,ViewModel层不会去操作UI,只是根据业务要求处理数据,这些数据自动映射到View层控件的属性上。关于ViewModel类中包含哪些模块和字段,这个需要开发者自己去衡量,我们建议ViewModel不要引入太多的成员变量,成员变量最好只有上面的提到的5种(context、model……),能不引入其他类型的变量就尽量不要引进来,太多的成员变量对于整个代码结构破坏很大,后面维护的人要时刻关心成员变量什么时候被初始化、什么时候被清掉、什么时候被赋值或者改变,一个细节不小心可能就出现潜在的Bug。太多不清晰定义的成员变量又没有注释的代码是很难维护的。&/p&&p&另外,我们会把UI控件的属性和事件都通过XML(如bind:text=@{...})绑定。如果一个业务逻辑要弹一个Dialog,但是你又不想在ViewModel里面做弹窗的事(ViewModel不希望做UI相关的事)或者说改变ActionBar上面的图标的颜色,改变ActionBar按钮是否可点击,这些都不是写在XML里面(都是用Java代码初始化的),如何对这些控件的属性做绑定呢?我们先来看下代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&public class MainViewModel implements ViewModel {
//true的时候弹出Dialog,false的时候关掉dialog
public final ObservableBoolean isShowDialog = new ObservableBoolean();
// 在View层做一个对isShowDialog改变的监听
public class MainActivity extends RxBasePmsActivity {
private MainViewModel mainViewM
protected void onCreate(Bundle savedInstanceState) {
mainViewModel.isShowDialog.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
if (mainViewModel.isShowDialog.get()) {
dialog.show();
dialog.dismiss();
&/code&&/pre&&/div&&p&简单地说你可以对任意的ObservableField做监听,然后根据数据的变化做相应UI的改变,业务层ViewModel只要根据业务处理数据就行,以数据来驱动UI。&/p&&h2&ViewModel与Model的协作  &/h2&&p&从图1中,ViewModel通过传参数到Model层获取网络数据(数据库同理),然后把Model的部分数据映射到ViewModel的一些字段(ObservableField),并在ViewModel保留这个Model的引用,我们来看下这一块的大致代码(代码涉及简单的RxJava,如看不懂可以查阅入门一下):&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& //Model
private NewsDetail newsD
private void loadData(long id) {
Observable&Bean& 用来获取网络数据
Observable&Notification&NewsDetailService.NewsDetail&&
newsDetailOb =
RetrofitProvider.getInstance()
.create(NewsDetailService.class)
.getNewsDetail(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
// 将网络请求绑定到Activity 的生命周期
.compose(((ActivityLifecycleProvider) context).bindToLifecycle())
//变成 Notification&Bean& 使我们更方便处理数据和错误
.materialize().share();
// 处理返回的数据
newsDetailOb.filter(Notification::isOnNext)
.map(n -& n.getValue())
// 给成员变量newsDetail 赋值,之前提到的5种变量类型中的一种(model类型)
.doOnNext(m -& newsDetail = m)
.subscribe(m -& initViewModelField(m));
// 网络请求错误处理
NewsListHelper.dealWithResponseError(
newsDetailOb.filter(Notification::isOnError)
.map(n -& n.getThrowable()));
//Model --&ViewModel
private void initViewModelField(NewsDetail newsDetail) {
viewStyle.isRefreshing.set(false);
imageUrl.set(newsDetail.getImage());
Observable.just(newsDetail.getBody())
.map(s -& s + &&style type=\&text/css\&&& + newsDetail.getCssStr())
.map(s -& s + &&/style&&)
.subscribe(s -& html.set(s));
title.set(newsDetail.getTitle());
&/code&&/pre&&/div&&blockquote&&p&注1:我们推荐MVVM和RxJava一块儿使用,虽然两者皆有观察者模式的概念,但是RxJava不使用在针对View的监听,更多是业务数据流的转换和处理。DataBinding框架其实是专用于View-ViewModel的动态绑定的,它使得我们的ViewModel只需要关注数据,而RxJava提供的强大数据流转换函数刚好可以用来处理ViewModel中的种种数据,得到很好的用武之地,同时加上Lambda表达式结合的链式编程,使ViewModel的代码非常简洁同时易读易懂。&/p&&p&注2:因为本文样例Model层只涉及到网络数据的获取,并没有数据库、存储、数据状态变化等其他业务,所以本文涉及的源码并没有单独把Model层抽出来,我们是建议把Model层单独抽出来放一个类中,然后以面向接口编程方式提供外界获取和存储数据的接口。&/p&&/blockquote&&h2&ViewModel与ViewModel的协作&/h2&&p&在图1中我们看到两个ViewModel之间用一条虚线连接着,中间写着Messenger。Messenger可以理解是一个全局消息通道,引入Messenger最主要的目的是实现ViewModel和ViewModel的通信,虽然也可以用于View和ViewModel的通信,但并不推荐。ViewModel主要是用来处理业务和数据的,每个ViewModel都有相应的业务职责,但是在业务复杂的情况下,可能存在交叉业务,这时候就需要ViewModel和ViewModel交换数据和通信,这时候一个全局的消息通道就很重要。&/p&&p&关于Messenger的详细使用方法可以参照 &strong&&a href=&/?target=http%3A///p/43ea7a531700& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVVM Light Toolkit 使用指南的 Messenger 部分&i class=&icon-external&&&/i&&/a&&/strong&。这里给出一个简单的例子仅供参考:场景是这样的,你的MainActivity对应一个MainViewModel,MainActivity 里面除了自己的内容还包含一个Fragment,这个Fragment 的业务处理对应于一个FragmentViewModel,FragmentViewModel请求服务器并获取数据。&strong&刚好这个数据MainViewModel也需要用到&/strong&,我们不可能在MainViewModel重新请求数据,这样不太合理,这时候就需要把数据传给MainViewModel,那应该怎么传呢,如果彼此没有引用或者回调?那么只能通过全局的消息通道Messenger。&/p&&p&FragmentViewModel获取消息后通知MainViewModel并把数据传给它:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&combineRequestOb.filter(Notification::isOnNext)
.map(n -& n.getValue())
.map(p -& p.first)
.filter(m -& !m.getTop_stories().isEmpty())
.doOnNext(m -&Observable.just(NewsListHelper.isTomorrow(date)).filter(b -& b).subscribe(b -& itemViewModel.clear()))
// 上面的代码可以不看,就是获取网络数据 ,通过send把数据传过去
.subscribe(m -& Messenger.getDefault().send(m, TOKEN_TOP_NEWS_FINISH));
&/code&&/pre&&/div&&p&MainViewModel接收消息并处理:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& Messenger.getDefault().register(activity, NewsViewModel.TOKEN_TOP_NEWS_FINISH, TopNewsService.News.class, (news) -& {
// to something....
&/code&&/pre&&/div&&p&在MainActivity onDestroy取消注册就行了(不然导致内存泄露):&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span& @Override
protected void onDestroy() {
super.onDestroy();
Messenger.getDefault().unregister(this);
&/code&&/pre&&/div&&p&上面的例子只是简单地说明,Messenger可以用在很多场景,通知、广播都可以,不一定要传数据,在一定条件下也可以用在View层和ViewModel上的通信和广播,运用范围特别广,需要开发者结合实际的业务中去做更深层次的挖掘。&/p&&h1&总结和源码&/h1&&p&本文主要讲解了一些个人开发过程中总结的Android MVVM构建思想,更多是理论上各个模块如何分工、代码如何设计。虽然现在业界使用Android MVVM模式开发还比较少,但是随着DataBinding 1.0的发布,相信在Android MVVM 这一领域会更多的人来尝试。刚好我最近用MVVM开发了一段时间,有点心得,写出来仅供参考。&/p&&p&本文和源码都没有涉及到单元测试,如果需要写单元测试,可以结合&a href=&/?target=https%3A///googlesamples/android-architecture& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Google开源的MVP框架&i class=&icon-external&&&/i&&/a&添加Contract类实现面向接口编程,可以帮助你更好地编写单测。同时MVP和MVVM并没孰好孰坏,适合业务、适合自己的才是最有价值的,建议结合Google开源的MVP框架和本文介绍的MVVM相关的知识去探索适合自己业务发展的框架。&/p&&p&MVVM Light Toolkit只是一个工具库,主要目的是更快捷方便地构建Android MVVM应用程序,在里面添加了一些控件额外属性和做了一些事件的封装,同时引进了全局消息通道Messenger,个人觉得用起来会比较方便,你也可以尝试一下。当然这个库还有不少地方需要完善和优化(后续会持续改进),如果不能达到你的业务需求,可以clone下来自己做一些相关的扩展。如果想更深入了解MVVM Light Toolkit,请看我这篇博文 &strong&《&a href=&/?target=http%3A///p/43ea7a531700& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MVVM Light Toolkit 使用指南&i class=&icon-external&&&/i&&/a&》&/strong&。&/p&&p&项目的源码地址 &a href=&/?target=https%3A///Kelin-Hong/MVVMLight& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/Kelin-Hong/M&/span&&span class=&invisible&&VVMLight&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& 。 其中:&/p&&p&library是MVVM Light Toolkit的源码,源码很简单,感兴趣的同学可以看看,没什么技术难度,可以根据自己的需求,添加更多的控件属性和事件绑定。&/p&&p&sample是一个实现知乎日报首页样式的Demo,本文的代码实例均出自这个Demo,代码包含了一大部分MVVM Light Toolkit的使用场景(Data、Command、Messenger均有涉及),同时sample严格按照本文阐述的MVVM的设计思想开发,对理解本文会有比较大的帮助。&/p&&p&本文和源码涉及RxJava+Retrofit+Lambda如有不懂或没接触过,花点时间入门一下,用到的都是比较简单的东西。&/p&&br&&br&&p&&strong&不想错过技术博客更新?想给文章评论、和作者互动?第一时间获取技术沙龙信息?&/strong&&/p&&p&&strong&请关注我们的官方微信公众号“美团点评技术团队”。&/strong&&/p&
概述说到Android MVVM,相信大家都会想到Google 2015年推出的DataBinding框架。然而两者的概念是不一样的,不能混为一谈。MVVM是一种架构模式,而DataBinding是一个实现数据和UI绑定的框架,是构建MVVM模式的一个工具。之前看过很多关于Android MVVM的博客…
&img src=&/v2-76e2c1fba8d0c332cd7f5df33ca29c03_b.jpg& data-rawwidth=&750& data-rawheight=&340& class=&origin_image zh-lightbox-thumb& width=&750& data-original=&/v2-76e2c1fba8d0c332cd7f5df33ca29c03_r.jpg&&&p&原文链接:&a href=&/?target=http%3A//www.oschina.net/news/78857/best-android-libraries-for-developers& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://www.&/span&&span class=&visible&&oschina.net/news/78857/&/span&&span class=&invisible&&best-android-libraries-for-developers&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&作者:达尔文&/p&&p&&/p&&p&软件库的存在使得Android编码更方便快捷。在如此多 Android库中,我们该如何寻找最合适的一款呢?下面我们做了一个列表供你参考。&br&&/p&&h2&动画(Animation)&/h2&&p&&a href=&/?target=https%3A///daimajia/AndroidViewAnimations& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android View Animations&i class=&icon-external&&&/i&&/a&:强大的动画库,通过对它的使用,可以轻松创建各种动画效果。&/p&&p&&a href=&/?target=https%3A///wasabeef/recyclerview-animators& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&RecyclerView Animators&i class=&icon-external&&&/i&&/a&:允许操作者使用含炫酷动画效果的RecyclerView类。&/p&&h2&APIs&/h2&&p&&a href=&/?target=https%3A///& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CloudRail&i class=&icon-external&&&/i&&/a&:帮助操作者更快地进行API集成服务。它将多个服务(例如Dropbox,Google Drive和OneDrive)捆绑成一个统一的API。此外,它可自动处理API更新,并保持API的代码一致。其接口可用于云存储,社交,支付等。&/p&&p&&a href=&/?target=http%3A//square.github.io/retrofit/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Retrofit&i class=&icon-external&&&/i&&/a&:Retrofit由Square公司开发,是Android的REST客户端,其类型安全,可将http API转换成Java接口。&/p&&h2&图表(Charts)&/h2&&p&&a href=&/?target=https%3A///PhilJay/MPAndroidChart& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&MPAndroidChart&i class=&icon-external&&&/i&&/a&:一款强大的图表生成库,可在Android上生成图表,同时还提供8种不同的图表类型和多种手势。&/p&&p&&a href=&/?target=https%3A///HackPlan/AndroidCharts& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&AndroidCharts&i class=&icon-external&&&/i&&/a&:一款简单的图表创建工具,具有自定义的功能。&/p&&h2&数据库(Database)&/h2&&p&&a href=&/?target=https%3A///square/sqlbrite& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&SQLBrite:&i class=&icon-external&&&/i&&/a& 围绕SQLiteOpenHelper和ContentResolver创建封装,以在查询中使用流式语义。&/p&&p&&a href=&/?target=https%3A///satyan/sugar& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Sugar ORM&i class=&icon-external&&&/i&&/a&:使用SQLite数据库的简单操作,优势在于无需编写SQL查询。&/p&&p&&b&数据架构(Data Structure)&/b&&/p&&p&&a href=&/?target=https%3A//www.eclipse.org/collections/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Eclipse Collections&i class=&icon-external&&&/i&&/a&:用于Java的Collections框架,可为容器类型附加迭代方法的操作等。&/p&&h2&日期和时间(Date & Time)&/h2&&p&&a href=&/?target=https%3A///square/android-times-square& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&TimesSquare for Android&i class=&icon-external&&&/i&&/a&:简单独立的日历小部件,可在日历视图中选择日期。&/p&&p&&a href=&/?target=https%3A///prolificinteractive/material-calendarview& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Material Calendar View&i class=&icon-external&&&/i&&/a&:移植到Material design的安卓CalendarView。&/p&&h2&依赖(Dependencies)&/h2&&p&&a href=&/?target=https%3A///square/dagger& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Dagger&i class=&icon-external&&&/i&&/a&:进行Android依赖注入的快捷方式。&/p&&h2&文档(Files)&/h2&&p&&a href=&/?target=https%3A///DroidNinja/Android-FilePicker& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android-FilePicker&i class=&icon-external&&&/i&&/a&:一款照片选择器,可从列表中选择图片或文档。&/p&&p&&a href=&/?target=https%3A///nbsp-team/MaterialFilePicker& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Material File Picker&i class=&icon-external&&&/i&&/a&:Material design中的文件选择器。&/p&&h2&函数式编程(Functional Programing)&/h2&&p&&a href=&/?target=https%3A///orfjackal/retrolambda& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Retrolambda&i class=&icon-external&&&/i&&/a&:在Java 7中使用Lambdas的一种操作。&/p&&h2&手势(Gestures)&/h2&&p&&a href=&/?target=https%3A///nisrulz/Sensey& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Sensey&i class=&icon-external&&&/i&&/a&:在Android应用程序中添加手势的简单操作。&/p&&p&&a href=&/?target=https%3A///klinker24/Android-3DTouch-PeekView& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android 3D Touch – PeekView&i class=&icon-external&&&/i&&/a&:类似于iOS上的3D Touch,用以“预览”程序内容。&/p&&h2&蓝牙(Bluetooth)&/h2&&p&&a href=&/?target=https%3A///akexorcist/Android-BluetoothSPPLibrary& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Android-BluetoothSPPLibrary&i class=&icon-external&&&/i&&/a&:使用序列化蓝牙端口(Bluetooth Serial Port Profile)的简单操作。&/p&&p&&a href=&/?target=https%3A///Polidea/RxAndroidBle& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&RxAndroidBle&i class=&icon-external&&&/i&&/a&:蓝牙低耗能处理工具。&/p&&h2&相机(Camera)&/h2&&p&&a href=&/?target=https%3A///nekocode/CameraFilter& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&CameraFilter&i class=&icon-external&&&/i&&/a&:使用OpenGL着色器的实时相机滤镜。&/p&&p&&a href=&/?target=https%3A///afollestad/material-camera& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Material Camera&i class=&icon-external&&&/i&&/a&:Android开发的快速简易的相机框架。&/p&&h2&位置(Location)&/h2&&p&&a href=&/?target=https%3A///mcharmas/A

我要回帖

更多关于 我编程我快乐 pdf 的文章

 

随机推荐