第三部分:Android 应用程序接口指南---第一节:应用程序组件---第一章1.Activity

第1章  Activity

Activity是一个应用程序组件,它提供了一个屏幕,用户可以与它进行交互来做一些事情,比如拨打电话,拍照,发邮件,或查看地图。每个Activity都被给予了一个窗口,可在其中绘制它的用户界面。窗口通常会占据整个屏幕,但可能比屏幕和浮在上方的其他窗口都要小。

一个应用程序通常由多个互相松散绑定的activity组成。当首次启动应用时,应用程序中会有一个activity被指定为“main”activity呈现给用户。这样每个activity就可以启动另一个activity来执行不同的操作。每次启动一个新的activity,先前的activity就会停止,但系统会保留堆栈(回栈)中的activity。当一个新的activity启动时,它被推送到底部栈中并获得用户焦点。回堆栈基本遵守“后进先出”的堆栈机制。因此当用户完成当前activity并按下返回按钮,当前activity就会出栈并被摧毁,还会恢复先前的activity。

当一个activity因一个新activity的启动而停止时,可通过activity的生命周期回调方法来通知状态的改变。系统无论是创建它、阻止它、恢复它还是摧毁它,都会有几个回调方法接受和处理这些状态的改变,每一个回调方法都会在状态改变时给你提供适当的机会来让你执行具体的工作。例如,停止时,你的activity应该释放一些大型的对象,如网络或数据库连接。恢复activity时,你可以重新获得必要的资源并恢复被中断的动作。这些状态转换全都属于activity生命周期。

本文接下来将讨论如何构建和使用activity的一些基础知识,全面介绍activity生命周期是如何工作的,所以你可以妥善管理各种activity状态之间的转换。

1.1 创建一个activity

要创建一个activity,你就必须创建一个activity的子类(或是现有的子类)。当activity在它各种生命周期状态之间转换,如activity正被创建、停止、恢复、或销毁时,你需要在你的子类实现系统调用的回调方法。最重要的两个回调方法是:

1. OnCreate()

你必须实现这个方法。当创建你的activity时,系统就会调用这个方法。在这个方法的实现中,你应该初始化activity中的必要组件。最重要的是,为了定义activity用户界面的布局你还必须在此方法基础上调用setContentView()。

2. onPause()

系统调用此方法是用户正准备离开你的activity(尽管它并不总是意味着activity正被销毁)的第一个迹象。通常你应该提交一些改变(因为用户可能不回来)。

还有几个其他生命周期回调的方法,为了提供更流畅的用户体验你应该使用它们,因为它们能处理导致你的activities停止或摧毁之类的异常中断问题,

1.1.1实现一个用户界面

activity通过view类派生的对象并根据这些views的层次来提供用户界面。在这个activity的窗口中,每个view会控制一个特定的矩形空间并且可以响应用户的操作。例如,view可能被当成一个按钮,当用户触摸时就启动一个动作。

Android提供了一些已经设计好的views。“Widgets”是为屏幕提供可视化(和互动)元素的views,如按钮、文本域、复选框或者只是一个图像。通过ViewGroup派生的views叫“Layouts”,Layouts是ViewGroup的子类views,每一个Layout提供一个独特的布局模式,如线性布局、网格布局或相对布局模式。你也可以细分View和ViewGroup类(或现有的子类)来创建自己的Widgets和布局,并将它们应用到你的activity布局中。

使用view定义一个布局最常见的方式是使用一个XML布局文件,它保存在你的应用程序资源里。

用这种方式,你可以让代码和用户界面分离,从而让你更方便的维护想要维护的内容。在activity中可以根据布局的资源ID使用setContentview()方法,会把这个布局资源ID当成一个IU设置为一个布局。然而,你也可以在activity代码中创建新的Views,并通过插入新Views到ViewGroup来构建一个view层次结构,然后把ViewGroup当成根结点,通过setContentView()方法来使用该布局。

1.1.2声明manifest中的activity

为了让系统能访问你的activity,你必须在manifest文件中声明activity。打开你的manifest文件并把一个<activity>元素作为子元素添加到<application>元素中就可以声明activity。

如代码清单3-1所示:

<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >

 

代码清单3-1

这个节点还有一些其他的属性,包括定义属性,如activity的标签、图标或者是主题风格。指定activity类名的android:name属性是唯一必需的属性。一旦发布了你的应用程序,你就不应该更改这个名称,因为如果你改动了,你就可能破坏某些功能,比如应用程序的快捷方式。

1.1.3使用intent filter

为了声明其他的应用程序组件是如何的激活它, <activity>节点中可以使用<intent-filter>节点来指定不同的view filter。

当你使用Android SDK工具创建一个新的应用程序时,它会为你自动创建一个包含有intent filter的activity,这个activity会声明响应的action为“main”的并且在category中它被放到“launcher”的位置。如代码清单3-2所示:

<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

 

代码清单3-2

<action>节点为应用程序指定“main”的切入点。<category>节点指定这个activity应该被列入系统应用程序的launcher(为了允许用户启动activity)。

如果你打算让你的应用程序是独立的,并且不允许其他应用程序激活它的activity,那么你并不需要任何其他的intent filter。正如前面例子所看到的,只有一个activity应该有“main”action和“launcher”category。如果不想让其他应用程序使用,activity就不应该有intent filters,而且你自己就可以使用明确的intents来启动它。

然而,如果你想要activity能响应隐式的intents从而启动其他应用程序的话(和你自己的应用程序),那么你必须为你的activity定义额外的intent filters。对于每种你想要响应的intent类型来说,你都必须包含一个<intent-filter>,它里面有一个<action>节点和一个可选的<category>节点和/或<data>节点。这些节点指定你的activity可以响应的intent类型。

1.2 启动一个activity

你可以通过调用startActivity()方法来启动另一个activity,并给它传递一个Intent,这个intent描述了你要启动的activity。Intent要么是明确指定你要启动的activity,要么是你要执行的动作类型(系统甚至可以为你从不同的应用程序中选择最恰当的activity)。Intent也可以携带少量用于启动activity的数据。

当你的应用程序工作时,你会经常需要简单地启动一个已知的activity。你可以通过创建一个明确的intent来完成,这个intent使用类名来明确定义你想要启动的activity。如代码清单3-3所示,我们想要直接SignInActivity启动:

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

 

代码清单3-3

然而,你的应用程序可能还需要执行一些动作,如发送电子邮件、文本消息或状态更新、使用activity中的数据。在这种情况下,你的应用程序可能没有自己的activities来执行这些动作,但你可以转而利用设备上其他应用程序提供的activities,来为你执行。这是intent真正有价值的地方——你可以创建一个描述你想要执行什么样动作的intent,然后系统会从另一个应用程序中启动一个最恰当的activity。如果有多个能处理intent的activity,用户就可以选择使用哪一个。例如,如果你想允许用户发送电子邮件,你可以创建下面这些intent,如代码清单3-4所示:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

 

代码清单3-4

上述代码中的EXTRA_EMAIL这个键表示一个将要被发送的email地址,而右边的值是表示这个email地址的字符串数组。当邮件应用程序响应这个intent时,它会读取在右边中的字符串数组并且把他们放到电子邮件的“to”字段中。在这种情况下,电子邮件应用程序中的activity就会启动,并且当用户执行完操作后后,你的activity就会恢复。

1.2.1启动一个activity 并给一个结果

有时,你可能就想要从一个启动的activity接收一个结果。在这种情况下,可以调用startActivityForResult()(而不是startActivity())方法来启动activity。之后为了能接收到后续activity的结果,你就需要实现onActivityResult()回调方法。后续activity完成时,它就返回一个结果,结果就在onActivityResult()方法的Intent参数中。例如,你可能希望用户选择一个他们的联系人,这样你的activity就可以利用与联系人有关的信息做一些事情。下面让我们来看一个例子,如代码清单3-5所示:

private void pickContact() {
    //创建一个intent来 "pick" 一个联系人,通过content provider URI来定义
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);
    startActivityForResult(intent, PICK_CONTACT_REQUEST);
}
 
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // 如果请求进展顺利并且请求码是PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        //根据联系人的名字content provider执行一次查询
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // 如果cursor 不为空 返回的就是true
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // 用挑选出来的联系人名字做一些事情...
        }
    }
}

 

代码清单3-5

这个例子展示了一个为了处理activity结果而使用onActivityResult()方法的基本逻辑。首要条件是检查请求是否成功——如果它是,那resultCode将会是RESULT_OK,然后同时检查请求代码是不是我们已知的响应请求结果,那么在这种情况下,requestCode就会与通过startActivityForResult()方法发送过来的第二个参数相匹配。因此,代码就可以通过intent里的数据来处理activity的结果。

另外一个情况是,ContentResolver针对一个content provider来执行查询,然后返回一个Cursor,它允许我们读取查询的数据。

1.3 关闭一个activity

你可以通过调用finish()方法关闭一个activity。你也可以通过finishActivity()方法关闭之前启动的一个单独的activity。在大多数情况下,用这些方法应该不能明确地finish掉一个activity。接下来的部分将讨论activity的生命周期,Android系统会为你管理activity的声明周期,所以你并不需要自行finish你的activity。调用这些方法可能对用户体验造成不利的影响,所以只有当你绝对不想用户回到这个activity实例时才应该使用他们。

1.4管理activity的生命周期

通过实现回调方法来管理你的activity生命周期,对开发强大和灵活的应用程序来说是至关重要的。Activity的生命周期直接受到与它相关的其他activities,它的任务以及返回堆栈操作的影响。

一个activity基本上存在三种状态:

1. Resumed(已恢复):这种状态的activity处于屏幕前台并获取了用户焦点(此状态有时也被称为“运行”)。

2. Paused(已暂停):当另一个activity处于屏幕前台并获取了用户焦点时,我们先前的activity就处于Paused状态了,但先前的这个activity仍然可见。也就是说,另一个activity在它的顶部可见并且是半透明或者没有覆盖整个屏幕。一个已暂停的activity仍然是运行的(Activity对象保存在内存中,它会保留所有状态和成员的信息并保持与窗口管理器的连接),但在极低内存情况下,它会被系统关闭。

3. Stopped(已停止):这个状态的activity就完全被另一个activity遮住了(该activity目前正处于“后台”)。一个已停止的activity也仍然还活着(Activity对象保存在内存中,它会保留所有状态和成员的信息,但没有与窗口管理器的连接)。它已不再对用户可见,并且当其他地方需要内存时,它就会被系统关闭。

如果一个activity被暂停或停止,系统可以通过调用finish()方法或直接kill掉它的进程来从内存中移除它。当要再次启动activity时(被系统移除后),就必须从新创建。

 

 

1.4.1实现生命周期回调

当activity在上述的状态间转换时,可通过各种回调方法来通知它状态的改变。所有的回调方法是hooks(直译:钩子),当activity状态发生改变时,你可以重写这些方法来做一些相应的工作。以下是一个activity的框架,它包含了每个基本生命周期方法,如代码清单3-6所示:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // activity被创建时
    }
    @Override
    protected void onStart() {
        super.onStart();
        // activity即将可见时.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // activity已经可见时.
    }
    @Override
    protected void onPause() {
        super.onPause();
        // 另一个activity获得焦点时 (这个 activity即将处于已暂停(paused)状态).
    }
    @Override
    protected void onStop() {
        super.onStop();
        // activity不在可见时
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // activity即将被摧毁
    }
}

 

代码清单3-6

注意:在做任何工作之前,实现这些生命周期方法都必须始终调用父类的实现,正如上面的例子所示。

 

综合起来,这些方法定义了一个activity的整个生命周期。通过实现这些方法,你可以监测activity生命周期中的三个状态循环:

1. 整体生命期:一个activity的整循环是在调用OnCreate()方法和OnDestroy()方法之间出现的。activity应该用onCreate()方法执行“global”的状态设置(如定义布局),并用OnDestroy()方法释放所有剩余资源。例如,如果你的activity有一个从网上下载数据的线程在后台运行,则它可能会用onCreate()方法创建该线程,然后调用OnDestroy()方法来停止线程。

2. 可见生命期:activity的可见循环是在调用onStart()方法和onStop()方法之间出现的。在此期间,用户可以看到屏幕上的activity,并可以与它进行交互。例如,当一个新的activity启动时,调用onStop(),它就不再是可见的。使用这两种方法,你可以保留一些需要展示给用户的资源。例如,你可以用onStart()方法注册一个广播BroadcastReceiver(),用来监测UI的改变,当用户不再能看到你展示的东西时你可以在onStop()方法注销这个广播。在activity整个生命循环中,系统可能会调动多次onStart()和onStop()方法,因为activity经常会对用户可见和对用户隐藏之间进行交替。

3. 前台生命期:屏幕前台循环是在调用onResume()和onPause()方法之间出现的。在这段时间内,此activity是在所有其他activity的前面,显示到屏幕上,并获取了用户输入焦点。这个activity可以在屏幕前台频繁地转换。因为这种状态是能经常转换的,所以这两个方法中的代码应该是相当轻量级的,这是为了避免使得用户等待的时间太长。

 

下图3-1说明了不同状态下的activity生命周期循环。矩形代表的是回调方法,当activity在不同状态间转换时,你可以用他们实现执行操作。

如图3-1所示:

 

图3-1 activity的生命周期

下面的表3-1列出了和上图一样的生命周期回调方法,这个表详细的介绍了每个回调方法,包括回调方法完成后系统是否能关闭activity。

方法

描述

Killable after

紧接着

onCreate()

当activity首次创建时调用。在这里你应该做你所有的常规的静态设置,如创建view、将数据绑定到列表等。如果状态被捕获,那么这方法会传递一个包含先前activity状态的Bundle对象。

onStart()

    

onRestart()

这个方法只在Activity已经停止,被再次启动前调用。

onStart()

onStart()

在activity对用户可见前调用。如果activity在屏幕前台就紧跟onResume()方法或它对用户隐藏就紧跟onStop()方法。

onResume() 
or
onStop()

    

onResume()

在Activity与用户进行交互前调用。此刻这个activity在activity的堆栈顶部,马上可以响应用户输入。

onPause()

onPause()

当系统即将启动另一个activity时调用。这种方法通常用于提交未保存的更改到持久化数据、停止动画和可能大量耗费CPU的事务。它应该做一些做起来很快的事情,因为直到它返回,下一个activity才能恢复。如果activity返回前台就紧跟onResume()方法或者它对用户不可见就紧跟onStop()方法。

onResume() 
or
onStop()

onStop()

当activity不再对用户可见时调用。当activity正被销毁或者另一个activity(无论是现有的还是新的)已恢复并且覆盖了它,这个方法就有可能调用。如果activity返回与用户进行交互就紧跟onRestart()方法,或者它正离开就紧跟onDestroy()方法。

onRestart()
or
onDestroy()

onDestroy()

在activity被销毁前调用。这是activity接收到的最后调用。要么是在一个activity结束时调用,要么是在系统为节省空间而暂时销毁activity的实例时被调用。你可以用isFinishing()方法来区分这两种情形。

表格3-1 activity生命周期回调方法的总结

标有“Killable after”的那一栏说明了方法返回后,系统是否可以关闭进程。三个方法被标记为“是”:(onPause()、onStop()和onDestroy())。这个进程是如果系统必须在紧急情况下恢复内存,但onStop()和onDestroy()方法可能调用不到。因此,你应该调用onPause()方法来写重要的持久性数据以便于存储。然而,在调用onPause()方法时,你应该有选择性地保留信息,因为在这方法中处理的事情不要太多而阻塞用户界面,并且它转换到下一个activity并且还会使用户体验的速度变慢。

注意:Killable after标记为是,也有可能被系统关闭。但这仅仅是在没有其他资源的极端情况下才有可能发生。

1.4.2保存activity的状态

上节提到,当暂停或停止一个activity时,activity的状态会被保留下来。这是事实,因为当暂停或停止它时,Activity对象仍保存在内存中,其成员信息和当前状态仍是活着的。因此,用户在activity中做出的任何改变都会被保存,这样当activity返回屏幕前台(“resumes”)时,这些改变仍然会存在。

然而,当系统为恢复内存而销毁一个activity时,相应的Activity对象也会被销毁,系统不能令它的的状态完好无损,只能简单地恢复它。相反,如果用户导航回来,系统就必须重建Activity对象。但用户不知道的是,系统销毁了activity并重新创建了它,从而,activity有可能是完全相同的。在这种情况下,你可以通过实现一个额外的回调方法:onSaveInstanceState()来确保有关activity状态的重要信息已经保存,这个方法允许你保存activity的状态信息。

在activity被销毁之前,系统会调用onSaveInstanceState()方法。系统会传递一个Bundle给这个方法,在Bundle中你可以调用putString()方法或者putInt()方法,来保存有关activity的名称-键值对的状态信息。然后,如果系统关闭你的应用程序进程并且用户导航回到你的activity,系统就会重新创建activity,并传递Bundle给onCreate()方法和onRestoreInstanceState()方法。使用任意一种方法,你都可以从Bundle中提取你保存的状态并恢复它。如果没有状态信息要恢复,那么传递给你的Bundle就是空(这是在首次创建activity的情况下)。如图3-2所示:

 

图3-2 activity的状态保存

图3-2说明了,activity要返回到用户焦点并保持状态完好的两种方法是:要么activity被销毁,然后重新创建一个并且必须恢复之前保存的状态,要么是停止activity,然后恢复它并且保持它的状态完好。

 

注意:这并不能保证在你的activity被销毁之前就会调用onSaveInstanceState()方法,因为在某些情况下,它可能不需要保存状态(如用户使用“后退”按钮就离开activity,是因为用户已经明确关闭了activity)。如果系统真的要调用onSaveInstanceState()方法,它就可能会在调用onStop()和onPause()方法之前这样做。

 

然而,即使你什么也不做,也不实现onSaveInstanceState()方法,通过Activity类中的onSaveInstanceState()方法的默认实现,一些activity状态也会被恢复。具体来说, onSaveInstanceState()方法的默认实现会调用每个布局中的View,它允许每个View提供自身应该保存的信息。几乎每一个在Android框架内的widget实现这个方法都被视为是恰当的,如当你重新创建一个activity,UI任何明显的改变都会自动保存和恢复。例如,EditText widget会保存用户输入的任何文字,CheckBox widget则会保存是否选中。你需要做的唯一工作是为每一个你想保存其状态的widget提供一个唯一的ID(带有android:id属性)。如果一个widget没有一个ID,系统就无法保存其状态。

尽管默认调用onSaveInstanceState()方法的实现保存了一些有关activity UI的有用信息,你仍然可能需要重写它,以保存额外的信息。例如,你可能需要在activity声明周期改变的时候保存成员变量的值(这可能与在UI中恢复的值相关,但在默认情况下,持有这些UI的成员变量值都不会被恢复)。你也可以通过设置android:saveEnabled属性为“false”,或调用setSaveEnabled()方法,来阻止在你的布局里的view保存其状态。通常情况下,你不应该禁用,但如果想恢复activity的UI,使其状态不同,你可能就会这样做。

因为onSaveInstanceState()方法的默认实现有助于保存UI的状态,但如果你想覆盖这个方法以保存额外的状态信息,你就应该在做任何事之前总是先调用父类onSaveInstanceState()方法。同样,你也应该调用父类onRestoreInstanceState()的方法来覆盖它,这样就可以恢复view的状态。

注意:因为onSaveInstanceState()方法也不能保证能调用,你应该使用它来记录activity的瞬时状态(UI的状态)-不应该用它来存储持久数据。相反,当用户离开activity时,你应该使用onPause()来存储持久数据(如应保存到数据库中的数据)。

 

测试你的应用程序恢复状态的能力的一个好方法是简单地旋转设备,使屏幕的方向改变。当屏幕方向改变,系统为了把可用的替代资源应用到新的屏幕配置上去,会销毁并重新创建activity。仅仅出于这个原因,当activity被重建时,它能完全恢复其状态是非常重要的,因为用户在使用应用程序时会经常旋转屏幕。

1.4.3处理配置更改

有些设备的配置在运行时可以更改(如屏幕方向,键盘的可用性和语言)。当这种改变发生时,Android会重新运行activity(系统调用OnDestroy()方法之后就立即调用onCreate()方法)。通过用你所提供的替代资源(屏幕方向不同和大小不同的布局)来自动重新加载应用程序,这种行为会帮助你的应用程序去适应新的配置。

如果恰当地设计你的activity,来处理重新启动问题,这问题是由屏幕方向的改变和保存上面所说的activity状态引起的,你的应用程序将在应对activity生命周期中出现的其他突发事件上更具弹性。

处理这样一个重启问题的最好方式是调用上面所说的onSaveInstanceState()方法和onRestoreInstanceState()方法(或OnCreate()方法)来保存和恢复activity的状态。

 

1.4.4 activity的协调

当一个activity启动另一个activity,他们就都经历了生命周期的转换。第一个activity被暂停和停止(如果仍然在后台可见,那么它就不会停止),其他的activity就会被创建。倘使这些activity共享保存到磁盘或其他地方的数据,那么你要理解的是,第一个activity在第二个activity被创建之前是不会完全停止的。相反,启动第二个activity的进程与停止第一个activity的进程相重叠。

生命周期回调的顺序被很好地定义了,尤其是当两个activity是在同一进程中并且其中一个可以启动另一个。这是当出现Activity A启动Activity B情况时操作的顺序:

(1).Activity A的onPause()方法执行。

(2).Activity B的onCreate(),OnStart(),onResume()方法依次执行。(Activity B现在获取了用户焦点。)

(3).然后,如果Activity A不再显示在屏幕上,那么它的onStop()方法执行。

这可预见的生命周期回调顺序,能让你管理从一个activity到另一个activity的转换信息。例如,当第一个activity停止时,你必须写入一个数据库以便接下来的activity可以读取它,那么你应该在onPause()方法调用期间写入数据库,而不是在onStop()方法调用期间。

 

本文来自jy02432443,QQ78117253。转载请保留出处,并保留追究法律责任的权利

posted @ 2012-08-03 18:21  jy02432443  阅读(2754)  评论(1编辑  收藏  举报