跳转至

翻译:koffuxu 校对:Ross.Zeng

应用开发者观点

鉴于Android开发API不同于其它已经存在的API,包含其它在Linux世界的事,这是非常重要的,发一点时间从应用开发者的角度来理解“Android”看起来是什么,就算是一个研究过AOSP的人来说,这都不是一件容易的事。作为一个以Android为系统的嵌入式设备的开发者,你也许没有理解Android的应用开发API的特质,但是你也许有同事了解。如果也没有,你不妨与应用开发者分享一些共同的信息。当然,这部分是简单的一个总要,我建议你读一些Android应用开发去获得一些更深的部分。

Android 概念

作为一个应用开发者,当开发Android应用时,需要把一些关键的概念弄清楚。这些概念能构建整个Android应用的架构,以及开发者可以或者不可以的指令。总之,他使用户的生活更好,但要处理好也是有挑战的。

组件

Android应用是一些松耦合的组件构成。一个应用的组件可以被其它应用调用或者使用。最重要的是,Android App没有一个单独的入口函数:也就是说没有main()或者其它类似的函数。相反,一些被intents(意图)预定义的事件,开发者可以把自已的组件挂在上面,从而,在相应的事件发生,就会调用定义的组件。一个简单的例子,组件处理用户通信录数据库,当用户在拨号或者其它APP按一个联系人按钮时,这就会被调用。因此,一个app根据它所有的组件会有多个入口。

这里有4大组件:

Activity

就像一个“窗口”,是基于GUI系统,构建了主要的交互窗口,activities组成了Android app主要的构架。跟窗口不一样的是,activities不能被 最大化,最小化,和调整大小。相反地,activities总是占据了整个可视区域用来堆叠彼此。同样的,浏览器的网页以一定的序列来访问,允许用户使用“后退”来浏览之前的内容。事实上,在前几章的描述中,所有Android设备必须有“后退”键让用户回到前一个行为。与网页浏览不同的是,这没有类似网页“前进”的按键,也许只有“后退”。

一个全局的Android intent允许一个activity以一个图标的形式显示在app Launcher(主要app list),因为许多重要的app想要在Launcher上,它们至少一个activity能够影响这个intent。通常,用户从一个特定的activity到结束,会创建一个堆栈,所有相关的他们最初起动;这个应用的堆栈就是task。用户可以通过HOME按键切换到另外一个任务,从Launcher启动另外一个应用堆栈。

services

Android services类似于Unix系统的后台进程或者是守护进程。本质上,一个service被激活是在其它组件请求这个服务,服务就通过这个调用者激活了。最重要的是,服务可以提供外部应用程序组件,因此,显露一些应用程序的核心功能给其它应用程序。一个服务被激活使用通常没有一个可视的界面。

Broadcast Receivers

广播接收器类似一个中断处理句柄。当一个关键的事件发生,一个广播接收器将会被触发去处理这个事件。比如,一个应用程序当电量较低的时候被提醒,或者当是“飞行模式”被激活的时候。当它们已经注册的事件没有发生,广播接收器将不会被激活。

Content Providers 内容提交者本质是一个数据库。通常,如果一个应用想提供其它应用程序访问的数据就需要一个内容提交者。比如,你编译一个Twitter客户端,你将要提供给其它应用访问Twitter的内容。所有的content prividers提供同校报API,不管你怎么实现你的内部。在Android,许多Content Provider依赖SQLite数据库的功能,但同样可以使用文件或者其它存储类型。

意图

意图Intent是Andoird非常重要的概念之一。它是一种在各组件之后延迟绑定(late-binding)机制。应用开发者可以从一个应用发送意图给“浏览”一个网页或者“浏览”一个PDF,因此,用户就可以浏览目标HTML或者PDF文档,尽管这个请求的APK没有包含这些内容。使用意图的更多乐趣也是可能的。应用开发者可以发送一个指定的意思去触发一个电话括号。

考虑到意图作为一种多态的Unix信号,没有必要预定义或者请求一个指定目的地的组件或者应用程序。意图类似于一个被动的对像。它的内容(负载),这种用于发送的机制与系统的行为规则一致。比如系统规则的一种,意图试图于组件发送。比如,一个意图发送给一个服务,那么只有服务才能收到这个,不是一个Activity或者Broadcast receiver.

组件可以被声明为指定意图的声明,通过manifest文件。在运行时,系统将传输相匹配的意图来触发相应的组件。一个意图同样被一个指定的组件发送,绕过需要声明,在接收组件的意图过滤器。明确的调用,需要应用知道指定的组件的提前时间,在同一个应用组件中想必改善是意图典型的应用。

组件生命周期

Android另外一个重要的概念,用户不能去管理这任务的切换。因此,Android的是没有任务条以及类似功能的。然而,用户可以打开任意多的应用。通过HOME按键进入Home主界面,然后再选择另外一个应用进入。这个应用也许是一个新的。也许是之前已经打开过,Acitivity Stack(或者说任务)已经存在的。

(corollary?)这种设计会导致应用程序逐渐消耗越来越多的系统资源,而这些资源不能保证操作永远继续下去。有些时候,系统会不得不回收一些最近较少使用或无优先级的组件来获取资源,留给新激活的组件使用。然而,资源回收应该对用户完全透明。换言之,当旧组件被新组件换下后,用户又回到原来的组件时,组件应该从它被换下的地方开始启动,就好像它一直在内存中等待一样。

为了实现这个行为,安卓定义了一个标准生命周期组件。应用程序开发人员必须通过实现一系列与生命周期相关事件触发的组件回调,从而管理组件的生命周期。例如,当一个活动不再存在于前台(因此比在前台更容易被杀),它的onPause()回调则被触发。

处理组件回调是应用开发人员最大的挑战之一,因为在处理关键事件时他们必须要小心地保存和恢复组件状态。最理想的结果是,用户不需要在应用间切换任务或担心开启新应用后之前的应用会被杀掉。

Manifest文件

如果一定要说一个应用中的“主”入口点,那么manifest文件可以算得上。基本上,它告诉了系统应用程序的组件,运行应用的要求,最小API等级以及硬件需求等等。Manifest格式像xml文件一样,以AndroidManifest.xml名称被保存在应用程序文件的最顶端目录中。应用的组件通常都是静态地描述在manifest中。事实上,除了广播接收器可以在运行时被动态注册,其余的组件都必须在构建时被声明在manifest文件中。

进程与线程

无论应用何时被激活,是由系统还是其他应用激活,都会起一个进程来容纳该应用的组件。除非开发人员重载了系统默认方法,否则初始组件起来之后所有的应用组件都会跑在同一个进程中。换句话说,一个应用的所有组件都会运行在单一的Linux进程里。因此,开发人员需要避免在标准组件中进行耗时或阻塞操作,并将这类操作放在线程中实现。

而且,用户基本会允许激活任意数量的组件,基本上,这几个为包含用户组件的应用程序服务的Linux进程会一直存在于系统中。当运行太多进程导致无法无法启动新进程时,Linux内核将启动OOM机制。这时,安卓内核中的OOM将被调用,并会杀掉必须结束的进程以释放空间。

简而言之,安卓的整体行为取决于低内存情况。

如果应用开发人员已经正确实现了组件的生命周期,这时应用的进程被OOM杀掉后,用户不会看到任何不良的行为。实际上,出于实际目的,用户甚至不会注意到容纳应用程序组件的进程消失,并且随后又会“自动”重新创建。

远程过程调用(RPC)

与系统中许多其他组件一样,安卓定义了它自己的RPC/IPC(IPC-进程间通信)机制:Binder。所以跨组件通信不是使用典型的系统IPC或套接字实现,而是通过访问/dev/binder的方式使用内核Binder机制,这部分会在本章稍后部分介绍。

应用开发人员并不会直接使用Binder机制,相反,他们必须使用安卓的接口描述语言(IDL)。接口描述通常保存在一个.aidl文件中,由aidl工具处理生成适当的存根,使用binder机制传输数据,以及在传输对象的要求下编组和解编代码。

框架介绍

除了之前讨论的概念之外,安卓还定义了自己的开发框架,它允许开发人员在其他开发框架中访问基本功能。我们来看一看这个框架以及它的能力。

用户接口 User Interface

安卓的UI元素包含了如按钮,文本框,对话框,菜单和事件处理这些传统控件。如果开发人员使用过其他UI框架,那么接触这部分接口相对简单。

安卓中所有的UI对象都是继承于View类,并组织在ViewGroup的层次结构中。一个活动的UI可以静态地在XML(也是最常用的方法)中定义,也可以在java中动态声明。如果需要的话,也可以在运行时由java修改。一个活动的UI会在其处于ViewGroup顶部时才会显示。

数据存储 Data Storage

安卓为开发人员提供了多种数据存储选项。对于简单的存储需求,安卓提供了shared preferences,允许将密钥对值保存在所有组件共享的组件或特定的单独文件中。开发者也可以直接操作这些文件,文件由应用程序私下存储,所以其他程序可能无法读写。应用开发人员也可以使用安卓中的SQLite管理自己的私有数据库。其他应用可以通过内容提供者组件来访问这些数据库。

安全和权限 Security and Permissions

安卓的安全是在进程级别的。换言之,安卓依赖Linux现有的进程隔离机制来实现自己的策略。为此,每个应用安装后都会获得自己的UID个GID。本质上就像每个应用都是系统中单独的用户一样。在多任务的Unix系统中,如果没有明确的权限授权,“用户”们不能够访问彼此的资源。实际上,每个应用都运行在独立的沙盒中。

要离开沙盒并访问关键的系统功能或资源,应用必须要使用安卓权限机制。这就需要开发人员在manifest文件中静态地声明应用所需的权限。如访问互联网(即使用套接字)、拨打电话或使用摄像头都需要由安卓去预先定义。其他权限可由开发者声明,为其他应用与指定应用之间的组件交互。当应用安装之后,提示用户批准应用程序所需的权限。

安卓开发框架提供了更多功能,当然不是这小节内容涵盖得了的。这部分内容可以阅读其他安卓应用开发的相关信息,或者访问developer.android.com获取更多关于2D、3D图像,多媒体,定位及地图,蓝牙,NFC等内容。

应用开发工具

就算你不想为你的嵌入式系统开发任何应用,还是强烈建议你在工作站上搭建应用开发环境。这也方便编写测试应用验证AOSP修改的影响。如果计划扩展AOSP的API并且创建和发布自己的SDK,这也是必不可少的。

搭建应用开发环境可以参考之前网站中谷歌给出的手册,此处作者顺便推销了一下自己的另一本书。

原生开发

事实上,绝大部分的应用都是在我们刚才讨论的环境中使用java所开发的,有的开发者也需要运行原生的C代码。为此,谷歌提供了原生开发工具包(NDK)。正如宣传的那样,这主要是为了让游戏开发者们挤出设备最后一丝性能。因此,NDK中的API主要提供图形渲染以及传感器方面的功能。例如极为出名的游戏——愤怒的小鸟,就非常依赖原生代码。

另一个可能用到NDK的情景是将现有的代码库移植到安卓上。如果你手上是开发了多年的古老C代码(对于开发了其他移动应用的工作室来说十分常见),并不希望把这些东西再用java重写一遍。相反,你可以使用NDK编译之后打包到java代码中,并通过SDK的方式提供给安卓使用。例如安卓上的火狐浏览器,就非常依赖NDK去运行那部分历史代码。

使用NDK的好处是可以通过SDK的形式将java代码与C代码结合使用。也正如刚才暗示的,NDK能够提供的仅仅是安卓API中有限的子集。比如你无法在NDK编译的C代码中发出一个意图,必须由java编写的SDK来做这件事情。需要提醒的是,NDK提供的API主要面向游戏开发人员。

有时嵌入式及系统开发人员希望通过NDK在安卓上完成一些平台级的工作。NDK中的“原生”一词或许在这里有一些误导,因为使用NDK时还需涉及到刚才所说对开发人员的限制和要求。所以对嵌入式开发者而言,请记住,NDK为应用开发人员在java中调用C代码提供了极大的帮助。除此之外,NDK对你所承担的工作类型几乎毫无用处。