好东西, 写的挺幽默的,转载 php
深刻Android |
【一】 —— 序及开篇
【二】 —— 架构和学习
虽贵为富二代,但Android要是没任何可圈点的地方,开不过70迈,在玲琅满目的手机平台竞争中,充其量也就作几个俯卧撑打一桶酱油,而后被落的远远的。说到底,出来混,靠的仍是技术。 架构 从SDK文档中,偷来一幅Android平台的架构图,如上。在整个架构最底层红彤彤的部分,是Linux Kernel在移动平台的一个移植,它隐藏了硬件、网络等相关的细节,为上层提供了一个相对纯洁的统一接口。除非要作的是Android到不一样设备的移植工做,不然对于大部分普通开发者而言,基本上是远观而没必要亵玩的。Google一直强调,Android的底层实现异常NB,可移植性超强,暂没有功夫研读,实属遗憾。 靠上一层,是一些核心的和扩展的类库,它们都是原生的C++实现。在这一层,你能够看到不少熟悉的面孔,一如SQLite、WebKit、OpenGL,开源的力量与贡献因而可知。若是,该层类库须要被上层函数调用,就必需要经过JNI的导出相应的接口函数,不然就只能在层次内部自个把玩。 也是在这一层次上,还有为上层Java程序服务的运行时。Dalvik虚拟机,是Android的Java虚拟机,之因此不采用J2ME的虚拟机,一方面是由于J2ME的设计是为了低端机器而优化,而Dalvik则是为了高端一些的机器进行优化,提供更好的性能。另外一方面,从商业角度来看,必须绕开J2ME虚拟机,Android才能完全解放,想怎么开源就怎么开源,再也不须要考虑License的问题。 再往上,终于有Java出没了。首先是框架层,这里包含全部开发所用的SDK类库,另外还有一些未公开接口的类库和实现,它们是整个Android平台核心机制的体现。 而在最上面,就是应用层了,系统的一些应用和第三方开发的全部应用都是位于这个层次上,也许要纠结二者的差异,就是系统应用会用一些隐藏的类,而第三方的应用,老是基于SDK提供的东西来搞。 通常来讲,Android开发,就是在SDK的基础上,吭哧吭哧用Java写应用。但自从有了NDK,一切有了写小变化。NDK的出现意味着,最上面应用层的内容,能够穿越Java部署的框架层,直接和底层暴露出来的,或者自行开发的C++库直接对话,固然在这些库中须要包含JNI的接口。 人说,这就不是Android也能够用C++开发应用么,但其实,这样的说法不够确切,纯C++应用,是没法被接受的。由于在Android中,大量的核心机制部署在框架层,它们都是用Java实现的,好比控件库,Activity的调度之类的。所以,没了界面,没了调度,仍是只用C++作类库比较合适,不然一切都乱了套了。 特征 基于这样的架构,Android有不少的设计显得颇有意思。纵览整个SDK和核心机制的设计,工整漂亮,是Android给人的第一感受。为了说明这一点,找一个反面教材是颇有必要的,Symbian同窗毫无悬念的担当这个伟岸的角色。 写Symbian程序,感受就像是在玩一个猜谜游戏。哪怕你是一个Symbian老手,当须要用到Symbian中某块陌生功能的时候,你可能依然一筹莫展。你每每须要猜并反复找寻,在这里我须要使用哪种奇巧淫技呢,是该臆想某些事件,仍是应该用一个神秘的UID寻找某个特定应用,诸如此类。 而作Android应用的时候,就像是作高考模拟试题,题看上去不同,解答模式摸清楚,就一通百通,一了百了。监听某个系统事件,查一下SDK就好;访问某个应用的数据,看看它有没有提供Content Provider就能够。全部的一切,都是按套路出牌,只要你了解了套路,再陌生的牌也能够看得懂,出的顺。人说武林高手,都应该是无招胜有招,而一个好的应用框架,也应该作到举重若轻,可举一反三。 而Android框架最文采飞扬的一点,就是引入了Mash-Up的思想。所谓Mash-Up,就是把写应用搞成搭积木,要出效果的时候,东家一块西家一块现场拼起来就好。这里面关键有两点,一个是模块化,另外一个就是动态性。所谓模块化,就是一个应用的功能要明确的被封成一个个边界清晰的功能点,每个功能点都像是一个黑盒,由预先定义的规则描述出其交互方式;而动态性,就是这些独立的模块可以在运行的时候,按照需求描述,链接在一块儿,共同完成某项更大的功能。在这两点上,Android都作得很是出色。 站在可Mash-Up构造应用这一点去看其余的一些Android中的核心功能设计,就显得颇有章可循了。好比为何要把文件私有化,为何要让进程被托管,等等(固然也能够站在别的角度看出不一样的效果,视角不一样,视野天然不一样...)。 在UI机制方面,Android也有很不错的表现。它采起xml格式的资源文件,描述全部界面相关的内容。资源文件不是什么新东西了,xml格式也是老调重弹,但难得的是Android作的更为的丰富和完全,基本把界面相关的逻辑,所有从代码中剥离到了资源文件中,和Symbian那四不像的资源文件相比,真是强大了不知多少倍了。 Android学习入门 不积跬步,无以致千里。 -- 《劝学》 说,万事开头难。想开始Android的开发,最重要的应该是先把马步扎稳,套路摸清楚,后面的事情就顺当多了。打开怀抱,拥抱Android,也许能够先作下面这些事。 开发环境 辣手摧花成性的GFW,无情的把Android开发者官网关在了墙外。不过不要紧,猛击这里,一样能够异曲同工(Shit,一直在用代理,刚试了下,发现居然也被盾了,若是不行,那就只能FQ了...)。 若是旅途顺利,你能够在路径sdk/index.html下找到安装说明,成功配置好Android的开发环境(【注】:在之后,若是要给开发者页面上的连接,都会给一个像sdk/index.html这样的相对路径,你能够在前面加上官网地址,或者本地SDK的doc地址拼凑成完整的路径,在一个盾牌横行的朝代,只能用这样委屈求全的方法保证能更好的使用...)。 在2.0以前,每一次版本更新,你都要本身去下个全新的SDK,而后按照说明,当心翼翼的一步步修改eclipse的设置,甚是麻烦。在2.0后,这个模式有所改善,你会先下到一个相似于下载器的插件,经过它能够来管理和升级SDK,不只简化了整个升级模式,还使得你能够更好的在各个不一样的SDK版本间游走,利国利民。 Eclipse + ADT(Android Development Tool),是正牌的Android开发环境。你能够在Windows,Linux,Mac下作开发,甚为自由。比之Symbian的开发环境,ADT显得尤其强大,它对SDK提供的一堆优秀的命令行工具进行了UI上的封装,提供了图形界面(命令行控固然一样幸福,具体参见:guide/developing/tools/index.html)。经过ADT,你能够用运行和管理模拟器,使用调试器进行调试,过滤和查看Log,浏览模拟器上的文件信息,模拟拨号、短信等手机才有的事件,等等。 文档 我知道,有不少人在学习一个新平台开发的时候,都习惯去买一些《xxx 21天精通》之类的书籍。但其实,最好的入门学习资料,就是SDK文档。由于只有作平台的本身,才能最了解平台中的各个玄机,各方面的轻重缓急,从而可以更好的对症下药药到病除。 在Android的SDK中,guide/index.html是由浅入深的教学文档,reference/packages.html是标准的API文档。对于教学文档,个人意见是,一字不拉的通读一遍甚至多遍,至少作到能对Android摸着头脑,而且碰到问题的时候,可以快速想起在哪里能够找到,回来深入阅读。 而API文档方面,Android作的算是还不错了。基本上每一个类,每一个接口,都有标准而详尽的说明,在一些尤其重要的类中,还具备大量的学习性的内容,不和Symbian似的,有太多的太监类,只有光秃秃的一个函数,一行文档说明都没有。整个文档结构是按照Java包来组织的,自己Java包命名的结构性和可读性很强,找起来也颇为方便。 不少人对SDK文档有抵触情绪,我想,有两方面的缘由。一则是SDK文档广泛缺乏文学性,麻木不仁的八股文,难如下咽。Android在这方面作得算是乏善可陈,虽然算不上文采华丽,但仍是挺适合阅读的。另外一则,就会是语言方面的缘由了。SDK文档多为英语,偶尔像MSDN这样有中文的,也停留在机器翻译的水平上,阅读起来颇为难受。特意在网上搜了下,找到一些翻译SDK的中文文档,好比这里。虽然是基于1.5 r1版本SDK所著,稍显过期,但翻译的仍是有小用心的,做为辅助,也不失为一份好资料,特表明广大看官向这些为人民福利着想的同志致敬。 Tutorials 光说不练假把式,除了读,在入门阶段,写也是一项不能少的运动。一样是在SDK中,Android提供了一组Tutorials和一些列的Samples,详见:resources/index.html。 Tutorials很简单,Hello World只是在教你如何在eclipse中,在ADT的帮助下,建立一个Android项目。相比之下,Hello Views复杂了些,它集中展现了几种标准的Android Layout 样式是如何构建的,不少时候,你都是在这些样式下扩展所需的UI。 Hello Localization,是教你如何使用资源的,作完这个,就能够了解Android的资源有多杀~。最后收官的是一个更为完整的Notepad Tutorial,它展现了不少Android的核心机制,好比基于Intent的Activity整合,Activity的生命周期等。迈过这个Tutorial,欢迎你,进入Android的大门。 固然,作完Tutorials,对于Android而言,只是管中窥豹略见一斑。在SDK中,还提供了一系列的Samples。能够根据本身的需求,挑选合适的Sample编译运行和学习。但其中,有一个是不论你作什么,都须要必看必读必熟悉的,就是API Demos。在这个Sample中,集中展现了Android重点功能的API使用,把这个Sample用熟悉,须要作什么的时候过去找一下就能够很快的入手了。 源码 到这里,不少看官必定很不屑,前面所谓的学习入门介绍,只不过是围着SDK打转。其实,事实也是如此,SDK中包含的内容是真的很是重要,我只是指望经过一些简短的介绍,激起一些初学者的重视,如是而已。 固然,SDK每个平台都有,没什么稀罕的。但Android有另外一个很是稀罕而值钱的看家法宝,就是源代码。从Android Source的主站上: http://source.android.com/,你能够得到整个平台的源码以及相关介绍。很是苦口婆心的指望你们都去down一份源码放在机器上,哪怕你不须要进行修改编译,放在机器上当百科全书也是远胜于任何一本Android教学书籍的。本系列文章后续不少内容,都是从源码中学习到的一些浅薄见识。 对于大部分开发者都有学习价值的源码,主要在源码的frameworks和packages目录下。前者包含的是平台核心的一些实现,好比你须要自定义一个控件,也许你就能够翻到一个系统控件的实现中去,看看它是怎么来作的。后者包含一些系统的应用实现,好比你想作个播放器,也许你能够先去参考参考系统自带的是具体怎么作的。这样的实现,即使不算是最华美,至少也是最标准,其价值不容小视。 另外,你也能够把它当个代码库来使,不会使用某个类,grep一把,也许就能得到一份最漂亮的Sample。固然,若是你有时候对某些系统机制表示费解,抑或有一些bug不知道源头在哪,均可以跟着源码顺藤摸瓜的搞清楚。这样的好东西,可不是每一个平台都可以享用的。 其余 论坛,其实对于开发和学习都是很重要的资源。毕竟,全部的资料都是死的,只有人是活得,可以最大限度的因地制宜解决问题。 只不过,标准的官方论坛,放在Google Group上,已经惆怅的被盾了。中文论坛方面,没有特别优秀和活跃的,这一方面是因为Android的发展示状还不算很磅礴,另外一方面是因为Android的开发相对于Symbian而言,奇技淫巧少了不少,没有那么多好问的。也许你能够去去csdn这样的传统论坛,或者eoe这样专门的论坛。有的时候,仍是多少能得到一些帮助的。 书籍方面,真没有什么推荐,豆瓣上搜索一下,你能够看到,目前的书籍,基本上仍是集中在SDK使用层面上,不多有解析的很透彻,作的很深刻的。而SDK的使用,看SDK的文档就足够了,若是实在对e文不感冒,买一两本评价不太差的中文书籍,放着翻翻也仍是挺好。 更进一步,也许能够读读一些经验性的文档,去Google Code上搜索一些代码回来看看。好比,SDK文档中,有个经验性文档的集合:resources/articles/index.html,就能够翻看一下。 最后,更多的一切还须要本身在工程和思考中,慢慢总结。相信,好的代码,会垂青一个勤于动手和思考的人。
【三】 —— 组件入门
组件(Component),在谈及所谓架构和重用的时候,是一个重要的事情。不少时候都会说基于组件的软件架构,指的是指望把程序作乐高似的,有一堆接口标准封装完整的组件放在哪里,想用的时候取上几个一搭配,整个程序就构建完成了。 在开篇的时候就在说,Android是一个为组件化而搭建的平台,它引入所谓Mash-Up的概念,这使得你在应用的最上层,想作的不组件化都是很困难的一件事情(底层逻辑,好吧,管不了...)。具体说来,Android有四大组件四喜丸子:Activity、Service、Broadcast Receiver、Content Provider。 Activity 作一个完整的Android程序,不想用到Activity,真的是比较困难的一件事情,除非是想作绿叶想疯了。由于Activity是Android程序与用户交互的窗口,在我看来,从这个层面的视角来看,Android的Activity特像网站的页面。 首先,一个网站,若是一张页面都没有,那...,真是一颗奇葩。而一张页面每每都有个独立的主题和功能点,好比登陆页面,注册页面,管理页面,如是。 在每一个页面里面,会放一些连接,已实现功能点的串联,有的连接点了,刷,跑到同一站点的另外一个页面去了;有的连接点了,啾,可能跳到其余网站的页面去;还有的连接点了,恩...,此次没跑,但当前页面的样子可能有所变化了。这些模式,和Activity给人的感受很像,只不过实现策略不一样罢了,毕竟Android这套架构的核心思想,自己就来自源于Web的Mash-Up概念,视为页面的客户端化,也何尝不可。 Activity,在四大组件中,无疑是最复杂的,这年头,同样东西和界面挂上了勾,都简化不了,想想,独立作一个应用有多少时间沦落在了界面上,就能琢磨清楚了。从视觉效果来看,一个Activity占据当前的窗口,响应全部窗口事件,具有有控件,菜单等界面元素。从内部逻辑来看,Activity须要为了保持各个界面状态,须要作不少持久化的事情,还须要妥善管理生命周期,和一些转跳逻辑。对于开发者而言,就须要派生一个Activity的子类,而后埋头苦干上述事情。对于Activity的更多细节,先能够参见:reference/android/app/Activity.html。后续,会献上更为详尽的剖析。 Service 服务,从最直白的视角来看,就是剥离了界面的Activity,它们在不少Android的概念方面比较接近,都是封装有一个完整的功能逻辑实现,只不过Service不抛头露脸,只是默默无声的作坚实的后盾。 但其实,换个角度来看,Android中的服务,和咱们一般说的Windows服务,Web的后台服务又有一些相近,它们一般都是后台长时间运行,接受上层指令,完成相关事务的模块。用运行模式来看,Activity是跳,从一个跳到一个,呃...,这有点像模态对话框(或者还像web页面好了...),给一个输入(抑或没有...),而后无论不顾的让它运行,离开时返回输出(同抑或没有...)。 而Service不是,它是等,等着上层链接上它,而后产生一段持久而缠绵的通讯,这就像一个用了Ajax页面,看着没啥变化,偷偷摸摸的和Service不知眉来眼去多少回了。 但和通常的Service仍是有所不一样,Android的Service和全部四大组件同样,其进程模型都是能够配置的,调用方和发布方均可以有权利来选择是把这个组件运行在同一个进程下,仍是不一样的进程下。这句话,能够拿把指甲刀刻进脑海中去,它凸显了Android的运行特征。若是一个Service,是有指望运行在于调用方不一样进程的时候,就须要利用Android提供的RPC机制,为其部署一套进程间通讯的策略。 Android的RPC实现,如上图所示(好吧,也是从SDK中拿来主义的...),无甚稀奇,基于代理模式的一个实现,在调用端和服务端都去生成一个代理类,作一些序列化和反序列化的事情,使得调用端和服务器端均可以像调用一个本地接口同样使用RPC接口。 Android中用来作数据序列化的类是Parcel,参见:/reference/android/os/Parcel.html,封装了序列化的细节,向外提供了足够对象化的访问接口,Android号称实现很是高效。 还有就是AIDL (Android Interface Definition Language) ,一种接口定义的语言,服务的RPC接口,能够用AIDL来描述,这样,ADT就能够帮助你自动生成一整套的代理模式须要用到的类,都是想起来很乏力写起来很苦力的那种。更多内容,能够再看看:guide/developing/tools/aidl.html,若是有兴致,能够找些其余PRC实现的资料lou几眼。 关于Service的实现,还强推参看API Demos这个Sample里面的RemoteService实现。它完整的展现了实现一个Service须要作的事情:那就是定义好须要接受的Intent,提供同步或异步的接口,在上层绑定了它后,经过这些接口(不少时候都是RPC的...)进行通讯。在RPC接口中使用的数据、回调接口对象,若是不是标准的系统实现(系统可序列化的),则须要自定义aidl,全部一切,在这个Sample里都有表达,强荐。 Service从实现角度看,最特别的就是这些RPC的实现了,其余内容,都会接近于Activity的一些实现,也许再也不会详述了。 Broadcast Receiver 在实际应用中,咱们常须要等,等待系统抑或其余应用发出一道指令,为本身的应用擦亮明灯指明方向。而这种等待,在不少的平台上,都会须要付出不小的代价。 好比,在Symbian中,你要等待一个来电消息,显示归属地之类的,必须让本身的应用忍辱负重偷偷摸摸的开机启动,消隐图标隐藏任务项,潜伏在后台,监控着相关事件,等待转瞬即逝的出手机会。这是一件很发指的事情,不但白白耗费了系统资源,还留了个流氓软件的骂名,这真是卖力不讨好的正面典型。 在Android中,充分考虑了普遍的这类需求,因而就有了Broadcast Receiver这样的一个组件。每一个Broadcast Receiver均可以接收一种或若干种Intent做为触发事件(有不知道Intent的么,后面会知道了...),当发生这样事件的时候,系统会负责唤醒或传递消息到该Broadcast Receiver,任其处置。在此以前和这之后,Broadcast Receiver是否在运行都变得不重要了,及其绿色环保。 这个实现机制,显然是基于一种注册方式的,Broadcast Receiver将其特征描述并注册在系统中,根据注册时机,能够分为两类,被我冠名为冷热插拔。所谓冷插拔,就是Broadcast Receiver的相关信息写在配置文件中(求配置文件详情?稍安,后续奉上...),系统会负责在相关事件发生的时候及时通知到该Broadcast Receiver,这种模式适合于这样的场景。某事件方式 -> 通知Broadcast -> 启动相关处理应用。好比,监听来电、邮件、短信之类的,都隶属于这种模式。而热插拔,顾名思义,插拔这样的事情,都是由应用本身来处理的,一般是在OnResume事件中经过registerReceiver进行注册,在OnPause等事件中反注册,经过这种方式使其可以在运行期间保持对相关事件的关注。好比,一款优秀的词典软件(好比,有道词典...),可能会有在运行期间关注网络情况变化的需求,使其能够在有廉价网络的时候优先使用网络查询词汇,在其余状况下,首先经过本地词库来查词,从而兼顾腰包和体验,一箭双雕一石二鸟一举两得(注,真实在有道词典中有这样的能力,但不是经过Broadcast Receiver实现的,仅觉得例...)。而这样的监听,只须要在其工做状态下保持就好,不运行的时候,管你是天大的网路变化,与我何干。其模式能够归结为:启动应用 -> 监听事件 -> 发生时进行处理。 除了接受消息的一方有多种模式,发送者也有很重要的选择权。一般,发送这有两类,一个就是系统自己,咱们称之为系统Broadcast消息,在reference/android/content/Intent.html的Standard Broadcast Actions,能够求到相关消息的详情。除了系统,自定义的应用能够放出Broadcast消息,经过的接口能够是Context.sendBroadcast,抑或是Context.sendOrderedBroadcast。前者发出的称为Normal broadcast,全部关注该消息的Receiver,都有机会得到并进行处理;后者放出的称做Ordered broadcasts,顾名思义,接受者须要按资排辈,排在后面的只能吃前面吃剩下的,前面的心情很差私吞了,后面的只能喝西北风了。 当Broadcast Receiver接收到相关的消息,它们一般作一些简单的处理,而后转化称为一条Notification,一次振铃,一次震动,抑或是启动一个Activity进行进一步的交互和处理。因此,虽然Broadcast整个逻辑不复杂,倒是足够有用和好用,它统一了Android的事件广播模型,让不少平台都相形见绌了。更多Broadcast Receiver相关内容,参见:/reference/android/content/BroadcastReceiver.html。 Content Provider Content Provider,听着就和数据相关,没错,这就是Android提供的第三方应用数据的访问方案。在Android中,对数据的保护是很严密的,除了放在SD卡中的数据,一个应用所持有的数据库、文件、等等内容,都是不容许其余直接访问的,但有时候,沟通是必要的,不只对第三方很重要,对应用本身也很重要。 好比,一个联系人管理的应用。若是不容许第三方的应用对其联系人数据库进行增删该查,整个应用就失去了可扩展力,必将被其余应用抛弃,而后另立门户,自个玩自个的去了。 Andorid固然不会真的把每一个应用都作成一座孤岛,它为全部应用都准备了一扇窗,这就是Content Provider。应用想对外提供的数据,能够经过派生ContentProvider类,封装成一枚Content Provider,每一个Content Provider都用一个uri做为独立的标识,形如:content://com.xxxxx。全部东西看着像REST的样子,但实际上,它比REST更为灵活。和REST相似,uri也能够有两种类型,一种是带id的,另外一种是列表的,但实现者不须要按照这个模式来作,给你id的uri你也能够返回列表类型的数据,只要调用者明白,就无妨,不用苛求所谓的REST。 另外,Content Provider不和REST同样只有uri可用,还能够接受Projection,Selection,OrderBy等参数,这样,就能够像数据库那样进行投影,选择和排序。查询到的结果,以Cursor(参见:reference/android/database/Cursor.html )的形式进行返回,调用者能够移动Cursor来访问各列的数据。 Content Provider屏蔽了内部数据的存储细节,向外提供了上述统一的接口模型,这样的抽象层次,大大简化了上层应用的书写,也对数据的整合提供了更方便的途径。Content Provider内部,经常使用数据库来实现,Android提供了强大的Sqlite支持,但不少时候,你也能够封装文件或其余混合的数据。 在Android中,ContentResolver是用来发起Content Provider的定位和访问的。不过它仅提供了同步访问的Content Provider的接口。但一般,Content Provider须要访问的多是数据库等大数据源,效率上不足够快,会致使调用线程的拥塞。所以Android提供了一个AsyncQueryHandler(参见:reference/android/content/AsyncQueryHandler.html),帮助进行异步访问Content Provider。 在各大组件中,Service和Content Provider都是那种须要持续访问的。Service若是是一个耗时的场景,每每会提供异步访问的接口,而Content Provider不论效率如何,都提供的是约定的同步访问接口。我想这遵循的就是场景导向设计的原则,由于Content Provider仅是提供数据访问的,它不能确信具体的使用场景如何,会怎样使用它的数据;而相比之下,Service包含的逻辑更复杂更完整,能够抉择大部分时候使用某接口的场景,从而肯定最贴切的接口是同步仍是异步,简化了上层调用的逻辑。 配置 四大组件说完了,四大组件幕后的英雄也该出场了,那就是每一个应用都会有一份的配置文件,名称是AndroidManifest.xml,在工程的根目录下。在这个配置文件中,不只会描述一些应用相关的信息,很重要的,会包含一个应用中全部组件的信息。若是你派生Activity或者Service实现了一个相关的类,这只是把它组件化的第一步,你须要把这个类的相关信息写到配置文件中,它才会做为一个组件被应用到,不然只能默默无闻的黯淡度过余生。 摆了一幅图出来,此次不是偷来的,是敝帚自珍原创,因此没有意外的画的很丑,但基本仍是能够体现出一些意思。在In Others的部分,这里是通常平台应用之间通讯和交互的模型,每一个应用都有很强烈的应用边界(每每表现为进程边界...),App 1的仍是App 2的,分得非常清楚。每一个应用内部,都有本身的逻辑去切分功能组件,这样的切分一般没有什么标准,率性而为。应用间的交互逻辑也比较零散,App 1与App 2交互,每每须要明确知道对方应用的具体信息,好比进程ID,进程名称之类的,这样使得应用和应用之间的联系,变得很生硬。而上层应用和系统应用的通讯,每每有不少特定的模式,这种模式,极可能是没法直接应用在普通应用之间的,换而言之,系统应用是有必定特殊性的。 重点,在图的下半部,描述的是Android的应用情形。在Android中,应用的边界,在组件这个层面,是极度模糊,什么进程、什么应用,均可以没必要感知到。举个例子,App 1,实现了A和B两个组件,App 2,实现了C这个组件。A和C,都想使用B这个组件,那么它们的使用方式是彻底一致的,都须要经过系统核心的组件识别和通讯机制,找到和使用组件B。A,虽然说和B是一个娘胎里蹦出来的,很很差意思,没有任何特殊的后面和捷径,仍是要跑规矩的途径才能用到,一片和谐社会的景象油然而生。 在Android中,全部组件的识别和消息传递逻辑都必须依赖底层核心来进行(通讯能够没有底层核心的参与,好比一旦Service找到了,就能够和它产生持久的通讯...),没有底层核心的牵线搭桥,任何两个组件都没法产生联系。好比一个Activity,跳到另外一个Activity,必需要向底层核心发起一个Intent,有底层解析并承认后,会找到另外一个Activity,把相关消息和数据传给它。一个Activity想使用Content Provider中的数据,必须经过底层核心解析相关的uri,定位到这个Content Provider,把参数传递给它,而后返回Activity须要的Cursor。这样的设计,保证了底层核心对全部组件的绝对掌控权和认知权,使得搭积木似的开发变成可能。 为了,使得核心系统可以完整的掌握每一个组件的信息,这就须要配置文件了。配置文件,就是将组件插到底层核心上的这个插头。只有经过这个插头插在底层核心的插座上(不要乱想,非十八禁...),组件才可以发光发热,闪耀光芒。 组件的配置信息在我看来主要包含两个方面,一部分是描述如何认知。好比,Activity、Service、Broadcast Receiver都会有名字信息,和但愿可以把握的Intent信息(姑且当作消息好了...),Content Provider会有一个描述其身份的uri。当其余组件经过这样的名字或者Intent,就能够找到它。 另外一部分是运行相关的信息。这个组件,指望怎么来运行,放在单独的进程,仍是和调用者一个进程,仍是找相关的其余组件挤在同一个进程里面,这些内容,均可以在配置的时候来决定(调用者在这个约束范围内,有进一步的选择权...)。更多配置项,请参见:guide/topics/manifest/manifest-intro.html。 经过前续内容,也许能够帮助你们对Android组件有个初略的了解。但这些了解都还停留在静态层面,程序是个动态的概念,关于各个组件具体是怎么联系在一块儿的,如何手拉手运行起来完成一项功能的,这即是后话了。
【四】 —— 组件调用
Intent解析 基于组件的架构体系,除了有定义良好的组件,如何把这些组件组装在一块儿,也是一门艺术。在Android中,Intent(貌似一般译做:意图...),就是链接各组件的桥梁。 前段时间看同事们作Symbian平台的网易掌上邮(真的是作的用心,NB的一米,热情欢迎全部163邮箱的S60v3用户,猛点击之...),有个功能是为邮件添加附件,好比你想要经过邮件发送一副图片泡mm,可能须要有个很直观的方式从本地选一副珍藏美图,抑或是拿相机来个完美自拍。在Symbian中,这样的功能,都须要你用底层的API,本身一点点写。为了让选图片体验更好,可能须要作一个相似于图片浏览器之类的东西,为了把拍照作的更为顺畅,甚至须要实现从聚焦到调节亮度之类一整套的相机功能。 而其实呢,用户的手机中可能自己就装了其余的专业图片浏览器、相机等应用,这些应用已经很是出色好用,而用户也已然能很纯属使用它们,若是能进行调用,对邮箱的开发者和用户而言,都会是个更好的选择。但在Symbian这样残败的系统里,应用和应用之间的结合能力奇弱无比,想复用,基本比登天还难,做为开发者,只能忍住一次又一次的恶心,为了用户,作这些重复造轮子吃力不讨好的附加工做。 还好还好,在Android中,一切变得美好多了,它将开发者从接口和对象的细节中解救出来,让咱们有更多精力投入到核心功能的开发中去。在Android中,若是你须要选个图拍个片,只须要构造一个描述你此项意愿的Intent,发送出去,系统会帮你选择一个可以处理该项业务的组件来知足你的需求,而再也不须要纠结在具体的接口和实现上,Perfect World,便应如此。 Intent构成 Intent被译做意图,其实仍是很能传神的,Intent指望作到的,就是把实现者和调用者彻底解耦,调用者专心将以意图描述清晰,发送出去,就能够梦想成真,达到目的。 固然,这么说太虚了,庖丁解牛,什么东西切开来看看,也许就清晰了。Intent(reference/android/content/Intent.html),在Android中表现成一个类,发起一个意图,你须要构造这样一个对象,并为下列几项中的一些进行赋值: Action。当平常生活中,描述一个意愿或愿望的时候,老是有一个动词在其中。好比:我想作三个俯卧撑;我要看一部x片;我要写一部血泪史,之类云云。在Intent中,Action就是描述看、作、写等动做的,当你指明了一个Action,执行者就会依照这个动做的指示,接受相关输入,表现对应行为,产生符合的输出。在Intent类中,定义了一批量的动做,好比ACTION_VIEW,ACTION_PICK,之类的,基本涵盖了经常使用动做,整一个降龙十八掌全集。固然,你也能够与时俱进,创造新的动做,好比lou这样的。与系统预约义的相比,这些自定义动做的流通范围非常有限,除非作了很是NB的应用,你们都须要follow你,不然一般都是应用内部流通。 Data。固然,光有动做仍是不够的,还须要有更确切的对象信息。好比,一样是泡这个动做,但泡咖啡,和泡妞,就差之千里了。Data的描述,在Android中,表现成为一个URI。用在内部通讯中,可能描述是Content Provider用的形如content://xxxx这样的东东,抑或是外部的一个形如tel://xxxx这样的连接。总而言之,是可以清楚准确的描述一个数据地址的uri。 Type。说了Data,就必需要提Type,不少时候,会有人误解,觉着Data和Type的差异,就犹如泡妞和泡马子之间的差异同样,微乎其微。但其实否则,Type信息,是用MIME来表示的,好比text/plain,这样的东西。说到这里,二者差异就很清晰了,Data就是门牌号,指明了具体的位置,具体问题具体分析,而type,则是强调物以类聚,解决一批量的问题。实际的例子是这样的,好比,从某个应用拨打一个电话,会发起的是action为ACTION_DIAL且data为tel:xxx这样的Intent,对应的人类语言就是拨打xxx的电话,很具象。而若是使用type,就宽泛了许多,好比浏览器收到一个未知的MIME类型的数据(好比一个视频...),就会放出这样的Intent,求系统的其余应用来帮助,表达成天然语言应该就是:查看pdf类文档,这样的。 Category。经过Action,配合Data或Type,不少时候能够准确的表达出一个完整的意图了,但也会有些时候,还须要加一些约束在里面才可以更精准。好比,若是你虽然很喜欢作俯卧撑,但一次作三个还只是在特殊的时候才会发生,那么你可能表达说:每次吃撑了的时候,我都想作三个俯卧撑。吃撑了,这就对应着Intent的Category的范畴,它给所发生的意图附加一个约束。在Android中,一个实例是,全部应用主Activity(就是单独启动时候,第一个运行的那个Activity...),都须要可以接受一个Category为CATEGORY_LAUNCHER,Action为ACTION_Main的意图。 Component。在此以前,咱们企图用Action,Data/Type,Category去描述一个意图,这是Android推荐,并指望你们在大多数时候使用的,这样模式在Android中称作Implicit Intents,经过这种模式,提供一种灵活可扩展的模式,给用户和第三方应用一个选择权。好比,仍是一个邮箱软件,他大部分功能都好,就是选择图片的功能作的很土,怎么办?若是它采用的是Implicit Intents,那么它就是一个开放的体系了,手机中没有其余图片选择程序的话,能够继续使用邮箱默认的,若是有,你能够任意选择来替代原有模块完整这功能,一切都天然而然。但这种模式,也不是没有成本,须要付出的是一些性能上的开销,由于毕竟有一个检索过程。因而,Android提供了另外一种模式,叫作Explicit Intents,就须要Component的帮助了。Component就是类名,完整的,形如com.xxxxx.xxxx,一旦指明了,一切都清晰了,找的到这个类(固然会是一个特定的子类...),成功,反之,失败。这个好处,天然是速度,适合在你明确知道这就是一个内部模块的时候,使用它。 Extras。经过上面的这些项,识别问题,基本完美解决了,剩下一个重要的问题,就是传参。Extras是用来作这个事情的,它是一个Bundle类的对象,有一组可序列化的key/value对组成。每个Action,都会有与之对应的key和value类型约定,发起Intent的时候,须要按照要求把Data不能表示的额外参数放入Extras中(固然,若是不须要额外附加参数,就算了...),不然执行者拿到的时候会抓狂的。 Flags。能识别,有输入,整个Intent基本就完整了,但还有一些附件的指令,须要放在Flags中带过去。顾名思义,Flags是一个整形数,有一些列的标志位构成,这些标志,是用来指明运行模式的。好比,你指望这个意图的执行者,和你运行在两个彻底不一样的任务中(或说进程也无妨吧...),就须要设置FLAG_ACTIVITY_NEW_TASK的标志位。 有了上述这些,一个Intent的形象就跃然纸上了,如此丰富的内容,决定了它比传统的模式,都来得强大。 Intent匹配 上次在moto dev上,听人作Android的讲座,下面有不少听客都对Intent这个概念表示出了强烈的兴趣,拿出本身熟悉领域的各种概念进行类比,好比事件、消息之类。当时我在想,Intent做为组件间的通讯协定,与通常的简单的通讯方式不一样,首先,从前面部分能够看到,它的描述是针对需求而不是实现者来进行的。其次,它的解析是依托第三方而不是两方直接进行。 这个概念和设计模式中的中介模式(Mediator Pattern)是一脉的,即全部的外围组件,都只和系统的核心模块发生联系,经过它进行中转,组件之间不直接勾搭。 如上图所示,要想跑通整个流程,另外一个很重要的东西,就是Intent Filters,它是用来描述一个Activity或Serveice等组件,指望可以响应怎么样的Intent。若是一个组件,只但愿别的组件经过Explicit Intents(就是指明Component...)的方式来找到它,那么就不须要添加Intent Filters,反之,必定须要一个或若干个Intent Filters。Intent Filter的各个项,犹如Intent照镜子过来的效果,包括Action,Catagory,Data,Type等。 Intent Filters能够写到配置文件中,和那些组件的配置一块儿(不记得什么是配置文件了,能够看这里...),若干的实例能够在Intent介绍页面上找到(reference/android/content/Intent.html)。一样,Intent Filters能够在代码中,动态插拔,这个是和动态插拔的Broadcast Receiver是配套使用的。 系统核心的模块,会负责收集这些Intent Filters,和它们对应的组件信息。当请求者须要一个组件帮忙,并构造了描述它需求的Intent发送到系统核心,系统核心会将其与已知的各个Intent Filters进行匹配,挑选一个符合需求的组件返回。若是有多个符合的,会尝试看看有没有默认执行的,若是没有默认的,就会构造UI,让用户帮助抉择,如是,整个流程就跑通了。 Intent实现 上图,是请求一个Activity组件的简单实现流程图,算是用的最多的Intent解析实例。流程从调用Context.startActivity(Intent)开始,调用者传入构造好的Intent对象,而后流程会让实际的执行者,是Instrumentation对象来完成。它是整个应用激活的Activity管理者,集中负责该应用内全部Activity的起承转合生离死别。它有一个隐藏的方法execStartActivity方法,就是负责根据Intent启动Activity的。去掉一些细节,它作得最重要的事情,就是将此调用,经过RPC的方式,传递到ActivityManagerService。 前面一直再说,系统核心层,其实这里所谓的系统核心层,就是负责Android一些关键事务的一组服务。它们一样运行在虚拟机上,和普通的Service实现机理是一致的,只不过它们不抛头露脸只是默默的在下层服务,故谓之核心嘛。AcitivityManagerService,是负责Activity调度的服务,也许往后说起调度细节的时候还会有涉及。 在这里,AcitivityManagerService会分两个步骤完成相关操做,首先把Intent递交给另外一个服务PackageManagerService,此服务掌握整个软件包及其各组件的信息,它会将传递过来的Intent,与已知的全部Intent Filters进行匹配(若是带有Component信息,就不用比了...),找到了,就把相关Component的信息通知回AcitivityManagerService,在这里,会完成启动Activity这个不少细节的事情。 由此可知,启动Activity,要通过多个服务的处理,并非很是轻量的过程,在Android随机文档介绍性能的一节中,对此有一个评估。但这样的操做不是会放在循环里反复折磨的那种,所以总体效果与其付出的性能代价相比,以为是物超所值的。
【五】 —— 任务和进程
关于Android中的组件和应用,以前涉及,大都是静态的概念。而当一个应用运行起来,就不免会须要关心进程、线程这样的概念。在Android中,组件的动态运行,有一个最不同凡响的概念,就是Task,翻译成任务,应该仍是比较瓜熟蒂落的。 Task的介入,最主要的做用,是将组件之间的链接,从进程概念的细节中剥离出来,能够以一种不一样模型的东西进行配置,在不少时候,可以简化上层开发人员的理解难度,帮助你们更好的进行开发和配置。 任务 在SDK中关于Task(guide/topics/fundamentals.html#acttask),有一个很好的比方,说,Task就至关于应用(application)的概念。在开发人员眼中,开发一个Android程序,是作一个个独门独户的组件,但对于通常用户而言,它们感知到的,只是一个运行起来的总体应用,这个总体背后,就是Task。 Task,简单的说,就是一组以栈的模式汇集在一块儿的Activity组件集合。它们有潜在的先后驱关联,新加入的Activity组件,位于栈顶,并仅有在栈顶的Activity,才会有机会与用户进行交互。而当栈顶的Activity完成使命退出的时候,Task会将其退栈,并让下一个将跑到栈顶的Activity来于用户面对面,直至栈中再无更多Activity,Task结束。 事件 Task栈(粗体为栈顶组件) 点开Email应用,进入收件箱(Activity A) A 选中一封邮件,点击查看详情(Activity B) AB 点击回复,开始写新邮件(Activity C) ABC 写了几行字,点击选择联系人,进入选择联系人界面(Activity D) ABCD 选择好了联系人,继续写邮件 ABC 写好邮件,发送完成,回到原始邮件 AB 点击返回,回到收件箱 A 退出Email程序 null 如上表所示,是一个实例。从用户从进入邮箱开始,到回复完成,退出应用整个过程的Task栈变化。这是一个标准的栈模式,对于大部分的情况,这样的Task模型,足以应付,可是,涉及到实际的性能、开销等问题,就会变得残酷许多。好比,启动一个浏览器,在Android中是一个比较沉重的过程,它须要作不少初始化的工做,而且会有不小的内存开销。但与此同时,用浏览器打开一些内容,又是通常应用都会有的一个需求。设想一下,若是同时有十个运行着的应用(就会对应着是多个Task),都须要启动浏览器,这将是一个多么残酷的场面,十个Task栈都堆积着很雷同的浏览器Activity,是多么华丽的一种浪费啊。因而你会有这样一种设想,浏览器Activity,可不能够做为一个单独的Task而存在,无论是来自那个Task的请求,浏览器的Task,都不会归并过去。这样,虽然浏览器Activity自己须要维系的状态更多了,但总体的开销将大大的减小,这种舍小家为你们的行为,仍是很值得歌颂的。 如此值得歌颂的行为,Android固然会举双手支持的。在Android中,每个Activity的Task模式,都是能够由Activity提供方(经过配置文件...)和Activity使用方(经过Intent中的flag信息...)进行配置和选择。固然,使用方对Activity的控制力,是限定在提供方容许的范畴内进行,提供方明令禁止的模式,使用方是不可以越界使用的。 在SDK中(guide/topics/fundamentals.html#acttask),将二者实现Task模式配置的方式,写的很是清晰了,我再很絮叨挑选一些来解释一下(完整可配置项,必定要看SDK,下面只是其中经常使用的若干项...)。提供方对组件的配置,是经过配置文件(Manifest)<activity>项来进行的,而调用方,则是经过Intent对象的flag进行抉择的。相对于标准的Task栈的模式,配置的主要方向有两个:一则是破坏已有栈的进出规则,或样式;另外一则是开辟新Task栈完成本应在同一Task栈中完成的任务。 对于应用开发人员而言,<activity>中的launchMode属性,是须要常常打交道的。它有四种模式:"standard", "singleTop", "singleTask", "singleInstance"。 standard模式, 是默认的也是标准的Task模式,在没有其余因素的影响下,使用此模式的Activity,会构造一个Activity的实例,加入到调用者的Task栈中去,对于使用频度通常开销通常什么都通常的Activity而言,standard模式无疑是最合适的,由于它逻辑简单条理清晰,因此是默认的选择。 而singleTop模式,基本上于standard一致,仅在请求的Activity正好位于栈顶时,有所区别。此时,配置成singleTop的Activity,再也不会构造新的实例加入到Task栈中,而是将新来的Intent发送到栈顶Activity中,栈顶的Activity能够经过重载onNewIntent来处理新的Intent(固然,也能够无视...)。这个模式,下降了位于栈顶时的一些重复开销,更避免了一些奇异的行为(想象一下,若是在栈顶连续几个都是一样的Activity,再一级级退出的时候,这是怎么样的用户体验...),很适合一些会有更新的列表Activity展现。一个活生生的实例是,在Android默认提供的应用中,浏览器(Browser)的书签Activity(BrowserBookmarkPage),就用的是singleTop。 singleTop模式,虽然破坏了原有栈的逻辑(复用了栈顶,而没有构造新元素进栈...),但并未开辟专属的Task。而singleTask,和singleInstance,则都采起的另辟Task的蹊径。标志为singleTask的Activity,最多仅有一个实例存在,而且,位于以它为根的Task中。全部对该Activity的请求,都会跳到该Activity的Task中展开进行。singleTask,很象概念中的单件模式,全部的修改都是基于一个实例,这一般用在构形成本很大,但切换成本较小的Activity中。在Android源码提供的应用中,该模式被普遍的采用,最典型的例子,仍是浏览器应用的主Activity(名为Browser...),它是展现当前tab,当前页面内容的窗口。它的构形成本大,但页面的切换仍是较快的,于singleTask相配,仍是挺天做之合的。 相比之下,singleInstance显得更为极端一些。在大部分时候singleInstance与singleTask彻底一致,惟一的不一样在于,singleInstance的Activity,是它所在栈中仅有的一个Activity,若是涉及到的其余Activity,都移交到其余Task中进行。这使得singleInstance的Activity,像一座孤岛,完全的黑盒,它不关注请求来自何方,也不计较后续由谁执行。在Android默认的各个应用中,不多有这样的Activity,在我我的的工程实践中,曾尝试在有道词典的快速取词Activity中采用过,是由于我以为快速取词入口足够方便(从notification中点选进入),而且会在各个场合使用,应该作得彻底独立。 除了launchMode能够用来调配Task,<activity>的另外一属性taskAffinity,也是经常被使用。taskAffinity,是一种物以类聚的思想,它倾向于将taskAffinity属性相同的Activity,扔进同一个Task中。不过,它的约束力,较之launchMode而言,弱了许多。只有当<activity>中的allowTaskReparen ting设置为true,抑或是调用方将Intent的flag添加FLAG_ACTIVITY_NEW_TASK属性时才会生效。若是有机会用到Android的Notification机制就可以知道,每个由notification进行触发的Activity,都必须是一个设成FLAG_ACTIVITY_NEW_TASK的Intent来调用。这时候,开发者极可能须要妥善配置taskAffinity属性,使得调用起来的Activity,可以找到组织,在同一taskAffinity的Task中进行运行。 进程 在大多数其余平台的开发中,每一个开发人员对本身应用的进程模型都有很是清晰的了解。好比,一个控制台程序,你能够想见它从main函数开始启动一个进程,到main函数结束,进程执行完成退出;在UI程序中,每每是有一个消息循环在跑,当接受到Exit消息后,退出消息循环结束进程。在该程序运行过程当中,启动了什么进程,和第三方进程进行通讯等等操做,每一个开发者都是心如明镜一本账算得清清楚楚。进程边界,在这里,犹如国界通常,每一次穿越都会留下深深的印迹。 在Android程序中,开发人员能够直接感知的,每每是Task而已。倍感清晰的,是组件边界,而进程边界变得难以琢磨,甚至有了进程托管一说。Android中不但剥夺了手工锻造内存权力,连手工处置进程的权责,也绝不犹豫的独占了。 固然,Android隐藏进程细节,并非刻意为之,而是天然而然水到渠成的。若是,咱们把传统的应用称为面向进程的开发,那么,在Android中,咱们作得就是面向组件的开发。从前面的内容能够知道,Android组件间的跳转和通讯,都是在第三方介入的前提下进行,正因为这种介入,使得两个组件通常不会直接发生联系(于Service的通讯,是不须要第三方介入的,所以Android把它所有假设成为穿越进程边界,统一基于RPC来通讯,这样,也是为了掩盖进程细节...),其中是否穿越进程边界也就变得不重要。所以,若是这时候,还须要开发者关注进程,就会变得很奇怪,很费解,干脆,Android将全部的进程一并托管去了,上层无须知道进程的生死和通讯细节。 在Android的底层,进程构造了底部的一个运行池,不只仅是Task中的各个Activity组件,其余三大组件Service、Content Provider、Broadcast Receiver,都是寄宿在底层某个进程中,进行运转。在这里,进程更像一个资源池(概念形如线程池,上层要用的时候取一个出来就好,而不关注具体取了哪个...),只是为了承载各个组件的运行,而各个组件直接的逻辑关系,它们并不关心。但咱们能够想象,为了保证总体性,在默认状况下,Android确定倾向于将同一Task、同一应用的各个组件扔进同一个进程内,可是固然,出于效率考虑,Android也是容许开发者进行配置。 在Android中,总体的<application>(将影响其中各个组件...)和底下各个组件,均可以设置<process>属性,相同<process>属性的组件将扔到同一个进程中运行。最多见的使用场景,是经过配置<application>的process属性,将不一样的相关应用,塞进一个进程,使得它们能够同生共死。还有就是将常常和某个Service组件进行通讯的组件,放入同一个进程,由于与Service通讯是个密集操做,走的是RPC,开销不小,经过配置,能够变成进程内的直接引用,消耗颇小。 除了经过<process>属性,不一样的组件还有一些特殊的配置项,以Content Provider为例(经过<provider>项进行配置...)。<provider>项有一个mutiprocess的属性,默认值为false,这意味着Content Provider,仅会在提供该组件的应用所在进程构造一个实例,第三方想使用就须要经由RPC传输数据。这种模式,对于构造开销大,数据传输开销小的场合是很是适用的,而且可能提升缓存的效果。可是,若是是数据传输很大,抑或是但愿在此提升传输的效率,就须要将mutiprocess设置成true,这样,Content Provider就会在每个调用它的进程中构造一个实例,避免进程通讯的开销。 既然,是Android系统帮助开发人员托管了进程,那么就须要有一整套纷繁的算法去执行回收逻辑。Android中各个进程的生死,和运行在其中的各个组件有着密切的联系,进程们依照其上组件的特色,被排入一个优先级体系,在须要回收时,从低优先级到高优先级回收。Android进程共分为五类优先级,分别是:Foreground Process, Visible Process, Service Process, Background Process, Empty Process。顾名思义不难看出,这说明,越和用户操做紧密相连的,越是正与用户交互的,优先级越高,越难被回收。具体详情,参见:guide/topics/fundamentals.html#proclife。 有了优先级,还须要有良好的回收时机。回收太早,缓存命中几率低可能引发不断的创造进程销毁进程,池的优点荡然无存;回收的太晚,总体开销大,系统运行效率下降,好端端的法拉利可能被糟蹋成一枚QQ老爷车。Android的进程回收,最重要的是考量内存开销,以及电量等其余资源情况,此外每一个进程承载的组件数量、单个应用开辟的进程数量等数量指标,也是做为衡量的一个重要标识。另外,一些运行时的时间开销,也被严格监控,启动慢的进程会很被强行kill掉。Android会定时检查上述参数,也会在一些极可能发生进程回收的时间点,好比某个组件执行完成后,来作回收的尝试。 从用户体验角度来看,Android的进程机制,会有很可喜的一面,有的程序启动速度很慢,可是在资源充沛的前提下,你反复的退出再使用,则启动变得极其快速(进程没死,只是从后台弄到了前台),这就是拜进程托管所赐的。固然,可喜的另外一面就是可悲了,Android的托管算法,还时不时的展示其幼稚的一面,明明用户已经明显感受到操做系统运行速度降低了,打开任务管理器一看,一票应用还生龙活虎的跳跃着,必需要手动帮助它们终结生命找到坟墓,这使得任务管理器基本成为Android的装机必备软件。 从开发角度上来看,Android这套进程机制,解放了开发者的手脚。开发人员不须要处心积虑的构造一个后台进程偷偷默默监听某个时间,并尝试用各类各样的守护手段,把本身的进程锻造的犹如不死鸟一辉通常,进程生死的问题,已经原理了普通开发人员须要管理的范畴内。但同时,于GC和人肉内存管理的争议同样,全部开发人员都不相信算法能比本身作得效率更高更出色。但我一直坚信一点,全部效率的优点都会随着算法的不断改良硬件的不断提高而消失殆尽,只有开发模式的简洁不会随时间而有任何变化。 组件生命周期 任何架构上的变化,都会引发上层开发模式的变化,Android的进程模型,虽然使开发者再也不须要密切关注进程的建立和销毁的时机,但仍然须要关注这些时间点对组件的影响。好比,你可能须要在进程销毁以前,将写到内存上的内容,持久化到硬盘上,这就须要关注进程退出前发生的一些事件。 在Android中,把握这些时间点,就必须了解组件生命周期(Components Lifecycles)。所谓组件的生命在周期,就是在组件在先后台切换、被用户建立退出、被系统回收等等事件发生的时候,会有一些事件通知到对应组件上,开发人员能够选择性的处理这些事件在对应的时间点上来完成一些附加工做。 除Content Provider,其余组件都会有生命周期的概念,都须要依照这个模型定时定点处理一些情况,所有内容参见:guide/topics/fundamentals.html#lcycles。在这里,擒贼先擒王,仍是拿Activity出来做楷模。 继续偷图,来自SDK。一个天然的Activity生命旅途,从onCreate开始,到onDestroy消亡。但月有阴晴圆缺组件有祸福旦夕,在系统须要的时候且组件位于后台时,所在的进程随时可能为国捐躯被回收,这就使得知道切入后台这个事情也变得很重要。 当组件进入栈顶,与用户开始交互,会调用onResume函数,相似,当退出栈顶,会有onPause函数被呼唤。onResume和onPause能够处理不少事情,最常规的,就是作一些文件或设置项的读写工做。由于,在该组件再也不前台运行的时候,可能别的组件会须要读写一样一份文件和设置,若是再也不onResume作刷新工做,用的可能就是一份脏数据了(固然,具体状况,还须要具体分析,若是文件不会被多头读写,能够放到onCreate里面去作读工做)。 除了前述切入后台会被其余组件骚扰的问题,另外,死无定因也是件很可怕的事情。在Android中,组件都有两种常见的死法,一种是天然消亡,好比,栈元素ABC,变成AB了,C组件就天然消亡了。这种死发轻如鸿毛,不须要额外关心。但另外一种状况,就是被系统回收,那是死的重如泰山,为国捐躯嘛。 但这种捐躯的死法,对用户来讲,比较费解。想象一下,一款游戏,不能存盘,你一直玩啊玩,三天三夜没合眼,这时候你mm打来电话鼓励一下,你精神抖擞的准备再接再砺,却发现你的游戏进程,在切入后台以后,被系统回收了,一晚上回到解放前三天努力成为一场泡影,你会不会想杀作游戏的人,会不会会不会会不会,必定会嘛。这时候,若是没有Activity生命周期这码事,游戏程序员必定是被冤死的,成了Android的替罪羊。可是,Android的组件是有生命周期的,若是真的发生这样状况,不要犹豫,去杀开发的程序员吧。 为了逃生,程序员们有一块免死金牌,那就是Android的state机制。所谓state,就是开发人员将一些当前运行的状态信息存放在一个Bundle对象里面,这是一个可序列化键值对集合。若是该Activity组件所处的进程须要回收,Android核心会将其上Activity组件的Bundle对象持久化到磁盘上,当用户回到该Activity时候,系统会从新构造该组件,并将持久化到磁盘上的Bundle对象恢复。有了这样的持久化的状态信息,开发人员能够很好的区分具体死法,并有机会的使得死而复生的Activity恢复到死前状态。开发者应该作的,是经过onSaveInstanceState函数把须要维系的状态信息(在默认的状态下,系统控件都会本身保存相关的状态信息,好比TextView,会保存当前的Text信息,这都不须要开发人员担忧...),写入到Bundle对象,而后在onRestoreInstanceState函数中读取并恢复相关信息(onCreate,onStart,也均可以处理...)。 线程 读取数据,后台处理,这些猥琐的伙计,天然少不了线程的参与。在Android核心的调度层面,是不屑于考量线程的,它关注的只有进程,每个组件的构造和处理,都是在进程的主线程上作的,这样能够保证逻辑的足够简单。多线程,每每都是开发人员须要作的。 Android的线程,也是经过派生Java的Thread对象,实现Run方法来实现的。但当用户须要跑一个具备消息循环的线程的时候,Android有更好的支持,来自于Handler和Looper。Handler作的是消息的传送和分发,派生其handleMessage函数,能够处理各类收到的消息,和win开发无异。Looper的任务,则是构造循环,等候退出或其余消息的来临。在Looper的SDK页面,有一个消息循环线程实现的标准范例,固然,更为标准的方式也许是构造一个HandlerThread线程,将它的Looper传递给Handler。 在Android中,Content Provider的使用,每每和线程挂钩,谁让它和数据相关呢。在前面提到过,Content Provider为了保持更多的灵活性,自己只提供了同步调用的接口,而因为异步对Content Provider进行增删改查是一个常作操做,Android经过AsyncQueryHandler对象,提供了异步接口。这是一个Handler的子类,开发人员能够调用startXXX方法发起操做,经过派生onXXXComplete方法,等待执行完毕后的回调,从而完成整个异步调用的流程,十分的简约明了。 实现 整个任务、进程管理的核心实现,尽在ActivityManagerService中。上一篇说到,Intent解析,就是这个ActivityManagerService来负责的,其实,它是一个很名存实亡的类,由于虽然名为Activity的Manager Service,但它管辖的范围,不仅是Activity,还有其余三类组件,和它们所在的进程。 在ActivityManagerService中,有两类数据结构最为醒目,一个是ArrayList,另外一个是HashMap。ActivityManagerService有大量的ArrayList,每个组件,会有多个ArrayList来分状态存放。调度工做,每每就是从一个ArrayList里面拿出来,找个方法调一调,而后扔到另外一个ArrayList里面去,当这个组件没对应的ArrayList放着的时候,说明它离死不远了。HashMap,是由于有组件是须要用名字或Intent信息作定位的,好比Content Provider,它的查找,都是依据Uri,有了HashMap,一切都瓜熟蒂落了。 ActivityManagerService用一些名曰xxxRecord的数据结构,来表达各个存活的组件。因而就有了,HistoryRecord(保存Activity信息的,之因此叫History,是相对Task栈而言的...),ServiceRecord,BroadcastRecord,ContentProviderRecord,TaskRecord,ProcessRecord,等等。 值得注意的,是TaskRecord,咱们一直再说,Task栈这样的概念,其实,真实的底层,并不会在TaskRecord中,维系一个Activity的栈。在ActivityManagerService中,各个任务的Activity,都以HistoryRecord的形式,集中存放在一个ArrayList中,每一个HistoryRecord,会存放它所在TaskRecord的引用。当有一个Activity,执行完成,从概念上的Task栈中退出,Android是经过从当前HistoryRecord位置往前扫描同一个TaskRecord的HistoryRecord来完成的。这个设计,使得上层不少看上去逻辑很复杂的Task体系,在实现变得很统一而简明,值得称道。 ProcessRecord,是整个进程托管实现的核心,它存放有运行在这个进程上,全部组件的信息,根据这些信息,系统有一整套的算法来决议如何处置这个进程,若是对回收算法感兴趣,能够从ActivityManagerService的trimApplications函数入手来看。 对于开发者来讲,去了解这部分实现,主要是能够帮助理解整个进程和任务的概念,若是以为这块理解的清晰了,就不用去碰ActivityManagerService这个庞然大物了。
【六】 —— 界面构造
UI界面,对于每一个应用而言,是它与用户进行交互的门脸。好的门脸,不仅是是要亮丽可人,最好还能秀色可餐过目不忘,甚至还应该有涵养有气质,彬彬有理温柔耐心。 对于开发者来讲,锻造这样的面容,不但须要高超的技艺,也须要有称手的工具和对得起党的料子。俗话说,朽木不可雕也,芙蓉不是一日炼成的,不是什么平台都能叫特能书。有套好用的UI框架,对于开发者而言,真有如沙漠中的甘露,而要是撞见了杯具的UI套件,整个界面开发就有如梦魇了。 Android的UI框架,最核心的,是资源和Layout体系,而后,经过完善的控件库,简明的接口设计,进一步帮助开发者,可以最快的搭建本身须要界面(听到这里,Symbian同窗开始钻土...)。 UI控件 作UI,有时候就像搭积木,在Android中,这个最原子的积木块,就是View。全部其余的UI元素,都是派生于此类的子孙类们。 又从SDK中偷来张图,用来描述Android的UI控件结构,在每个window下,这都是一个标准而完整的树结构。View有一个子类ViewGroup,它至关于一个容器类或者是复合控件,全部派生与ViewGroup的子类在这颗UI树中均可以承担着父节点的职责,而另外一些绕过ViewGroup从View直通下来的,就只能蜷局在叶节点的范畴内了。 之全部说这是一个很标准的控件树,是由于父控件对子控件有绝对的掌控权,每一个子控件的占地面积和位置,都是基于父控件来分配的,它可以接受和处理的事件,也是父控件派发下去的。这样的结构,被不少平台和框架普遍的承认,和传统的win开发和杯具的Symbian相比,虽然由于事件传播途径变长了,不少操做的效率变低了,但整个结构更有层次性,每一个控件只须要多其父控件负责指挥子控件就好,职责明确,逻辑简单,利于开发和设计。 谈及任何平台的控件,都有一些不可避免的主题,好比,每一个控件如何标识,如何设定大小和位置,如何接受和处理事件,如何绘制,诸如此类。 标识 在Android中,你能够为每一个控件选择设定一个id,这个id的全局的惟一性不须要保证,但在某个局部的范围内具备可识别性,这样就能够经过这个id找到这个控件(若是不须要查找,就别设置了...)。 可是,在父控件中逐级的find比较,找到id匹配的控件,而后再作转型,是一个比较重量的操做,因而Android又为控件憋出另外一个属性,tag。它接受任意object类型的数据,你能够把和这个控件对象相关的内容堆在里面。好比,在list中,咱们经常将和每一个list item相关的全部控件元素封装成一个object,扔到tag中,就不须要每次都去比较id进行寻找,更加高效快捷。 尺寸 在Android中,控件最重要的大小属性,就是width/height,开发者能够明确的指明控件的大小,能够设定成为fill_parent和wrap_content,这样的概念性的大小。丈量并设定控件的位置,是经过两步来进行的。 第一步是measure。它传入此控件的width/height信息,控件会根据本身的参数,计算出真实须要的width/height,而后调用setMeasuredDimension方法,缓存成成员变量,留做后用。 在计算出大小以后,会进行另外一个步骤,layout。在这个过程当中,父控件会计算其上各个子控件的位置,从而完成整个大小和位置的肯定流程。整个measure和layout的流程,都是自上到下,从树顶往叶子来推动的。 当开发人员须要自定义控件的时候,可能须要关注这些内容,经过重载onMeasure和onLayout方法,能够定义本身控件的丈量方式。 事件 在Android中,全部的按键,触屏等事件,都是从顶至下进行分发的。每一个ViewGroup的对象,会维系一个focused变量,它表示在这个父控件中具有focus的控件,当有按键时间发生的时候,会找到这个focused子控件,并传递给它。同理,触屏事件的分发也是相似,只不过和focus无关,父控件会遍历全部子控件,看看谁处于触碰位置,从而传递给谁。 另外还有一些事件,逻辑上并非从顶至下发起的。好比,当你修改某个子控件的内容,使得该子控件的大小和内容都发生了变化,就须要进行控件的重排和重绘,这些操做不只是子控件本身的事情,须要整个控件树上的全部控件都须要配合。在Android中,处理这类事情的实现策略是子控件维系一个ViewParent对象,该对象象征着整个控件树的管理者,子控件产生影响整个控件树的事件时,会通知到ViewParent,ViewParent会将其转换成一个自顶向下的事件,分发下去。 Android的事件处理逻辑,采用的是观察者模式。Android的控件提供了一些列的add/set Listener的接口,使得外部观察者,有机会处理控件事件。好比,你须要在某个button被点击时作一些事情,你就须要派生一个View.OnClickListener对象做为观察者,调用该控件的setOnClickListener接口注册进去,当button被点击,就能够得到处理点击事件的机会了。固然,有的时候,你须要处理的逻辑更为复杂,光是站在外面围观叫好不能解决问题,可能就须要派生某个控件,去重载onXXXX之类的事件处理函数,进行更完整的控制。 焦点 对于一个非触屏的机器,焦点的维系是一个极其重要的事情,而在有触屏的年代,焦点的地位虽有所降低,但依然仍是须要妥善保护的。 Android中,是以控件树为单位,来管理焦点的。每一个控件,能够设置上下左右四向的focus转移对象。当在一个控件上发生焦点转移事件,Android会如前述,自顶向下根据设定好的焦点转移逻辑,跳转到正确的控件上。和Symbian相比,真是,真是。。。 Layout Layout是一类特殊的ViewGroup控件,它们自己没有任何可显示内容,形如透明的玻璃盒子,存活的惟一理由,就是其中的内部结构,可以更好的摆放它的子控件们。 好比线性的Layout,LinearLayout。放入这个Layout的子控件,会按水平或垂直方向,排排坐,一个挨着一个按顺序排列下去。TableLayout,能够将子控件按照表格的形式,一枚枚放置好。而RelativeLayout则更灵活,能够设定各个控件之间的对齐和排列关系,适合定制复杂的界面。 有了Layout的存在,控件和控件之间再也不割裂的存在,而是更有机的结合在了一块儿,设定起来也更为方便。比Symbian那样人肉维系各个控件的关系,轻松自在多了。 更多 这些问题的完整答案,参见SDK中View的页面:/reference/android/view/View.html。 实现 有了这些对Android的UI控件的认知,能够看更总体性的实现细节,那就是Activity的UI实现。 如上图所示,假设你作了个如同虚线框中结构的一个界面,经过Activity的setContentView方法,塞进了Activity中,就会造成图示的一个逻辑关系。每个Activity,都包含一个Window对象,它表示的是一个顶级的一整屏幕上面的界面逻辑。在Android源码中,其实现是MidWindow,它包含了一个FrameLayout对象,呈现出来就是那种带着一个title的界面样子。自定义的一堆控件,会插进Window的界面部分,在Activity中,全部事件的处理逻辑,是Window先享用,没消费掉在交由这堆控件吃剩的。 在整个控件树的最顶端,是一个逻辑的树顶,ViewParent,在源码中的实现是ViewRoot。它是整个控件树和WindowManager之间的事件信息的翻译者。WindowManager是Android中一个重要的服务。它将用户的操做,翻译成为指令,发送给呈如今界面上的各个Window。Activity,会将顶级的控件注册到WindowManager中,当用户真是触碰屏幕或键盘的时候,WindowManager就会通知到,而当控件有一些请求产生,也会经由ViewParent送回到WindowManager中。从而完成整个通讯流程。
【七】 —— 资源文件
做为一枚coder,作界面,不少时候都是一场梦魇。不少时候,咱们会感受对于底层逻辑实现的颇有把握性,哪怕需求一直在变,也能够经过不断的重构一直跟进,一切尽在掌握。但遭遇界面,每每就再也不如此,它的好坏老是和审美、体验之类的词汇扯在一块儿,在凤姐芙蓉出没的年头,谈审美成为一件恐怖的事情。你可能会被要求不停的改代码,就为了移动一个像素,调整一枚按钮,琐碎而无聊。 为了改变这样的情况,挽救coder们于水生活热之中,不少开发平台,都采用了相似于资源文件的解决方案。此类方案的基本思想是,将界面的实现与底层逻辑的实现彻底剥离开来,用资源文件这样的东西来描述界面。资源文件的描述语言,每每是结构化很强,好比Html,Xml(及其变形体)之类的。于开发语言相比,此类语言逻辑性较弱但结构更好可读性更强更容易理解,并对自动化工具很是友好,能够于界面的拖拽配置结合的更加完美。这样的剥离,能够是的底层逻辑和上层界面独立变化,甚至不一样的人员开发(这一点在web开发上表现的应该很明显...),二者之间的耦合性很是的小,coder们的负担,陡然减小(好吧,一个很挫的资源架构也会额外增长开发人员的负担,Symbian同窗,请不要对号入座...)。 结构和格式 Android的资源文件,是由目录结构,Xml格式的文件,和纯数据文件构成。从格式上来看,无疑,学习门槛很是低。Xml做为coder们的瑞士军刀,哪怕使不习惯,弄得清楚并会用至少是没有问题。从配套的工具来看,Android的ADT,提供了一套可视化的配置工具,说不上特别好用,但至少是差强人意能凑合着用,比不上iPhone的,调戏Symbian仍是没有问题的[强档广告首播:有道词典 for iPhone新版火热上线,增长了超强单词本功能,特有的触电式颤抖单词切换功能,让你欲罢不能,持有相关设备的童鞋不要犹豫,蜂拥而上吧...]。 Android的资源文件,覆盖面超级广,只要是和界面相关的,均可以用资源文件表示,好比:UI的样式,菜单,配置文件,各类描述性字符串,图片,音频视频文件,动画,颜色,尺寸,风格和样式,等等等。全部的资源文件(不考虑asset,它和讨论暂无关联...),都放在res目录下,不一样类别的资源,须要放置在不一样的特定名称的子文件夹中,或者是写在特定文件名的文件中(或者ms不是必须的,但,不用在这里特立独行,寻章办事也挺好...)。好比,全部做为UI背景之类的图片,都须要扔在drawable这类的文件夹中,全部字符串相关的,都会放到values目录下形如strings.xml这样的文件中(以下图所示,是一个资源文件目录结构的截图...)。 每一个xml文件,都有必定的约定。好比一个字符串,会放在<string></string>这样的xml element中(以下图所示...),你能够经过eclipse的ADT插件提供的但是界面去填而不关注具体规范,也能够直接人肉打造,前者对于新手来讲更为直观,后者对于老鸟而言更为迅捷。 可配置性 程序逻辑老是不变应万变的,但界面每每是须要可以72变。首先一种变化因素,就是状态。想象一下,咱们每每会有这样相似的需求,一个按钮,咱们须要没有按下去的时候是一种背景,按的过程当中刷的变成另外一副模样,当它可用的时候须要鲜鲜亮的一个样子,不可用的时候最好是灰不溜秋没人愿点的怂样,诸如此类。传统编程模型下(Symbian,哥叫你出来当模特...),咱们老是须要不厌其烦的用代码控制这样的事情。监听不一样的事件,见缝插针的切换背景,并祈祷上天,千万别让哥调整,不然哥和你没完。 在Android中,作这个事情,变得简单许多,经过预设的一些Xml属性,可以轻松的搞定。如上图所示,是Radio Button的背景。经过搭配不一样的属性,就能够自动转换背景。好比第一个<item>,说的是当Radio Button被选中,而且具备焦点的时候,显示btn_radio_on这幅图片,而最后一个<item>,说的是前述条件都不知足,而且处于选中状态,那么显示btn_radio_on这幅图片。 另一个更易变的因素,就是手机硬件/软件环境了,毕竟,不是家家都是苹果,一个平台搭一款手机,手机款形多样化,几乎是避免不了的问题。没有人但愿本身作的软件在大屏幕手机上闪亮光鲜,换个小屏幕就惨不忍睹,竖屏看像那么回事横屏看就挤作一团。还有就是语言环境了,作为一个有国际眼光的coder,做面向世界的NB软件是咱的梦想,但咱们不能由于本身的梦想逼迫你们都去学中文,作一款软件能够根据手机的语言环境选择最合适展现的语言,不少时候,是一个须要具有的功能点。 在Android中,实现这些,都是举手之劳。方法就是将和环境相关的资源,放入特定名称的文件夹中。好比,表示简体中文字符信息的资源,能够放到values-zh-rCN中去,当系统语言环境为简体中文时,就会呈现出中文的字符信息。在Android中,不少相关配置项,均可以按照这样的方式参与到资源自适应的活动中来,包括屏幕大小,屏幕朝向,屏幕分辨率,语言环境,触屏类型,SDK版本等等。系统会给全部配置项一个优先级(或者说权重,次序之类的),当用户提供了多份资源的时候,系统会根据优先级从高到底淘汰备选资源,若是淘汰仅剩了一个,那就是最符合当前系统软硬件语言环境的资源项,若是一个不剩,择启用默认项(最是形如values这样没有任何尾巴目录中的资源...)。所以,默认的资源是很是重要的,它必须是其余全部可选资源项的超集,不然在资源选择失败的状况下,应用会凄凉的崩溃。 关于资源配置,以及选择的详情,参见SDK中的:guide/topics/resources/resources-i18n.html部分。 R类 在使用资源后,界面逻辑与底层逻辑的耦合被下降了,但这不意味着,二者没有关联了。好比,须要为某个按钮增长一个点击事件,就须要定位到所需的那个按钮;再好比,你须要使用某个字符串资源,通知用户某件事情,就须要能定位到资源中放置的该字串。 最显而易见的一种方式,就是经过字符串比较,用名字信息在资源的xml描述文件中定位到所需的内容,加载并使用。这种方式,解决了查找的问题,但反复的字符串比较,势必带来严重的效率隐患。所以,在Android中,相似于Symbian的方法,引入了一个R类。 它的基本思想是,经过增长一个额外的编译器,为全部的资源项,都赋予一个32位的整形数来表示,同一个资源像的不一样配置,都使用同一个id。这个整形数,就至关于这个资源项的门牌号码,可以帮助定位到对应的资源项。全部的这些整形数,都以常量的方式,整合到一个Java类中,这个类就是R类。这样,在程序中,就能够经过使用这个R类,来查找所需的资源,这就将字符串比较,简化成了一个整形数的比较,大大的节约了开销。 不得不说,这整套逻辑和Symbian中的资源文件预编译一致。但二者很不一样的点在于Symbian中的整形数,表明的是一个二进制流的偏移量,资源中的内容在编译时决定了。而Android中的整形数,是一个有逻辑意义的数值,它表达了这个资源所处的资源包,类别,和脚标,它的具体内容在运行时才肯定,这使得它的灵活性大大加强,付出的则是必定的效率代价。 实现 按照惯例,仍是要说实现的,以一个查找流程为示例。当在Activity中须要使用字符串的,会调用它的getString方法,传入R.stirng.xxx的一个整形数,换取一个符合当前机器环境配置的字符串。 getString,追根溯源,来到AssetManager类中。Asset类,实际上是一个空壳,它仅仅是提供了一些便利的接口,而将请求,经过JNI的接口,传入到了底层C++实现的类库中。 在底层的实现,主要是在C++实现的,AssetManager,ResourceTypes等等之中。其中: JNI文件在:framework/base/core/jni 头文件在:framework/base/include/utils CPP文件在:framework/base/libs/utils 具体实现,和前述的算法逻辑是一致的。每个资源的id,32位,高8位表示资源包,低16位用于描述脚标,中间8位,用来讲明类别。全部资源中的文件,都被预处理了,放入到了一系列的队列和表中,经过id,能够查到具体的位置。而后根据缓存的环境设置对象,跑一次淘汰算法,得到匹配的资源对象的对应文件和偏移量。而后将值读取出来,经过JNI接口,拷贝回去。 以上这些描述,并不能帮助了解真实的实现细节,主要是为了促使你们对读取资源的效率有一个比较直观的认知。整个资源读取的流程比较长,可是实如今C++中,能够预想,效率比Java高一些,开发人员,应该可以根据本身的需求,决定是否将内容写入资源文件中(仍是写在代码中...),是否是须要本身稍微缓存一下,诸如此类。
【八】 —— Activity间数据传输
当对Android有一些了解后,不难发现,Android程序UI框架接近于Web页面的概念。每个用于呈现页面的组件,Activity,都是彼此独立的,它们经过系统核心来调度整合,彼此之间的经过Intent机制来串联。 每一种架构都会有其利弊,Android固然也不能超然脱俗。因为Activity之间的松耦合关系,使得其复用能力特别的出色,Mash-Up方式能够有效的提升开发效率。但另外一方面,因为Activity过于的独立,它们之间的数据共享,成为一个麻烦的事情。 基于消息的传输 最标准的Activity之间的数据传输,就是经过Intent的Extra对象。好比,你在A这个Activity上拿到一坨用户输入的文本信息,兴高采烈的想把它放到B这个Activity上展现并发送,一个很可行的方式,是经过Intent的putExtra接口,把用户输入的那些字符信息,按照key/value的形式放进Intent,传输到B这个Activity上。 如上图示,从A到B的传输,看上去是一个直连,但其实,Intent都是要经由系统核心层去分析调度的,这个操做,跨越了进程边界,天然而然,其中的数据,就是须要序列化和反序列化的,而不能够仅经过一个指针就倒腾过去了。 基于这样类消息的传输模式,好处很少说,直接谈问题: 首先,对于大数据,就是一场杯具,不可能一坨上M的数据,也来来回回的传来传去,慢死了谁来负责; 再则,Activity之间,维系的是一种线性关系,当我想把一份数据,从队尾一级级传到队头的话,本身历经磨难不提,会把中间全部的Activity都搭上,他们明明本身可能不须要这份数据,也得拿着搁着,为他人作嫁衣裳,不惆怅都不行; 此外,基于消息的传输,会把同一份数据生成若干个副本,有时候,这样很好,没有反作用,你们本身玩本身的不须要看别人脸色,但还有些时候,你就上杆子须要把这些数据都修改一下,同步起来那就太惨烈了; 最后,写序列化代码实在是太无聊了,稍微复杂点的代码,就要本身写个序列化接口,整个吃力不讨好的活计。 基于外部存储的传输 既然兽兽手手相传太幸苦,天然而然的想法就是找个地方,A把数据搁在那里,把地址信息告诉B,B须要的话,按图索骥,自取就好。这个搁东西的地方,能够考虑选择外部存储。 在Android中,预设了一些快捷便利类和模块,更好的支持不一样类别数据的存取。若是,须要存储的是一些小数据量的配置信息,能够选择Preference,它等同于传统意义上的设置文件。Preference提供了一些基于key/value的存取接口,能够放置一些简单的基本数据或者派生了Parcelable接口的对象。一个很好的应用场景是Cookie的存放。你在登陆界面得到了一份Cookie,你能够把它扔进Preference,谁想要谁去拿,不再要来来回回的折腾了。 Preference适合于小数据、设置信息,若是大数据,你能够考虑使用数据库。在Android中,使用的是Sqlite,相关的类,堆放在android.database名字空间下,自查,无需赘述。 在Android里,数据库是私有的,若是想分享给第三方组件使用,就须要用ContentProvider来封装了。好比你用系统的录音机组件即时搞一段音频信息,它不是返回可能大到恐怖的录音数据,而是会返回给你一个Uri,它标明了这份数据在ContentProvider的地址信息,拿着这个Uri,领取数据就好。 固然固然,若是你足够淡定,也能够用赤果果的File来存储。若是这个文件存在手机私有目录下,那就内部使用,放在SD卡上,那就能够全部应用,一切分享。 基于这样外部存储的数据传输,优缺点显而易见,它解决了困扰Intent的传输路径复杂,不利于传输大批量数据的问题,但同时,它有留下了效率隐患,复杂了编程模型。由于面对外部存储,开发者必需要考虑效率问题,不少时候,多线程就会被提上议程,这样,想不麻烦,都不行鸟。 基于Service的传输 既然存在外部太慢,那么仍是在内存级别解决问题好了,这时候,你可能就须要请出Android四大组件之一的Service了。Service设计的本意,就是提供一些后台的服务,数据存取,也能够归于其职责的一部分。 Service是提供了直连机制,调用的Activity,能够经过bindService方法,与目标Service创建一条数据通路,拿到IBinder。这样,经过Android提供的IPC模型,就能够进行远程方法的调用和数据的传输了。 如上,经过这种模式,能够解决必定问题,可是对于Service来讲,实在是太大才小用了,Service的专长,不是在数据,仍是在逻辑。对于传数据而言,Service仍是重量了一点,不可是有链接耗精力,传输经由IPC,写起来也够费劲。并且做为组件,Service随时可能死掉,你仍是要费劲心机的处理数据的持久化,得不偿失。 利用Application传输 好吧,若是你须要在不一样页面之间共有某个内存对象,很合适的一种方式是把它们扔到Application里面。Application是Context的一个子类,它会在整个应用任何一个组件起来以前,先起来嘘嘘。它的生命周期会贯穿整个应用全部组件的生命旅途,所以,放在其中的对象,不会被处理掉。 在Activity中,能够经过getApplication接口,随时得到Application对象的引用,用于实现一些全局对象的存储,和处理,真是最合适不过的地方了。 固然,好东西也不要使用过分,能够想象,因为Application存活周期长,其上引用的对象一直缺乏被释放的机会,若是你把它当成垃圾场,什么东西都往里扔,污染环境,混乱逻辑不提,单就是滥用内存资源这一项,就够罪孽深重一把了。 所以,若是数据不是真的须要全局使用,不要搁在其中,若是数据太大,不要所有load出来,合理使用数据库等外存储设备,仍是必需要的。 结语 还有一些特殊状况,能够考虑用一些特殊的方式。好比子Activity之间,能够经过调用getParent得到父Activity的引用,来访问期间的对象,云云。小众状况,姑且不提。 以上这些概念,我相信全部的coder都了如指掌,如何处理这样的数据,都心如明镜。我只是给它们套上了一件Android的外衣,让初入Android的coder们,能迅速找到心仪的兵器,劈山砍石,攻城拔寨。
yeaxzz 上传了这个附件:
yeaxzz 上传了这个附件:
yeaxzz 上传了这个附件:
yeaxzz 上传了这个附件:
yeaxzz 上传了这个附件:
|