Android基础知识资料_第1页
Android基础知识资料_第2页
Android基础知识资料_第3页
Android基础知识资料_第4页
Android基础知识资料_第5页
已阅读5页,还剩27页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android基本概念和模型

学习在一个全新的平台上开发最重要的第一步是什么?我觉得当然不是语言的问题,也更不是工

具的问题。学习Windows开发,我们得了解Win32窗口的运行原理,消息队列等机制,不了解这

些背后的机制和基本的运行模型,只靠RAD工具我们还是只能停留在普通应用的阶段。学习Web

开发,不了解HTMLDOM,HTTP协议原理等内容,只靠掌握如ASP.Net,JSP等厂商技术,我

们也做不到Web开发的“心想事成”,“胸有成竹”;同样如AnrsBlog中说到的正则表示式的应用,

我们还得了解其背后的匹配原理。所以,我觉得面对一个全新的平台,我们得首先了解其工作和

运行的基本模型和原理,才能做到以后在这个平台上的“得心应手”。

各位看官先不要被图形中的生词吓死,接下来我会详细解释每一个概念。

先来看图形中的灰色部分,这部分描述了一个完整的Android应用程序可以包含的各个组成部分,

我们将组成一个Android程序的组件称为AndroidComponent(图中中间部分的基类),由若干个

AndroidComponent就组成了一个完整的Android应用程序。先看图中左下方的Activity,这个组

件我们可以认为它是Windows中的窗体概念,这是Android程序的基本组成部分,也就是程序的

人机交互界面。比如一个简单的短信程序就应该包含三个Activity,一个短信列表界面,一个阅

读短信详细内容的界面和一个编辑短信的界面。图中左上角的Service顾名思议就是服务,一个

Android程序中哪些部分是服务呢?举例来说,短信程序并不只是在我们打开短信界面的时候才

去收取短信,我们退出界面后,手机仍然会去收取短信,并在新的短信到达时通知我们,所以一

定有某个任务在后台运行着,这就是Service了;再比如说音乐播放功能,当我们从播放界面返回

手机待机界面的时候仍然可以继续听音乐,这也是一个Service的例子。其实Activity+Service是

非常常见的手机软件应用,比如我要做的BlogMessage同样也是这样的结构。左边中间部分的

-BroadcastReceiver”是用于接收各种系统定义事件或自定义事件的接收器,如果我们的程序想侦

测•些系统事件的发生,我们就需要写一个BroadcastReceiver。例如我们的程序想在手机打开

Wifi的时候立即去刷新最新的数据,或者我们想在手机来电时执行某个动作,这些都可以由

BroadcastReceiver订阅特定的事件来完成。图中左边剩下的“ContentProvider”,我们可以把它理

解成一种特殊的Service,一种可以给其他程序提供数据的Service,例如手机中的联系人信息,我

们任何程序都可以和其通信去获取联系人的信息,这就可实现为一个典型的ContentProvider。

再来看图中蓝色的部分,这是一个静态的部署概念,就如同我们.Nei开发的程序集的概念一样。

Apk是我们Android程序发布和部署的基本单位,一个完整的Android程序就可以打包为一个或

多个Apk进行发布,我们从AndroidMarketing上下载安装的程序也是一个个的Apk包,我们在

Eclipse中的一个Project的最终Build结果也就是一个Apk文件。一个Apk中包含了上面介绍的

4种AndroidComponento

最后,图中黄色的部分就是系统运行时的概念了。由于Android平台是基于Linux的,所以Process

(进程)和Thread(线程)的概念和Linux中的一致,在代码中我们可以编写一个普通的JavaThread

来实现多线程。需要注意的是,Android中的Process是受系统自动管理的,并不是说我们在一个

程序界面中按了手机上的Back键或者Home键程序就结束了,大家也很难在Android的各种程序

中找到类似Symbian程序中的“退出”功能。Android系统会给每一个进程都计算出一个“重要程度”

等级,在系统运行的某个时候例如资源不足的时候,系统会根据各个进程的“重要程度”来决定先

释放哪个进程。(进程“重要程度''的判断在Google的官方文档还是说的比较清楚的,实际上各个

AndroidComponent都有很完整的运行时生命周期,由于我们不太清楚进程结束的时机,了解各个

AndroidComponent的运行时生命周期以及相关事件就对我们的开发来说非常重要,我会陆续在后

续的手记中详细阐述这些内容)。

一个Apk中包含的AndroidComponent在运行时可以运行在同一个进程中,也可以运行在不同

的进程中,这取决于我们在Apk的AndroidManifest.xml上进行的配置(大家可以将这个

AndroidManifest.xml看成是Apk的全局配制信息,其中会描述这个Apk中包含了哪些Android

Component以及各个Component的运行和启动方式等,我会在后续的Post中讲解这些内容)。

最后,图中下面中间部分的“Task”是Android中一个很特殊的运行时概念,也是很复杂的一个

概念,Google的官方文档用了很大的篇幅来说明这个概念。它有别于进程和线程,并且只和

Activity的运行时有关系。

我们可以将其理解成“窗口栈”,这是由手机上的特殊操作方式所引出的概念。由于手机上的程

序,用户一般只能在同一时间看到一个界面,例如在编辑短信的时候一般就不能看到短信列表的

界面。而一个完整的程序一般会由多个Activity组成,所以这些Activity会在运行时随着打开的

先后顺序会被放到同一个窗口栈(Task)中,当前活动窗口栈中最上面的Activity就是用户当前

看到的界面,按手机上的“Back”则是销毁当前栈顶的Activity,回到上一个界面。

然而Task这个概念之所以复杂,是因为不同Process中的Activity可以被放到同一个Task中,

例如在我们的程序中可能会打开GoogleMap的地图界面。具体Activity在运行时该被放到哪个

Task中,这会由Activity的taskAffinity属性决定,一般情况下一个Apk中的所有Activity在运行

时会被放到同一个Task中,但是运行时Activity的taskAffinity是可以修改的。例如上面说的Google

M叩的例子,地图显示界面默认是存在于GoogleM叩这个程序的默认Task中的,但是我们却可

以在运行时将这个界面带到我们自己程序的当前Task中来。窗口在Task中的“入栈”和“出栈”操

作和Activity的运行时生命周期息息相关,后面我也会用更详细的篇幅来介绍Task和Activity运

行时生命周期的关系。

Activity的运行时生命周期模型

由于在Android中,进程的生命周期大多数时候是由系统管理的;另外也由于手机应用的一些特

殊性,所以我们需要更多的去关注各个AndroidComponent的运行时生命周期模型。(所谓手机

应用的特殊性主要是指这样2点:1.手机应用的大多数情况下我们只能在手机上看到一个程序

的一个界面,用户除了通过程序界面上的功能按钮来在不同的窗体间切换,还可以通过Back键

和Home键来返回上一个窗口,而用户使用Back或者Home的时机是非常不确定的,任何时候

用户都可以使用Home或Back来强行切换当前的界面。2.往往手机上一些特殊的事件发生也

会强制的改变当前用户所处的操作状态,例如无论任何情况,在手机来电时,系统都会优先显示

电话接听界面。)了解这些Component的生命周期模型一方面是让我们对软件在手机中的运行

情况做到心中有数,更重要的,对于程序开发来说,生命周期中的每一个关键事件都会有我们可

以覆写于各种Component对应基类型的事件处理方法,了解各Component的生命周期就是让我

们在开发程序时明白我们该怎样去编写各种事件的处理代码。例如Activity的Create,就会有对

应的事件处理函数onCreate,我们可以从Activity基类覆写这个事件处理函数完成我们需要的相

关事件处理:

publicclassactMainextendsActivity{

@Override

publicvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedlnstanceState);

……〃我们的事件处理代码

这篇Post我们就来看看最常用的Activity的运行时生命周期模型(Service的运行时生命周期模型

在下一篇讲述了如何启动一个Service并和其通信后再做描述)。Activity的生命周期模型在

Google提供的官方文档上有比较详细的一个图示(请自行翻墙查看)。其一共包含7个我们需要

关心的关键事件,下面对其分别详细说明(文字中的粗体字表示后文中会经常用到的概念在第一

次出现时会给出解释,之后后文不再详细说明):

1.voidonCreate(BundlesavedlnstanceState)

当Activity被第首次加载时执行。我们新启动一个程序的时候其主窗体的onCreate事件就会被执

行。如果Activity被销毁后(onDestroy后),再重新加载进Task时,其onCreate事件也会被重

新执行。注意这里的参数savedlnstanceState(Bundle类型是一个键值对集合,大家可以看成

是.Net中的Dictionary)是一个很有用的设计,由于前面已经说到的手机应用的特殊性,一个

Activity很可能被强制交换到后台(交换到后台就是指该窗体不再对用户可见,但实际上又还是存

在于某个Task中的,比如一个新的Activity压入了当前的Task从而“遮盖“住了当前的Activity,

或者用户按了Home键回到桌面,又或者其他重要事件发生导致新的Activity出现在当前Activity

之上,比如来电界面),而如果此后用户在一段时间内没有重新查看该窗体(Android通过长按

Home键可以选择最近运行的6个程序,或者用户直接再次点击程序的运行图标,如果窗体所在

的Task和进程没有被系统销毁,则不用重新加载Process,Task和Task中的Activity,直接重

新显示Task顶部的Activity,这就称之为重新查看某个程序的窗体),咳窗体连同其所在的Task

和Process则可能已经被系统自动销毁了,此时如果再次查看该窗体,则要重新执行onCreate事

件初始化窗体。而这个时候我们可能希望用户继续上次打开该窗体时的操作状态进行操作,而不

是一切从头开始。例如用户在编辑短信时突然来电,接完电话后用户又去做了一些其他的事情,

比如保存来电号码到联系人,而没有立即回到短信编辑界面,导致了短信编辑界面被销毁,当用

户重新进入短信程序时他可能希望继续上次的编辑。这种情况我们就可以覆写Activity的void

onSaveInstanceStdte(BundleoulStale)事件,通过向outSlate中写入,些我们需要在窗体销毁前

保存的状态或信息,这样在窗体重新执行onCreate的时候,则会通过savedlnstanceState将之

前保存的信息传递进来,此时我们就可以有选择的利用这些信息来初始化窗体,而不是-一切从头

开始。

2.voidonStart()

onCreate事件之后执行。或者当前窗体被交换到后台后,在用户重新查看窗体前已经过去了一段

时间,窗体已经执行了onStop事件,但是窗体和其所在进程并没有被销毁,用户再次重新查看

窗体时会执行onRestart事件,之后会跳过onCreate事件,直接执行窗体的onStart事件。

3.voidonResume()

onStart事件之后执行。或者当前窗体被交换到后台后,在用户重新查看窗体时,窗体还没有被销

毁,也没有执行过onStop事件(窗体还继续存在于Task中),则会跳过窗体的onCreate和onStart

事件,直接执行onResume事件。

4.voidonPause()

窗体被交换到后台时执行。

5.voidonStop()

onPause事件之后执行。如果一段时间内用户还没有重新查看该窗体,则该窗体的onStop事件

将会被执行;或者用户直接按了Back键,将该窗体从当前Task中移除,也会执行该窗体的onStop

事件。

6.voidonRestart()

onStop事件执行后,如果窗体和其所在的进程没有被系统销毁,此时住户又重新查看该窗体,则

会执行窗体的onRestart事件,onRestart事件后会跳过窗体的onCreate事件直接执行onStart

事件。

7.voidonDestroy()

Activity被销毁的时候执行。在窗体的onStop事件之后,如果没有再次查看该窗体,Activity则会

被销毁。

最后用一个实际的例子来说明Activity的各个生命周期。假设有一个程序由2个ActivityA和

B组成,A是这个程序的启动界面。当用户启动程序时,Process和默认的Task分别被创建,接

着A被压入到当前的Task中,依次执行了onCreate,onStart,onResume事件被呈现给了用户;

此时用户选择A中的某个功能开启界面B,界面B被压入当前Task遮盖住了A,A的onPause事

件执行,B的onCreate,onStart,onResume事件执行,呈现了界面B给用户;用户在界面B操

作完成后,使用Back键回到界面A,界面B不再可见,界面B的onPause,onStop,onDestroy

执行,A的onResume事件被执行,呈现界面A给用户。此时突然来电,界面A的onPause事件被

执行,电话接听界面被呈现给用户,用户接听完电话后,又按了Home键回到桌面,打开另一个程

序“联系人”,添加了联系人信息又做了一些其他的操作,此时界面A不再可见,其onStop事件

被执行,但并没有被销毁。此后用户重新从菜单中点击了我们的程序,由于A和其所在的进程和

Task并没有被销毁,A的onRestart和onStart事件被执行,接着A的onResume事件被执行,A

又被呈现给了用户。用户这次使用完后,按Back键返回到桌面,A的cnPause,onSlop被执行,

随后A的onDestroy被执行,由于当前Task中己经没有任何Activity,A所在的Process的重要

程度被降到很低,很快A所在的Process被系统结束。

在Android中窗体与窗体之间如何互相调用和交换数据?窗体(Activity)和后台的服务(Service)

如何通信?基于Unix(Linux)的系统都有一个很优秀的传统,就是倡导非常轻便的进程间通信

(IPC)机制;倡导进程通过IPC来互相协作;倡导功能单一,小巧而强壮的进程,而不是又大

又复杂的''万金油〃。同样,在Android中我们可以将我们的Activity和Service放在不同的进程中

运行,我们可以在我们的Task中加载其他进程的Activity,这些机制都鼓励我们''尽量利用已有

的功能,利用IPC和包含这些己有功能的程序协作,来完成一个完整的应用〃,例如在我们的程序

中充分利用GoogleMap的相关窗体和服务。所有这些都建立在一套轻更好用的IPC机制上。

Android的组件和进程间通信都建立在一种基于称为Intent的消息基础之上。Intent就是一种消

息,它包含了两个重要的内容:1.消息的目的,即这个消息是发给哪个组件的?(消息的目的中

不会包含”消息是发给哪个进程”这样的信息,这里Android有意淡化进程的概念,而只让我们关

心组件,因为了解太多关于进程的具体信息会加大复杂度,而又如何做到进程间的消息传递呢?

下文会说到一种Android中关于这点比较特别的设计方式,我认为是一种简捷有用又符合手机特

点的设计);2.消息所携带的数据内容,即需要传递给目标的数据。下面是一个简单的利用Intent

来启动一个Service并向其传递数据的代码示例:

Intentserviceintent=newIntent(contextzsvrMain.class);

serviceIntent.putExtra(''Network_Report^networkstatus);

context,startService(servicelntent);

上面的代码首先构造了一个Intent对象,并在构造的时候指定了这个Intent的目的地,即

wsvrMain.class/z,表示这个Intent是要传递给一个类名叫svrMain的Service<.然后向这个Intent

中放入了一个数据,数据的key为''Network_Report〃,value为一个叫networkstatus的int类型

变量,用来指明当前网络的状态。最后我们使用系统提供的上下文API,将这个Intent传递给指

定的Serviceo

Intent的消息目的地分为两种模式,一种是显式的,一种是隐式的。我们上面的例子中看到的就

是一个显式消息的例子。显式消息直接指定消息目的地组件的类元信息,例如上面例子中svrMain

就是我们写的一个类名为svrMain的Service,class操作符就是获取其类元信息。这种模式的消

息由于己经确切知道了消息目标的确切信息,所以只适用于同一进程内的不同组件之间通信,例

如打开一个子窗体,和同一进程中的service通信等。

对应的,隐式消息就一般用于跨进程的通信了,隐式消息没有确定的消息目的地,除了数据

外,隐式消息只是包含了一些用于表征消息特征的描述字段。而一些需要收到某种特定特征消息

的某个程序中的某个组件,需要通过在其所在程序的AndroidMainifest.xml中注册一种被称为

intent-filte「的消息特征筛选器,然后Android系统会按照一定的匹配规则来匹配发出的消息特征

和所有拥有响应这种特征的intent-filter的组件(无论是同一进程内的组件还是不同程序中的组

件),匹配到的组件就会接受到相应的消息。前面的描述多少有些拗口,我们举个实际的例子来说

明,如果我们想开启一个子窗体(无论这个窗体来自同一进程还是不同进程),我们除了使用显式

消息外,我们还可以使用隐式消息:

IntentopenSomeDiaglntent=newIntent();

openSomeDiagIntent.setAction(''edwin.demo.fooActivity,z);

this.startActivity(openSomeDiaglntent);

上面的隐式消息不包含具体的目的地,而是仅包含一个名位''Action〃的特征字符串,Action就是上

文所说的Intent特征的一种。从字面上来理解,可以理解为这个消息所代表的是完成某一个动作

的含义,由action来标明动作的名字。所有能够处理这种动作的Activity都可以收到该消息。对

应的,可能在同•个程序或者另外的某个程序的AndroidMainifest.xml中声明了下面这样的•个

Activity:

<activityandroid:name=//.fooActivity/,>

<intent-fliter>

<actionandroid:name=//edwin.demo.fooActivity///>

</intent-filter>

</activity>

那么这就表示这个Activity能够收到并处理action为"edwin.demo.fooActivity”的消息。所以上面

的代码串起来的效果就是,打开了这个名为.fooActivity的窗口,无论这个窗口是在当前的进程中

还是另外的一个程序中。

除了Action这种消息特征外,Intent还有category,data这两个特征描述属性。Category同

样是一个字符串,从字面上理解就是“消息的分类特征“。从程序上看其和Action的不同在于,一

个Intent只能有惟一的一个Action名称,但是却可以包含多个Category字符串;一个Intent-Filter

可以包含多个Action节点但至少要包含一个,另一方面一个Intent-Filter可以包含零到多个

Category节点。Android在做Intenr-Filter匹配的时候,Intent的Action属性匹配到Intent-Filter

中的任何一个action节点,就表明拥有这个Intent-Filter的组件能够处理这种消息;而对于

Category来讲一个Intent中的所有的Category都必须存在于Intent-Filter中的Category节点中

时,才表明匹配成功。

Data属性可以描述一个Intent所要传递的数据类型和URL每一个Intent只能包含一个Data

属性。其中数据的类型使用MIME类型描述方式来描述,例如video/mpeg表示编码格式为mpeg

的视频,这里也可以使用通配符,例如video/*表示任意格式的视频文件类型;数据的URI由

scheme(协议),host,port,path四部分组成:scheme:〃host:port/path,例如

:8080/file/filel或者

content://edwin.demo.contentProvider:100/forder/contentl,其中path部分也是可以支持通配

符的。Data属性是一个很有用的描述特征,例如下面这样的一个包含data节点的Intent-Filler:

<activityandroid:name=zz.actHttpVideoMan,/>

<intent-fliter>

<actionandroid:name=,<edwin.demo.actHttpVideoMan.Mainzz/>

<dataandroid:scheme=//http,zandroid:type=wvideo/*w/>

</intent-filter>

</activity>

它表示窗体actHttpVideoMan能够处理来自web服务器的视频文件。这样的filter有什么作用呢?

最典型的情况就是配合浏览器工作。浏览器在打开一个链接的时候首先会尝试显示这个链接对应

的html页面,如果这个链接不是一个html页面,而是一个视频文件或者其他浏览器本身不能处

理的格式的话,浏览器会使用隐式消息尝试开启•个能够处理这种数据格式的Activity求处理,

浏览器发出的隐式消息就是一个包含data属性,其中URIscheme为http,数据类型为video/*

的消息,如果有能够匹配这个inten:的组件,例如我们上面的那个activity,浏览器就会启动这个

窗体,接着这个窗体会根据data属性指定的URI去播放在线视频,如果没有可以处理这个intent

的Activity,浏览器才会调用下载管理器下载文件。

隐式消息这个设计简单有效,它忽略了进程的细节,让IPC在一个更高的更接近人脑思维模式的

层次工作,让系统中的不同进程协作看起来就像是同一程序中的协作一样,这种简单的IPC机制

在很大的程度上鼓励我们和其他进程协作,通过协作的进程来完成一个复杂的任务,而不是把什

么功能都做到一个大而全的程序里面。不过上文还有一些细节没有提到,例如如果一个intent有

多个可匹配的处理组件,系统如何处理?这就要分响应消息的组件类型来说了,如果是service,

那么这些service都可以启动并处理消息,如果是Activity,则android会弹出一个对话框让用户

进行选择。比如我们安装了多个可以处理在线视频的软件,当我们在浏览器中点击一个在线视频

的链接时,系统会让用户选择使用哪个软件来观看。另外大家一定会想到安全性的问题,如果不

同进程间的组件可以通过隐式消息互相通信,那我们的程序不是可以轻易调用到其他的程序或者

系统中的一些敏感程序的组件,这样会不会很不安全呢?其实Android在安全方面有一个统一,

完备和轻便的安全策略模型,Intent的安全自然是被考虑在内的,关于android的安全模型我会

在后续的系列blog中专门说明。

最后,除了Intent这种基于消息的进程内和进程间通信模型外,android中也有一种相比起来梢

显笨重一些的IPC机制,它采用类似远程方法调用的方案,通过接口定义文件AIDL来定义一个

IPC接口,然后通过接收方实现接口,调用方调用接口的本地代理实现来完成IPCo这种模型只

适用于Activity和Service间的通信,之所以需要这种稍显重量的模式,是因为Activity除了发送

intent去启动一个service外,可能还需要能够在Service的运行过程中连接到service,对Service

发送一些控制请求。例如音乐播放程序,其后台的播放服务往往独立运行,以方便我们在使用其

他程序界面时也能听到音乐。同时这个后台播放服务也会定义一个控制接口,包含比如播放,暂

停,快进之类的方法,任何时候播放程序的界面都可以通过使用bindSer/iceAPI连接到播放服务,

获取这个接口的包含IPC细节的实现代理,通过这组控制接口方法对其进行控制,这时这种IPC

的方案就显的更方便更直观一些了。有关使用AIDL这种IPC的更详细描述,Gooqle的官方文档

已做了详细的讲解。

apkAPK是AndroidPackage的缩写,即Android安装包(anapk)。APK是类似SymbianSis或Sisx

的文件格式。通过将APK文件直接传到Android模拟器或Android手机中执行即可安装。apk

文件和sis一样最终把androidsdk编译的工程打包成一个安装程序文件格式为apk。APK文件其

实是zip格式,但后缀名被修改为apk,通过UnZip解压后,可以看到Dex文件,Dex是DalvikVM

executes的全称,即AndroidDalvik执行程序,并非JavaME的字节码而是Dalvik字节码。一个

APK文件结构为:META-INF\Jar文件中常可以看到res\存放资源文件的目录

AndroidManifest.xml程序全局配置文件classes.dexDalvik字节码resources.arse编译后的二进制

资源文件总结下我们发现Android在运行一个程序时首先需要UnZip,然后类似Symbian那样直

接,和WindowsMobile中的PE文件有区别,这样做对于程序的保密性和可靠性不是很高,通过

dexdump命令可以反编译,但这样做符合发展规律,微软的WindowsGadgets或者说WPF也采

用了这种构架方式。在Android平台中dalvikvm的执行文件被打包为叩k格式,最终运行时加载

器会解压然后获取编译后的andioidmanifest.xml文件中的permission分支相关的安全访问,但仍

然存在很多安全限制,如果你将apk文件传到/system/app文件夹下会发现执行是不受限制的。最

终我们平时安装的文件可能不是这个文件夹,而在androidrom中系统的apk文件默认会放入这个

文件夹,它们拥有着root权限。

【三】组件入门

组件(Component),在谈及所谓架构和重用的时候,是一个重要的事情。很多时候都会说基

于组件的软件架构,指的是期望把程序做乐高似的,有一堆接口标准封装完整的组件放在哪里,

想用的时候取上几个一搭配,整个程序就构建完成了。

在开篇的时候就在说,Android是一个为组件化而搭建的平台,它引入所谓Mash-Up的概念,

这使得你在应用的最上层,想做的不组件化都是很困难的一件事情(底层逻辑,好吧,管不了…)。

具体说来,Android有四大组件四喜丸子;Activity>ServiceBroadcastReceiver^Content

Provider,

Activity

做一个完整的Android程序,不想用到Activity,真的是比较困难的一件事情,除非是想做绿叶

想疯了。因为Activity是Android程序与用户交互的窗口,在我看来,从这个层面的视角来看,

Android的Activity特像网站的页面。

首先,一个网站,如果一张页面都没有,那…,真是一颗奇葩。而一张页面往往都有个独立的主

题和功能点,比如登录页面,注册页面,管理页面,如是。

在每个页面里面,会放一些链接,已实现功能点的串联,有的链接点了,刷,跑到同一站点的另

一个页面去了;有的链接点了,啾,可能跳到其他网站的页面去;还有的链接点了,恩…,这次

没跑,但当前页面的样子可能有所变化了。这些模式,和Activity给人的感觉很像,只不过实现

策略不同罢了,毕竟Android这套架构的核心思想,本身就来自源于Web的Mash-Up概念,

视为页面的客户端化,也未尝不可。

Activity,在四大组件中,无疑是最复杂的,这年头,一样东西和界面挂上了勾,都简化不了,

想一想,独立做一个应用有多少时间沦落在了界面上,就能琢磨清楚了。从视觉效果来看,一个

Activity占据当前的窗口,响应所有窗口事件,具备有控件,菜单等界面元素。从内部逻辑来看,

Activity需要为了保持各个界面状态,需要做很多持久化的事情,还需要妥善管理生命周期,和

一些转跳逻辑。对于开发者而言,就需要派生一个Activity的子类,然后埋头苦干上述事情。对

于Activity的更多细节,先可以参见:refe「ence/android/app/Activity.html。后续,会献上

更为详尽的剖析。

Service

服务,从最直白的视角来看,就是剥离了界面的Activity,它们在很多Android的概念方面比较

接近,都是封装有一个完整的功能逻辑实现,只不过Service不抛头露脸,只是默默无声的做坚

实的后盾。

但其实,换个角度来看,Android中的服务,和我们通常说的Windows服务,Web的后台服

务又有一些相近,它们通常都是后台长时间运行,接受上层指令,完成相关事务的模块。用运行

模式来看,Activity是跳,从•个跳到•个,呃…,这有点像模态对话框(或者还像web页面

好了…),给一个输入(抑或没有…),然后不管不顾的让它运行,离开时返呵I输出(同抑或没

有…)。

而Service不是,它是等,等着上层连接上它,然后产生一段持久而缠绵的通信,这就像一个用

了Ajax页面,看着没啥变化,偷偷摸摸的和Service不知眉来眼去多少回了。

但和一般的Service还是有所不同,Android的Service和所有四大组件一样,其进程模型都是

可以配置的,调用方和发布方都可以有权利来选择是把这个组件运行在同一个进程下,还是不同

的进程下。这句话,可以拿把指甲刀刻进脑海中去,它凸显了Android的运行特征。如果一个

Service,是有期望运行在于调用方不同进程的时候,就需要利用Android提供的RPC机制,

为其部署•套进程间通信的策略。

Android的RPC实现,如上图所示(好吧,也是从SDK中拿来主义的…),无甚稀奇,基于代

理模式的一个实现,在调用端和服务端都去生成一个代理类,做一些序列化和反序列化的事情,

使得调用端和服务器端都可以像调用一个本地接口一样使用RPC接口。

Android中用来做数据序列化的类是Parcel,参见:/「eference/android/os/Pa「cel.html,

封装了序列化的细节,向外提供了足够对象化的访问接口,Android号称实现非常高效。

还有就是AIDL(AndroidInterfaceDefinitionLanguage),一种接口定义的语言,服务的

RPC接口,可以用AIDL来描述,这样,ADT就可以帮助你自动生成一整套的代理模式需要用到

的类,都是想起来很乏力写起来很苦力的那种。更多内容,可以再看看:

guide/developing/tools/aidl.html,如果有兴致,可以找些其他PRC实现的资料lou几眼。

关于Service的实现,还强推参看APIDemos这个Sample里面的RemoteService实现。

它完整的展示了实现一个Service需要做的事情:那就是定义好需要接受的Intent,提供同步

或异步的接口,在上层绑定了它后,通过这些接口(很多时候都是RPC的…)进行通信。在RPC

接口中使用的数据、回调接口对象,如果不是标准的系统实现(系统可序列化的),则需要自定

义aidl,所有一切,在这个Sample里都有表达,强荐。

Service从实现角度看,最特别的就是这些RPC的实现了,其他内容,都会接近于Activity的

一些实现,也许不再会详述了。

BroadcastReceiver

在实际应用中,我们常需要等,等待系统抑或其他应用发出一道指令,为自己的应用擦亮明灯指

明方向。而这种等待,在很多的平台上,都会需要付出不小的代价。

比如,在Symbian中,你要等待一个来电消息,显示归属地之类的,必须让自己的应用忍辱负

重偷偷摸摸的开机启动,消隐图标陷藏任务项,潜伏在后台,监控着相关事件,等待转瞬即逝的

出手机会。这是一件很发指的事情,不但白白耗费了系统资源,还留了个流氓软件的骂名,这真

是卖力不讨好的正面典型。

在Android中,充分考虑了广泛的这类需求,于是就有了BroadcastReceive「这样的一个组件。

每个BroadcastReceiver都可以接收一种或若干种Intent作为触发事件(有不知道Intent的

么,后面会知道了…),当发生这样事件的时候,系统会负责唤醒或传递消息到该Broadcast

Receiver,任其处置。在此之前和这以后,BroadcastReceive「是否在运行都变得不重要了,

及其绿色环保。

这个实现机制,显然是基于一种注册方式的,BroadcastReceiver将其特征描述并注册在系统

中,根据注册时机,可以分为两类,被我冠名为冷热插拔。所谓冷插拔,就是BroadcastReceiver

的相关信息写在配置文件中(求配置文件详情?稍安,后续奉上…),系统会负责在相关事件发

生的时候及时通知到该BroadcastReceiver,这种模式适合于这样的场景。某事件方式->通

知Broadcast,启动相关处理应用。比如,监听来电、邮件、短信之类的,都隶属于这种模式。

而热插拔,顾名思义,插拔这样的事情,都是由应用自己来处理的,通常是在OnResume事件

中通过registerReceiver进行注册,在OnPause等事件中反注册,通过这种方式使其能够在

运行期间保持对相关事件的关注。比如,一款优秀的词典软件(比如,有道词典…),可能会有

在运行期间关注网络状况变化的需求,使其可以在有廉价网络的时候优先使用网络查询词汇,在

其他情况下,首先通过本地词库来查词,从而兼顾腰包和体验,一举两得一石二鸟一箭双雕(注,

真实在有道词典中有这样的能力,但不是通过BroadcastReceive「实现的,仅以为例…)。而

这样的监听,只需要在其工作状态下保持就好,不运行的时候,管你是天大的网路变化,与我何

干。其模式可以归结为:启动应用->监听事件・>发生时进行处理。

除了接受消息的一方有多种模式,发送者也有很重要的选择权。通常,发送这有两类,一个就是

系统本身,我们称之为系统Broadcast消息,在refe「ence/android/content/Intent.html

StandardBroadcastActions,可以求到相关消息的详情。除了系统,自定义的应用可以放

出Broadcast消息,通过的接口可以是Context.sendBroadcast,抑或是

Context.sendOrderedBroadcasto前者发出的称为Normalbroadcast,所有关注该消息的

Receiver,都有机会获得并进行处理;后者放出的称作Orderedbroadcasts,顾名思义,接受

者需要按资排辈,排在后面的只能吃前面吃剩下的,前面的心情不好私吞了,后面的只能喝西北

风了。

当BroadcastReceiver接收至U相关的消息,它们通常做一些简单的处理,然后转化称为一条

Notification,一次振铃,一次震动,抑或是启动一个Activity进行进一步的交互和处理。所以,

虽然Broadcast整个逻辑不复杂,却是足够有用和好用,它统一了Android的事件广播模型,

让很多平台都相形见细了。更多BroadcastReceive「相关内容,参见:

/reference/android/content/BroadcastReceiver.htmlo

ContentProvider

ContentProvider,听着就和数据相关,没错,这就是Android提供的第三方应用数据的访问

方案。在Android中,对数据的保户是很严密的,除了放在SD卡中论数据,一个应用所持有的

数据库、文件、等等内容,都是不允许其他直接访问的,但有时候,沟通是必要的,不仅对第三

方很重要,对应用自己也很重要。

比如,一个联系人管理的应用。如果不允许第三方的应用对其联系人数据库进行增删该查,整个

应用就失去了可扩展力,必将被其他应用抛弃,然后另立门户,自个玩自个的去了。

Andorid当然不会真的把每个应用都做成一座孤岛,它为所有应用都准备了一扇窗,这就是

ContentProvidero应用想对外提供的数据,可以通过派生Contentprovider类,封装成一枚

ContentProvider,每个ContentProvider都用一个uri作为独立的标识,形如:

content://com.xxxxxo所有东西看着像REST的样子,但实际上,它比REST更为灵活。和

REST类似,uri也可以有两种类型,一种是带id的,另一种是列表的,但实现者不需要按照这

个模式来做,给你id的uri你也可以返回列表类型的数据,只要调用者明白,就无妨,不用苛求

所谓的RESTo

另外,ContentProvider不和REST一样只有uri可用,还可以接受Projection,Selection,

OrderBy等参数,这样,就可以像数据库那样进行投影,选择和排序。查询到的结果,以Cursor

(参见:reference/and「oid/database/Cu「sor.html)的形式进行返回,调用者可以移动

Cursor来访问各列的数据。

ContentProvider屏蔽了内部数据的存储细节,向外提供了上述统一的接口模型,这样的抽象

层次,大大简化了上层应用的书写,也对数据的整合提供了更方便的途径。ContentProvider

内部,常用数据库来实现,Android提供了强大的Sqlite支持,但很多时候,你也可以封装文件

或其他混合的数据。

在Android中,ContentResolvei■是用来发起ContentProvider的定位和访问的。不过它仅

提供了同步访问的ContentProvide「的接口。但通常,ContentProvider需要访问的可能是

数据库等大数据源,效率上不足够快,会导致调用线程的拥塞。因此Android提供了一个

AsyncQueryHandler(参见:reference/android/content/AsyncQueryHandler.html),

帮助进行异步访问ContentProvidero

在各大组件中,Service和ContentProvider都是那种需要持续访问的。Service如果是一个

耗时的场景,往往会提供异步访问的接口,而ContentProvider不论效率如何,都提供的是约

定的同步访问接口。我想这遵循的就是场景导向设计的原则,因为ContentProvider仅是提供

数据访问的,它不能确信具体的使用场景如何,会怎样使用它的数据;而相比之下,Service包

含的逻辑更史杂更完整,可以抉择大部分时候使用某接口的场景,从而确定最贴切的接口是同步

还是异步,简化了上层调用的逻辑。

配置

四大组件说完了,四大组件幕后的英雄也该出场了,那就是每个应用都会有一份的配置文件,名

称是AndroidManifest.xml,在工程的根目录下。在这个配置文件中,不仅会描述一些应用相

关的信息,很重要的,会包含一个应用中所有组件的信息。如果你派生Activity或者Service实

现了一个相关的类,这只是把它组件化的第一步,你需要把这个类的相关信息写到配置文件中,

它才会作为一个组件被应用到,否则只能默默无闻的黯淡度过余生。

摆了一幅图出来,这次不是偷来的,是敝帚自珍原创,所以没有意外的画的很丑,但基本还是可

以体现出一些意思。在InOthers的部分,这里是一般平台应用之间通信和交互的模型,每个应

用都有很强烈的应用边界(往往表现为进程边界…),Appl的还是App2的,分得很是清楚。

每个应用内部,都有自己的逻辑去切分功能组件,这样的切分通常没有什么标准,率性而为。应

用间的交互逻辑也比较零散,Appl与App2交互,往往需要明确知道对方应用的具体信息,

比如进程ID,进程名称之类的,这样使得应用和应用之间的联系,变得很生硬。而上层应用和系

统应用的通信,往往有很多特定的模式,这种模式,很可能是无法直接应用在普通应用之间的,

换而言之,系统应用是有一定特殊性的。

重点,在图的下半部,描述的是Android的应用情形。在Android中,应用的边界,在组件这

个层面,是极度模糊,什么进程、什么应用,都可以不必感知到。举个例子,App1,实现了A

和B两个组件,App2,实现了C这个组件。A和C,都想使用B这个组件,那么它们的使用方

式是完全一致的,都需要通过系统核心的组件识别和通信机制,找到和使用组件B。A,虽说和B

是一个娘胎里蹦出来的,很不好意思,没有任何特殊的后面和捷径,还是要跑规矩的途径才能用

到,一片和谐社会的景象油然而生。

在Android中,所有组件的识别和消息传递逻辑都必须依赖底层核心来进行(通信可以没有底层

核心的参与,比如一旦Service找到了,就可以和它产生持久的通信…),没有底层核心的牵线

搭桥,任何两个组件都无法产生联系。比如一个Activity,跳到另一个Activity,必须要向底层

核心发起•个Intent,有底层解析并认可后,会找到另•个Activity,把相关消息和数据传给它。

一个Activity想使用ContentProvider中的数据,必须通过底层核心解析相关的uri,定位到

S4"ContentProvider,把参数传递给它,然后返回Activity需要的Cursor。这样的设计,保

证了底层核心对所有组件的绝对掌控权和认知权,使得搭积木似的开发变成可能。

为了,使得核心系统能够完整的掌握每个组件的信息,这就需要配置文件了。配置文件,就是将

组件插到底层核心上的这个插头。只有通过这个插头插在底层核心的插座上(不要乱想,非十八

禁…),组件才能够发光发热,闪耀光芒。

组件的配置信息在我看来主要包含两个方面,一部分是描述如何认知。比如,Activity.Service.

BroadcastReceiver都会有名字信息,和希望能够把握的Intent信息(姑且看成消息好了…),

ContentProvide「会有一个描述其身份的uri。当其他组件通过这样的名字或者Intent,就可

以找到它。

另一部分是运行相关的信息。这个组件,期望怎么来运行,放在单独的进程,还是和调用者一个

进程,还是找相关的其他组件挤在同一个进程里面,这些内容,都可以在配置的时候来决定(调

用者在这个约束范围内,有进一步的选择权…)。更多配置项,请参见:

guide/topics/manifest/manifest-intro.htmL

通过前续内容,也许可以帮助大家对Android组件有个初略的了解。但这些了解都还停留在静态

层面,程序是个动态的概念,关于各个组件具体是怎么联系在一起的,如何手拉手运行起来完成

一项功能的,这便是后话了。

【四】一一组件调用

Intent解析

基于组件的架构体系,除了有定义良好的组件,如何把这些组件组装在一起,也是一门艺术。在

Android中,Intent(貌似通常译作:意图…),就是连接各组件的桥梁。

前段时间看同事们做Symbian平台的网易掌上邮(真的是做的用心,NB的一米,热情欢迎所

有163邮箱的S60V3用户,猛点击之…),有个功能是为邮件添加附件,比如你想要通过邮件

发送一副图片泡mm,可能需要有个很直观的方式从本地选一副珍藏美图,抑或是拿相机来个完

美自拍。在Symbian中,这样的功能,都需要你用底层的API,自己一点点写。为了让选图片

体验更好,可能需要做一个类似于图片浏览器之类的东西,为了把拍照做的更为顺畅,甚至需要

实现从聚焦到调节亮度之类一整套的相机功能.

而其实呢,用户的手机中可能本身就装了其他的专业图片浏览器、相机等应用,这些应用已经非

常出色好用,而用户也已然能很纯属使用它们,如果能进行调用,对邮箱的开发者和用户而言,

都会是个更好的选择。但在Symbian这样残败的系统里,应用和应用之间的结合能力奇弱无比,

想复用,基本比登天还难,作为开发者,只能忍住一次又一次的恶心,为了用户,做这些重复造

轮子吃力不讨好的附加工作。

还好还好,在Android中,一切变得美好多了,它将开发者从接口和对象的细节中解救出来,让

我们有更多精力投入到核心功能的开发中去。在Android中,如果你需要选个图拍个片,只需要

构造一个描述你此项意愿的Intent,发送出去,系统会帮你选择一个能够处理该项业务的组件来

满足你的需求,而不再需要纠结在具体的接口和实现上,PerfectWorld,便应如此。

Intent构成

Intent被译作意图,其实还是很能传神的,Intent期望做到的,就是把实现者和调用者完全解

耦,调用者专心将以意图描述清晰,发送出去,就可以梦想成真,达到目的。

当然,这么说太虚了,庖丁解牛,什么东西切开来看看,乜许就清晰了。Intent

(reference/android/content/Intent.html),在Android中表现成一个类,发起一个意图,

你需要构造这样一个对象,并为下列几项中的一些进行赋值:

Actiono当日常生活中,描述一个意愿或愿望的时候,总是有一个动词在其中。比如:我想做三

个俯卧撑;我要看一部x片;我要写一部血泪史,之类云云。在Intent中,Action就是描述看、

做、写等动作的,当你指明了一个Action,执行者就会依照这个动作的指示,接受相关输入,表

现对应行为,产生符合的输出。在Intent类中,定义了一批量的动作,比如ACTION_VIEW,

ACTION_PICK,之类的,基本涵盖了常用动作,整一个降龙十八掌全集。当然,你也可以与时

俱进,创造新的动作,比如lou这样的。与系统预定义的相比,这些自定义动作的流通范围很是

有限,除非做了非常NB的应用,大家都需要follow你,否则通常都是应用内部流通。

Data。当然,光有动作还是不够的,还需要有更确切的对象信息。比如,同样是泡这个动作,但

泡咖啡,和泡妞,就差之千里了。Data的描述,在Android中,表现成为一个URI。用在内部

通信中,可能描述是ContentProvider用的形如content:〃xxxx这样的东东,抑或是外部的

一个形如tel:〃xxxx这样的链接。总而言之,是能够清楚准确的描述一个数据地址的uri。

Type.说了Data,就必须要提Type,很多时候,会有人误解,觉着Data和Type的差别,就

犹如泡妞和泡马子之间的差别一样,微乎其微。但其实不然,Type信息,是用MIME来表示的,

比如text/plain,这样的东西。说到这里,两者差别就很清晰了,Data就是门牌号,指明了具

体的位置,具体问题具体分析,而type,则是强调物以类聚,解决一批量的问题。实际的例子是

这样的,比如,从某个应用拨打一个电话,会发起的是action为ACTION_DIAL且data为

tel:xxx这样的In

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论