第6章 Android的核心——Activity

从本章你可以学到:

什么是Activity

掌握Activity的生命周期

掌握Activity四个基本状态

掌握Activity三个重要循环

掌握配置的改变

掌握Activity不同的加载模式

怎么保存和恢复Activity的状态

启动Activity并获取结果

6.1 什么是Activity

Activity是Android四大组件之一,也是Android中最基本的模块之一。在官网中是这样介绍Activity的。

几乎所有的的Activity都是用来与用户交互的,因此Activity主要关注于视图窗体的创建(你可以通过setContentView(View)方法来放置你的UI),而且Activity对于用户来说通常都表现为全屏的窗体,当然,它们也能以其他的方式呈现,比如浮动窗体。

通俗一点来讲,我们可以把手机比作一个浏览器,那么Activity就相当于一个网页。在Activity中,我们可以添加不同的View,并且可以对这些View做一些事件处理。例如,在Activity中添加button、checkbox 等元素。因此,Activity 的概念在某种程度上和网页的概念是相当类似的。网页对于一个完整的Web站点来说有多重要,Activity对Android应用程序就有多重要。

6.2 Activity的生命周期

Activity的重要性在Activity介绍中已经大概描述了,为了更好地使用Activity,接下来我们介绍一下Activity的生命周期。

在讲Activity生命周期之前,我们先看图6-1(Activity的生命周期)。

▲图6-1 Activity生命周期

从图6-1中我们可以看到Activity的生命周期其实就是由以下函数组成的。

    public class Activity extends ApplicationContext{
      protected void onCreate(Bundle savedInstanceState);
      protected void onStart();
      protected void onRestart();
      protected void onResume();
      protected void onPause();
      protected void onStop();
      protected void onDestroy();
    }

通常情况下Activity生命周期的动作如下所示。

onCreate():该方法是在Activity第一次被创建的时候调用的。这个方法通常用来做一些常规的设置,比如创建视图,绑定数据到 list 等。这个方法还提供了一个 Bundle 对象来保存先前冻结的状态,当然,前提是你之前已经将你需要冻结的内容放到了 Bundle 中。之后总是会调用 onStart()方法,并且在调用了这个方法之后,是不能被系统意外杀死的。

onRestart():从名字就能看出,在Activity被停止后,如果需要重新启动,则会调用这个方法,之后会调用onStart()方法。

onStart():该方法在 Activity 将要对用户可见时调用,如果 Activity 将显示在前台,接着调用onResume(),如果Activity将变隐藏,则调用onStop()方法。不能被系统意外杀死。

onResume():该方法是在Activity将开始于用户交互时被调用的,这个时候的Activity在Activity栈中处于最顶部,之后总是调用onPause()方法。也不能被系统意外杀死。

onPause():该方法是在系统准备恢复其他Activity时调用,这个方法通常用来提交未保存变化的持久化数据,停止动画和其他可能消耗CPU的操作等。由于在这个方法返回之前,下一个Activity是无法被恢复的,所以这个方法的实现不宜做耗时的操作。如果调用了该方法之后,Activity又打算重新返回到前台,则会调用onResume()方法,如果Activity变得对用户不可见,则调用onStop()方法。在系统极端低内存的情况下可以被杀死。

onStop():该方法在Activity不再对用户可见时调用,因为其他Activity已经恢复并且正在覆盖当前Activity。这个可能发生在当一个新的Activity正在启动,而已经存在的Activity又被带到了这个Activity的前面,或者这个Activity正在被销毁。调用了这个方法后,可能会被系统意外地杀死。

onDestory():该方法是在 Activity 被销毁之前最后调用的一个方法,这个可能发生在 Activity被完成的时候。

小提示

上述提到的可能被系统意外杀死或者不能被杀死,是指Android系统在运行时,会在内存极端低下的情况下有选择性地杀死某些“不必要”进程以达到缓解内存不足的情况。

6.3 Activity的监控范围内的三个主要循环

Activity的“整个生命周期”是发生在第一次调用onCreate(Bundle)和唯一最后调用onDestroy()方法之间。一个Activity会在onCreate()方法中设置全局状态,并在onDestrory()方法中释放余下的资源。例如:Activity有一个运行在后台的线程用来从网络上下载数据,则这个线程可能在onCreate()方法中被创建,并在onDestroy()方法停止线程。

Activity的“显示生命周期”是发生在调用onStart()方法以及调用相对应的onStop()方法之间。这段期间,用户可以在屏幕上看到 Activity,尽管该 Activity 可能不在前面(可能隐藏被透明的Activity覆盖等)并与用户交互。在这两个方法中间你可以维护所需要的显示给用户的资源。例如:你可以在onStart()方法中注册一个BroadcastReceiver来检测影响你用户界面的改变,并当你的用户不在见到显示的东西时在onStop()方法中撤销该BroadcastReceiver。随着Activity对用户的可见和不可见状态的转变,onStart()方法和onStop()方法能被调用多次。

Activity的“前台生命周期”(foreground lifetime的意思就是当前Activity显示在屏幕上并且用户能与之交互的一个状态)发生在调用onResume方法以及相应的onPause方法之间。在这段期间, Activity处在其他Activity的前面并能与用户直接交互。Activity会经常在恢复和暂停的状态中转换。例如,当设备休眠时,当一个新的intent被传递到另一个Activity时。因此在这些方法中代码应该要相当轻量级。

6.4 Activity拥有四个基本的状态

活动中:如果Activity在屏幕前(即在栈的最顶部),它是可视的,可接受用户输入的。

暂停:如果Activity已经失去了焦点,但是仍然可见(即,一个非全屏或者透明的Activity在你的Activity的上方拥有焦点),它的状态是暂停。一个暂停状态下的Activity是完全活着的(它保留了所有状态和成员信息并仍然附加到视图管理器),但在系统极端低内存的情况下可以被杀死。

停止:如果一个Activity完全被另一个Activity遮住了,它的状态是停止的。它虽然仍然保存着所有状态和成员信息,但是,它不再对用户可见,所以它的窗口是隐藏的,这个状态下的Activity往往会在其他地方需要内存时被系统意外杀死。

待用:如果一个Activity处于暂停或者停止状态,系统可以让它完成,或者直接杀掉它的进程。当它再重新显示给用户时,它必须完全重启并恢复到以前的状态。

6.5 Task、栈以及加载模式

在Android应用程序中,应用程序中的Activity是可以启动其他程序的Activity的,例如,你在 A 程序中单击了某一串链接地址,应用会自动调用系统的浏览器帮你打开这个链接(如果你的系统中存在多个浏览器,则会打开多个并让你选择其中一个),虽然A程序和浏览器不属于同一个应用,但是你单击“回退”按钮后,依然可以回退到 A 程序中。像这种无缝的用户体验,主要得益于Android中的Task。

那什么是Task呢?通俗来讲,Task就是一组与用户交互并执行特定工作的Activity的集合。它们都根据被启动的顺序排列在栈中(我们可以称这个栈为“回退栈[back stack]”)。比如我们先启动了A程序,依次调用了A、B、C 3个Activity,之后又通过C Activity启动了B程序的D Activity,最后又通过D Activity回到了A 程序并依次退出结束,那么我们可以将由A B C D Activity实例组成的集合称为一个Task,而这些Activity的实例都会根据被启动的顺序存放在栈中(所以,我们讲的A、B、C、D实例组成的集合,不一定只有4个Activity实例,这主要依赖每个Activity的启动模式,后续我们会讲到)。

当用户在应用程序界面(Home界面)单击一个图标(或者Home界面的快捷方式)时,这个应用程序的Task就会启动,如果不存在这个程序的Task(即这个应用程序最近没有被用过),则会创建一个新的Task,并且该程序的“main”Activity会作为栈的根Activity存在。

当当前的Activity启动另一个Activity,新的Activity就会被压入栈的顶部并且得到焦点。上一个Activity仍然存在栈中,但是它停止活动了。当一个Activity停止,系统仍然会保存它的用户界面的当前状态。当用户单击“回退”按钮时,当前Activity就会从栈的顶部被弹出并且被销毁。而之前的Activity将会被恢复(之前的UI状态都将修复)。栈中的Activity是不会进行重新排序的,仅仅只是压入或者弹出栈(被当前 Activity 启动则压入栈,用户单击“回退”按钮离开则弹出)。栈的管理方法是典型的“后进先出”。具体如图6-2所示。

▲图6-2 栈的“后进先出”

比如我们首先启动了Activity1,这时栈中就只存在Activity1一个实例,当使用Activity1启动Activity2 时,Activity1 就被压在了栈下面,Activity2 则被压到了栈的顶部,当我们使用 Activity2再启动Activity3时,Activity2又被压了下去,Activity3则处于栈的顶部。这个时候整个栈中就有了Activity1,Activity2,Activity3三个实例了,并且Activity3处于最顶部,也是获得焦点的Activity。如果我们这个时候按一下“回退”按钮,系统则会将栈最顶部的Activity(即Activity3)弹出并销毁,这个时候Activity2又处于了栈的顶部,并获得了焦点,整个栈里就只有Activity2和Activity1了。

当用户不停地单击“回退”按钮,则在栈中的每一个Activity都会被弹出并恢复前一个,直到用户最后返回到了Home界面,当stack中的所有Activity不再存在了。该Task也就不再存在了。

其实大多数情况下,我们是没必要去关心Activity与Task是如何关联,怎样存在于“回退栈”中的。然而,如果你想不使用Activity的默认行为,比如你希望你的应用程序在启动一个Activity时创建一个新的Task,而不是直接放入当前的Task中,或者当你启动一个Activity,你希望将已经存在在栈中的 Activity 带到栈的顶部,而不是在栈中创建一个新的,或者你想当用户离开 Task 的时候,清除栈中的所有Activity,除了根Activity。

注意

大多数的应用我们不应该打断Activity和Task的默认行为,如果你确定需要为你的Activity修改默认的行为,请谨慎,并多测试可能产生的与用户预期相冲突的行为。

为了达到打断默认行为的效果,我们可以自己定义启动模式(launch Mode),这里有两种方法。

一是使用 manifest 文件。在 manifest 文件中通过指定 Activity 的 launchMode 属性来定义。launchMode属性支持四种不同的值(即四种不同的启动模式)。

1.standard模式

这是默认的模式,系统会在Task中创建新的Activity实例,并且这个Activity能被实例化多次,每个实例都能属于不同的Task,一个Task也能拥有多个实例。

2.singleTop模式

在这个模式下,如果一个 Activity 实例已经存在于当前 Task 的顶部,系统会通过调用它的onNewIntent方法发送intent请求调用已经存在于顶部的Activity实例,而不是创建一个新的Activity实例。Activity可以被实例化多次,每个实例也能属于不同的Task,一个Task也能有多个实例(但是只有当回退栈的顶部不是这个Activity已经存在的实例)。

例如,假设Task的回退栈已经存在根ActivityA,以及ActivityB、C、D(D在栈的顶部),当intent接收到类型D的Activity时,如果D是默认的standard模式,则会创建一个新的ActivityD,栈里的实例就变成了A、B、C、D、D。然而,如果D是singleTop模式,栈中已经存在的ActivityD就会通过onNewIntent方法接受到Intent请求,(因为D在栈的顶部),这个时候栈中仍然是A,B, C,D。然而,如果Intent接受到的Activity类型为B,则还是会创建一个新的B实例到栈中,即使它的加载模式是“singleTop”。

3.singleTask模式

系统会创建一个新的Task并将Activity作为新Task的根(root)实例化。然而,如果Activity的实例已经存在于一个其他的Task中,系统会通过onNewIntent方法发送Intent请求到已经存在的实例,而不是创建一个新的实例。在同一时间,Activity的实例只能有一个。

注意

即使 Activity 启动了一个新的 Task,当我们单击回退按钮时,还是会回到前一个Activity。

4.singleInstance模式

跟singleTask效果一样,不同的是,系统不会加载其他的Activity到包含了这个实例的Task中, Activity实例只有一个并且它是Task的唯一成员。之后被该Activity启动的其他Activity都会在不同的Task中启动。

举个例子,Android的浏览器程序通过指定Activity的singleTask模式声明它的Activity总是在它自己的Task中打开,这也意味着如果你的程序发送Intent去打开浏览器,浏览器的Activity和你程序的Activity不在同一个Task中,而是为浏览器创建一个新的Task,如果浏览器已经有一个Task运行在后台,那么它会被带到前台来处理这个新的Intent。

另外一种打断默认行为效果的方式则是通过Intent中的flag。

在使用 Intent 方式时,我们可以通过设置 Intent 的值为 FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_SINGLE_TOP以及FLAG_ACTIVITY_CLEAR_TOP来达到我们需要的效果。

FLAG_ACTIVITY_NEW_TASK:它的效果和前面提到的singleTask启动模式的效果是一致的。

FLAG_ACTIVITY_SINGLE_TOP:它的效果和前面提到的singleTop启动模式的效果一致。

FLAG_ACTIVITY_CLEAR_TOP:如果要启动的 Activity 已经运行在当前 Task 中,那么不会再创建该 Activity 的新实例,而是所有在这个 Activity 实例上面的 Activity 都会被销毁,并通过onNewIntent方法启动该Activity(此时,该Activity已经处于栈的顶部)。之前提到的配置launchMode的方式,没有一个属性的效果跟此值的效果一致。

6.6 配置改变

如果设备的配置改变了(定义在Resource.Configuration类中),任何显示在界面上的东西都需要更新以适应配置。由于Activity是与用户交互的主要机制,所以它也包括一些处理配置改变的特殊支持。

除非你指定了,否则配置改变(比如改变屏幕方向,语言,输入设备等)会导致你当前的Activity会销毁,并调用相应的Activity生命周期进程函数onPause(),onStop()以及onDestroy()。如果这个Activity运行在前台或者对用户可见,一旦这个实例(Activity)的onDestroy()被调用后就会马上又创建一个新的该Activity实例,并且前一个Activity实例中的onSaveInstanceState(Bundle)方法中产生的savedInstanceState 也还存在。

这样做是因为任何程序资源,包括布局文件都能在任何配置值被改变的情况上被动地改变,因此唯一安全的处理配置改变的方式就是重新获取所有的资源,包括布局(layout),图片资源(drawables)以及字符资源(strings)。因为Activity必须知道怎样去保存自己的状态和重新创建自己的这种状态,所以根据新配置重新启动一个Activity是非常简便的方式。

当然,在某些特殊的情况下,我们可能希望在某些配置类型改变时绕过重新启动Activity来直接做某些应对配置值改变的情况。这个可以使用在 manifest 文件中配置的 Activity 的 android:configChanges 属性来做到。任何你在 manifest 中定义的配置类型,都会回调你当前 Activity 的onConfigurationChanged(Configuration)方法,而不是重新启动你的Activity。如果一个配置的改变涉及任何你不想处理的,这个Activity还是会被重新启动,而且onConfigurationChanged(Configuration)也不会被调用。

6.7 如何保存和恢复Activity状态

之前我们提到了Activity的生命周期,也稍微了解了onPause和onStop方法,在调用了这两个方法后,Activity暂停或者停止(界面可能直接被覆盖了),但是这个Activity的实例仍然存在于内存中,并且它的信息和状态数据都不会销毁,当Activity重新回到前台后,所有的这些信息和状态又会回到和以前一样。

但是,如果系统在内存不足的情况下调用了onPause或onStop方法,Activity可能会被系统销毁,这个时候,内存中是不会存在Activity实例的,如果该Activity再次回到前台,之前的信息和状态可能无法保存,页面也就无法根据这些信息和状态回到原来的样子。为了避免这种情况, Activity中提供了onSaveInstanceState方法,这个方法接收一个Bundle类型参数,我们可以将状态和数据保存到Bundle对象中,这样的话,就算Activity被系统销毁,只要用户重新启动Activity调用onCreate方法,我们就能在onCreate方法中得到Bundle对象,并根据这个对象中的数据将Activity恢复到之前的样子。

具体可以看以下代码。

    @Override
        protected void onCreate(Bundle savedInstanceState) {
            // TODOlt@span b=1> Auto-generatedlt@span b=1> methodlt@span b=1> stub
            super.onCreate(savedInstanceState);
            savedInstanceState.get("preState");
        }
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            // TODOlt@span b=1> Auto-generatedlt@span b=1> methodlt@span b=1> stub
            super.onSaveInstanceState(outState);
            outState.putString("preState", "eoe");
        }

代码解释

我们在onSaveInstanceState方法中将eoe这个值以键为preState存入了outState这个Bundle对象,之后我们就能在onCreate方法中,通过savedInstanceState这个Bundle对象取得eoe这个值了。

注意

onSaveInstanceState方法并不一定会被调用,因为有些场景是不需要保存状态数据的,比如,当用户单击“后退”按钮的时候,因为用户已经明确要关闭当前Activity了。

其实,即使不覆写onSaveInstanceState方法,该方法依然会默认保存Activity的某些状态数据,比如 Activity 里各个 UI 控件的状态。Android 里几乎所有的 UI 控件都适当地实现了onSaveInstanceState方法,所以,当Activity被摧毁并重新恢复时,这些控件会自动保存和恢复状态。比如EditText控件会自动保存和恢复输入的数据,checkbox也会保存它是否已经选中的状态,当然,要做到这点你也需要给这些控件指定 ID,不然这个控件是不会自动进行数据和状态的保存与恢复的。

由于onSaveInstanceState方法不一定会被调用,所以,我们不适合在这个方法中保存持久化数据,例如向数据库中插入记录等,类似这种操作,应该放到 onPause 方法中进行(前面提过)。onSaveInstanceState方法其实只适合保存瞬时状态数据,比如某些成员变量等。

小知识

除了系统因为内存不足,会摧毁你处于暂停或停止状态的Activity之外,系统设置的改变也会导致Activity的摧毁和重建。这个我们在本章上面节点“配置改变”中提到过,所以,如果你想要测试你的程序恢复状态的能力,简单的旋转装置,让屏幕横竖屏切换是非常好的方式。

6.8 启动Activity并得到结果

在Activity中,你可以调用startActivity(Intent)方法被用来启动一个新的Activity,并将这个新的Activity置于Activity栈的最顶部。但是有时候,你却可能希望当一个Activity结束时从这个被结束的Activity中得到一个返回结果,例如,你可能启动了一个Activity让用户在联系人名单上选择一个人,当这个 Activity 结束时,它返回这个被选中的人给你。为了做到这个,你可以调用startActivityForResult(Intent,int),结果将会通过onActivityResult(int,int,Intent)方法返回。

当一个Activity退出时,它可以调用setResult(int)将数据返回到它的父类,当然,它也必须要提供一个结果代码,可以是标准的结果代码RESULT_CANCELED,RESULT_OK,或者任何其他自定义起始于RESULT_FIRST_USER值。另外,也可以返回一个带有你想要的附加数据的Intent。所有的这些信息会随着最初提供的整数标识符显示回父类的Activity.onActivityResult()方法中。

如果子 Activity 因为任何原因失败了(比如报错了),父 Activity 就会收到一个结果代码RESULT_CANCELED。

6.9 Activity小实例

在介绍完Activity相关基础内容后,现在我们来针对Activity开发一个简单的小实例。

这个实例指定了3个界面(Activity)。HelloWorldActivity界面有两个按钮,Button 1和Button 2 (见图6-3),Button 1单击后会跳转到Activity B,而Activity B简单地显示“This is Activity B, Welcome!”(见图6-4)。单击Button 2后则跳转到Activity C,Activity C界面有一个输入框以及一个“确定”按钮(见图6-5),当单击“确定”按钮之后,将关闭Activity C,并获取输入框中的内容,回传到HelloWorldActivity,并将回传的内容显示在Button2的下方,如图6-6所示。

▲图6-3 程序主界面

▲图6-4 单击Button1后进入Activity B

▲图6-5 单击Button 2后进入Activity C

▲图6-6 获取Activity C的值显示在按钮下方

实例编程实现

第1步:新建一个Android项目(相信大家看完前面的章节已经知道如何去创建一个Android项目了,这里就不再赘述),并创建包名,3个Activity(全部继承自Activity类,并重写onCreate方法)以及3个布局文件。如图6-7所示。

▲图6-7 实例项目架构(基于Android 2.2)

第2步:打开main.xml布局文件,编写如下代码。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        <Button android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button1"/>
        <Button android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Button2"/>
        <TextView android:id="@+id/tvDisplay"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"/>
    </LinearLayout>

代码解释

定义一个LinearLayout(线性布局),并在这个布局中定义两个Button和一个TextView控件。两个Button的长和宽都是wrap_content(包裹住内容)就可以了。TextView的宽度则是fill_parent (填满父控件)。

注意

上述代码中的Button控件的android:text属性的值理应放置在项目结构中values文件夹下strings.xml文件中定义,并采用@string/xxx来引用对应的值。由于这里主要是介绍Activity,所以就直接将值写在了属性后面。

第3步:打开activityb.xml布局文件,编写如下代码。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        <TextView
          android:id="@+id/tvActivityb"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="This is ActivityB,Welcome!"/>
    </LinearLayout>

代码解释

这里只是定义了一个线性布局,并在布局中定义了一个 Textview 控件,并显示“This is ActivityB,Welcome”字样。

第4步:打开activityc.xml布局文件,编写如下代码。

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="vertical" >
        <EditText
          android:id="@+id/etActivityc"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"/>
        <Button android:id="@+id/buttonc1"
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:text="确定"/>
    </LinearLayout>

代码解释

定义一个线性布局,并在布局中定义一个EditText和Button控件。

第5步:打开HelloWorldActivity.java文件,找到onCreate方法,编写如下代码。

    //将布局文件设置为main.xml
    setContentView(R.layout.main);
    //得到两个Button控件
    Button mButton1 = (Button)findViewById(R.id.button1);
    Button mButton2 = (Button)findViewById(R.id.button2);
    //为Button1绑定单击事件
    mButton1.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODOlt@span b=1> Auto-generatedlt@span b=1> methodlt@span b=1> stub
            //使用intent启动ActivityB
            Intent _intent =
            new Intent(HelloWorldActivity.this, ActivityB.class);
            startActivity(_intent);
        }
    });

代码解释

HelloWorldActivity首先将main.xml文件设置为布局文件,之后通过findViewById方法得到两个 Button 控件,由于先打算实现单击 Button1跳转到 ActivityB 这个功能,我们暂时只为 Button1绑定单击事件。

    Intent _intent =
      new Intent(HelloWorldActivity.this,  ActivityC.class)语句新建了一个Intent,这个Intent描
    述了从HelloWorldActivity跳转到ActivityB的一次操作。
    startActivity(_intent)语句用来启动_intent,由_intent描述的这次操作才正式执行。

小知识

什么是Intent?在Android官方文档中是这么定义的,Intent是一次即将操作的抽象描述,现在理解这个定义还有些抽象,但是看完本书就会对这个定义理解了。在Android当中,一共用到了3种Intent,现在使用的是第一种,它的作用就是启动一个新的Activity并且可以携带数据。还有两种分别如下:

(1)通过Intent启动一个服务(Service)。

(2)通过Intent广播事件。

以上两种我们会在后续的章节讲到,这里不再细述。

第6步:打开ActivityB.java文件,这个文件中没有复杂的代码,只是将activityb.xml文件设置为ActivityB的布局文件。具体代码如下。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activityb);
    }

通过这一步之后,从HelloWorldActivity跳转到ActivityB就已经完全实现了。接下来就要实现稍微复杂一点的从 HelloWorldActivity 跳转到 ActivityC,并得到返回值显示在 HelloWorldActivity的逻辑了。

第7步:重新打开HelloWorldActivity.java并为Button2添加监听事件,具体代码如下。

    mButton2.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
        // TODO Auto-generated method stub
        Intent _intent =
        new Intent(HelloWorldActivity.this,  ActivityC.class);
        startActivityForResult(_intent, 100);
        }
    });

代码解释

Intent _intent =new Intent(HelloWorldActivity.this,ActivityC.class)跟Button1一致,也是描述一次从 HelloWorldActivity 到 ActivityC 的跳转操作。不同的是,这次启动时,使用的方法是startActivityForResult()方法。

上述代码中的startActivityForResult方法有两个参数,第一个是intent对象,还有一个则是“请求码”(requestCode),这个请求码是用来区分不同的请求。

例如,A Activity使用了startActivityForResult方法启动了B Activity以及C Activity,在回调的时候,A Activity中的回调方法只有一个,这样,我们就能够根据不同的requestCode在不同的时机只取B Activity或C Activity返回的值。

小知识

startActivity与startActivityForResult的区别。

startActivity在启动了其他Activity之后是不会再回调回来的,相当于启动者与被启动者在启动完毕之后是没有关系的。

startActivityForResult在启动了其他Activity之后是有回调的,也就是说启动者与被启动者在启动完毕之后依然是有关系的。

第 8步:前面我们已经在 HelloWorldActivity中为 Button2 添加了事件并使用startActivityForResult来启动ActivityC,现在我们看看ActivityC又需要做些什么呢?关键代码如下所示:

    //设置activityc.xml为布局文件
    setContentView(R.layout.activityc);
    //得到Button实例
    Button button1 = (Button)findViewById(R.id.buttonc1);
    button1.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            // TODO Auto-generated method stub
        //实例化一个intent对象
        Intent data = new Intent();
        //获取EditText实例
        EditText editText = (EditText)findViewById(R.id.etActivityc);
        //得到EditText的值
        String val = editText.getText().toString();
        //将EditText的值存到intent对象中(以键值对的形式)
        data.putExtra("helloworld", val);
        //调用setResult方法,将intent对象(data)传回父Activity
        setResult(Activity.RESULT_OK, data);
        //关闭当前Activity
        finish();
        }
      });

代码解释

从上面的代码注释中也能了解到,先是获取了 EditText 这个控件对象,并使用editText.getText().toString()得到EditText中输入的值,再通过intent的putExtra方法将获取的值以键值对的形式存入Intent中,之后调用setResult方法将成功的状态码以及intent对象传到父Activity (HelloWorldActivity)中。

小知识1

Intent在传递数据时提供了putExtra和对应的getExtra方法来实现存值与取值。而这里的put和get方法其实和Bundle的put,get方法是一一对应的。在Intent类中有一个Bundle的mExtras成员变量,所有的putExtra和getExtra方法实际上都是调用mExtras对象的put和get方法进行存取。所以,在正常情况下,传递数据可以直接使用intent的putExtra和getExtra方法即可,无需再创建一个Bundle对象。

小知识2

Bundle类型。这里简单介绍一下,Bundle是一个类型安全的容器,它的实现就是对HashMap做了一层封装。对于HashMap来说,任何键值对都可以存进去,值可以是任何的Java对象。但是对于Bundle来说,同样是存键值对,但是这个值只能是基本类型,或者基本类型数组,比如int,byte,boolean,char等。

如果大家对Bundle的概念还是有点模糊,没关系,在以后的学习过程中会慢慢了解,这里只需要知道,我们可以使用Intent对象的putExtra和getExtra方法来存取数据就行了。

第 9步:在完成了Activity C.java文件的代码编写后,我们接着再继续打开HelloWorldActivity文件,最开始,我们在HelloWorldActivity中实现了Button 2的事件,这个事件启动了对Activity C的调用,而在Activity C中我们刚刚也得到了一个EditText对象的值并通过setResult方法回传到了父 Activity(HelloworldActivity),那么现在我们就需要在 HelloworldActivity中来实现我们的回调函数了。具体代码如下。

      @Override
    protected void onActivityResult(int requestCode,int resultCode,
                                          Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode == 100 && resultCode == Activity.RESULT_OK){
            String val = data.getExtras().getString("helloworld");
            TextView textView  =  (TextView)findViewById(R.id.tvDisplay);
            textView.setText("来自ActivityC的值 :"+ val);
        }
    }

代码解释

if(requestCode == 100 && resultCode == Activity.RESULT_OK)这句代码是判断requestCode是不是等于当初你在startActivityForResult方法中设置的requestCode,并且ActivityC返回的resultCode是不是等于RESULT_OK,如果是,则通过data.getExtras().getString("helloworld")获取ActivityC中通过putExtra方法存的值。得到值之后,再获取main.xml布局文件中的TextView控件,并将值赋给它显示出来。

第 10步:这也是最容易被忽视的一步,我们所有的Activity都必须在Androidmanifest.xml文件中进行注册,如果不注册,程序将会出错。具体注册代码如下。

    <activity android:name=".ActivityB"/>
    <activity android:name=".ActivityC"/>

注册完毕之后,整个实例就完成了,赶紧运行试试看吧!

6.10 本章小结

本章主要对Android中最重要的组件之一Activity进行了基本的讲解。在本章的最开始就已经说明了Activity对整个应用程序的重要性,所以学好Activity可以说是开发Android应用程序必备基础技能之一,尤其是对Activity的生命周期以及基本状态的了解,掌握了这些,在开发应用时,你就能游刃有余地把握每个Activity不同时期的不同状态,从而做出最合理的操作。最后又补充了一个Activity的小实例,希望大家能跟着本书动手编写,因为只有多写才能真正学好Android。