Android程序用java语言编写。SDK工具编译所有的数据和资源到一个Android文件中,这个文件以.apk为后缀,表示一个应用程序,android设备可以安装这个程序。
程序安装好以后,它就运行在自己的安全沙盒中:
- Android系统是多用户Linux系统,每个程序是一个不同的用户。
- 默认情况下,系统给每个程序分配一个唯一的Linux用户ID(这个ID只是被系统使用,对于程序来说是未知的)。系统给每个用户ID分配了只有属于这个ID的程序才可以访问的文件。
- 每个进程有自己的虚拟机,所以程序间是分开运行的。
- 默认情况下,每个程序运作在自己的Linux进程中。当程序需要执行时会启动这个进程,在程序长时间不需要使用或者系统需要为其他程序提供更多内存时,系统会停止这个进程。
运用这种方法,Android系统实现了最小权限原则。就是说,默认情况下,每个程序自己访问自己的组件,做自己的工作。程序不能访问那些系统没有给与权限的部分,这就创造了一个安全的环境。
不过,程序间也可以共享数据,而且程序也可以访问系统服务:
- 可以安排两个程序使用同一个用户ID,这样就可以相互访问对方的文件了。为了节约系统资源,相同ID的程序运行在相同的Linux进程中,共享相同的虚拟机(程序也必须分配相同的证书)。
- 程序可以取得访问设备数据的权限,比如用户的联系人信息,SMS信息,可安装存储器(SD卡),摄像头,蓝牙等。程序权限必须在安装的时候分配。
下面的内容将告诉你程序是怎么存在于系统中的:
- 定义程序的核心框架组件。
- 为程序声明组件和设备需求的清单文件。
- 和代码分离的资源文件 ,以及可优化的设备配置。
程序组件
程序组件是Android程序的基本组成部分。每个组件都是系统进入的程序的不同的点。不是所有的组件都是真实的用户进入点,一些需要相互依赖,不过每个组件有存在的意义并扮演特定的角色 - 每个组件都是一个独特的构建,帮助你定义程序的行为。
程序组件分为4个类型,有不同的用途,每个组件都有不同的生命周期,定义了组件的是怎么被创建和销毁的。
Activities
Services一个activity就代表一个用户界面。例如,一个邮件程序可能有一个activity来显示新邮件列表,一个activity用来写邮件,另外一个activity用来读邮件。每个独立的activity结合在一起就形成了一个邮件程序。同样地,其他程序可以开启你的程序中的任何一个activity(如果你允许的话),例如,一个拍照程序可以开启邮件程序的写邮件的activity来分享他的照片。
Content providers一个service组件是运行在系统后台的,为了执行长时间的操作,或者执行远程处理。service没有用户界面。例如,运行在后台的播放音乐的service,或者是为activity取得网络数据的service。其他的组件,如activity可以开启一个service,或者绑定一个service从而与它交互。
Broadcast receivers一个content provider管理程序数据的共享集合。你可以保存数据在文件系统中,SQLite数据库中,网络中,或者任何其他程序可访问的存储器中。其他程序可以通过content provider访问和修改数据(前提是content provider允许)。例如,Android系统提供了一个content provider管理用户的联系人信息,任何有对应权限的程序都可以读写部分的content provider(比如ContactsContract.Data)。content provider也可以用来读写程序的私有数据。比如,Note Pad实例中就使用它来保存笔记。
Android系统的设计是,任何程序都可以启动另外一个程序的组件。例如,你想要用户通过设备拍一张照片,这里可能有一个已经实现的程序,你只需要用就可以,而不用重新开发一个。你也不需要包含或者链接摄像程序的代码,而只是启动摄像程序中拍照的activity。当这个activity执行完就会返回一张照片,你的程序就可以使用它了。对于用户来说,拍照就像是你的程序的一部分一样。一个broadcast receiver是一个用来响应系统广播的组件。很多广播发起于系统 - 例如,一个屏幕关闭的通知,电量不足的通知,或者一个照片被获取的通知。程序也能发出通知 - 例如,让其他程序知道某些数据已经被下载到设备的通知。虽然broadcast receiver不显示用户界面,不过可以创建一个状态条来提醒用户广播事件的发生。通常情况下,broadcast receiver只是程序间的一个“通道”,只需要做很简单的工作。例如,可以使用它来发起一个service去执行某些工作。
当系统开启一个组件时,它为程序开启进程(没有开开启的前提下),然后初始化需要的组件。例如,如果你的程序需要启动摄像程序的拍照activity,那么这个activity是运行在摄像程序的进程中,不是在你的程序进程中。和其他系统程序不同的是,android程序没有一个单一的进入点(比如类似main()这样的函数)。
由于每个程序都有自己的文件权限,其他程序无法直接访问到,你的程序也不能直接启动另外一个程序的组件,而Android系统可以运行任何程序组件,所以你必须发送一个信息给系统,指明你想启动某个组件的intent(意图),系统就会为你激活这个组件。
激活组件
四分之三的组件 - activity,service,broadcast receiver - 能被一个叫intent的异步消息所激活。在程序运行中,intent绑定两个独立的组件(你可以认为它们是从其他组件请求动作的信使)。
使用Intent对象创建一个intent,它定义一个消息去激活一个指定的组件,或者一个指定类型的组件,一个intent要么是显式的,要么就是隐式的。
对于activity和service,一个intent指定了要执行的动作(view或send)和指定的URI数据(激活组件可能需要知道的数据)。例如,一个intent可能为activity传递一个请求去展示一个图片或者打开一个网页。一些情况下,你可以开启一个activity来接收结果,这种情况下,返回来的结果也是通过intent实现的(例如,你可以发布一个intent让用户选择一个联系人,然后返回结果给你,返回的intent包含一个选择后的联系人的URI)。
对于broadcast receiver,intent只是简单定义了一个广播通知信息(比如一个电量不足的广播只包含一个动作字符“电量不足”)。
content provider不被intent所激活,而是被一个从ContentResolver发出的请求所激活。内容解析器调用ContentResolver对象的函数处理所有content provider的转换,而content provider不需要做任何事情。ContentResolver提供了content provider和组件请求信息间的一个抽象层。
下面是各种类型组件间特别的激活函数:
- 使用startActivity()传递一个intent开启一个activity(或者给一个activity一些新的事情做),要取得返回的结果的话使用startActivityForResult()。
- 通过startService()启动一个service(或者给正在运行的service发送新的指令)。或者你可以通过bindService()传递一个intent去绑定一个服务。
- 通过sendBroadcast(), sendOrderedBroadcast(), sendStickyBroadcast()传递intent去发送一个广播。
- 通过ContentResolver对象调用query()执行一个content provider的查询。
清单文件
在android系统启动程序组件前,必须先读取程序的AndroidManifest.xml文件(清单文件)来了解组件是否存在。你需要在这个文件中定义所有的组件,这个文件保存在程序的根目录下。
清单文件做下面这些事:
- 定义程序需要的用户权限。
- 定义最低API等级需求。
- 定义程序需要的硬件和软件特征,比如摄像头,蓝牙,多点触控屏幕。
- 程序需要的API库,比如Google Maps library。
- 已经其他更多东西。
声明组件
清单文件的主要任务就是告诉系统程序相关的组件。下面的代码声明了一个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>
- <service>
- <receiver>
- <provider>
activity,service,和content provider都要在清单文件中声明,不然程序不能运行。但是broadcast receiver可以在清单文件中声明,也可以在代码中动态创建并调用registerReceiver()在系统中注册。
声明组件功能
前面激活组件里面我们说到,可以使用Intent去开启activity,service和broadcast receiver。你可以在intent中使用组件的完整类名来激活组件。不过intent真正强大的地方是概念性的意图动作。你只是简单的描述你想执行的动作的类型,然后允许系统去为你寻找合适的组件来执行。如果有多个组件符合你的要求,那么你可以选择其中一个。
系统通过在清单文件中定义组件的intent过滤器来声明自己的功能,这些功能可以响应其他程序的intent。
声明组件的intent过滤器是在组件的声明元素中添加<intent-filter>元素。
例如,一个邮件程序中的写新邮件activity可以声明一个“send”意图过滤器(为了发送邮件)。在你的程序activity中,你可以创建一个send动作(ACTION_SEND)的intent,当你使用startActivity()方法时,系统就会匹配邮件程序中的sendactivity,并启动它。
声明程序需求
Android设备比较多样化,提供的特征和功能都不是完全一样。为了防止缺少你程序需求的设备安装你的程序,在清单文件中声明程序的设备和软件需求是非常重要的。大部分声明信息系统都不会去读取,但是Google Play这些外部服务会去读取他们,在用户搜索程序的时候提供一个过滤。
例如,如果你的程序需要一个摄像头和使用Android2.1和API,你需要声明这些需求到清单文件中。那么,那些没有摄像头并且版本低于2.1的设备就不能从Google Play中安装这个程序。
不过,你也可以声明你的程序使用摄像头不是必须的,这种情况下,你的程序必须执行一个检测去判断设备是否有摄像头,从而决定哪些特征是可用或不可用。
下面这些比较重要的设备特征是你开发和设计程序时要考虑的:
屏幕尺寸和密度
输入控制为了给屏幕尺寸分类,Android为设备定义了个特征:屏幕尺寸(屏幕的物理尺寸)和屏幕密度(屏幕的物理密度,单位是像素,或者dpi - 每英寸点数)。为了简化不同屏幕类型的控制,Android系统给它们进行了分组。屏幕尺寸是:small, normal, large, extra large。屏幕密度:low密度,medium密度,high密度,和extra high密度。默认情况下,你的程序应该兼容所有的屏幕尺寸和密度,因为Android系统为UI和图片资源做了适当的调节。不过,你要提供特别的样式去适应屏幕尺寸,提供特定的图片区适应屏幕密度,在样式文件中使用<supports-screens>指定程序支持的屏幕尺寸。
设备特征不同的设备提供不同的输入类型,比如硬键盘,轨迹球,五向导航板。如果你的程序需要一种特别的输入设备,你就需要在清单文件中使用<uses-configuration>元素声明,不过这种情况不太常见。
平台版本一些硬件和软件特征在设备中可能存在也可能不存在,比如摄像头,光线传感器,OpenGL版本或者高保真的触摸屏。你不能假设某个特征在所有的设备都能使用(除了标准的Android库),所以你应该在清单文件中使用<uses-feature>元素声明所有的特征。
当你在Google Play中发布你的程序时,在程序中声明所有需求是很重要的,Google商店使用这些声明过滤那些程序可用。这样,你的程序只对那些符合要求的设备可用。不同的android设备一般运行不同的android版本,比如android1.6或者android2.3,每个连续的版本都包含一些附加的低版本不可用的API。每个平台版本指定一个API级别(比如Android1.0的API级别是1, 2.3的级别是9)。如果你使用任何高于1.0级别的API,你都需要使用<uses-sdk>声明一个最低API级别。
应用程序资源
Android程序不仅仅是代码,还有很多必须的资源,比如,图片,声音文件,和任何可视化相关描述。例如,你可以定义动画,菜单,样式,颜色,界面布局在XML文件中。使用这些程序资源能让你在不修改代码的情况很容易的更改程序特征,并且能提供可选的资源集合来优化不同的设备类型(比如不同的语言和屏幕尺寸)。
对于每个包含在工程中的资源,SDK工具定义一个唯一的整形ID,你可以在代码中使用这些ID引用对应的资源。例如,你的程序包含一个名为logo.png的图片(保存在res/drawable目录中),SDK工具生成了一个名为R.drawable.logo的资源ID,你可以在用户界面中引用这个图片。
从代码中把资源文件分离出来的另外一个原因就是为不同的设备配置提供可选的资源。例如,你可以定义UI中的字符串到XML中,使用其他语言翻译这些字符串,并保存到其他文件中。然后,根据语言限定符定义目录名(比如法语保存在res/values-fr目录中)。android系统会根据你的设备设置应用合适的语言到你的UI中。
Android还支持很多不同限定符,限定符是一个短字符串,然后包含到资源目录名中,去指定设备使用那个资源。像另外一个例子,你需要基于不同的设备位置和尺寸定义不同的布局文件。例如,设备是直立方向时,你可能需要使用垂直按钮的布局,当屏幕是横向时,这个按钮就应该被定义为水平的。为了实现上面的功能,你需要定义两个不同的布局文件,使用合适的限定符定义每个样式目录名。然后系统会自动匹配合适的布局到正确的设备方位。