官方文档原文地址
应用程序原理
Android应用程序是通过Java编程语言来写。Android软件开发工具把你的代码和其他数据、资源文件一起编译、打包成一个APK文件,这个文档以.apk为后缀,保存了一个Android应用程序所有的内容,Android设备通过它来安装对应的应用。
一旦安装到设备上,每个Android应用程序就运行在各自独立的安全沙盒中:
- Android系统是一个多用户的Linux系统,每一个应用都是一个用户。
- Android系统默认会给每个应用分配一个唯一的用户ID(这个ID只被系统使用,应用并不了解)。系统给每个应用对应的所有文件都设置了权限,只有用户ID正确的应用才能访问。
- 每个进程都有它独立的虚拟机,因此一个应用程序代码才能独立运行,不受其他应用干扰。
- 默认的,每个应用运行在各自独立的Linux进程中。当一个应用中有一个组件需要执行时,系统就会开启它的进程。当这个进程里没有活动或者系统内存不足需要关闭进程为其他应用回收内存时,会关闭这个进程。
每个应用运行在各自独立的沙盒中,通过这样的方式Android系统实现了最少权限原则—-即每个应用默认只有访问它工作需要使用的资源的权限(译者注:注意这里是默认,很多情况下其实我们需要访问其他应用的资源,这个时候就需要用到另外一个关键技术“进程通信”)。这样就创建了一个非常安全的环境,在这个环境下一个应用不能访问那些它没有权限的文件。
然而,系统还是提供了一些应用间共享数据和应用访问系统服务的方式:
- 我们可以给两个应用共享同样的Linux用户ID,那样它俩就可以访问彼此的文件了。为了节省系统资源,有相同UID的应用可以运行在同一个Linux进程中、使用同一个虚拟机,前提是这些应用必须有同样的签名。
- 一个应用可以请求一些访问设备数据的权限,比如说用户的联系人、短信、SD卡、相机、蓝牙等等。用户可以决定是否授予应用这些权限。
上面概述了关于Android应用在系统中如何存在的原理。下面将介绍:
- 构建应用的核心框架组件
- 在manifest文件中为你的应用声明组件和请求设备特性
- 与代码分开、让你的应用在许多设备配置下表现的尽可能优雅的资源
应用组件
应用组件是构建一个Android程序必备的模块。系统可以通过不同组件的组件来进入你的应用。不是所有的组件用户都能看得到、摸得着,有些组件需要依靠其他组件才能启动,但是它们又作为一个独立个体存在,同时扮演着不同的角色帮助你决定你的应用都有什么行为。
应用组件有四种类型,每种类型都有其独特的功能,同时还有决定其如何创建、销毁的生命周期。
下面是四种不同类型的应用组件:
- Activity 一个Activity代表了一个界面。比如说,一个电子邮件应用可能有一个现实新邮件列表的Activity,还有一个创建邮件的Activity,另外还有一个阅读邮件的Activity。这些Activities一起工作组成一个完整的电子邮件用户体验,同时每个Activity又是相互独立的,因此其他应用可以调用上述几个(如果这个电子邮件应用允许)。比如说一个相机应用为了让用户分享相片,可以直接启动创建邮件的Activity。
- Services Service是一个运行在后台的组件,常用来进行长耗时操作或者执行跨进程操作。Service不提供用户界面。比如说,Service可以在用户使用别的应用的时候后台播放音乐,或者也可以在不妨碍用户交互的情况下,在后台从服务器请求数据。其他组件,比如说Activity,可以开始Service,或者跟Service绑定从而可以发生交互。
- Content Providers Content provider 管理着一套可共享的应用数据。你可以把数据存到文件系统、SQLite数据库,网络或者其他任何你应用能访问的可持久存储的地方。通过content provider,其他应用可以查询甚至修改数据(如果该content provider允许的话)。比如说,Android系统 提供了一个管理用户联系人信息的内容提供者。通过它任何应用只要有权限就可以查询内容提供者的部分,然后读或者写特定的联系人信息。 Content provider还可以用来读写应用私有的数据。
- Broadcast Receivers Broadcast receivers可以对全部广播进行回应的组件。许多广播都是系统发送的,比如说当屏幕关闭时发送的广播通知、电池 电量低时的广播、或者拍照完毕的广播。应用也可以创建广播,比如说发个广播让其他程序知道有数据下载到设备上,已经可以使用了。虽然广播接受者没有界面,但是当一个广播事件发生时它可以在状态栏创建一个通知来通知用户。通常广播接受者只是一个做一个其他组件的入口,做的工作很少。比如说当收到某广播时启动一个Service来执行一些操作。
Android系统设计的一个独特之处是任何应用都能启动其他应用的组件。
比如说你想让用户使用设备的摄像头拍个照,设备上有专门的应用做了这个功能,你不需要写一个相机应用,只需简单的调用系统相机应用就可以拍照。当拍完后,会给你返回要使用 的照片的数据。对用户来说,看起来就像相机是你应用的一部分。
当系统启动一个组件时,会为这个应用单独启动一个进程(如果这个应用还没启动),然后实例化一些要用到的类。比如说,你的应用启动了拍照应用的activity,那个activity运行在相机应用所在的进程,而不是你的应用的进程。因此,不像其他平台系统的应用,Android应用不仅有一个入口(没有Java里的main方法)。
由于系统把每个应用运行在不同进程,同时限制访问其他应用文件的权限,你的应用不能直接激活其他应用的组件。然而你可以直接调用Android系统的组件。想要激活其他应用的组件,你必须在你的intent里标明信息告诉系统要启动一个特定的组件。系统就会为你激活那个组件。
激活组件
四大组件里的三种:activities、services、broadcast receivers都可以通过异步调用intent来激活。Intent在运行时(可以理解为当请求调用其他组件时)绑定调用和被调用的组件,无论组件是不是属于你的应用。
通过Intent对象来创建一个intent,这个intent可以决定激活一个特定的组件还是激活一类组件。Intent可以是分为2中,显式和隐式。
对activities和services来说,一个intent决定了要调用的行为(比如要显示或者发送一些东西),也可能标明要使用数据(被调用的组件可能需要输入一些数据)的URI。比如说,一个intent可能发出一个请求让一个activity显示某张照片或者打开一个网页。有些时候你启动一个activity后可能还需要接收返回结果,在这种时候,activity会在一个Intent里返回结果(比如说你可以发起一个intent让用户选择一个联系人,然后给你返回信息,返回的intent里可能就包括了指向被选联系人的URI)。
对broadcast receivers来说,intent只需简单的定义要接收广播的通告就可以(比如想要接收设备电量低的广播只需在intent里表明一个指示“电量低”的action)。
另外的组件 – content provider,不能被intent激活。它是被针对Content Resolver发出的请求所激活。这个content resolver处理content provider的所有直接事务,因此组件不需要调用provider而是直接调用content resolver的方法就可以了。这样就在content provider和组件请求间保留了一个中间层,比较安全。
下面是几种激活不同类型组件的不同方法:
- 当你要开启一个activity或者给一个已经开启activity传递新数据时,通过给startActivity()或者startActivityForResult()(当你想要接收返回结果时)方法传递一个Intent就可以了。
- 当你要开启一个service或者给一个正在运行的service新指令时,通过给starService()方法传递一个Intent或者给bindService()传入一个Intent来和service绑定就可以了。
- 你可以通过给sendBroadcast(),sendOrderedBroadcast(),sendStickyBroadcast()方法传入一个Intent来实例化一个broadcast。
- 通过调用ContentResolver的query()方法来执行对content provider的查询操作。
The Manifest 文件
想要让Android系统能启动一个应用组件,系统需要通过查看应用的AndroidManifest.xml文件来知道该组件的是否存在。你的应用必须在应用代码根目录下的这个文件里声明所有的组件。
The manifest 除了声明应用组件外还做了很多事,比如:
- 识别应用要使用的用户权限,比如说访问网络或者访问用户的联系人;
- 声明应用要求的用户最低手机版本;
- 声明应用要求的硬件、软件特性,比如摄像头、蓝牙或者多点触屏;
- 声明应用要使用的API库(不是Android框架接口),比如说谷歌地图接口。
你使用的activities,services,和content providers如果没有在manifest里声明,对系统来说是找不到的,因此就无法运行。然而broadcast receivers有两种选择,要么在manifest里声明,要么也可以在代码里动态创建(创建一个BroadcastReceiver对象,然后调用registerReceiver()注册)。
声明组件能力的 intent filter
正如上面所说,你可以使用一个Intent来启动activities,services,broadcast receivers。你可以在intent中显式地使用组件的class名来指定要调用的组件。然而intent真正厉害的地方是隐式调用的概念。一个隐式的intent简单的描述了要执行行为的类型(你也可以给想调用的行为传递数据),允许系统找到能够执行你要求的行为的组件然后开启它。如果有多个组件可以执行intent里描述的行为,用户需要选择使用哪个。
系统是如何找到你的intent调用的组件的呢?答案是通过比较设备上其他应用的manifest文件里组件的intent filters标签。
当你在应用的manifest里声明一个activity时,你还可以给这个activity里添加一个声明activity能力的intent filter标签,然后这个activity就可以给其他应用做出回应了。你可以通过给组件的标签下添加一个标签来为你的组件添加一个intent filter。
比如说如果你创建一个邮件应用,需要有一个activity来创建一个新邮件,你可以在该activity里声明一个intent filter,来对发送(新邮件)的intent做出回应,比如这样:
代码语言:javascript复制<manifest ... >
...
<application ... >
<activity android:name="com.example.project.ComposeEmailActivity">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<data android:type="*/*" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
如果有其他应用创建一个带有ACTION_SEND的action的intent,然后通过startActivity()启动,系统就可能启动你的activity,然后用户就可以编辑、发送一篇电子邮件。
声明应用配置要求
市面上搭载Android系统的设备种类太多了,这些设备拥有的硬件配置都不一样。为了避免你的应用被安装到不具备你要求配置的手机上,你需要在manifest文件里标明你的应用要求的硬件、软件配置。通常这些生命只是用来提示,手机系统不会浏览,但是第三方服务商比如谷歌应用市场会浏览他们,然后在用户下载应用时帮助用户过滤那些有额外配置要求的应用。
比如说,如果你的应用要求使用摄像头,并且要求设备最低配置为Android2.7(API Level7),你需要在manifest里这样标明:
代码语言:javascript复制<manifest ... >
<uses-feature android:name="android.hardware.camera.any"
android:required="true" />
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" />
...
</manifest>
声明了以后那些没有摄像头或者Android版本低于2.1的设备在谷歌应用市场上就不可以安装你的应用了。
你也可以在你的应用中声明要使用摄像头但是不是必须要求。这种情况下你的应用必须把上述标签里的required属性设置为false,然后在运行过程中检查设备是否支持摄像头,如果没有的话就禁止摄像头相关操作。
应用资源介绍
一个Android应用不仅仅是由代码组成,它还有很多代码以外的资源文件,比如说图片、音频文件,或者其他跟应用显示有关的东西。比如说你需要定义动画、菜单、样式、颜色还有交互界面的布局文件等等。使用应用资源文件使得你更新一些东西更简单,不用修改代码,直接替换对应的资源文件。这样你的应用就能尽可能多的适应不同的设备环境(比如不同语言或者不同屏幕大小等)。
对于你项目中的每个资源,软件开发工具都会给它定义一个唯一的整型ID,通过这个ID你可以从代码中或者其他资源文件中引用对应的资源。比如说,你的应用中包含一个名为logo.png的图片文件(保存在res/drawable/目录下),SDK会生成一个名为R.drawable.logo的ID,你通过这个ID就可以引用logo图片,插入到界面里。
提供和源代码分离的资源文件最重要的好处之一就是你可以为适配不同设备提供不同的资源文件。比如说在XML中定义界面中的字符,你可以为这些字符准备不同语言版本,存储在不同的文件中。然后系统会根据用户设备设置中的语言,在文件所在文件夹的后缀名字来找合适的字符来显示(比如存储在res/values-fr/下的法语字符,当用户系统语言为法语时会显示这个文件夹下存储的字符)。
Android支持许多不同的资源选择方式。这个选择方式主要取决于你为了在不同配置下使用不同资源时、创建的资源文件夹名称中的字符后缀。再举个栗子,你应该习惯根据设备屏幕尺寸或者方向为activity创建不同的布局文件。当设备屏幕竖屏时,你的布局里有一个按钮,当屏幕转成横屏时,你希望这个按钮转到横屏。为了实现随着屏幕方向改变布局,你可以定义2个不同的布局,然后给每个布局放到适当的、可以被正确选择的、不同的文件夹里。这样系统就会根据屏幕方向自动加载合适的布局了。