1. Android应用程序
- $file Sample.apk
- Sample.apk: Zip archive data, at least v1.0 to extract
- AndroidManifest.xml,
- classes.dex,
- resources.arsc,
- META-INF,
- res,
到目前为止,我们也只是分析.apk文件,于是我们可以回过头来看看Android应用被编译出来的过程。
2. Android编程
在Android开发里,决定我们应用程序表现的,也就是我们从一个.apk文件里看到的,我们实际上只需要:
Þ 修改AndroidManifest.xml文件。AndroidManifest.xml是Android应用程序的主控文件,类型于Windows里的注册表,我们通过它来配置我们的应用程序与系统相关的一些属性。
Þ 修改UI显示。在Android世界里,我们可以把UI编程与Java编程分开对待,处理UI控件的语言,我们可以叫它UI语言,或是layout语言,因为它们总是以layout类型的资源文件作为主入口的。Android编程里严格地贯彻MVC的设计思路,使我们得到了一个好处,就是我们的UI跟要实现的逻辑没有任何必然联系,我们可先去调整好UI显示,而UI显示后台的实现逻辑,则可以在后续的步骤里完成。
Þ 改写处理逻辑的Java代码。也就是我们MVC里的Controller与Model部分,这些部分的内容,如果与UI没有直接交互,则我们可以放心大胆的改写,存在交互的部分,比如处理按钮的点击,取回输入框里的文字等。作为一个定位于拓展能力要求最高的智能手机操作系统,android肯定不会只实现画画界面而已,会有强大的可开发能力,在android系统里,我们可以开发企业级应用,大型游戏,以及完整的Office应用。
当然,还有一种特殊的情况就是,这个源代码工程并非直接是一个Android应用程序,只是Unit Test工程或是库文件工作,并不直接使用.apk文件,这里则可能后续的编程工作会变得不同。我们这里是分析Android应用程序,于是后面分别来看应用程序编程里的这三部分的工作如何进行。
2.1 AndroidManifest.xml
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.lianlab.hello"
- android:versionCode="1"
- android:versionName="1.0">
- <application android:label="@string/app_name">
- <activity android:name=".Helloworld"
- android:label="@string/app_name">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- </manifest>
标签可以包含子标签,如果没有子标签,则我们也可以使用</>来进行标识。
<manifest>,是主标签,每个文件只会有一个,这是定义该应用程序属性的主入口,包含应用程序的一切信息,比如我们的例子里定义了xml的命名空间,这个应用程序的包名,以及版本信息。
<application>,则是用于定义应用程序属性的标签,理论上可以有多个,但多个不具有意义,一般我们一个应用程序只会有一个,在这个标签里我们可以定义图标,应用程序显示出来的名字等。在这一标签里定义的属性一般也只是辅助性的。
<activity>,这是用来定义界面交互的信息。我们在稍后一点的内容介绍Android编程细节时会描述到这些信息,这一标签里的属性定义会决定应用程序可显示效果。比如在启动界面里的显示出来的名字,使用什么样的图标等。
<intent-filter>,这一标签则用来控制应用程序的能力的,比如该图形界面可以完成什么样的功能。我们这里的处理比较简单,我们只是能够让这个应用程序的HelloWorld可以被支持点击到执行。
从这个最简单的AndroidManifest.xml文件里,我们可以看到Android执行的另一个特点,就是可配置性强。它跟别的编程模型很不一样的地方是,它没有编程式规定的main()函数或是方法,而应用程序的表现出来的形态,完全取决于<activity>字段是如何定义它的。
2.2 图形界面(res/layout/main.xml)
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <TextView
- android:id="@+id/textView1"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerInParent="true"
- android:padding="@dimen/padding_medium"
- android:text="@string/hello_world"
- tools:context=".HelloWorld" />
- </RelativeLayout>
我们比如在Eclipse里的工程里查看,我们会发现,我们打开res/layout/main.xml,会自动弹出来下面的窗口,让我们有机会使图形工具来操作界面。
有了这种工具,就有可能实现设计师与工程师合作来构建出美观与交互性更好的Android应用程序。
但可惜的是,Android的这套UI设计工具,太过于编程化,而且由于版本变动频繁的原因,非常复杂化,一般设计师可能也不太容易学好。更重要的一点,Android存在碎片化,屏幕尺寸与显示精度差异性非常大,使实现像素级精度的界面有技术上的困难。也这是Android上应用程序不如iOS上漂亮的原因之一。但这种设计至少也增强了界面上的可设计性,使Android应用程序在观感上也有不俗表现。
- <string name="hello_world">Hello world!</string>
从通过这种方式,我们可以看另外一些特点,就是Android应用程序在多界面、多环境下的自适应性。对于上面的字符串修改的例子,我们如果像下面的示例环境那样定义了res/layout-zh/strings.xml,并提供hello_string的定义:
- <string name="hello_world">欢迎使用!</string>
在res目录里,我们看到,对于同一种类型的资源,比如drawable、values,都可以在后面加一个后缀,像mdpi,hdpi, ldpi是用于适配分辨率的,zh是用来适配语言环境的,large则是用来适配屏幕大小的。
于是,透过资源文件,我们进一步验证了我们对于Android MVC的猜想,在Android应用程序设计里,也跟iOS类似,可以实现界面与逻辑完全分离。而另一点,就是Android应用程序天然具备屏幕自适应的能力,这一方面带来的影响是Android应用程序天生具备很强的适应性,另一方面的影响是Android里实现像素精度显示的应用程序是比较困难的,维护的代价很高。
我们可以再通过应用程序的代码部分来看看应用程序是如何将显示与逻辑进行绑定的。
2.3 Java编程
在Android编程里,实现应用程序的执行逻辑,几乎就是纯粹的Java编程。但在编程上,由于Android的特殊性,这种Java编程也还是被定制过的。
- package org.lianlab.hello;
- import android.os.Bundle;
- import android.app.Activity;
- public class HelloWorld extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- }
- }
那Model部分由谁来提供呢?这是由Android系统层,也就是Framework来提供的功能。当界面失去焦点时,当界面完全变得不可见时,这些都属于Framework层才会知道的状态,Framework会记录下这些状态变更的信息,然后再回调到Activity类提供的相应状态的回调方法。关于Activity我们后面来详细说明,而见到Activity类的最简单构成,我们大体上就可以形成Android世界里的完整MVC框架构成完整印象了。
到这里还无法解决我们对于Android应用程序与Java环境的区别的疑问:
Þ Android有所谓的MVC,将代码与显示处理分享,但这并非是标准Java虚拟机环境做不到。一些J2EE的软件框架也有类似的特征。
Þ AndroidManifest.xml与On*系列回调,这样的机制在JAVA ME也有,JAVA ME也是使用类似的机制来运行的,难道Android是JAVA ME的加强版?
Þ 至于Listener模式的使用,众所周知,Java是几乎所有高级设计模式的实验田,早就在使用Listener这样模式在处理输入处理。唯一不同的是ClickListener,难道Android也像是可爱的触摸版Ubuntu手机一样,只在是桌面Java界面的基础加入了触摸支持?
Þ Activity从目前的使用上看,不就是窗口(Window)吗?Android开发者本就有喜欢取些古怪名字的嗜好,是不是他们只是标新立异地取了个Activity的名字?
对于类似这样的疑问,则是从代码层面看不清楚了,我们得回归到Android的设计思想这一层面来分析,Android应用程序的执行环境是如何与众不同的。
3 所谓的Android应用程序
我们从前面的例子中看到,无论是编写的代码,还是最后生成的.apk文件,都是没有所谓的应用程序的。应用程序本身是一种虚无的概念,只是一种以zip格式进行压缩的一个文件,一种容器而已。
于是,我们可以得到Android里关于应用程序的第一个印象,作为Android应用程序的载体,.apk文件只是一种进行包装与传输的格式,而每个.apk文件必然包含一个AndroidManifest.xml文件,由这一文件来描述该.apk文件提供的内容。当然,这一文件里,还会包含一些权限控制的信息。
3.1 Android世界里的共享
作为一个智能手机操作系统,其用户可能在功能上有各种各样的功能组合。比如最简单的打电话,则后续动作会有保存联系人,同时需要给联系人拍照做来电大头贴。
Android的解决之道,是将传统意义上的应用程序,细化成一个个完成某项功能的部分,这种功能部分,在Android世界里被称为Activity。Activity都应该被设计成可以独立地被执行以解决某个问题,当它完成或是用户选择退出执行时,又会自动跳回到调用这一Activity的界面。当然,在一个Android系统里有可能存在无限多的Activity,在他们进行跳转切换时,我们就需要一种很灵活的消息传输机制(因为我们必须兼容系统里所有可能的互相调用的情况)。而且这种传输机制还必须能够跨进程,不然,我们所有的涉及Activity互相调用部分都必须在同一进程里完成。于是,Android系统里又有了Intent,用于解决交互通信。
这样的编程模型也需要有一定前提,那就是我们Application概念必须被弱化,我们不能有main函数入口(如果系统执行依赖main作入口,则不能实现Activity之间互相调用了,所有的Activity执行之前,必须先通过main入口来初始化环境)。出于这样的设计,所以Application必须只是一个容器,将各种不同的Activity实现包装起来加载到系统里。
当然,将功能拆分成一个个的单一功能界面之后,我们需要有种机制可以将用户一路点击过去历史记录下来,于是我们可以找函数调用时的基本数据结构—栈来帮忙,发生调用时,需要退出的Activity及其状态压栈,当从调用退出时则进行栈的弹出操作,这时我们的Activity管理就演变成如下图所示的简单栈管理。
有了这样的概念,于是我们响应用户点击操作的问题便迎刃而解,我们在设计应用程序时,不再是设计一个复杂的功能实现,而是实现一组完成单项功能的实现,也就是Activity。
到这时,我们就可以看到Activity之所以会不被称为Window的原因,Activity这个名字代表的是某种单一交互功能上的实现。这种功能的实现将在系统里通过Intent串接起来,构成了一个在功能上具备极大可拓展性的系统。基于这样的特点,Android也就被称作是“无边界”系统,因为它在功能上延展不再受限于系统的能力,而只受限于智商与创意。
这就是Android世界里的功能共享。
3.2 Intent与Intent Filter
Intent,英文原意就是要“干什么”的意思,之所以取这个名字,也是因为在Android系统里,Intent所起到的作用就是用来指明下一步具体是做什么,具体是不是执行,由谁来执行,则会由根据当前的系统状态(能不能解析这个Intent请求)来决定。这不只是简简单单地发个消息而已,而是一种更安全的、更加松散的消息机制。
在一个Intent消息对象里,共有六个成员(并不都是必须赋值的,只要一个Intent对象能够被解析,就会得以执行,否则就会会被舍弃):
成员 | 类型 | 说明 | 示例 |
ComponentName | String | 用于定义谁将处理这一Intent。它由一个Activity的具体实现的全名(加上包名)来指定 | org.lianlab.hello.HelloActivity |
Action | String | 用于定义这一动作是做什么,可以被拓展自定义类型 | ACTION_CALL 开始通话. ACTION_EDIT 进行编辑. |
Data | String | 用一个URI来指定Intent的操作对象,因为URI一般会包含种类信息,于是这个值也可能被用作MIME设别。 | “content://contacts/people/1” 指定联系列表中的第一个 |
Category | String | 用来进一步明确什么样的可执行实体将处理这一Intent。是可选项,也可多选。 | CATEGORY_HOME 主界面应用程序 CATEGORY_LAUNCHER 可在主界面里被点击 |
Type | String | 用来指定特定的MIME类型 | "video/*" 视频 |
Extra | Bundle | 用来传递额外的数据传递,前面我们也介绍了Bundle是一种key:value配对的字典类型,于是Extra里可以转递复杂的数据 | putExtra("sms_body", "some text"); 发短信时指定内容 |
Flags | int | 预定义一系列用来控制Intent行为的属性值 |
|
在这个成员变量里,最能体现灵活性的就是Component,如果指定了这个值,则我们在通过startActivity()方法来发送Intent时,就会自动启动Component指定的Activity。如果没有指定,则会由系统来选择一个能够处理这一Intent的Activity来执行,这时就引入了Intent Filter的概念。
在AndroidManifest.xml里面定义<intent-filter>很简单,就是通过指定Intent对象的Action,Data,Type,和Category这四个成员变量来指定。比如:
<activityandroid:name=".PlayerActivity"android:label="@string/app_name"
android:configChanges="orientation" >
<intent-filter>
<actionandroid:name="android.intent.action.MAIN" />
<categoryandroid:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<actionandroid:name="android.intent.action.VIEW" />
<categoryandroid:name="android.intent.category.DEFAULT" />
<dataandroid:scheme="file" />
</intent-filter>
...
</activity>
public void onClick(Viewv) {
Intent request = new Intent(Intent.ACTION_VIEW);
request.putData(“file:///sd-ext/Movies/test.mp4”);
startActivity(request);
finish();
}