《Android和PHP开发最佳实践》一2.3 Android应用框架

2.3 Android应用框架

前面介绍了Android的系统框架,主要目的是让大家对Android系统有整体的概念,也为日后更深入的学习打好基础。然而,目前我们更需要重点学习和掌握的则是Android的应用框架,因为是否能掌握和理解Android应用框架,直接关系到是否能学好Android应用开发。
Android的应用框架是一个庞大的体系,想要理解透彻并不是那么简单的事情,但是,好在其中有一些比较清晰的脉络可以帮助我们快速地熟悉这个系统,因此抓住这些脉络中的核心要点对于能否学好Android的应用开发来说是至关重要的。一般来说,Android应用框架中包含四个核心要点,即活动(Activity)、消息(Intent)、视图(View)和任务(Task)。
如果你觉得上述核心要点的概念很陌生,不好理解,那么我们来看看下面这个比喻:如果把一个Android应用比喻成海洋,那么每个Activity就是这个海洋中的岛屿,假设我们眼前有一项任务(也就是Task),需要我们在其中若干个岛屿上建立起自己的王国。于是问题来了,我们要怎么样从一座岛屿去到另一座岛屿呢?没错,我们需要交通工具,而Intent就是我们最重要的交通工具。当然,Intent不仅可以带我们去,而且还可以帮我们带上很多需要的东西。接着,到了岛上,我们开始建立一个自己的王国,要知道这可需要很多的资源,这个时候,我们就会想到View这个建筑公司,因为他可以帮助我们快速地建出我们需要的东西。这样,Activity、Intent、View以及Task一起配合完成了一个完整的Android应用的王国。
从以上的比喻中,我们还可以认识到,在这四个要点中,Activity是基础,Intent是关键,View是必要工具,而Task则是开发的脉络。对于开发者来说,只有掌握了Activity、Intent、View和Task这几个核心要素之后,才能够做出多种多样的应用程序。接下来,让我们分别学习一下这四个核心要点。

2.3.1 活动(Activity)

活动(Activity)是Android应用框架最基础、最核心的内容和元素,每个Android应用都是由一个或者若干个Activity构成的。在Android应用系统中,Activity的概念类似于界面,而Activity对象我们通常称之为“界面控制器”(从MVC的角度来说)。从另一个角度来理解,Activity的概念比较类似于网站(Web)开发中“网页”的概念。此外,当Android应用运行的时候,每个Activity都会有自己独立的生命周期,图2-2所示的就是Activity的生命周期。
《Android和PHP开发最佳实践》一2.3 Android应用框架

其实,在Android系统内部有专门的Activity堆栈(Stack)空间,用于存储多个Activity的运行状态。一般来说,系统会保证某一时刻只有最顶端的那个Activity是处于前端的活动(foreground)状态。也正因如此,一个Activity才会有如图2-2所示的生命周期。当一个Activity启动并进入活动状态的时候,调用顺序是onCreate、onStart、onResume;退居后台的时候,调用顺序是onPause、onStop;重新回到活动状态的时候,调用顺序是onRestart、onStart、onResume;销毁的时候,调用顺序是onPause、onStop、onDestroy。我们应该深刻理解这些状态的变化过程,因为在Android应用开发的过程中我们会经常用到。至于如何更好地掌握Activity的特性,大家可以尝试将以下代码(代码清单2-1)放入Android应用中运行,并对照程序打印出的调试信息来理解Activity生命周期各阶段的使用。
代码清单 2-1

// 基础Activity类,用于测试
public class BasicActivity extends Activity {
    
    private String TAG = this.getClass().getSimpleName();
    
    public void onCreate(Bundle savedInstanceState) {
        Log.w(TAG, "TaskId:"+this.getTaskId());
    }

    public void onStart() {
        super.onStart();
        Log.w(TAG, "onStart");
    }

    public void onRestart() {
        super.onStart();
        Log.w(TAG, "onRestart");
    }
    
    public void onResume() {
        super.onResume();
        Log.w(TAG, "onResume");
    }
    
    public void onPause() {
        super.onPause();
        Log.w(TAG, "onPause");
    }
    
    public void onStop() {
        super.onStop();
        Log.w(TAG, "onStop");
    }
    
    public void onDestroy() {
        super.onDestroy();
        Log.w(TAG, "onDestroy");
    }
    
    public void onNewIntent() {
        Log.w(TAG, "onNewIntent");
    }
}

此外,所有的Activity必须在项目基础配置文件AndroidManifest.xml中声明,这样Activity才可以被Android应用框架所识别;如果你只写了Java代码而不进行声明的话,运行时就会抛出ActivityNotFoundException异常。关于Activity声明的具体操作,我们会在2.10.2节中结合Hello World项目进行详细介绍。

2.3.2 消息(Intent)

参考之前我们对Android应用框架的几个核心要点的比喻,我们应该知道Intent消息模块对于Android应用框架来说有多重要;如果没有它的话,Android应用的各个模块就像一座座“孤岛”,根本不可能构成一个完整的系统。在Android应用系统中,我们常常把Intent称为消息,实际上,Intent本身还是一个对象,里面包含的是构成消息的内容和属性,主要有如下几个属性,我们来分别认识一下。

  1. 组件名称(ComponentName)
    对于Android系统来说,组件名称实际上就是一个ComponentName对象,用于指定Intent对应的目标组件,Intent对象可以通过setComponent、setClass或者setClassName方法来进行设置。
  2. 动作(Action)
    消息基类(Intent)中定义了各种动作常量(字符串常量),其中比较常见的有:ACTION_MAIN(对应字符串android.intent.action.MAIN)表示应用的入口的初始化动作;ACTION_EDIT(对应字符串android.intent.action.EDIT)表示常见的编辑动作;ACTION_CALL(对应字符串android.intent.action.CALL)则表示用于初始化电话模块动作等。Intent对象常使用setAction方法来设置。
  3. 数据(Data)
    不同的动作对应不同的数据(Data)类型,比如ACTION_EDIT动作可能对应的是用于编辑文档的URI;而ACTION_CALL动作则应该包含类似于tel:xxx的URI。多数情况下,数据类型可以从URI的格式中获取,当然,Intent也支持使用setData、setType方法来指定数据的URI以及数据类型。

4.类别(Category)
既然不同的动作应该对应不同的数据类型,那么不同的动作也应该由不同的类别的Activity组件来处理,比如CATEGORY_BROWSABLE表示该Intent应该由浏览器组件来打开,CATEGORY_LAUNCHER表示此Intent由应用初始化Activity处理;而CATEGORY_PREFERENCE则表示处理该Intent的应该是系统配置界面。此外,消息对象(Intent)可以使用addCategory添加一种类型,而一个Intent对象也可以包含多种类型属性。

  1. 附加信息(Extras)
    一个Intent对象除了可以包含以上的重要信息之外,还可以存储一些自定义的额外附加信息,一般来说,这些信息是使用键值对(key value)的方式存储的。我们可以使用putExtra方法设置附加信息,信息类型非常丰富(一般还是以字符串为主);在接收的时候使用getExtras方法获取。
  2. 标志(Flags)
    除了上面提到的几个功能属性,消息基类中还定义了一系列特殊的消息行为属性(也就是标志),用于指示Android系统如何去启动Activity以及启动之后如何处理。关于标志(Flags)的使用我们还会在2.3.4节中介绍。

在Android应用中,消息(Intent)的使用方式通常有两种,一是显式消息(Explicit Intent),另一个则是隐式消息(Implicit Intent)。显式消息的使用比较简单,只需要在Intent中指定目标组件名称(也就是前面提到的ComponentName属性)即可,一般用于目标Activity比较明确的情形。比如在一个固定流程中,我们需要从一个Activity跳转到另一个,那么我们就会使用显式的消息。而隐式消息则比较复杂一点,它需要通过消息过滤器(IntentFilter)来处理,一般用于目的性不是那么明确的情形,比如应用中的某个功能需要往目的地发送消息,但是我们却不确定要使用短信发送还是微博发送,那么这个时候就应该使用隐性消息来处理了。下面是一个典型的消息过滤器的配置范例,如代码清单2-2所示。
代码清单 2-2

<activity...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" android:mimeType="image/*" />
    </intent-filter>
</activity>

我们看到,配置消息过滤器使用的是标签,一般需要包含三个要素:action、category以及data。其中,action是必需的,category一般也是需要的,而data则允许没有设置。接下来,我们学习一下这几个要素的使用方法。
:在Android应用中,一般会通过元素来匹配消息(Intent),如果找到Action就表明匹配成功,否则就是还没找到目标。需要注意的是,如果消息过滤器没有指定元素,那么此消息只能被显式消息匹配上,不能匹配任何的隐式消息;相反,当消息没有指定目标组件名称时,可以匹配含有任何包含的消息过滤器,但不能匹配没有指定信息的消息过滤器。
:元素用于标注消息的类别。值得注意的是,假如我们使用元素来标识消息类别,系统在调用Context.startActivity方法或者Context.startActivityForResult方法时都会自动加上DEFAULT类别。因此,除了Intent已经指定为Intent.ACTION_MAIN以外,我们还必须指定为android.intent.category.DEFAULT,否则该消息将不会被匹配到。另外,对于Service和BroadcastReceiver,如果Intent中没有指定,那么在其消息过滤器中也不必指定。
< data/>:通过data字段来匹配消息相对来讲比较复杂,通常的data字段包含uri、scheme(content, file, http)和type(mimeType)几种字段。对于Intent来说,我们可以使用setData和setType方法来设置,对于IntentFilter来讲,则可以通过android:scheme和android:mimeType属性分别来指定,使用范例如代码清单2-3所示。
代码清单 2-3

<activity ...>
    <intent-filter>
        <action android:name="android.intent.action.SEND" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="file" android:mimeType="image/*" />
    </intent-filter>
</activity>

以上的配置表明该Activity可以发送图片,而且内容必须是单独的一个文件,也就是说,该文件的URI路径必须是以“file://”开头的。当然,如果我们把这里的“android:scheme”改成“content”的话,则表明该图片内容必须是由ContentProvider提供的,即URI必须是以“content://”开头的。
至此,我们已经介绍了消息(Intent)和消息过滤器(IntentFilter)的基本概念和用法。我们必须清楚的是,消息分为显式消息和隐式消息两种,而消息过滤器一般是提供给隐式消息使用的。Android消息过滤器的过滤规则比较严格,只要我们申明了除了默认值(DEFAULT)之外的action、category和data,那么,只有当对应消息对象的动作(action)、类别(category)和数据类型(data)同时符合消息过滤器的配置时才会被考虑。关于标签的具体使用方法,我们将会在本书7.2.4节中结合实例进行讲解。

2.3.3 视图(View)

视图(View)系统主管Android应用的界面外观显示,因此也称作Android UI系统,是Android应用框架中最重要的组成部分之一。我们在Activity中展示或者操作的几乎所有控件都属于View。Android应用框架的View System包含View和ViewGroup两类基础组件。下面我们来理解一下Android视图系统的层次结构,如图2-3所示。
《Android和PHP开发最佳实践》一2.3 Android应用框架

视图类(View)是所有视图(UI)控件(包括ViewGroup)的基类。视图组(ViewGroup)则类似于集合,一个视图组可以包含多个ViewGroup和View,类似于Html标签中的层(div)。接下来,我们再来看看View中会经常使用的一些UI控件(见表2-3),你也可以在Android SDK参考文档(Reference)中的android.widget包下找到它们。
从表2-3中可以看出,Android应用框架为我们提供了非常丰富的视图控件,从某种程度上来说,Android应用的界面是通过各种各样的视图控件组合起来的。至于这些视图控件的具体用法,我们将在第7章中结合项目实例进行介绍。

《Android和PHP开发最佳实践》一2.3 Android应用框架

本节只是从应用程序框架组成部分的角度简单地介绍了Android UI系统的概念,关于UI系统的更多知识以及UI控件的具体用法,我们将在本章2.7节中更系统地介绍。

2.3.4 任务(Task)

本节介绍Android任务(Task)的概念。区别于以上介绍的活动、消息和视图这几个要点,任务的概念显得比较抽象,且我们在日常编码过程中也不会直接接触到,但是,理解任务却是理解整个Android应用框架的关键。
首先,我们来认识一下Android系统中的任务是如何运行的。简单来说,当我们在手机的应用列表(Application Launcher)中点击某个应用图标的时候,一个新的Task就启动了,后面的操作可能会涉及多个应用中不同Activity的界面,而这些Activity的运行状态都会被存储到Task的Activity堆栈(Activity Stack)中去。和其他的堆栈一样,Activity堆栈采用的是“后进先出”的规则。图2-4展示就是一个常见任务中Activity堆栈的变化情况。
每次启动一个新的Activity,其都会被压入(push)到Activity堆栈的顶部,而每次按“BACK”键,当前的Activity就会被弹出(pop)Activity堆栈;另外,如果按了“HOME”键的话,该Task会失去焦点并被保存在内存中;而一旦重新启动,Task会自动读出并显示上次所在的Activity的界面。那么,从一个应用进入另一个应用的情况是怎样呢?比如,应用中需要配置一些系统设置,那么我们就需要考虑一下多任务切换的情况了,如图2-5所示。

《Android和PHP开发最佳实践》一2.3 Android应用框架

《Android和PHP开发最佳实践》一2.3 Android应用框架

我们假设Task A是应用A的任务,也是我们所在的任务,当运行到Activity 3的时候我们按了“Home”键,于是Task A中的所有Activity就都被停止了,同时Task A暂时退居到后台(Background);这时,我们点击应用B的图标激活了Task B,于是Task B就被推到了前台(Foreground),并展示出最上层的Activity Z;当然,我们还可以用类似的操作把Task A激活并放置到前台进行操作。以上也是我们使用Android系统最经常使用的行为操作,大家可以结合实际情况好好理解一下。
以上的策略已经可以满足大部分Android应用的需求。此外,Android还提供了一些其他的策略来满足一些特殊的需求。比较常见的,如我们可以在Android基础配置文件(Menifest File)中使用元素的launchMode属性来控制Activity在任务中的行为特征。launchMode有以下四种模式可供选择。
Standard模式:Standard模式为默认模式,无论是打开一个新的Activity,还是接收Intent消息,系统都会为这个Activity创建一个新的实例(instance);每个Activity都可以被实例化多次,并且每个任务都可以包含多个实例。此模式最常用,但是其缺点就是太耗费系统资源。
singleTop模式:该模式下的行为和Standard模式下的行为基本相同,如果该Activity正好在运行状态(也就是在Activity堆栈的顶部),那么其接收Intent消息就不需要重新创建实例,而是通过该类的onNewIntent()方法来处理接收到的消息。这种处理方式在一定程度上会减少一些资源浪费。
singleTask模式:此模式保证该Activity在任务中只会有一个实例,并且必须存在于该Task的根元素(即栈底)。此模式比较节省资源,手机浏览器使用的就是这种模式。
singleInstance模式:此模式与singleTask模式类似,不同之处是该模式保证Activity独占一个Task,其他的Activity都不能存在于该任务的Activity堆栈中。当然,Activity接收Intent消息也是通过onNewIntent方法实现。
此外,我们还可以通过设置Intent消息的flag标志来主动改变Activity的调用方式,比较常见的flag如下。
FLAG_ACTIVITY_NEW_TASK:在新的Task中启动目标Activity,表现行为和前面提到的singleTask模式下的行为一样。
FLAG_ACTIVITY_SINGLE_TOP:如果目标Activity正好位于堆栈的顶部,则系统不用新建Activity的实例并使用onNewIntent()方法来处理接收到的消息。表现行为和前面提到的singleTop模式下的行为一样。
FLAG_ACTIVITY_CLEAR_TOP:如果目标Activity的运行实例已经存在,使用此方法系统将会清除目标Activity所处的堆栈上面的所有Activity实例。
需要注意的是,官方文档中建议多使用默认的Task行为模式,因为该模式比较简单也易于调试。对于一些特殊的需求,如果需要使用到其他模式的话,需要模拟不同的情况多进行一些测试,以防止在一些特殊情况下出现不符合预期的情况。当然,说句实话,目前主流移动设备上的Android版本都还比较旧,对多任务管理的支持和体现还不够明显,不过,我们应该可以在Android最新版本(如Android 4.0)里看到对系统任务管理功能的加强。

上一篇:Android系列之Fragment(四)----ListFragment的使用


下一篇:SpringBoot开发案例从0到1构建分布式秒杀系统