Android 应用程序构成
一般情况Android 应用程序是由以下四种组件构造而成的:
· 活动
· 广播接收器
· 服务
· 内容提供器
需要注意的是,并不是每个Andorid 应用程序都必须构建这4 个组件,有些可能由这些组件的组合而成。
一旦你确定了你的应用程序中需要的组件,那么你就应该在AndroidManifest.xml 中列出他们。 这是一个XML 配置文件,它用于定义应用程序中需要的组件、组件的功能及必要条件等。这个文件是必须的。详情参见Android manifest file documentation
四种组件说明如下:
活动
活动是最基本的Andorid 应用程序组件,应用程序中,一个活动通常就是一个单独的屏幕。每一个活动都被实现为一个独立的类,并且从活动基类中继承而来, 活动类将会显示由视图控件组成的用户接口,并对事件做出响应。 大多数的应用是由多屏幕显示组成。例如,一个文本信息的应用也许有一个显示发送消息的联系人列表屏幕,第二个屏幕用来写文本消息和选择收件人, 再来一个屏幕查看消息历史或者消息设置操作等。
这里每一个这样的屏幕就是一个活动,很容易实现从一个屏幕到一个新的屏幕并且完成新的活动。 在某些情况下当前的屏幕也许需要向上一个屏幕动提供返回值--比如让用户从手机中挑选一张照片返回通讯录做为电话拨入者的头像。
当打开一个新的屏幕时,之前一个屏幕会被置为暂停状态并且压入历史堆栈中。用户可以通过回退操回到以前打开过的屏幕。我们可以选择性的移除一些没有必要保留的屏幕,因为Android 会把每个从桌面打开的程序保留在堆栈中。
Intent 和 Intent Filters
调用Android 专有类 Intent 进行构屏幕之间的切换。 Intent 是描述应用想要做什么。Intent 数据结构两最重要的部分是动作和动作对应的数据。典型的动作类型有:MAIN(活动的门户)、VIEW、PICK、EDIT 等。而动作对应的数据则以URI 的形式进行表示。例如:要查看某一个人的联系方式,你需要创建一个动作类型为VIEW 的intent,以及一个表示这个人的URI。
与之有关系的一个类叫IntentFilter。当intent 被要求做某事的时候,intent filter 用于描述一个活动(或者BroadcastReceiver,看下面)能够操作哪些intent。一个活动如果要显示一个人的联系方式时,需要声明一个IntentFilter,这个IntentFilter 要知道怎么去处理VIEW 动作和表示一个人的URI。 IntentFilter 需要在AndroidManifest.xml 中定义。
通过解析各种intent,从一个屏幕切换到另一个屏幕是很简单的。当向前导航时,活动将会调用startActivity(myIntent)方法。然后,系统会在所有安装的应用程序定义的IntentFilter 中查找,找到最匹配myIntent 的Intent 对应的活动。新的活动接收到myIntent 的通知后,开始运行。当start 活动方法被调用将触发解析myIntent 的动作,这个机制提供了两个关键好处:
· 活动能够重复利用从其它组件中以Intent 的形式产生的一个请求
· 活动可以在任何时候被一个具有相同IntentFilter 的新的活动取代
广播接收器
你可以使用BroadcastReceiver 来让你的应用对一个外部的事件做出响应。比如:当电话呼入时,数据网络可用时,或者到了晚上时。BroadcastReceivers 不能显示UI,它只能通过 NotificationManager 来通知用户这些有趣的事情发生了。
BroadcastReceivers 既可以在AndroidManifest.xml 中注册,也可以在代码中使用Context.registerReceiver()进行注册。但这些有趣的事情发生时,你的应用不必对请求调用BroadcastReceivers,系统会在需要的时候启动你的应用,并在必要情况下触发BroadcastReceivers。各种应用还可以通过使用Context.sendBroadcast()将它们自己的intent broadcasts 广播给其它应用程序。
服务
一个服务是具有一段较长生命周期且没有用户界面的程序。比较好的一个例子就是一个正在从播放列表中播放歌曲的媒体播放器。在一个媒体播放器的应用中,应该会有多个活动,让使用者可以选择歌曲并播放歌曲。然而,音乐重放这个功能并没有对应的活动,因为使用者当然会认为在导航到其它屏幕时音乐应该还在播放的。在这个例子中,媒体播放器这个活动会使用Context.startService() 来启动一个服务,从而可以在后台保持音乐的播放。同时,系统也将保持这个服务一直执行,直到这个service 运行结束。(你可以通过阅读Life Cycle of an Android Application 获取更多关于服务的介绍). 另外,我们还可以通过使用Context.bindService() 方法,连接到一个服务上(如果这个服务还没有运行将启动它)。当连接到一个服务之后,我们还可以通过服务提供的接口与它进行通讯。拿媒体播放器这个例子来说,我们还可以进行暂停、重播等操作。
教程:一个记事本应用程序范例
本教程通过手把手教你的方式,讲解如何利用Android 框架和诸多工具建立自己的手机应用。从一个预先配置好的工程文件开始,该教程通过一个简单记事本应用程序完整的开发过程,并辅以贯穿始终的详尽例子,指导你如何搭建工程、组织应用逻辑以及UI,乃至接下来的编译及运行可执行程序等等。
该教程将这个记事本应用的开发过程视作一组练习(见如下),每一个练习都由若干步骤组成。你可以亦步亦趋地完成每个练习步骤,逐步建立并完善自己的应用程序。这些练习提供了你实现此应用所需的——细到每一步骤的——具体范例代码。
当你完成此教程后,一个具有实际功能的Android 应用就从你手上诞生了,并且你对Android 应用开发中的一些极为重要的概念也会有更加深刻的理解。若你想为你这个简单的记事本应用添加更多复杂功能的话,你可以用另一方式实现的记事本程序比照你的练习代码,具体可参看 Sample Code 文档部分。
本教程目标读者
该教程主要是面向有一定经验,尤其是那些具备一定Java 编程语言知识的开发者。如果你之前从未写过一个Java 应用程序的话,仍可以使用此教程,只是学习进度稍稍慢一点罢了。
本教程假定你已熟悉了一些基本的Android 应用概念和术语。如果你对这些还不够熟稔的话,你得将 Overview of an Android Application 好好温故一下,才能继续下面的学习。
同时需注意的时,该教程的集成开发环境是预装Android 插件的Eclipse。如果你不用Eclipse,仍可做下面的这些练习和建立应用,但你届时将不得不面对一些涉及Eclipse的步骤在非Eclipse IDE 中如何实现的问题。
Android 应用程序模块: 应用, 任务, 进程, 和线程
在大多数操作系统里,存在独立的一个1 对1 的可执行文件(如Windows 里的exe 文件),它可以产生进程,并能和界面图标、应用进行用户交互。但在Android 里,这是不固定的,理解将这些分散的部分如何进行组合是非常重要的。
由于Android 这种可灵活变通的,在实现一个应用不同部分时你需要理解一些基础技术:
· 一个android 包 (简称 .apk ) ,里面包含应用程序的代码以及资源。这是一个应用发布,用户能下载并安装他们设备上的文件。
· 一个 任务 ,通常用户能当它为一个“应用程序”来启动:通常在桌面上会有一个图标可以来启动任务,这是一个上层的应用,可以将你的任务切换到前台来。
· 一个 进程 是一个底层的代码运行级别的核心进程。通常.apk 包里所有代码运行在一个进程里,一个进程对于一个.apk 包;然而, 进程 标签常用来改变代码运行的位置,可以是 全部的.apk 包 或者是独立的 活动, 接收器, 服务, 或者 提供器组件。
任务
记住关键的一点:当用户看到的“应用”,无论实际是如何处理的,它都是一个任务。如果你仅仅通过一些活动来创建一个.apk 包,其中有一个肯定是上层入口(通过动作的intent-filter 以及分android.intent.category.LAUNCHER),然后你的.apk 包就创建了一个单独任务,无论你启动哪个活动都会是这个任务的一部分。
一个任务,从使用者的观点,他是一个应用程序;对开发者来讲,它是贯穿活动着任务的一个或者多个视图,或者一个活动栈。当设置Intent.FLAG_ACTIVITY_NEW_TASK标志启动一个活动意图时,任务就被创建了;这个意图被用作任务的根用途,定义区分哪个任务。如果活动启动时没有这个标记将被运行在同一个任务里(除非你的活动以特殊模式被启动,这个后面会讨论)。如果你使用 FLAG_ACTIVITY_NEW_TASK 标记并且这个意图的任务已经启动,任务将被切换到前台而不是重新加载。
FLAG_ACTIVITY_NEW_TASK 必须小心使用:在用户看来,一个新的应用程序由此启动。如果这不是你期望的,你想要创建一个新的任务。另外,如果用户需要从桌面退出到他原来的地方然后使用同样的意图打开一个新的任务,你需要使用新的任务标记。否则,如果用户在你刚启动的任务里按桌面(HOME)键,而不是退出(BACK)键,你的任务以及任务的活动将被放在桌面程序的后面,没有办法再切换过去。
任务亲和力(Affinities)
一些情况下Android 需要知道哪个任务的活动附属于一个特殊的任务,即使该任务还没有被启动。这通过任务亲和力来完成,它为任务中一个或多个可能要运行的活动提供一个独一无二的静态名字。默认为活动命名的任务亲和力的名字,就是实现该活动.apk 包的名字。这提供一种通用的特性,对用户来说,所有在.apk 包里的活动都是单一应用的一部分。
当不带 Intent.FLAG_ACTIVITY_NEW_TASK 标记启动一个新的活动,任务亲和力对新启动的活动将没有影响作用:它将一直运行在它启动的那个任务里。然而,如果使用NEW_TASK 标记,亲和力会检测已经存在的任务是否具有相同的亲和力。如果是,该任务会被切换到前台,新的活动会在任务的最上面被启动。
你可以在你的表现文件里的应用程序标签里为.apk 包里所有的活动设置你自己的任务亲和力,当然也可以为单独的活动设置标签。这里有些例子演示如何使用:
· 如果你的.apk 包里包含多个用户可启动的上层应用程序,那么你可能想要为每个活动分配不同的亲和力。这里有一个不错的协定,你可以将不同的名字字串加上冒号附加在.apk 包名字的后面。 例如,"com.android.contacts"的亲和力命名可以是"com.android.contacts:Dialer"and"com.android.contacts:ContactsList"。
· 如果你想替换一个通知,快捷键,或者其它能从外部启动的应用程序的内部活动,你需要在你想替换的活动里明确的设置任务亲和力(taskAffinity)。例如,如果你想替换联系人详细信息浏览界面(用户可以直接操作或者通过快捷方式调用),你需要设置任务亲和力(taskAffinity)为“com.android.contacts”。
启动模式以及启动标记
你控制活动和任务通信的最主要的方法是通过设置启动模式的属性以及意图相应的标记。这两个参数能以不同的组合来共同控制活动的启动结果,这在相应的文档里有描述。
这里我们只描述一些通用的用法以及几种不同的组合方式。
你最通常使用的模式是singleTop(除了默认为standard 模式)。这不会对任务产生什么影响;仅仅是防止在栈顶多次启动同一个活动。
singleTask 模式对任务有一些影响:它能使得活动总是在新的任务里被打开(或者将已经打开的任务切换到前台来)。使用这个模式需要加倍小心该进程是如何和系统其他部分交互的,它可能影响所有的活动。这个模式最好被用于应用程序入口活动的标记中。
(支持MAIN 活动和LAUNCHER 分类)。
singleInstance 启动模式更加特殊,该模式只能当整个应用只有一个活动时使用。有一种情况你会经常遇到,其它实体(如搜索管理器SearchManager 或者 通知管理器NotificationManager)会启动你的活动。这种情况下,你需要使用Intent.FLAG_ACTIVITY_NEW_TASK 标记,因为活动在任务(这个应用/任务还没有被启动)之外被启动。就像之前描述的一样,这种情况下标准特性就是当前和任务和新的活动的亲和性匹配的任务将会切换到前台,然后在最顶端启动一个新的活动。当然,你也可以实现其它类型的特性。
一个常用的做法就是将Intent.FLAG_ACTIVITY_CLEAR_TOP 和NEW_TASK 一起使用。这样做,如果你的任务已经处于运行中,任务将会被切换到前台来, 在栈里的所有的活动除了根活动,都将被清空,根活动的onNewIntent(Intent) 方法传入意图参数后被调用。当使用这种方法的时候 singleTop 或者 singleTask 启动模式经常被使用,这样当前实例会被置入一个新的意图,而不是销毁原先的任务然后启动一个新的实例。
另外你可以使用的一个方法是设置活动的任务亲和力为空字串(表示没有亲和力),然后设置finishOnBackground 属性。 如果你想让用户给你提供一个单独的活动描述的通知,倒不如返回到应用的任务里,这个比较管用。要指定这个属性,不管用户使用BACK还是HOME,活动都会结束;如果这个属性没有指定,按HOME 键将会导致活动以及任务还留在系统里,并且没有办法返回到该任务里。
请确保阅读过文档启动模式属性(launchMode attribute) 以及 意图标记(Intent
flags) ,关注这些选项的详细信息。
进程
在Android 中,进程是应用程序的完整实现,而不是用户通常了解的那样。他们主要用途很简单:
· 提高稳定性和安全性,将不信任或者不稳定的代码移动到其他进程。
· 可将多个.apk 包运行在同一个进程里减少系统开销。
· 帮助系统管理资源,将重要的代码放在一个单独的进程里,这样就可以单独销毁应用程序的其他部分。
像前面描述的一样,进程的属性被用来控制那些有特殊应用组件运行的进程。注意这个属性不能违反系统安全: 如果两个.apk 包不能共享同一个用户ID,却试图运行在通一个进程里,这种情况是不被允许的,事实上系统将会创建两个不同的进程。请查看安全相关文档以获取更多关于安全限制方面的信息。
线程
每个进程包含一个或多个线程。多数情况下,Android 避免在进程里创建多余的线程,除非它创建它自己的线程,我们应保持应用程序的单线程性。一个重要的结论就是所有呼叫实例, 广播接收器, 以及 服务的实例都是由这个进程里运行的主线程创建的。
注意新的线程不是为活动,广播接收器,服务或者内容提供器实例创建:这些应用程序的组件在进程里被实例化(除非另有说明,都在同一个进程处理),实际上是进程的主线程。这说明当系统调用时这些组件(包括服务)不需要进程远距离或者封锁操作(就像网络呼叫或者计算循环),因为这将阻止进程中的所有其他组件。你可以使用标准的线程 类或者Android 的HandlerThread 类去对其它线程执行远程操作。
这里有一些关于创建线程规则的例外:
· 呼叫IBinder 或者IBinder 实现的接口,如果该呼叫来自其他进程,你可以通过线程发送的IBinder 或者本地进程中的线程池呼叫它们,从进程的主线程呼叫是不可以的。特殊情况下,,呼叫一个服务 的IBinder 可以这样处理。(虽然在服务里呼叫方法在主线程里已经完成。)这意味着IBinder 接口的实现必须要有一种线程安全的方法,这样任意线程才能同时访问它。
· 呼叫由正在被调用的线程或者主线程以及IBinder 派发的内容提供器 的主方法。被指定的方法在内容提供器的类里有记录。这意味着实现这些方法必须要有一种线程安全的模式,这样任意其它线程同时可以访问它。
· 呼叫视图以及由视图里正在运行的线程组成的子类。通常情况下,这会被作为进程的主线程,如果你创建一个线程并显示一个窗口,那么继承的窗口视图将从那个线程里启动。Android 应用程序的生命周期。
在大多数情况下,每个Android 应用程序都运行在自己的Linux 进程中。当应用程序的某些代码需要运行时,这个进程就被创建并一直运行下去,直到系统认为该进程不再有用为止。然后系统将回收进程占用的内存以便分配给其它的应用程序。应用程序的开发人员必须理解不同的应用程序组件(尤其是Activity, Service, 和BroadcastReceiver)是如何影响应用程序进程生命周期的,这是很重要的一件事情。不正确地使用这些组件可能会导致系统杀死正在执行重要任务的应用程序进程。一个常见的进程生命周期bug 的例子是BroadcastReceiver, 当BroadcastReceiver在BroadcastReceiver.onReceive()方法中接收到一个Intent 时,它会启动一个线程,然后返回。一旦它返回,系统将认为BroadcastReceiver 不再处于活动状态,因而BroadcastReceiver 所在的进程也就不再有用了(除非该进程中还有其它的组件处于活动状态)。因此,系统可能会在任意时刻杀死进程以回收内存。这样做的话,进程中创建(spawned)出的那个线程也将被终止。对这个问题的解决方法是从BroadcastReceiver 启动一个服务,让系统知道进程中还有处于活动状态的工作。为了决定在内存不足时让系统杀死哪个进程,Android 根据每个进程中运行的组件以及组件的状态把进程放入一个”重要性分级(importance hierarchy)”中。进程的类型包括(按重要程度排序):
1. 前台(foreground)进程,与用户当前正在做的事情密切相关。不同的应用程序组件能够通过不同的方法使它的宿主进程移到前台。当下面任何一个条件满足时,可以考虑将进程移到前台:
1. 进程正在屏幕的最前端运行一个与用户交互的Activity (它的onResume()方法被调用)
2. 进程有一正在运行的BroadcastReceiver (它的BroadcastReceiver.onReceive()方法正在执行)
3. 进程有一个Service,并且在Service 的某个回调函数(Service.onCreate(),Service.onStart(), 或 Service.onDestroy())内有正在执行的代码。
1. 可见(visible)进程,它有一个可以被用户从屏幕上看到的Activity,但不在前台(它的onPause()方法被调用)。举例来说,如果前台的Activity 是一个对话框,以前的Activity 隐藏在对话框之后,就可能出现这种进程。这样的进程特别重要,一般不允许被杀死,除非为了保证前台进程的运行不得不这样做。
2. 服务(service)进程,有一个已经用startService() 方法启动的Service。虽然这些进程用户无法直接看到,但它们做的事情却是用户所关心的(例如后台MP3回放或后台网络数据的上传下载)。因此,系统将一直运行这些进程除非内存不足以维持所有的前台进程和可见进程。
3. 后台(background)进程, 拥有一个当前用户看不到的Activity(它的onStop()
方法被调用)。这些进程对用户体验没有直接的影响。如果它们正确执行了Activity生命期(详细信息可参考Activity),系统可以在任意时刻杀死进程来回收内存,并提供给前面三种类型的进程使用。系统中通常有很多个这样的进程在运行,因此要将这些进程保存在LRU 列表中,以确保当内存不足时用户最近看到的进程最后一个被杀掉。
4. 空(empty)进程,不包含任何处于活动状态的应用程序组件。保留这种进程的唯一原因是,当下次应用程序的某个组件需要运行时,不需要重新创建进程,这样可以提高启动速度。
系统将以进程中当前处于活动状态组件的重要程度为基础对进程进行分类。请参考Activity, Service 和 BroadcastReceiver 文档来获得有关这些组件在进程整个生命期中是如何起作用的详细信息。每个进程类别的文档详细描述了它们是怎样影响应用程序整个生命周期的。进程的优先级可能也会根据该进程与其它进程的依赖关系而增长。例如,如果进程A 通过在进程B 中设置Context.BIND_AUTO_CREATE 标记或使用ContentProvider 被绑定到一个服务(Service),那么进程B 在分类时至少要被看成与进程A 同等重要。
二、开发应用程序
Android 应用构成
Android 应用是由各种各样的组件来构成。 这些组件大部分都是松散连接的,准确 的说你可以把它们看成组件的联合而非是一个单一的应用。
通常,这些组件运行在同一个系统进程里面。你也可以在这个进程里面创建多个线程(这是很常见的),如果必要你也可以创建独立的子进程。不过这种情况是非常少见的,因为Android 尽力使代码进程间透明。
以下部分是很重要的Android APIs:
AndroidManifest.xml
AndroidManifest.xml 是系统的控制文件,它告诉系统如何处理你所创建的所有顶层组件(尤其是activities,服务,Intent 接收器和后面描述的内容管理器)。举例来说,控制文件就是把你的活动(Activities)要接收的Intents 连接在一起的“胶水”。
活动(Activities)
活动(Activity)就是一个有生命周期的对象。 一个Activity 就是完成某些工作的代码块, 如必要的话,这部分工作还可能包括对用户UI 界面的显示。不过这不是必须的,有些活 动从不显示UI 界面。典型地,你将会指定你的应用程序中的一个活动为整个程序的入口点。
视图(Views)
视图(Views)可以将其自身绘制到屏幕上。Android 的用户界面由一系列的视图树 (trees of views)构成。接口都是由一组以树的形式出现的视图组成的。开发者可 以通过创建一个新的视图的方法来使用自定义的图形处理技术(比如开发游戏,或者是 使用了不常用的用户图形(UI)窗口界面(widget))。
Intents
Intents 是一个简单的消息对象,它表示程序想做某事的“意图”(intention)。比如如果你的应用程序想要显示一个网页,那么它通过创建一个Intent 实例并将其传递给 系统来表示意图浏览这个URI。系统将定位于知道如何能处理这一Intent 的代码(在当 前情况下就是浏览器),并运行之。Intents 也可以用于广播系统范围内的有效事件 (例如通知事件)。
服务(Services)
服务是运行在后台的一段代码。它可以运行在它自己的进程,也可以运行在其他应用程序进程的上下文(context)里面,这取决于自身的需要。其它的组件可以绑定到一个服务(Service)上面,通过远程过程调用(RPC)来调用这个方法。例如媒体播放器的服务, 当用户退出媒体选择用户界面,她仍然希望音乐依然可以继续播放,这时就是由服务 (service)来保证当用户界面关闭时音乐继续播放的。
通知(Notifications)
通知将以小图标的形式呈现在状态栏里,用户通过与图标的交互式操来接收消息。最常见的通知包括短信息,通话记录,语音邮件,但是应用程序也可以创建它们自己的通知事件。 我们推荐采用通知事件实现提醒用户的注意。
内容管理器(ContentProviders)
内容管理器(ContentProvider)提供对设备上数据进行访问的数据仓库。典型的例子就 是使用内容管理器来访问联系人列表。你的应用程序也可以使用其它程序通过内容管理器提供的数据,同时你也可以定义你自己的内容管理器来向其它应用提供数据访问服务。
存、取、提供数据
典型的桌面操作系统一般能提供一种通用的文件系统,所有应用程序都能储存和读文件,并且其他应用程序也能访问该文件(可能需要一些访问控制设置). Android 使用不同的方式:在平台上,所有应用程序的数据(包括文件),对该应用程序是私有的。当然,Android 也提供一种标准方法将自己的私有数据提供给其他应用程序访问。这一章节讲了很多方法,描述应用如何存取数据,以及将数据提供给其他程序访问,当然,你也可以向其他应用程序请求并获得它的数据。
Android 提供下面的方式来存取数据:
参数选择
使用一个轻量级机制来存取基本数据类型的数据对,这是典型应用程序参数的
存储模式。
文件
你可以将你的文件存储在设备上或者其他移动媒介上,默认情况下,其他应用程序是不能访问这些文件的。
数据库
Android 有直接SQLite 数据库的API。应用程序可以创建以及使用SQLite 数据库。每个包创建的数据库都是私有的。
数据提供
数据提供是应用程序的一个可选组件,它可以提供读/写应用程序私有数据的方法。内容提供组件实现了一种标准请求数据的语法,和一种标准处理返回值的机制。Android 提供很多标准数据的提供方式,例如私有联系人。
网络
不要忘记,我们还可以使用网络存取数据。
Android 的安全与权限
Android 是一个多进程系统,每一个应用程序(和系统的组成部分)都运行在自己的进程中。在应用程序和系统间的安全通过标准的Linux 设备在进程级被执行,例如被分配给应用程序的用户和组ID。额外的细粒度安全特性通过“许可”机制来提供,该机制能够对一个指定进程可实现的特定操作进行约束。
安全结构
Android 安全学中的一个重要的设计点是在默认情况下应用程序没有权限执行对其它应用程序、操作系统或用户有害的操作。这些操作包括读/写用户的隐私数据(例如联系方式或e-mail),读/写其它应用程序的文件,执行网络访问,保持设备活动,等等。
应用程序的进程是一个安全的沙箱。它不能干扰其它应用程序,除非在它需要添加原有沙箱不能提供的功能时明确声明权限。这些权限请求能够被不同方式的操作所处理,特别的要基于证书和用户的提示被自动的允许或禁止。权限的请求在那个应用程序中通过一个应用程序被声明为静态的,所以在此之后在安装时或没有改变时它们会预先知道。
应用程序签名
所有的Android 应用程序(.apk 文件)必须通过一个证书的签名,此证书的私钥必须被开发者所掌握。这个证书的标识是应用程序的作者。这个证书不需要通过证书组织的签署:Android 应用程序对于使用自签署的证书是完全允许的和特别的。这个证书仅仅被用于与应用程序建立信任关系,不是为了大规模的控制应用程序可否被安装。最重要的方面是通过确定能够访问原始签名权限和能够共享用户ID 的签名来影响安全。
用户标识和文件访问
安装在设备中的每一个Android包文件(.apk)都会被分配给一个属于自己的统一的Linux用户ID,并且为它创建一个沙箱以防止影响其它应用程序(或者其它应用程序影响它)。
用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。
因为安全执行发生在进程级,所以一些不同包中的代码在相同进程中不能正常的运行,自从他们需要以不同Linux 用户身份运行时。你可以使用每一个包中的
AndroidManifest.xml 文件中的manifest 标签属性sharedUserId 拥有它们分配的相同用户ID。通过这样做,两个包被视为相同的应用程序的安全问题被解决了,注意为了保持安全,仅有相同签名(和请求相同sharedUserId 标签)的两个应用程序签名将会给相同的用户ID。
应用创建的任何文件都会被赋予应用的用户标识,并且,正常情况下不能被其它包访问。当你通过getSharedPreferences(String, int), openFileOutput(String, int) 或者openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建一个新文件时, 你可以同时或分别使用 MODE_WORLD_READABLE 和
MODE_WORLD_WRITEABLE 标志允许其它包读/写此文件。当设置了这些标志时,这个文件仍然属于你的应用程序,但是它的全局读、写和读写权限已经设置所以其它任何应用程序可以看到它。
权限命名
一个基本的Android 应用程序没有与其相关联的权限,意味着它不能做任何影响用户体验或设备中的数据的有害操作。要利用这个设备的保护特性,在你的应用程序需要时,你必须在AndroidManifest.xml 文件中包含一个或更多的<uses-permission>
标签来声明此权限。
例如:需要监听来自SMS 消息的应用程序将要指定如下内容:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission
android:name="android.permission.RECEIVE_SMS" />
</manifest>
在安装应用程序时,通过包安装器应用程序要通过权限请求的许可,使建立在与应用程序签名的核对下声明对于用户的那些权限和影响。在应用运行期间对用户不做检查:它要么在安装时被授予特定的许可,并且使用想用的特性;要么不被授予许可,并且使得一切使用特性的尝试失败而不提示用户。
例如,sendBroadcast(Intent) 方法就是当数据被发送给到每个接收器时检查许可的,
在方法调用返回之后,因此当许可失败时你不会收到一个异常。然而,几乎在所有例子中,许可失败都会被打印到系统日志中。通常,多次的许可错误会产生抛回至应用程序的SecurityException 异常。
Android 系统提供的许可可以在Manifest.permission 中找到。每个引用也可以定义和Enforce 它自己的许可,因此这不是全面的所有可能的列表。
在程序操作期间,个别权限在一些地方可能被强制:
· 在系统接到呼叫的时候,预防一个应用程序去执行特定的函数。
· 在启动Activity 时,防止一个应用启动其它应用程序的Activities。
· 发送和接收Intent 广播时,控制谁能接收你的广播或者谁能发送广播给你。
· 在一个内容提供器上访问和操作时。
· 绑定或开始一个服务时。
权限的声明和支持
为了执行你自己的权限,你必须首先在你的AndroidManifest.xml 中使用一个或多个<permission> 标签声明它们。
<protectionLevel> 属性是必需的,告诉系统用户应如何处理应用程序接到请求此权限的通知,或者在这个文档中对这个权限的许可的描述。
<permissionGroup> 属性是可选的,仅仅用于帮助系统为用户显示权限。通常,你要设置这些,向一个标准的系统组(列在android.Manifest.permission_group 中),或者在更多的情况下要自定义。它更偏向于使用一个已经存在的组,做为简化的权限用户界面显示给用户。
注意:应该为每个权限提供标签(label) 和描述(description)。当用户浏览权限列表时,它们可以为用户展示字符资源,如(android:label) 或者一个许可的详细信息( android:description) 。标签(label)比较短,用几个词来描述该权限保护的关键功能。描述(description)应该是一组句子,用于描述获得权限的用户可以做什么。我们写描述的习惯是两句话,第一句声明权限,第二句警告用户如果应用许可该权限时,会发生什么不好的事情。
你可以在系统中通过shell 命令 adb shell pm list permissions 查看权限当前
在AndroidManifest.xml 文件中支持权限
通过 AndroidManifest.xml 文件可以设置高级权限,以限制访问系统的所有组件或者使用应用程序。所有的这些请求都包含在你所需要的组件中的 android:permission属性,命名这个权限可以控制访问此组件。
Activity 权限 (使用 <activity> 标签) 限制能够启动与 Activity 权限相关联的组件或应用程序。此权限在 Context.startActivity() 和 Activity.startActivityForResult() 期间要经过检查;如果调用者没有请求权限,那么会为调用抛出一个安全异常
( SecurityException )。
Service 权限(应用 <service> 标签)限制启动、绑定或启动和绑定关联服务的组件或应用程序。此权限在 Context.startService(), Context.stopService() 和
Context.bindService() 期间要经过检查;如果调用者没有请求权限,那么会为调用抛出一个安全异常( SecurityException )。
BroadcastReceiver 权限(应用 <receiver> 标签)限制能够为相关联的接收者发送广播的组件或应用程序。在 Context.sendBroadcast() 返回后此权限将被检查,同时系统设法将广播递送至相关接收者。因此,权限失败将会导致抛回给调用者一个异常;它将不能递送到目的地。在相同方式下,可以使 Context.registerReceiver() 支持一个权限,使其控制能够递送广播至已登记节目接收者的组件或应用程序。其它的,当调用
Context.sendBroadcast() 以限制能够被允许接收广播的广播接收者对象一个权限(见下文)。
ContentProvider 权限(使用 <provider> 标签)用于限制能够访问 ContentProvider中的数据的组件或应用程序。(Content providers 有一个重要的附加安全设施可用于它们调用被描述后的URI 权限。) 不同于其它组件,它有两个不相连系的权限属性要设置:android:readPermission 用于限制能够读取提供器的组件或应用程序,android:writePermission 用于限制能够写入提供器的组件或应用程序。注意,如果一个提供者的读写权限受保护,意思是你只能从提供器中读,而没有写权限。当你首次收回提供者(如果你没有任何权限,将会抛出一个SecurityException 异常),那么权限要被检查,并且做为你在这个提供者上的执行操作。 使用 ContentResolver.query() 请求获取读权限; 使用 ContentResolver.insert(), ContentResolver.update() 和ContentResolver.delete() 请求获取写权限。在所有这些情况下,一个SecurityException异常从一个调用者那里抛出时不会存储请求权限结果。
发送广播时支持权限
当发送一个广播时你能总指定一个请求权限,此权限除了权限执行外,其它能发送Intent到一个已注册的BroadcastReceiver 的权限均可以。 通过调用
Context.sendBroadcast() 及一些权限字符串, 为了接收你的广播,你请求一个接收器应用程序必须持有那个权限。
注意,接收者和广播者都能够请求一个权限。当这样的事发生了,对于Intent 来说,这两个权限检查都必须通过,为了交付到共同的目的地。
其它权限支持
任意一个好的粒度权限都能够在一些调用者的一个服务中被执行。 和
Context.checkCallingPermission() method. 方法一起被完成。调用并产生一个需要的权限字符串,它将返回一个整型,以确定当前调用进程是否被许可。注意,仅仅当你执行的调用进入到其它进程的时候这些才会被使用,通常,通过IDL 接口从一个服务发布,或从一些其它方式通知其它进程。
这有许多其它有益的方式去检查权限。如果你有一个其它进程的PID,你可以使用上下文的方法 Context.checkPermission(String, int, int) 检查权限违反PID。 如果你有一个其它应用程序的包名, 你可以使用直接的包管理器方法
PackageManager.checkPermission(String, String) 去查看特定的包是否被指定的权限所许可。
URI 权限
迄今为止,在与内容提供器共同使用时,标准权限系统描述通常是不充分的。一个内容提供器要保护它自己及读和写权限,当为了它们产生作用,它的直接客户端总是需要手动的对其它应用程序指定URI。一个典型的例子是邮件应用程序中的附件。访问邮件的权限应该被保护,因为这是敏感用户数据。可以,如果一个网址图片附件提供给一个图片查看器,那个图片查看器将没有权限打开这个附件,因为它没有原因去拥有一个权限从而不能访问所有的电子邮件。
对于这个问题的解决是通过网址权限:当开始一个活动或对一个活动返回一个结果,调用者可通过设置Intent.FLAG_GRANT_READ_URI_PERMISSION 和
Intent.FLAG_GRANT_WRITE_URI_PERMISSION 中的一个或者两个。允许接收活动权限访问在Intent 中特定的数据地址,不论它有权限访问数据在内容提供器相应的Intent 中。
这种机制允许一个公用的功能性模型使用户相互交互(打开一个附件,从一个列表中选择一个联系人,等等)驱动ad-hoc 在优粒度权限的许可下。这可能是一个主要设备,应用程序为了减少这个权限而需要,仅仅直接关系到它们的行为。
这优粒度URI 权限的许可工作,然而,请求一些协作和内容提供者保持那些URI。强烈推荐内容提供者实现这种设备,并且通过android:grantUriPermissions 属性或者<grant-uri-permissions> 标签声明支持它。
更多信息可以在Context.grantUriPermission(), Context.revokeUriPermission(), 和Context.checkUriPermission() 方法中找到。
资源管理和多国版本
资源是外部文件(不含代码的文件),它被代码使用并在编译时编入应用程序。Android支持不同类型的资源文件,包括XML,PNG 以及JPEG 文件XML 文件根据描述的不同有不同格式。这份文档描述可以支持什么样的文件,语法,以及各种格式.源代码以及XML 文件将资源打包并编译进二进制文件,这种模式能使得资源更快得被加载。字符串也同样被压缩成更高效的模式。由于这些原因, Android 平台上存在不同的资源类型.
资源
Android 资源系统能跟踪所有非代码相关的应用程序。你可以使用 资源 类来访问应用程序的资源,资源的实例通常和应用程序联系在一起,你可以通过Context.getResources()来访问。
应用程序的资源在编译时就被编译到应用程序二进制代码里。为了使用某个资源,你需要将它在代码目录结构里放正确,然后编译。作为编译过程的一部分,产生的资源代号你可以在源代码里使用 -- 这允许编译器验证你的程序代码和你定义的资源是否相符。
创建资源
Android 支持字符串,图片以及很多其他类型的资源。每个对象语法、格式以及它们存储位置的支持,都是取决于不同类型的对象? 通常,你可以通过三种类型的文件来创建资源:XML 文件(除位图以及原数据文件),位图文件(对于图片)以及原始数据(其它类型,例如声音文件,等等。)。事实上,有两种不同类型的XML 文件,一种是编译到包里的,另外一种是通过aapt 来产生的资源文件,这里有一张包含所有资源类型,
文件格式,文件描述以及所有XML 文件的详细信息的列表。
在项目里,你可以在子目录res/下创建和存储资源文件。Android 有一个资源编译工具(aapt),它可以编译在这个目录下所有的子目录中的资源,这里有个各种资源的列表。你可以从 资源引用 这里看到各种类型的对象,包含其语法以及格式。
路径 资源类型res/anim/ XML 文件被编译进 逐帧动画 或 补间动画 的对象res/drawable/.png, .9.png, .jpg files 这些类型的文件被编译进下列这些图表资源列表为了获得这些资源的类型,使用Resource.getDrawable(id)
· 位图文件
· 9-patches (可改变尺寸的图像)
res/layout/ 可编译成屏幕布局的XML 文件 (或者屏幕的一部分). 查看 布局 res/values/ 可编译成多种类型资源的文件
注意: 不像其他 res/ 文件夹,它能容纳任何数量的文件,但只是描述其创建而不是资源本身. XML 的元素类型可以决定这些资源在R.class 里什么位置被替换 .文件可以被命名为任何名字,文件夹里有一些典型的文件(一般约定文件以定义的元素类型后面部分为文件名)::
· arrays.xml 定义数组
· colors.xml 定义 颜色 和 颜色字串数值. 你可以使用
Resources.getDrawable() 以及Resources.getColor(), respectively, 取得这些资源.· dimens.xml 定义 尺寸数据 . 使用Resources.getDimension() 取得这些资源。· strings.xml 定义字符串数值 (使用Resources.getString 或Resources.getText()取得资源,(后者更好一点)getText() 能取到在用户界面上显示的文本框里的文本。· styles.xml 定义类型 对象。res/xml/ 任何XML 文件可以进行编译,并能在运行时调用Resources.getXML() 显示XML 原文件。
res/raw/ 这里的任何文件都将直接被复制到设备上。编译产品时,这些数
据不会被编译,它们被直接加入到程序包里。 为了在程序中使用这些资源,你可以调用Resources.openRawResource() , 参数为
ID: R.raw.somefilename.
资源最终会被编译成APK 文件,Android 创建一个包装类,命名为R,这样你能做你的代码里使用这些资源类。根据资源路径和文件名的不同,R 包含很多子类。
全局资源
· 一些资源类允许你定义颜色。它能接受多种网络类型的值 -- 你可以写成 #RGB,
#ARGB, #RRGGBB, #AARRGGBB 这样16 进制常数都可以。
· 所有的颜色都可以设置一个阿尔法值,开始的两个16 进制数指定为透明。 0 在阿尔法值里意味着透明。当然,默认值是不透明的。
使用资源
编译时,Android 产生一个叫R 的类,它指向你程序中所有的资源。这个类包含很多子类。每一种都是Android 支持的,同时,编译后会产生一个资源文件。每个类提供一个或多个编译后资源的标识符,你可以在代码中使用。下面是个源代码的文件,里面包含了字符串,布局文件(全屏或者部分屏幕),以及图像资源。
注意: 这个R 类是自动产生的,你不能手动编写。当资源变化的时候它会自动更新。
在代码中使用资源
只要知道资源的ID 以及你编译进目标文件的资源类型就可以在代码里使用它来。下面是一些语法:
R.resource_type.resource_name或者android.R.resource_type.resource_name
resource_type 是R 子类的一种类型。 resource_name 是定义在XML 文件里的资源名或者为其他文件类型定义的资源文件(没有后缀)名。每种类型的资源会被加入到一个特定的R 的子类中;为了学习哪种R 的子类里有你编译的资源类型,参考资源引用 文档。被编译进应用程序的资源不需要包的名字就可以直接被访问到(像这样:
R.resource_type.resource_name). Android 包含一些标准资源,如屏幕的类型,按钮的背景。要使用这些代码,你需要包含 android, 如
android.R.drawable.button_background.
这里有一些好的和糟糕的例子说明如何在代码里使用编译后的资源:
// 从画图资源类里装载一个当前屏幕背景。
this.getWindow().setBackgroundDrawableResource(R.drawable.my_background_image);
// 错误! 将一个资源ID 装入一个需要字符串的方法中
this.getWindow().setTitle(R.string.main_title);
//正确!需要从资源封装类里取得标题。
this.getWindow().setTitle(Resources.getText(R.string.main_title));
// 从当前屏幕中装载布局数据。
setContentView(R.layout.main_screen);
//从ViewFlipper 对象中设置动画中一帧 。
mFlipper.setInAnimation(AnimationUtils.loadAnimation(this,
R.anim.hyperspace_in));
// 在TextView 对象中设置文本内容。
TextView msgTextView = (TextView)findViewByID(R.id.msg);
msgTextView.setText(R.string.hello_message);
资源引用
一个在属性(或者资源)里提供的数值可以被指向一个具体的资源。这常常被使用在布局文件中用于字符串(可以被本地化) 以及图片(存在于其他文件中的),通过一个引用可以是包括颜色和整数的任何资源类型。
例如,如果有 颜色资源, 我们可以将文本的颜色值写在布局文件中,颜色值可以从资源文件里取得:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="@color/opaque_red"
android:text="Hello, World!" />
注意这里使用‘@’的前缀是说明资源引用 -- 后面的文本是资源的名字
@[package:]type/name. 这里我们不需要指定包,因为我们在我们自己的包里引用资源。为了指定一个系统资源,你需要这样写:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="@android:color/opaque_red"
android:text="Hello, World!" />
另外一个例子,当你在布局文件里使用字符串,你必须做资源引用,这样字符串才能被使用:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="@android:color/opaque_red"
android:text="@string/hello_world" />
这段代码也能被用来创建资源间引用。例如,我们能这样创建图像资源:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<drawable
id="my_background">@android:drawable/theme2_background</drawab
le>
</resources>
主题属性引用
另一种资源数值允许你引用当前主题属性值。这种属性引用只能被用于特殊的资源类以及XML 属性中;它允许你根据现在主题风格将你定制的UI 变得更标准化,而不用使用大量的具体数值。
这里有个例子,我们能在布局文件中将文本颜色设置为基本系统主题中定义好的标准颜色:
<?xml version="1.0" encoding="utf-8"?>
<EditText id="text"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:textColor="?android:textDisabledColor"
android:text="@string/hello_world" />
注意除来我们将前缀'?'代替了'@',其他非常像资源引用。当你使用这个标记,系统会自动查找你提供的属性的名字 -- 资源工具知道肯定会有资源属性相符合,你不需要详细指定(?android:attr/android:textDisabledColor).
使用资源标识符到主题里去寻找相应的数据而不是直接使用原数据,其语法和'@'模式是一样的: ?[namespace:]type/name 这里的type 是可选择的.
使用系统资源
许多系统资源应用程序是可以使用的。这样的资源定义在"android.R"的类里。 例如,你可以使用下面的代码在屏幕上显示一个标准的应用程序图标:
public class MyActivity extends Activity
{
public void onStart()
{
requestScreenFeatures(FEATURE_BADGE_IMAGE);
super.onStart();
setBadgeResource(android.R.drawable.sym_def_app_icon);
}
}
用相似的方法,这段代码能将你的屏幕变成系统定义的标准的“绿色背景”:
public class MyActivity extends Activity
{
public void onStart()
{
super.onStart();
setTheme(android.R.style.Theme_Black);
}
}
对于不同的语言和设置支持不同的资源
你可以根据产品界面语言以及硬件配置设置不同的资源。注意,虽然你可以包含不同的字串,布局以及其他资源,但开发包(SDK)不会给你显式的方法去指定不同的资源去加载。Android 检测你的硬件以及位置信息选择合适的设置去加载。用户可以到设备上的设置界面去选择不同的语言。
要包含不同的资源,在同一目录下创建并行的文件夹,在每个文件夹后加上合适的名字,这个名字能表明一些配置信息(如语言,原始屏幕等等)。例如,这里的项目字符串文件一个是英文版的,另一个是法文版的:
MyApp/
res/
values-en/
strings.xml
values-fr/
strings.xml
Android 支持不同类型的修饰语,并可以加多条在文件夹名的后面, 修饰语之间以破折号分开。例如:一个绘图资源类指定全部配置名称命名会像这样:
MyApp/
res/
drawable-en-rUS-port-160dpi-finger-keysexposed-qwerty-dpad-480
x320/
更典型的,你可以仅仅指定部分特定的配置选项。只要保证所有的数值都是按顺序排列:
MyApp/
res/
drawable-en-rUS-finger/
drawable-port/
drawable-port-160dpi/
drawable-qwerty/
修饰语值
语言 两个小写字母 ISO 639-1。例如: en, fr, es
地区 两个大写字母加上一个小写字母'r' ISO 3166-1-alpha-2。 例如:
rUS, rFR, rES
屏幕方向 port, land, square
屏幕像素
92dpi, 108dpi, 等等。
触摸屏类型 notouch, stylus, finger
键盘是否有效 keysexposed, keyshidden
基本文本输入模式
nokeys, qwerty, 12key
无触摸屏的主要导航模式
notouch, dpad, trackball, wheel
屏幕分辨率
320x240, 640x480, 等等。大分辨率需要开始指定。
这个列表不包含一些特殊的参数,如载体,商标,设备/硬件,制造商。任何应用程序需要知道的信息都在资源修饰语里有说明。
这里有一些通用的关于资源目录的命名指导:
· 各个变量用破折号分开 (每个基本的目录名后跟一个破折号)
· 变量大小写敏感(其大小写法必须始终一致)例如,
o 一个drawable 的目录必须命名为 drawable-port, 而不是drawable-PORT。
o 你不能有两个目录命名为 drawable-port 以及 drawable-PORT,
甚至故意将"port" 和 "PORT"指为不同的参数也不可以。
· 一个式子里同一个类型修饰语中只有一个值是有效的(你不能指定像这样
drawable-rEN-rFR/)
· 你可以指定多个参数去定义不同的配置,但是参数必须是上面表格里的。例如,
drawable-en-rUS-land 意思在US-English 的机器里载入风景视图。
· Android 会寻找最适合当前配置的目录,这会在下面描述
· 表格里所列的参数是用来打破平衡以防止多重路径限制。 (看下面的例子)
· 所有目录,无论是限制的,还是不限制的,只要在 res/ 目录下.一些目录是不能嵌套的(这样 res/drawable/drawable-en 是不可以的)
· 所有的资源在被代码引用中最好都使用简单的、不加修饰的名字,如果一个资源这样命名:
MyApp/res/drawable-port-92dp/myimage.png
它将这样被引用:
R.drawable.myimage (code)
@drawable/myimage (XML)
Android 如何找到最合适的目录
Android 将会挑出哪些基本资源文件在运行时会被使用,这依靠当前的配置。 选择过程如下:
1. 删去一些和当前设备配置不符合的资源。例如,如果屏幕的像素是108dpi,这可以删除 MyApp/res/drawable-port-92dpi/.
2. MyApp/res/drawable/myimage.png
3. MyApp/res/drawable-en/myimage.png
4. MyApp/res/drawable-port/myimage.png
5. MyApp/res/drawable-port-92dpi/myimage.png
6. 挑出一些最经常符合配置的资源。例如,如果我们的地区是 en-GB, 方向是
port,那我们有两个符合配置的选项: MyApp/res/drawable-en/ 和
MyApp/res/drawable-port/. 这个目录 MyApp/res/drawable/ 可以被
删除了,因为当另外一个有一次匹配正确,而它没有。
7. MyApp/res/drawable/myimage.png
8. MyApp/res/drawable-en/myimage.png
9. MyApp/res/drawable-port/myimage.png
10. 根据配置的优先级选取最终适合的文件,它们按顺利被排列在上面的表格里。更确切得说,语言匹配比方位匹配更重要, 所以我们可以通过选择语言文件来平衡,MyApp/res/drawable-en/.
11. MyApp/res/drawable-en/myimage.png
12. MyApp/res/drawable-port/myimage.png
术语
资源系统将一系列分散内容集合在一起形成最终的完整的资源功能,去帮助我们了解整个系统。这里有一些核心概念以及组件的概要说明,你在开发中将可能使用到:
最终文件: 应用程序的独立的数据包。这包含所有从java 程序编译成的目标文件,图像(例如PNG 图片), XML 文件等等。这些文件以一种特定的方式组织在一起,在程序打包最后时,它们被打包成一个独立的ZIP 文件。
aapt: Android 最终文件打包工具。这个工具产生最终程序的ZIP 文件。除了将最终的元数据文件打包在一起,它也解析资源定义到最终的二进制数据里。
资源表:aapt 工具产生的特殊的文件,描述了所有在程序/包里的资源。这个文件可以通过资源类来访问;它不能直接和应用程序接触。
资源: 资源表里一条记录描述的是单一的命名值。大体上, 资源分成两种:基本的和包装的.资源标识符: 在资源表里所有的资源都被唯一的整数标识着。所有的代码中(资源描述,XML 文件,Java 源代码)你可以直接使用符号名代替真实的整数数值。
基本资源: 所有基本资源都可以被写成一个简单的字串,使用一定的格式可以描述资源系统里各种不同的基本类型:整数,颜色,字串,其他资源的引用,等等。像图片以及XML 描述文件这些复杂资源,被以基本字串资源储存,它们的值就是相关最终数据文件的路径。
包装资源: 有一种特殊类型的资源,不是简单的字符串,而是有一个随意的名字/数值配对列表。每个数值可以对应它本身的资源标识,每个值可以持相同类型的字符串格式的数据作为一个正常的资源。包装资源支持继承:一个包里的数据能从其他包里继承,有选择地替换或者扩展能产生你自己需要的内容。
种类: 资源种类是对于不同需求的资源标识符而言的。例如,绘制资源类常常实例化绘制类的对象,所以这些包含颜色以及指向图片或XML 文件的字符串路径数据是原始数据。其它常见资源类型是字符串(本地化字符串),颜色(基本颜色),布局(一个指向XML 文件的字串路径,它描述的是一个用户界面)以及风格(一个描述用户接口属性的包装资源)。还有一个标准的“attr”资源类型,它定义了命名包装数据以及XML 属性的资源标识符。
风格: 包含包装资源类型的名字常常用来描述一系列用户接口属性。例如,一个
TextView 的类可能会有一个描述界面风格的类来定义文本大小,颜色以及对齐方式。
在一个界面布局的XML 文件中,可以使用“风格” 属性来确定整体界面风格,它的值就是风格资源的名字。
风格类: 这里将详述一些属性资源类。其实数据不会被放在资源表本身,通常在源代码里它以常量的形式出现,这也可以使你在风格类或者XML 的标签属性里方便找到它的值。例如,Android 平台里定义了一个“视图”的风格类,它包含所有标准视图的属性:
画图区域,可视区域,背景等。这个视图被使用时,它就会借助风格类去从XML 文件取得数据并将其载入到实例中。
配置: 对许多特殊的资源标识符,根据当前的配置,可以有多种不同的值。配置包括地区(语言和国家),屏幕方向,屏幕分辨率,等等。当前的配置用来选择当资源表载入时哪个资源值生效。
主题: 一个标准类型的资源能为一个特殊的上下文提供全局的属性值。例如,当应用工程师写一个活动时,他能选择一个标准的主题去使用,白色的或者黑色的;这个类型能提供很多信息,如屏幕背景图片/颜色,默认文本颜色,按钮类型,文本编辑框类型,文本大小,等。当布置一个资源布局时,控件(文本颜色,选中后颜色,背景)的大部分设置值取自当前主题;如果需要,布局中的风格以及属性也可以从主题的属性中获得。
覆盖层: 资源表不能定义新类型的资源,但是你可以在其他表里替换资源值。就像配置值,这可以在装载时候进行;它能加入新的配置值(例如,改变字串到新的位置),替换现有值(例如,将标准的白色背景替换成"Hello Kitty"的背景图片),修改资源包(例如修改主题的字体大小。白色主题字体大小为18pt)。这实际上允许用户选择设备不同的外表,或者下载新的外表文件。
资源引用
资源引用 这份文档提供了不同类型资源的详细列表,并提供了如何在Java 代码中使用资源以及如何引用资源的描述。
国际化和本地化
即将完成: 国际化和本地化是非常关键的,但现在的SDK 还没有完全支持好。当SDK成熟时,这个章节会包含Android 平台国际化和本地化的相关信息。 那时,外部字串以及良好的结构将会使得创建和使用资源变得更省事。
三、开发工具箱
Android 设计哲学
即使平台之间有很大的不同,但是如何利用API 创建应用程序的学习过程是大同小异的。一般来说,有两个步骤:首先,应该知道怎么用API 实现你的功能。其次,要了解平台间的细微差别。换句话说,首先你应该学会如何创建应用程序(了解应用程序的基本结构等),然后就要学会根据具体情况实现这个应用程序。
相比而言,第二阶段(学习使用正确的方法来实现应用程序)通常需要很长一段时间,在这个过程中你会不断地写代码,犯错误,然后从错误中吸取教训。显然,这不是一个有效的学习方法,本小节和下面的一些连接针对这向你伸出援助之手,教你怎么学习创建你的应用程序。
在此之前,先讲一个要点:成功的应用程序往往提供一个突出的用户体验。当Android团队构建了一个有着健壮核心的系统时,大多数的用户体验将来源于用户和应用程序之间的的交互。因此,我们鼓励你们花时间去构建应用程序优秀的用户体验。
显著的用户体验体现在三个核心特征上:1、快速;2、响应;3、无缝。当然,自从计算机出现以后,每一个平台都曾经有过类似的三种性质。尽管如此,每个平台实现这些特性的方式也有所不同;下面将会简单的介绍在Android 平台下面你的应用程序将如何达到这些要求。
快速(Fast)
Android 程序执行应该是很快的。当然,准确来说它的程序应该执行的很有效率(有效率才会快)。在目前的计算机世界里哟这样一个倾向:假设摩尔定律能够最终解决我们所有的问题。当这种倾向遇到嵌入式应用程序的时候,摩尔定律就变得有些复杂了。
与桌面和服务应用程序不一样,摩尔定律在移动设备应用程序上面不是真正适用的。摩尔定律实际上是关于电子晶体管集成密度的,它原本的含义是:随着时间的流逝,在给定的电路尺寸上面可以集成更多的电路。对于桌面和服务应用陈旭来说,它的含义是:
你可以打包更多的“速度”在一个大小差不多的芯片上面,速度的提高,其他相应的一些性能也会显著的提高。对以像手机这样的嵌入式应用程序来说,相反的,使用摩尔定律是为了使芯片变得更小。这样,随着芯片密度的提高,相同功能的芯片会变得越来越小,功耗会越来越低,从而使手机做的越来越小,电池的持续的时间越来越长。这样就导致手持嵌入式设备相对于桌面系统来说正在以一种比较慢二实际的速度在增涨。因而,对于嵌入式设备来说,摩尔定律就意味着更多的功能和更少的功耗,速度只是次要的。这就是我们要写出高效代码的原因:你不能天真的认为电话在速度上面的增涨速度和桌面、服务应用程序是一样的。一般来说,高效的代码意味着最小的内存占用,意味着紧凑的风格,意味着避免了因为某种语言和编码习惯对性能的影响。我们用面向对象这个概念来理解,大部分这样的工作都是在方法层面上,与实际的代码,循环等等相类似。
在 “编写高效Android” 一文中,我们会对此做详细的介绍。
响应(Responsive)
我们有可能能够编写赢得世界上所有的性能测试的代码,但是用户使用起来会感到很恼火,这是因为应用程序没有足够的响应性——让人感觉反映迟钝,在关键的时刻失灵,或者处理输入太慢。在Android 平台下,那些响应性不够的应用程序会经常弹出"Application Not Responding" (ANR)这样的致命消息。
通常,这会在应用程序不能响应用户的输入的情况下发生。例如,你的应用程序在一些I/O 操作上(如网络接口调用)阻塞,这是主线程将不会处理用户的输入事件,一段时间之后应用系统就会认为你的程序挂起了,就会给一个选项给用户询问是否结束它。同样的,如果你的应用程序花费很多时间去构建内存中的一个结构、或者计算游戏的下一步,这时系统同样会认为程序已经挂起。当碰到上面情况的时候,要确保计算的高效性,但是即使是最高效的代码也需要花费时间。
在上面两个例子中,问题的解决方案是建立一个子线程来处理大部分的工作,这样就能保证你的主线程(响应用户界面事件)一直运行,这样防止系统认为你的程序已经僵化。因为这种线程的实现一般在“类”(CLASS)这个层次上,你可以把响应当成是类的问题来处理(这里,可以和方法层次描述的基本性能相比较)。
这里只是一个简单的介绍,在 “构建响应Android 应用程序” 一文中对于应用程序的响应性有详细的介绍。
无缝性(Seamless)
即使是你的应用程序执行很快,并且具有很高的响应性,它仍然有可能让用户苦恼。一个常见的例子是后台进程(比如Android 的 Service 和 BroadcastReceiver)对某些事件可能会突然弹出一个UI 响应。这似乎是无关紧要的,并且一般开发者会认为这这是正常的,因为他们花费了戴亮时间去测试和使用自己的应用程序。可是,Android 应用程序模型的构建是能够允许用户在不同的应用程序之间进行流畅的切换。这就意味着,当你的后台进程实际上弹出那个UI 的时候,用户可能正在系统的其他部分中,做一些其他的事情,如在接电话。想像一下,如果SMS 服务每次都会在文本消息传入时弹出一个对话框,这很快就会使用户崩溃。这就是为什么Android 标准对于这些事件使用的是通知(Notifications)机制;这使用户能够自己控制。
这仅仅是一个例子,相似的例子数不胜数。比如,如果Activities 没有正确的实现onPause()方法和其他生命周期方法,这将会导致数据丢失。或者如果你的应用程序有意的暴露数据给其他应用程序使用,你应该使用一个ContentProvider,而不是用一个路人皆可见的未加工过的文件或者数据库。
这些例子有一个共同的特点,他们都涉及到程序与程序或则程序与系统之间的交互。系统被设计为将多个应用程序视为一种松耦合组件的联合,而不是大块的代码黑盒。这就允许作为开发人员的你将整个系统看作是一个这些组件的大联合。这允许你干净地封装,无缝地和其他应用程序结合,因而你能设计自己喜欢的程序。
这使一种组件层次的概念(与性能和响应的类层次和方法层次相对应)。至于怎样编写无缝性能很高的代码, “与系统相结合” 一文中将会对此做出介绍,提供代码提示和最佳实例。
构建自定义组件
Android 中,你的应用程序程序与View 类组件有着一种固定的联系,例如按钮(Button)、文本框(TextView), 可编辑文本框(EditText), 列表框(ListView), 复选框(CheckBox),单选框(RadioButton), 滚动条(Gallery), 微调器(Spinner), 等等,还有一些比较先进的有着特殊用途的View 组件,例如 AutoCompleteTextView, ImageSwitcher 和TextSwitcher。除此之外,种类繁多的像 线性布局(LinearLayout), 框架布局(FrameLayout), 这样的布局组件(Layout)也被认为是View 组件,他们是从View类派生过来的。
你的应用程序就是这些控制组件和布局组件以某种方式结合显示在屏幕上,一般来说这些组件对你来说基本够用,但是你也应该知道你是可以通过类继承创建属于自己的组件,一般可以继承像View、Layouts(布局组件)这样的组件,甚至可以是一些比较高级的控制类组件。下面我们说一下为什么要继承:
· 你可以为实现某种功能创建一个完全自定义风格的组件,例如用二维的图形创建控制组件实现声音的控制,就像电子控制一样。
· 你可以把几种组件结合形成一个新的组件,你的组件可能同时包含ComboBox
(一个能输入的文本列表)和dual-pane selector control(左右两个List 窗口,
你可以分配窗口每一项的从属关系)等等。
· 你可以创建自己的布局组件(Layout)。SDK 中的布局组件已经提供了一系列的选项让你打造属于自己的应用程序,但是高级的开发人员会发现根据现有的
Layout 组件开发新的Layout 组件是很有必要的,甚至是完全从底层开发新的组
件。
· 你可以覆盖一个现有组件的显示或功能。例如,改变EditText(可编辑文本)组件在屏幕上的显示方式(可以参考Notepad 的例子,里面教你如何创建一个下划线的显示页面)。
· 你可以捕获像按键按下这样的事件,以一些通用的方法来处理这些事件(一个游戏的例子)。
为了实现某种目标你可能很有必要扩展一个已经存在的View 组件,下面我们结合一些例子教你如何去做。
基本方法(The Basic Approach )
下面的一些步骤都比较概括,教你如何创建自己的组件:
1. 让你的类(Class)继承一个现有的View 类或View 的子类。
2. 重载父类的一些方法:需要重载的父类方法一般以‘on’开头,如onDraw(),onMeasure()和 onKeyDown()等等。 这个在Activity 或则 ListActivity 派生中同样适用,你需要重载一些生命周期函数和一些其他功能性的HOOK 函数。
3. 使用你的继承类:一旦你的继承类创建完成,你可以在基类能够使用的地方使用你的继承类,但完成功能就是你自己编写的了。
继承类能够定义在activities 里面,这样你能够方便的调用,但是这并不是必要的(或许在你的应用程序中你希望创建一个所有人都可以使用的组件)。
完全自定义组件(Fully Customized Components)
完全自定义组件的方法可以创建一些用于显示的图形组件(graphical components),也许是一个像电压表的图形计量器,或者想卡拉OK 里面显示歌词的小球随着音乐滚动。
无论那种方式,你也不能单纯的利用组件的结合完成,无论你怎么结合这些现有的组件。
幸运的是,你可以以你自己的要求轻松地创建完全属于自己的组件,你会发现不够用的只是你的想象力、屏幕的尺寸和处理器的性能(记住你的应用程序最后只会在那些性能低于桌面电脑的平台上面运行)。
下面简单介绍如何打造完全自定义的组件:
1. 最为通用的VIEW 类的父类毫无疑问是View 类,因此,最开始你要创建一个基于此类的一个子类。
2. 你可以写一个构造函数从XML 文件中提取属性和参数,当然你也可以自己定义这些属性和参数(也许是图形计量器的颜色和尺寸,或者是指针的宽度和幅度等等)
3. 你可能有必要写自己的事件监听器,属性的访问和修改函数和一些组件本身的功能上的代码。
4. 如果你希望组件能够显示什么东西,你很有可能会重载 onMeasure() 函数,因而你就不得不重载 onDraw() 函数。当两个函数都用默认的,那么 onDraw()函数将不会做任何事情,并且默认的 onMeasure() 函数自动的设置了一个100x100 —的尺寸,这个尺寸可能并不是你想要的。
5. 其他有必要重载的on... 系列函数都需要重新写一次。
onDraw()和onMeasure() onDraw()函数将会传给你一个 Canvas 对象,通过它你可以在二维图形上做任何事情,包括其他的一些标准和通用的组件、文本的格式,任何你可以想到的东西都可以通过它实现。
注意: 这里不包括三维图形如果你想使用三维的图形,你应该把你的父类由View 改为SurfaceView 类,并且用一个单独的线程。可以参考GLSurfaceViewActivity 的例子。
onMeasure() 函数有点棘手,因为这个函数是体现组件和容器交互的关键部分,onMeasure()应该重载,让它能够有效而准确的表现它所包含部分的测量值。这就有点复杂了,因为我们不但要考虑父类的限制(通过onMeasure()传过来的),同时我们应该知道一旦测量宽度和高度出来后,就要立即调用setMeasuredDimension() 方法。
概括的来讲,执行onMeasure()函数分为一下几个阶段:
1. 重载的onMeasure()方法会被调用,高度和宽度参数同时也会涉及到
(widthMeasureSpec 和heighMeasureSpec 两个参数都是整数类型),同时你应该考虑你产品的尺寸限制。这里详细的内容可以参考View.onMeasure(int,int) (这个连接内容详细的解释了整个measurement 操作)。
2. 你的组件要通过onMeasure()计算得到必要的measurement 长度和宽度从而来显示你的组件,它应该与规格保持一致,尽管它可以实现一些规格以外的功能(在这个例子里,父类能够选择做什么,包括剪切、滑动、提交异常或者用不同的参数又一次调用onMeasure()函数)。
3. 一旦高度和宽度计算出来之后,必须调用setMeasuredDimension(int
width, int height),否则就会导致异常。
一个自定义组件的例子(A Customized Component Example)
在 API Demos 中的CustomView 提供了以一个自定义组件的例子,这个自定义组件在LabelView 类中定义。
LabelView 例子涉及到了自定义组件的方方面面:
· 首先让自定义组件从View 类中派生出来。
· 编写带参数的构造函数(参数可以来源于XML 文件)。这里面的一些处理都已经在View 父类中完成,但是任然有些Labelview 使用的自定义组件特有的新的参数需要处理。
· 一些标准的Public 函数,例如setText(), setTextSize(),
setTextColor()
· 重载onMeasure()方法来确定组件的尺寸(注意:在LabelView 中是通过一个私有函数measureWidth()来实现的)
· 重载onDraw()函数把Lable 显示在提供的canvas 上。
在例子中,你可以通过custom_view_1.xml 看到自定义组件LabelView 的用法。在XML文件中特别要注意的是android:和app:两个参数的混合运用,app:参数表示应用程序中被认为是LabelView 组件的个体,这些也会作为资源在R 类中定义。
组件混合技术Compound Components (or Compound
Controls)
如果你不想创建一个完全自定义的组件,而是由几个现有组件的组合产生的新的组件,那么混合组件技术就更加适合。简单的来说,这样把几个现有的组件融合到一个逻辑组合里面可以封装成一个新的组件。例如,一个Combo Box 组件可以看作是是一个EditText 和一个带有弹出列表的Button 组件的混合体。如果你点击按钮为列表选择一项,
在Android 中,其实还有其他的两个View 类可以做到类似的效果: Spinner 和
AutoCompleteTextView,,但是Combo Box 作为一个例子更容易让人理解。
下面简单的介绍如何创建组合组件:
1. 一般从Layout 类开始,创建一个Layout 类的派生类。也许在Combo box 我们会选择水平方向的LinearLayout 作为父类。记住,其他的Layout 类是可以嵌套到里面的,因此混合组件可以是任何组件的混合。注意,正如Activity 一样,你既可以使用外部XML 文件来声明你的组件,也可以嵌套在代码中。
2. 在新的混合组件的构造函数中,首先,调用所有的父类的构造函数,传入对应的参数。然后可以设置你的混合组件的其他的一些方面,在哪创建EditText 组件,又在哪创建PopupList 组件。注意:你同时也可以在XML 文件中引入一些自己的属性和参数,这些属性和参数也可以被你的混合组件所使用。
3. 你也可以创建时间监听器去监听新组件中View 类触发的事件,例如,对List 选项单击事件的监听,你必须在此时间发生后更新你EditText 的值。
4. 你可能创建自己的一些属性,带有访问和修改方法。例如,允许设置EditText
初始值并且提供访问它的方法。
5. 在Layout 的派生类中,你没有必要去重载onDraw()和onMeasure()方法,因为Layout 会有比较好的默认处理。但是,如果你觉得有必要你也可以重载它。
6. 你也可能重载一些on 系列函数,例如通过onKeyDown()的重载,你可以通过按某个键去选择列表中的对应的值。
总之,把Layout 类作为基类有下面几个优点:
· 正如activity 一样,你也可以通过XML 文件去声明你的新组件,或者你也可以在代码中嵌套。
· onDraw()函数和onMeasure()函数是没有必要重载的,两个函数已经做得很好了。
· 你可以很快的创建你的混合组件,并且可以像单一组件那样使用。
混合组件的例子(Examples of Compound Controls)
In the API Demos project 在API Demos 工程中,有两个List 类的例子——Example 4
和Example 6,里面的SpeechView 组件是从LinearLayout 类派生过来,实现显示演讲显示功能,对应的原代码是List4.java 和List6.java。
调整现有组件(Tweaking an Existing Component)
在某些情况下,你可能有更简单的方法去创建你的组件。如果你应经有了一个非常类似的组件,你所要做的只是简单的从这个组件派生出你的组件,重在其中一些有必要修改的方法。通过完全自定义组件的方法你也可以同样的实现,但通过冲View 派生产生新的组件,你可以简单获取一些已经存在的处理机制,这些很可能是你所想要的,而没有必要从头开始。
例如,在SDK 中有一个NotePad 的例子(NotePad application )。该例子演示了很多Android 平台实用的细节,例如你会学到从EditView 派生出能够自动换行的记事本。这还不是一个完美的例子,因为相比早期的版本来说,这些API 已经感变了很多,但它确实说明了一些问题。
如果你还未查看该程序,现在你就可以在Eclipse 中导入记事本例程(或仅通过提供的链接查看相应的源代码)。特别是查看NoteEditor.java 中的MyEditText 的定义。
下面有几点要注意的地方:
1. 声明(The Definition)
这个类是通过下面一行代码来定义的:
public static class MyEditText extends EditText
o 它是定义在NoteEditor activity 类里面的,但是它是共有的
(public),因此如果有必要,它可以通过NoteEditor.MyEditText 从
NoteEditor 外面来调用。
o 它是static 类(静态类),意味着不会出现所谓的通过父类访问数据的
“虚态方法”, 这样就使该类成为一个可以不严重依赖NoteEditor 的单独
类。对于不需要从外部类访问的内联类的创建,这是一个很清晰地思路,保
证所产生的类很小,并且允许它可以被其他的类方便的调用。
o 它是EditText 类的扩展,它是我们选择的用来自定义的父类。当我们
完成以后,新的类就可以作为一个普通的EditText 来使用。
2. 类的初始化
一般来说,父类是首先调用的。进一步来说,这不是一个默认的构造函数,而是
一个带参数的构造函数。因为EditText 是使用从XML 布局文件提取出来的参
数进行创建,因此我们的构造函数也要取出参数并且将这些参数传递给父类。
3. 方法重载
在本例中,仅对onDraw()一个方法进行重载。但你可以很容易地为你的定制组
件重载其他需要的方法。
对于记事本例子来说,通过重载onDraw()方法我们可以在EidtView 的画布
(canvas)上绘制蓝色的线条(canvas 类是通过重写的onDraw()方法传递)。
该函数快要结束时要调用super.onDraw()函数。父类的方法是应该调用,但
是在这个例子里面,我们是在我们划好了蓝线之后调用的。
4. 使用定制组件
现在,我们已经有自己定制的组件了,但是应该怎样使用它呢?在记事本例子中,
定制的组件直接在预定义的布局文件中使用,让我们看一看res/layout 目录
中的note_editor.xml 文件。
<view
xmlns:android="http://schemas.android.com/apk/res/android"
class="com.android.notepad.NoteEditor$MyEditText"
id="@+id/note"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@android:drawable/empty"
android:padding="10dip"
android:scrollbars="vertical"
android:fadingEdge="vertical" />
o 该自定义组件在XML 中是作为一个一般的View 类来创建的,并且是通过全路径包来描述的。注意这里内联类是通过NoteEditor$MyEditText 来表示的,这是Java 编程中引用内联类的标准方法。
o 在定义中的其他属性和参数将传递给定制组件的构造函数,然后才传到EditText 构造函数中,因此这些参数也是你使用EditText 组件的参数。注意,这里你也可以增加你自己的参数,我们将在下面讨论这个问题。这就是你全部需要做的,诚然这是一个简单的例子。但问题的关键是:你的需求有多复杂,那么你的自定义组件就有多么复杂。
一个更为复杂的组件可能需要重载更多的on 系列函数,并且还要很多特有的函数来充分实现自定义组件的功能。唯一的限制就是你的想象力和你需要组件去执行什么工作。
现在开始你的组件化之旅吧
如你所见,Android 提供了一种精巧而又强大的组件模型,让你尽可能的完成你的工作。从简单的组件调整到组件混合,甚至完全自定义组件,灵活的运用这些技术,你应该可以得到一个完全符合你外观要求的的Android 程序
Android 平台的可选API
Android 适用于各种各样的手机,从最低端直到最高端的智能手机。核心的Android API在每部手机上都可使用,但任然有一些API 接口有一些特别的适用范围:这就是所谓的“可选API”。这些API 之所以是“可选的”,主要是因为一个手持设备并不一定要完全支持这类API,甚至于完全不支持。例如,一个手持设备可能没有GPS 或Wi-FI 的硬件。在这个条件下,这类功能的API 任然存在,但不会以相同的方式来工作。例如Location API 任然在没有GPS 的设备上存在,但极有可能完全没有安装功能提供者,意味着这类API 就不能有效地使用。
你的应用应该无障碍地运行或连接在一个可能不支持你API 的设备,因为你的设备上有这些上层接口(the classes)。当然执行起来可能什么也不会做,或者抛出一个异常。
每个API 会做些什么我们可以参考这些API 的说明文档,你应该编写你的程序来适当的处理这类问题。
Wi-Fi API
Wi-Fi API 为应用程序提供了一种与那些带有Wi-FI 网络接口的底层无线堆栈相互交流的手段。几乎所有的请求设备信息都是可利用的,包括网络的连接速度、IP 地址、当前状态等等,还有一些其他可用网络的信息。一些可用的交互操作包括扫描、添加、保存、结束和发起连接。Wi-Fi API 在 android.net.wifi 包中。
定位服务(Location-Based Services)
定位服务允许软件获取手机当前的位置信息。这包括从全球定位系统卫星上获取地理位置,但相关信息不限于此。例如,未来其他定位系统可能会运营,届时,对其相应的API 接口也会加入到系统中。定位服务的API 在android.location 包中。
多媒体API(Media APIs)
多媒体API 主要用于播放媒体文件。这同时包括对音频(如播放MP3 或其他音乐文件以及游戏声音效果等)和视频(如播放从网上下载的视频)的支持,并支持"播放URI地址"(Note:URI 即是统一资源识别地址)模式-在网络上直接播放的流媒体。技术上来说,多媒体API 并不是“可选的”,因为它总是要用到。但是不同的硬件环境上面可能有不同的编解码的硬件机制,因而它又是“可选的”。多媒体API 在 android.media 包中。
基于OpenGL 的3D 图形(3D Graphics with OpenGL)
Android 的主要用户接口框架是一个典型的面向控件的类继承系统。但不要让表面的情况迷惑了你,因为在它下面是一种非常快的2D 和3D 组合的图形引擎,并且支持硬件加速。用来访问平台3D 功能的API 接口是OpenGL ES API。和多媒体API 一样,OpenGL 也不是严格意义上的“可选”,因为这些API 会总是存在并且实现那些固定的功能。但是,一些设备可能有硬件加速环节,使用它的时候就会影响你的应用程序的表现。