快读
- Android 应用程序由一个或多个程序组件构成(activitie, service, content provider, 和 broadcast receiver)
- 而每个组件呢,都在程序的所有行为中扮演不同的角色,并且它们都能单独被激活(甚至被其他的程序激活)。
- 配置文件必须对每一个程序中用到的组件进行声明。而且也要声明程序的各种需求配置,例如最小的Android版本支持和硬件配置支持。
- 非代码资源文件 (图片, 字符串, 布局文件, 等。) 应该对不同配置的设备进行选择性支持。 (例如不同的字符串对应不同的语言,不同的布局对应不同的屏幕尺寸。)
本文内容
Android 使用Java语言开发。Android SDK 工具编译代码—以及任意数据并连同相关资源打包进一个Android 包内,它是一个以.apk
为后缀的压缩文件。 一个 .apk
文件中的
所有代码就是一个程序。这个.apk文件就用于在Android设备上安装这个程序。
一旦安装成功,这个Android程序就拥有了自己独立的运行沙盒(沙盒是在受限的安全环境中运行应用程序的一种做法,这种做法是要限制授予应用程序的代码访问权限。):
- Android操作系统是一个多用户的Linux系统,其中的每一个应用程序都是一个独立的用户。
- 默认地,系统会为每一个应用程序分配一个唯一的Linux用户ID(这个ID只能被系统使用,并且对于应用程序来说,这个ID是未知的)。系统为一个应用程序的所有文件 设置了权限,所以,只有分配给这个应用程序的用户ID可以访问它们。
- 每一个进程有它主机的虚拟机 (VM), 所以一个应用程序的代码会独立与其它的应用程序运行。
- 默认地,每一个应用程序在它自己的Linux进程中运行。Android会在一个应用程序的任何一个组件需要被调用的时候启动这个进程。然后,当没有任何组件被调用或者系统需要为其它应用程序回收内存的时候,就会 关闭这个进程。
通过这种方式,Android 系统实现了 最少特权原则。就是说,每一个应用程序,默认地,都只能调用它所需要的工作组件。 这就创造了这样一种非常安全的环境,在这个环境中,一个应用程序不能访问没有被授予其权限的系统部分。
尽管如此,也有很多途径可以让一个应用程序和其它的应用程序共享数据,也可以让一个应用程序去调用系统服务:
- 为两个应用程序分配同一个Linux用户ID是可行的,这样它们就能访问对方的文件。为了节约系统资源,拥有相同用户ID的应用程序也可以被运行于相同的Linux进程 并且共享VM(它们必须用相同的证书签名)。
- 应用程序可以通过请求权限来访问设备数据,例如联系人,SMS信息,可插拔存储(SD卡),相机,蓝牙,等等。所有的应用程序权限必须在安装的时候由用户授予。
以上概括了一个Android程序在系统中的存在方式。接下来将介绍:
- 组装应用程序的那些核心framework组件。
- 为你的程序声明组件和请求设备特性的配置文件。
- 被代码分隔的那些能够优化你的程序在不同的设备配置中完美运行的资源。
应用程序组件
组件是一个Android程序至关重要的构建模块。每一个组件都是系统进入你的应用的不同途径。但并不是所有的组件都是用户进入程序的真实入口,其中一些要依赖于其它组件, 但是每一个组件都以自己独有的形式存在,并发挥特殊的作用;每一个组件都是一个唯一的模块,帮助你实现程序的各种行为。
有四种不同的应用程序组件。每一种组件都有其唯一的目的并且有独有的生命周期,这个生命周期定义了附件被创建和销毁的方式。
下面介绍四种类型的应用程序组件:
- Activity
- 一个 activity 为一个用户交互提供一个单独的界面。例如,一个邮件程序可能有一个activity,它展现了一个新邮件的列表。
另一个activity用来编辑邮件,还有一个是用来阅读邮件。
虽然这些activity组合在一起构成一个紧密的用户体验,但每一个都是相对独立的。 同样,其它程序也可以启动这些activity(如果这个邮件程序允许)。
例如,一个相机程序可以启动这个邮件程序的编辑邮件activity,如果用户想分享一张照片。
一个activity作为
Activity
的一个子类被实现。 你也可以通过 Activities开发者指南来了解更多。 - Service
- 一个 service是一个运行在后台的组件。它用于执行耗时操作或者远程进程。 一个service并不提供用户交互界面。例如,当用户在使用另外一个程序的时候,一个服务可能在播放音乐或者在 通过网络获取数据,这样不会阻塞住用户与activity的交互。其它的组件,例如一个activity,可以启动一个service并让其运行或者与其绑定,绑定后可以与其交互。
- Content provider
- 一个content provider负责管理应用程序的数据共享集。
你可以通过文件系、SQLite数据库、网站,或者其它的你的应用程序可以访问的持久化存储位置来存储数据。
通过content provider,其它的应用程序可以查询甚至修改你的数据(如果这个content provider允许它们这么做)。
例如,Android系统提供了一个content provider来管理联系人信息。
同样地,任何程序拥有了适当权限都可以查询这个content provider
(例如
ContactsContract.Data
) 去读写某人的信息。Content provider在读写程序的私有数据时也很有用。例如,Note Pad 范例程序就使用了一个content provider来保存笔记。
一个content provider是作为
ContentProvider
的一个子类被实现的。 并且必须实现一些标准的API集,这样其它的应用程序才能执行事务。更多信息,请查阅 Content Providers开发者指南。 - Broadcast receiver
- broadcast receiver 是一个用来响应系统范围内的广播的组件。
很多广播发自于系统本身。—例如, 通知屏幕已经被关闭、电池低电量、照片被拍下的广播。
应用程序也可以发起广播。—例如, 通知其它程序,一些数据被下载到了设备,且可供它们使用。
虽然广播并不提供用户交互界面,它们也可以创建一个状态栏通知
来提醒用户一个广播事件发生了。尽管如此,更多的情形是,一个广播只是进入其它组件的一个“门路”,并试图做一些少量的工作。
例如,它可能发起一个服务,并通过服务执行与这个广播事件相关的工作。
broadcast receiver是
BroadcastReceiver
的子类实现,而且每一个广播通过Intent
对象来传递。 更多信息,请阅读BroadcastReceiver
类。
Android系统设计的一个独特方面是,任何程序都可以启动其它程序的组件。 例如,如果你想让用户使用设备相机捕捉一个相片,有另外一个程序做这件事,那么你的程序将可以调用它, 而不是你自己开发一个拍照的activity。你不必从相机程序中嵌入代码或者连链接代码也不需要。取而代之地,你可以简单地启动相机程序中拍照的activity。 当拍照完成,相片就会返回给你的程序供你使用。从用户的角度,就好像相机就是你程序的一部分。
当系统启动一个组件,它其实就启动了这个程序的进程(如果这个进程还未被启动的话)并实例化这个组件所需要的类。 例如,如果你的程序启动了相机程序里的activity去拍照,这个activity实际上是运行在相机程序的进程里,而不是你自己的进程。 因此,不像其它系统里的程序,Android程序并不是单入口的(例如它没有main方法)。
由于系统把程序运行在一个个独立的进程中,并使用文件权限来限制对其它程序的访问,所以你的程序不能从其它程序中直接激活组件。 尽管如此,Android系统可以做到!激活一个其它程序的组件,你必须向系统发送一个信息,这个信息需要指定你的intent 来启动一个指定的组件。 然后系统就会为你激活这个组件。
激活组件
四分之三的组件类型—activitie, service, 和 broadcast receiver—是被一个叫做 intent的异步消息激活的。 Intent把不同的独立的组件在运行期绑定在一起(你可以把它们当作从其它组件中请求动作的消息), 无论这些组件属于你的或者其它的程序。
一个intent使用一个Intent
对象来创建,
它用于激活一个指定的或者指定类型的组件。—
一个intent可以分别是显示的或者隐式的。
对于activity和service来说,一个intent定义了将要执行的动作。(例如, 查看或发送什么)并且可以指定动作执行需要的数据URL(启动组件所需要知道的其它数据)。
例如,一个intent可能发送一个请求,让一个activity去显示一张图片或打开一个网页。
某些情况下,你可以启动一个activity来接收一个结果,此时,这个activity也通过一个Intent
来返回结果。
(例如, 你可以发送一个intent让用户获取一个个人联系人,并让这个结果返回给你—返回的intent就包含了一个指向你选择的联系人的URI)。
对broadcast receiver来说, intent只是简单地定义了需要广播的公告(例如,一个指明设备电池电量低的广播就只包含了一个已知的动作字符串:“电量低”)。
另外一个组件类型, content provider, 不是用intent来激活。 相对地,它是由一个 ContentResolver
发起的一个指向它的请求激活的。
这个content resolver掌握了所有content provider的直接事务,所以用这个provider来执行事务的组件不需要直接执行而是调用这个 ContentResolver
对象的方法。
它在content provider和这个组件请求信息之间放置了一个抽象层(为了安全)。
激活各种类型组件有不同的方法:
- 你可以启动一个activity(或者让让它来做一些新的工作)通过传递一个
Intent
给startActivity()
或者startActivityForResult()
(当你想让一个activity为你返回一个结果)。 - 你可以启动一个service(或者给一个正在运行的service发送新的指令)通过传递一个
Intent
给startService()
。 或者你可以绑定一个服务通过传递一个Intent
给bindService()
。 - 你可以初始化一个broadcast通过传递一个
Intent
给一些方法像sendBroadcast()
,sendOrderedBroadcast()
, 或者sendStickyBroadcast()
。 - 你可以让content provider执行一个查询操作通过调用
ContentResolver
的query()
方法。
想要了解更多关于intent的信息, 查阅 Intent和Intent Filters 文档。
更多信息关于激活特定的组件包含在下述文档中:
Activities, Services, BroadcastReceiver
和 Content Providers.
配置文件
在Android系统能够启动一个程序组件之前, 系统必须通过读取程序的AndroidManifest.xml
文件 ("配置"文件)来知道这个组件是否存在。
你的程序必须在这个文件中声明它所有的组件。这文件必须放在程序项目的根目录中。
除了声明程序组件外,这个配置文件还做一些其它的工作,例如:
- 确定程序需要哪些用户权限,例如网络访问或者读取用户的联系人。
- 声明程序需要的最小的 API Level 这个要参照程序都使用了哪些API。
- 声明程序使用或要求的硬件和软件特性,例如相机,蓝牙服务,或者多点触屏。
- 程序需要链接的API类库(除Android framework API之外的类库),例如 Google Map类库。
- 其它
声明组件
配置文件的主要任务是通知系统,该应用程序都使用了哪些组件。 例如,一个配置文件可以这样声明一个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
属性指定了应用程序的图标资源。
在 <activity>
元素中,
android:name
属性指定了 Activity
子类的完整正确类名, 而 android:label
属性
指定了一个用于显示activity的用户可见标签的字符串。
你必须像这样声明所有的组件:
<activity>
activity的元素<service>
service的元素<receiver>
broadcast receiver的元素<provider>
content provider的元素
你包含在你程序中但并没有在配置文件中声明的Activitie, service, 和 content provider
是不被系统识别的,因此,也无法运行。但是,
broadcast
receiver既可以在配置文件中声明,也可以在代码中被动态创建(作为
BroadcastReceiver
对象) 并且通过调用
registerReceiver()
注册于系统中。
想要学习更多如何搭建你程序的配置文件, 查阅 AndroidManifest.xml 文件 文档。
声明组件的用途
如前文 激活组件的讨论中所说, 你可以使用一个Intent
来启动一个activitie, service, 和 broadcast receiver。你可以明确地(使用组件的类名)在intent中声明一个目标组件。 但是,
intent最牛逼的地方其实是它的动作(action)概念, 使用action,你可以简单地描述以下你想执行的动作(并且你也可以指定你想把哪些数据放在这个动作上)
并且允许系统去寻找设备上可以执行这个动作的组件并启动它。如果有多个组件可以执行这个动作,那么由用户来决定哪个去执行。
系统识别能够响应intent的组件的方法是比较收到的intent和设备的其它程序的配置文件中的intent filters
你在程序的配置文件中声明组件的时候,你就可以选择性地包含intent过滤器来声明组件的功能,这样它就能响应由其它程序发起的intent。
你可以通过添加一个<intent-filter>
作为你组件的子元素
来为你的组件声明一个intent过滤器。
例如,一个包含编辑新邮件页面的邮件程序可能要在配置文件中声明一个intent过滤器,以此作为入口来响应“发送”intent(为了发送邮件)。
你程序中的一个Activity就要创建一个包含"发送"动作(ACTION_SEND
)
的intent,这样系统在你通过startActivity()
调用了这个intent时就去比较邮件程序的“发送”页面然后启动它。
更多关于创建intent过滤器,查看Intents和Intent Filter 文档。
声明程序的配置要求s
Android设备多种多样,并不是所有的都提供了相同的特性和能力。 为了避免不满足你程序要求的设备去安装,你有必要清楚地在配置文件中声明设备和软件要求以定义哪些设备是你的程序支持的。 这些声明多数只是参考信息,系统并不去读取他们。但是额外的程序例如google play会为了筛选用户对应用程序的搜索。
例如,如果你的程序需要相机,并且使用了Andriod 2.1介绍的api (API Level 7), 你就应该在配置文件中声明这些要求。这样的话,没有相机且Android版本低于2.1的就不能从Google Play中安装你的程序。
尽管如此,你也可以声明你的程序使用相机,但是不是硬性 要求 。这种情况下,你的程序必须在运行时执行一个校验,判断设备是否有相机功能,并且在没有相机的设备上禁用一切使用相机的特性。
下面有一些当你在设计和开发应用时需要考虑的设备特征:
- 设备尺寸和像素密度
-
为了根据屏幕类型给设备分类,Android定义了两个设备特性:屏幕尺寸(屏幕的物理尺寸)和屏幕像素密度(屏幕像素的物理密度,或者叫dpi—每英寸的点数)。
为了简化屏幕配置的不同类型,Android系统把他们概括为可选择的分组,以便于他们容易被适配。
屏幕尺寸包括: small, normal, large, 和 extra large.
屏幕像素密度包括: low density, medium density, high density, 和 extra high density.默认地,你的应用程序可以适配所有尺寸和像素密度的屏幕,因为Android系统会对你的ui布局和图像资源做合适的调整。 尽管如此,你应该为特定的屏幕尺寸创建专门的布局,为特定的像素密度提供专门的图像资源。你要使用可选的布局资源,还要用
<supports-screens>
元素在配置文件中精确声明你的程序所支持的屏幕尺寸。更多信息,请查阅 支持多屏幕 文档。
- 输入设置
- 许多设备提供了不同的用户输入形式。例如硬键盘,轨迹球,或者一个五向导航板。如果你的程序需要一个特殊类型的输入硬件。
那么你需要在配置文件中使用
<uses-configuration>
元素来声明。 尽管如此,很少由程序需要声明一个特定的输入配置。 - 设备特性
-
可能有很多硬件和软件特性并不一定都被一个已有的Android设备支持。例如相机,光感应器,蓝牙,一个特定版本的OpenGL,或者
一个精确的触摸屏。你可能永远不能断定某个特定的特性是被所有的Android设备支持的。(除非是是Android的标准类库),
所以你应该用
<uses-feature>
元素 来为你的程序使用到的特性加以声明。 - 平台版本
-
不同的Android设备经常运行了不同版本的Android平台,例如Android 1.6或者Android 2.3.每一个连续的版本
通常包含上个版本不支持的额外api。为了指明哪些api是可用的,么一个平台都指定了一个API Level。
(例如,Android 1.0是api 1,Android 2.3是api 9)。如果你使用了1.0版本以后加入的api,你应该声明这些API所在
的最小api level,需要使用
<uses-sdk>
元素来声明。
你为你的应用声明这些需求是很有必要的,因为当你在Google Play上发布应用时,它会根据这些声明来过滤出哪些应用时适用于这些设备的。 因此,你的程序也应该仅仅适用于满足于你程序的需求的设备。
关于google Play如何根据这些(以及其他)要求筛选应用,请查看Google Play上的筛选 文档。
程序资源
一个Android 应用不仅仅由代码构成,—它也需要由代码分隔的资源文件,例如图片,音频文件,以及任何与程序可见内容相关的东西。例如,你应该定义动画,菜单,风格,颜色以及由XML文件描述的用户交互页面的布局。 使用程序资源使得你修改很多程序特征变得很容易,同时你不必去修改代码。—而且通过提供可选择的资源集合,— 也使你能够为不同的设备配置优化程序(例如不同的语言和屏幕尺寸)。
SDK搭建工具为Android项目使用到的每一个资源定义一个整型的ID,你可以通过这个ID获得代码中的资源或者XML中的其它资源的引用。
例如,如果你的程序包含了一个名叫 logo.png
(保存在 res/drawable/
目录下)的图片文件, SDK工具生成一个叫做R.drawable.logo
的资源ID, 你可以用这个ID引用那个图片并把它添加到你的用户交互中去。
提供源代码分隔的资源的一个重要能力是为不同的设备配置提供可选择的资源。
例如,在XML中定义UI的字符串,你就可以把这些字符串翻译成其它的语言,并在分隔的文件中保存。
然后,根据一个你追加到资源目录名中的语言修饰符(例如res/values-fr/
是法语字符串的值)和用户对语言的设置,
Android系统就会提供适当的语言字符串到你的UI中。
Android支持很多不同的修饰符以支持可选的资源。这些修饰符是被加入到资源目录名中的简短字符串。这样定义是为了 定义设备的特定配置,以保证这个目录下的资源可能会被使用到。比如另外一个例子,你可能会因为设备的屏幕方向和尺寸为activity 创建不同的布局。例如,当屏幕是竖着的时候(高),你可能想要上下排列的一组按钮,而当屏幕横着的时候(宽),你就希望这些按钮水平排列。 改变布局的方式取决于屏幕的方向,你可以定义两种不同的布局,然后为每一种布局的目录名加上一个合适的修饰符。然后,系统就会根据当前屏幕 的方向来请求合适的布局。
关于更多你的程序可以导入的不同类型的资源以及怎样为不同的设备配置创建可选的资源,请查阅 应用程序资源开发者指南。