应用开发基础
一. 概述
一个应用程序由一个或多个应用组件组成,这些基本组件包括 activities, services, content provides 和 broadcast receivers 。每个组件在整个应用程序中都扮演不同的角色,而且每种组件都可以单独地运行,甚至可以通过别的应用来启动它们。必须要在 manifest 文件中声明应用中的所有组件,同时还要声明应用的所有要求,如最小版本及硬件要求等。对于非代码型的资源,如图片、strings、layout 文件等,要有选择性地包含在不同的设备配置中。
本章将简要地介绍以下内容
Application Components
Activating components
The Manifest File
Declaring components
Declaring application requirements
Application Resources
一般地,android 的应用是用 Java 写的,Android 的 SDK tools 会独立地(不包括数据和资源文件)把这些程序编译到一个 Android package中,这个
package 是以 .apk 为后缀的存档文件。所有在一个 .apk 文件中的代码构成一个应用程序,Android 会通过这个 .apk 来安装相应的应用程序。
设备上所安装的每个具体应用程序都有自己的安全的沙箱:
(1)Android 系统是一个多用户的 Linux 系统,每个不同的应用都被当成不同的用户。
(2)默认条件下,系统会为每个应用分配一个唯一的 Linux 用户 ID (只有系统才能使用这个 ID ,而且应用程序本身也知道这个 ID)。系统会为应用设置所有相关文件的权限,以使得只有相应 ID 的应用可以访问这些文件。
(3)每个进程都有自己的虚拟机(VM),所以不同的应用程序的代码会运行在各自的虚拟机里,彼此不相影响。
(4)默认情况下,每个应用程序都只执行自己的 Linux 进程。当应用程序的任一组件需要执行时,Android 系统就会启动相应的进程。当这个应用的所有组件都不需要再执行或者系统要回收其内存资源里,该应用程序就会被终止运行。
通过上面这些机制,Android 系统实现了最小特权机制。即在默认情况下,在每个应用程序中,只能访问相应组件执行时所允许访问的东西。这样就创建了一个非常安全的环境,应用程序在这样的环境里执行,就不能访问到未被授权的系统资源(文件等)。
当然,也有一些途径使得一个应用程序可以把一些数据共享给别的应用程序,也有方法让一个应用程序可以访问系统的服务:
(1)可以给两个不同的应用程序分配相同的 Linux 用户 ID ,这样它们就可以访问对方的文件。为了节省系统资源,具有相同 ID 的应用程序可以在同一个 Linux 进程中执行,并共享同一个 VM (当然这还得要求应用程序必须拥有相同的 certificate 才行)。
(2)一个应用程序可以申请访问用户设备上的数据的权限,如申请访问用户的 contacts 、SMS 信息等。应用程序所具的的权限必须都在安装应用的时候分配。
关于一个Android application 如何存在于一个系统中,本章接下来的内容将给你简要的介绍一下:
(1)应用程序中所用到的 framework 核心组件。
(2)manifest 文件,你要在这个文件中指明你的应用程序中的所有组件,及你的应用对设备有怎样的要求。
(3)独立于应用程序代码的资源,你的应用可以根据不同的设备配置,选择使用不同的资源。
二. 应用程序组件
应用程序组件是构建一个 Android 应用最重要的东西,每种组件都是系统进入你的应用的一个入口点,但并不是所有的组件都是用户进入应用的入口点。而每种组件都是一种独立的实体,它们扮演着不同的具体角色,每种组件都是唯一的,都可用于组建你的应用程序,用于定义你整个应用的行为。
有四种类型的组件,每种组件都有不同的作用,它们都有各自不同的生命周期,下面来简要介绍一下这四种组件:
1. Activites
一个 activity 代表一个具有用户接口的单独界面。如一个 email 应用程序也许会用一个 activity 来显示 emails 列表,用另外一个 activity 来编辑 email ,用另外一个 activity 来读取 emails 。虽然这些 activites 能够紧密的协作以提供良好的用户体验,但是它们都是独立的,彼此没有什么直接的联系。在一个应用程序允许的情况下,别的应用程序可以启动这个应用程序的任一 activities 。如一个 camera 应用可以启动 email 应用中的 activity ,以让用户实现把图片分享到网络的功能。
2. Services
service 是一个用于在后台执行耗时操作或执行远程调用的组件,
service 是不提供用户界面的。如当一个
service 在后台播放音乐时,用户可能在使用别的应用程序,或者正在下载一些东西。别的组件如 activity 可以启动一个 service 以让它运行或者与它绑定到一起,以便于与它交互。
3. Content providers
content provider 用于管理应用程序用于共享的数据集,你可以把你的数据存储在文件系统中、SQLite 数据库中、web 上或别的你的应用可以访问到的地方。通过 content provider ,别的应用可以查询指定应用的相关数据,在该应用允许的情况下,别的应用还可以修改这些数据。如 Android 系统提供了一个 content provider ,用于管理用户的联系人信息。这样,拥有相应权限的应用程序就可以查询这个 content provider 的部分信息,就可以读取指定联系人的相关信息。
content provider 也可以用来读写应用私有的数据,一个应用程序是无法访问别的应用私有的数据的。自定义 的 content provider 必须是 ContentProvider 类的子类,而且必须实现标准的 APIs ,这样应用程序才能够实现相关的操作。
4. Broadcast receivers
broadcast receiver 是负责系统广播的一种组件,许多广播来源于系统,如屏幕被关闭的广播、电源电量过低的广播等。应用程序也可以发出广播,如在下载完成后,为了让别的应用程序或组件知道这件事情,以做相应的操作,此里可以通过发出广播消息来实现。虽然广播不会有用户界面,但是当有广播事件发生里,它们可以通过在 status bar 上显示通知的方式来提醒用户该事件的发生。通常情况下,一个
broadcast receiver 只是别的组件的一个 "gateway" ,它只是用于做很少的工作。如,它可能仅仅用于初始化一个 service 用以执行该事件相应的工作。每个广播都会被当成一个 Intent 进行传递。
Android 系统的一个独特之处是一个应用程序可以启动别的应用程序的 component 。例如,如果你想让你的应用程序能够通过设备的 camera 实现拍照功能,那么你可以调用现成的已经实现这种功能的 application 来做,而不用从头自己开发这样的一个 activity 。而且你不需要包含甚至不用链接到能够拍照的 application 的代码,你只需要启动 camera app 的 activity 就可以了。当拍照完成后,可以把相应照片返回给你的 app 。对用户来说,好像 camera 就是你的 app 的一部分一样。
当系统启动一个 component 的时候 ,系统就会为应用程序启动相应的进程(如果这个应用不处于 running 状态)并实例化这个组件所需要的类。例如,如果你的 app 启动了 camera app 来拍照,那么拍照的 activity 是运行在属于 camera app 的进程中的,而不是运行在你的 app 进程中。因此,Android 的 app 往往不仅仅只有一个入口点(和那些单入口点的系统不同,在 Android app 中,你是看不到 main() 方法的)。
因为系统中的每个 app 都运行在相互隔离的进程中,而且对相关文件的访问都有严格的权限控制,所以在一个 app 中,你是没法直接激活别的 app 的 component 的。但是在 Android 系统却可以,当你想要激活别的 app 中的 component 时,你需要给系统传递一个 message 中,在这个 message 指明你想要激活的特定 app 的哪个 component,这样系统就能为你启动这个 component 。
5. Activating Components
四大组件中的三个:activities、services 和 broadcast receivers 是由一个异步的 intent message 来激活的。一个 Intent 可以在运行时绑定一个特定的 component ,而不管这个 component 是属于你的 app ,还是属于别的 app (你可以把这样的 intent 当成信使,这个信使的任务是向别的 components 请求某种操作)。
intent 是通过 Intent 类实例化得到的对象,用于指定要激活的具体 component 或者激活某种特定类型的 component ,这可分别由显示(explicit)的 intent 和隐式(implicit)的 inent 来实现。
对于 activities 和 services 来说,一个 inent 定义了一个要执行的具体的动作(action)(如查看或发送一些东西),而且还可以指明具体数据的 URI 。如一个发送给某个 activity 的 intent 可能搭载着一个打开某张图片或打开某个网页的请求,当 activity 在接到这个 intent 的时候,就能响应它的请求(根据它指定的 URI 打开图片或网页) 。在某些情况下,你可以让某个 activity 给你返回一个 intent (如你想要查询某个联系人的信息,那么你可以让相关的 activity 通过一个 inent 把这些信息返回给你,在这个 intent 中包含相应联系人信息的 URI)。
对 broadcast receivers 来说,intent 只是简单的定义了要广播的应消息,如当设备电量过低时发出电量过低的广播,在这个广播中只包含 "battery is low" 这样一个简单的字符串。
另外一种 component ,即 content provider ,不是通过 intent 来激活的。当 ContentResolver 请求一个具体的 content provider 里,它才会被激活。实际上,content resolver 负责处理所有直接与 content provider 的交互工作,所以要与 content provider 进行交互的 component 只需要调用 ContentResolver 对象的相应方法就可以了。所以 ContentResolver 就相当于 content provider 和 component 之间的一个中介。
下面是激活每种 component 的方法
(1)Activity:可以通过给 startActivity() 传递 intent 来启动一个特定的 activity (在 intent 中指明即可),如果你想让被启动的 activity 返回一些结给你,那么你可以通过调用 startActivityForResult() 来实现。
(2)Services:可以通过给 startService() 传递一个 intent 来启动一个特定的 service ,你也可以通过发送一个 intent 给 bindService() 方法来绑定一个特定的 service 。
(3)Broadcast receivers:可以通过给 sendBroadcast()、sendOrderedBroadcast() 或 sendStickyBroadcast() 传递一个 intent 来创建一个 broadcast。
(4)Content providers:可以通过 ContentResolver 调用 query() 方法来查询一个特定的 content provider。
三. Manifest 文件
在 Android 系统启动一个 app 里,系统会先通过该 app 的 AndroidManifest.xml 文件来查询该 app 中所包含的 components ,所以你必须在该文件中声明你的 app 中所有的 components,这个文件必须放在 app 工程的根目录下。
除了声明所有组件外,manifest 还负责了些额外的事情,如:
(1)读取你 app 的所有用户权限申请,如 Internet 访问权限、联系人信息的读取权限等。
(2)声明 app 要求的最小 API Level,app 运行在哪个 Level 上的 APIs 。
(3)声明 app 所要求使用的软件和硬件特性,如 camera、bluetooth services 或一个多点触控的屏幕等。
(4)除了 Android framework 的 APIs 外,app 需要链接的 API libraries 。
1. 声明组件(components)
manifest 的主要任务是告知系统 app 中会用到的组件,如你可以如下声明一个 activity:
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application android:icon="@drawable/app_icon.png" ... >
<activity android:name="com.example.project.ExampleActivity"
android:label="@string/example_label" ... >
</activity>
...
</application>
</manifest>
在 <application> 元素中,android:icon 属性指明了 app 要用到的 icon 资源的位置。
在 <activity> 元素中,android:name 属性指明了该 Activity 类的全类名,而 android:label 属性指明了一个用户可见的代表该 activity 的字符串。
声明所有 app 中用到的四大组件的方式,如下:
<activity> 用于声明 activities
<service> 用于声明 services
<receiver> 用于声明 broadcast receivers
<provider> 用于声明 content providers
没有在 manifest 声明的 Activities、services、content providers ,系统是看不到它们的,而且它们也不可能运行。不同的是,broadcast receivers 既可以在 manifest 中声明,也可以在代码中动态地创建(即可以在代码中动态地创建一个 BroadcastReceiver 对象),然后通过 registerReceiver() 把它注册到系统中。
2. 声明component 的功能(capabilities)
正如上面所讨论的一样,你可以通过一个 Intent 来启动一个 activities、services 和 broadcast receivers 。你可以在 intent 中显示地指定你要激活的目标 intent ,但是你也可以通过隐式的 intent 来启动一个 component 。隐式 intent 是通过 intent actions 来实现的,intent actions 才是 intent 的真正强大之处。通过 intent actions ,你只要简单地描述你要执行哪种 action (还可以选择该 action 所需要的 data),并允许系统在设备上查找一个适当的、能够执行这个 action 的 component 并启动它。如果系统中有多个可以完成该 intent 中所描述的 action ,那么用户可以选择其中任一个来执行。
系统判定一个 component 是否能够执行一个 action 的方式是通过将它所收到的 intent 和系统中各 app 的 manifest 中所声明的 intent filters 进行比较来实现的。
当你在 manifest 中声明一个 component 时,你可以为它添加一个 intent filters ,在这个 intent filters 中声明该 component 所具有的功能,这样它就可以响应来自其它应用的 intents 。可以通过给 component 添加 <intent-filter> 子元素来声明该 component 的 intent filters 。
例如,一个 email app 中有一个负责写新 email 的 activity ,并且该 app 的 manifest 中声明了该 activity 的 intent filters ,声明它可以响应 "send”类型的 action (即发送一个 email)。那么你在另一个 app 中就"可以创建一个 intent ,指明它要执行 "send" 操作(ACTION_SEND),这个 action 与 email app 中具有 "send" 的 activity 是匹配的。则当你通过 startActivity() 来调用这个 intent 里,系统就能会启动那个写 email 的 activity 。
3. 声明 app 的要求
Android 设备的各类繁多,而且它们的特性和性能不尽相同。为了防止你的 app 安装到性能不足以安装该 app 的设备上,你必须在 manifest 中声明你的 app 所需要的硬件和软件特性。虽然系统不会去检查所有的这些声明,但是其它外部的服务如 Google Play 会读取这些声明,当用户要为他们的设备查找可有的 app 里,就用这些信息来过滤掉一些 app 。
例如,如果你的 app 要求设备有 camera 且运行的是 Android 2.1 的 APIs ,那么你就应该在应用的 manifest 中声明这些信息。这样,没有有 camera 的设备或运行环境低于 Android 2.1 的设备就不能从 Google Play 上安装这个 app 。
但是你也可以声明你的 app 会用到 camera ,但不是必须要用到。在这种情况下,你的 app 就要在运行里检查设备是否有 camera ,以决定是否启用相关的功能。
下面是一些重要的、应该给予重要的设备要求特征
(1)屏幕尺寸和分辨率:
为了给设备的屏幕类型进行分类,Android 定义了两种识别方式:screen size (即屏幕的尺寸)、screen desity (即屏幕密度,屏幕的物理像素密度 pixels、dpi(dpts per inch))。为了简化所有不同的屏幕配置,Android 系统把它们进行了归类:
screen size:small、normal、large、extra large
screen densities:low density、medium density、hight density、extra high density
默认情况下,app 可以适配到所有的屏幕大小和密度,因为 Android 系统可以调整你的 UI 布局和你的图片资源。但是为了良好的用户体验,你应该为不同的屏幕提供不同的布局和图片。使用可选择的 layout 资源,你可以在 manifest 中通过 <supports-screens> 来声明你的 app 支持哪种屏幕尺寸。
(2)Input configurations
许多设备提供了不同的用户输入方式,如通过硬件键盘、一个 trackball 或一个五向导航板等。如果你的应用需要特别的输入方式,那么你应该在你的 manifest 中通过 <uses-configuration> 元素来声明。但是通常这是没有必要的,很少需要这么做。
(3)Device features
在每个具体的 Android 设备上,可以存在一些硬件或软件特性,如 camera、light sensor 或 bluetooth 或一个特定版本的 OpenGL 或一个保真性很好的触摸屏。不要以为某种特性是所有的 Android 设备都支持的(当然除了标准的 Android 库的东西之外),所以你要通过 <uses-feature> 元素来声明 app 中所需要的特性。
(4)Platform Version
不同的 Android 设备可运行在不同版本的 Android 平台上,如 Android 1.6 或 Android 2.3 。对于每两个连续的 Android 版本,高版本的 platform 会包含一些低版本的 platform 中没有的 APIs,即在低版本中,这些 APIs 是不可用的。所以你需要通过 <uses-sdk> 元素来声明你的 app 所支持的最代版本的 APIs 。
指明 app 的这些特性是非常重要的,因为当你在 Google Play 上发布你的 app 的时候,Google Play 会根据这些信息来为不同的设备过滤掉一些 app ,所以只有当一个设备的特性满足你的 app 的特性需求时,这个设备才能安装你的 app 。
四. Applicaiton Resources
一个 Android app 不仅仅由代码组成,它还包括与代码分离的资源,如图片、音频及任何与 app 的视觉层面相关的东西(如动画、菜单、颜色、styles 及用于用户界面的 xml 布局文件)。
对于你的 Android 工程中所包含的所有资源,SDK build tools 都会为它们定义一个整型的 ID,通过这个 ID ,你可以在代码或 XML 定义中引用到这些资源。如,你的工程的 res/drawable/ 目录下有一个名为 logo.png 的图片,那么SDK tools 就为给这个图片生成一个名为 R.drawable.logo 的 ID,那么在你的程序中或 XML 中就可以通过这个 ID 来引用到这个图片了。
把资源和代码分离的一个主要好处是可以根据不同的设备配置,选择使用不同的资源。如通过在 XML 中定义不同的 UI string ,你可以根据需要把这些 strings 翻译成不同的语言,并把不同语言的 strings 放到不同的文件夹中。然后你就可以通过用户设置来选择给你的 UI 使用不同的语言。
For more about the different kinds of resources you can include in your application and how to create alternative resources for various device configurations, see the Application Resources
developer guide.