【Android API指南】App组件(二) - Activities

【Android API指南】App组件(2) - Activities
Activity提供一个用户可交互的界面,比如一个拨号界面,拍照界面,发送邮件的界面。每个activity都被提供一个窗口来绘制界面。窗口一般都是填满屏幕,也可以小于屏幕或者浮动在其他窗口上。

一个程序通常包含多个activity,它们彼此被比较松散的绑定在一起。有一个activity会被指定为“main”activity,在启动程序时被第一个运行。每个activity都可以启动其他的activity来执行不同的动作。新的activity启动,先前的activity就被停止,但是系统会保存先前的activity到堆栈中。当一个新的activity启动时,这个activity被推入后退堆栈中,用户获得它的焦点。后退堆栈遵循“后进,先出”的原则,所以用户完成当前activity然后按Back键时,当前activity就被推出堆栈,先前的activity被恢复。

当一个activity由于新的activity启动而被停止时,通过activity生命周期回调函数来通知状态的改变。当系统创建,停止,恢复,消耗activity时,都会用相应的回调函数执行必要的工作。例如,当activity停止时,你应该释放一些类似网络连接或者数据库连接这样的对象。当activity恢复时,你需要重新获取必要的资源。

下面的内容讨论怎么创建和使用activity,包括activity的生命周期是怎么工作的。

创建Activity

创建一个activity你需要先继承activity类,然后实现两个重要的回调函数:

onCreate()
这个方法是必须实现的,用来初始化activity的基本组件,比较重要的是调用setContentView()定义activity的界面布局。

onPause()
用户离开activity前第一调用的就是这个方法,这个方法中你应该保存任何需要永久保存的数据,因为用户可能不会再回到这个activity。

除了这两个回调函数,还有其他一些生命周期回调函数,下面会介绍。

实现一个用户界面
activity的用户界面由一些视图组成,视图由View类提供,每个view控制activity窗口的一块矩形空间,用来相应用户交互。例如,一个view可以是一个按钮,用户触摸它的时候会执行一个动作。

Android提供了很多已经做好的view来让我们使用,“Widgets”是一组view,用来提供屏幕可视的元素,比如按钮,文本框,复选框,或者一张图片。“Layouts”也是一组view,从ViewGroup衍生出来,为子view提供布局模型,比如线性布局,表格布局,或者相对布局。你也可以继承View类和ViewGroup类创建自己的widget和layout。

通用的方法是在XML文件中定义布局,使用setContentView()设置activity的UI界面,当然你也可以在代码中直接创建View,并加入到ViewGroup中,然后传递这个ViewGroup到setContentView()函数中。

在清单文件中声明activity
在清单文件中的<application>元素中添加<activity>元素,例如:
<manifest ... >
  <application ... >
      <activity android:name=".ExampleActivity" />
      ...
  </application ... >
  ...
</manifest >
android:name是唯一一个必须的属性,定义了activity的类名,一旦程序发布,这个类名就不能被改变,因为一旦改变就会破坏一些功能,比如程序的快捷方式。

使用intent过滤器
一个<activity>元素可以指定多个intent过滤器 - 使用<intent-filter>元素 - 为了声明哪个其他应用程序可以激活它。

使用Android SDK工具创建一个新的程序时,默认的intent过滤器像下面这样:
<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>
指定为main的<action>元素被指定为应用程序的入口。<category>元素指定了这个activity会被列入系统的程序启动栏中。

如果你不想其他程序调用你的activity,那么你就不需要设置任何intent过滤器,仅仅有“main”action和“launcher”category就可以了,你可以在自己的程序中使用显式intent调用。

要让其他程序能够隐式的调用你的activity,你就需要包含一个<intent-filter>元素,里面包含<action>元素,可选的<category>或者<data>元素。

启动一个activity

你可以调用startActivity(),传递一个intent去启动一个activity。这个Intent指定一个确切的activity或者描述一个你想执行的动作类型。一个Intent还可以携带少量数据。

在自己的程序中,我们直接使用类名就可以启动一个自己的activity了:
Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);
不过,你的程序也可以执行一些动作,比如,发送邮件,发送短信,或者更新状态等,这种情况下,你的程序可能没有实现这些功能,所以你可以使用设备上的其他程序来实现。你可以创建一个intent描述一个动作,系统就会运行相应的activity。如果有多个activity符合描述的动作,就会让用户选择一个合适activity来使用,例如,要发送一个邮件的话,可以创建下面的intent:
Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
EXTRA_EMALL附加了邮件地址数组,指定了邮件的收件人。当邮件程序接受了这个intent,就对读取这个邮件地址数组,然后填写到收件人文本框中,当邮件发送完成,你的activity就会恢复。

为取得结果启动一个activity
有时你要从启动的activity中获取一个结果,那么就要调用startActivityForResult(),实现onActivityResult()回调函数可以获得前面启动的activity返回的结果。取得的结果同样也是通过intent返回的。

例如,下面的程序启动一个取得联系人的程序,然后获取选择的联系人的信息:
private void pickContact() {
    // Create an intent to "pick" a contact, as defined by the 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) {
    // If the request went well (OK) and the request was PICK_CONTACT_REQUEST
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) {
        // Perform a query to the contact's content provider for the contact's name
        Cursor cursor = getContentResolver().query(data.getData(),
        new String[] {Contacts.DISPLAY_NAME}, null, null, null);
        if (cursor.moveToFirst()) { // True if the cursor is not empty
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);
            String name = cursor.getString(columnIndex);
            // Do something with the selected contact's name...
        }
    }
}
在onActivityResult()函数中,先检测请求是否成功,如果成功,那么resultCode就是RESULT_OK,为了确保结果被对应,第二个参数requestCode就是用来区别的。

关闭一个Activity

你可以使用finish()停止一个activity,你也可以使用finishActivity()来关闭先前启动的activity。

提示:大多数情况下你不需要使用上面的方法明确的关闭一个activity。Android系统会替你管理activity的生命周期,而且调用上面的函数会影响用户体验,当你确定用户不会返回这个activity时才应该使用上面的方法。

管理Activity生命周期

管理activity生命周期函数对实现一个健壮的程序至关重要,activity生命周期直接影响到相关的其他activity,它的任务和后退堆栈。

一个activity存在三个状态:
Resumed
activity处于屏幕前台,有用户焦点。

Paused
Activity处于部分可见或者不充满整个屏幕。一个Paused状态的activity是完全存活的,不过可能会在系统急需内存的时候被关闭。

Stopped
完全被其他activity覆盖,一个stopped状态的activity也是存活在系统后台的,在长时间不使用或者系统急需内存时被关闭。

如果activity处于paused或者stopped状态下,系统可以把它从内存中丢弃,不管是调用finish()还是简单的杀死进程。重新打开的话就需要被重新完全创建。

实现生命周期回调
下面是基本的生命周期函数的实现:
public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The activity is being created.
    }
    @Override
    protected void onStart() {
        super.onStart();
        // The activity is about to become visible.
    }
    @Override
    protected void onResume() {
        super.onResume();
        // The activity has become visible (it is now "resumed").
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Another activity is taking focus (this activity is about to be "paused").
    }
    @Override
    protected void onStop() {
        super.onStop();
        // The activity is no longer visible (it is now "stopped")
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // The activity is about to be destroyed.
    }
}
提示:在做其他工作前通常要先调用超类的实现。

上面那些方法定义了activity的整个生命周期函数,你可以监视生命周期中的三个循环:
  • 整个生命周期的发生是从调用onCreate()开始到调用onDestroy()结束。在onCreate()函数中做全局的设置(例如定义布局)。在onDestroy()中释放所有的资源。
  • 可见声明周期从onStart()开始,到onStop()结束。这个阶段用户可以看见activity在屏幕上,并且可以做交互。
  • 前台生命周期从onResume()到onPause()。这个阶段的activity处于所有activity的上面,用户可以取得输入焦点。
下面的图展示了activity整个生命周期过程:
【Android API指南】App组件(二) - Activities
【Android API指南】App组件(二) - Activities
下面的表格介绍了每个回调函数:
Method Description Killable after? Next
onCreate() 第一次创建时调用,做一些普通静态设置 - 创建视图,绑定数据到list等。 No onStart()
  onRestart() 如果activity已经被停止,调用它重启。 No onStart()
onStart() 调用它让activity变得可见。 No onResume() 
or
onStop()
  onResume() 调用它后activity处于堆栈的顶端,用户可以开始和activity进行交互。 No onPause()
onPause() 系统启动其他activity时会调用它,这个方法通常用来保存永久的数据,停止动画,或者停止其他消耗cpu的工作。这个方法必须快速执行完毕,因为它执行完才会启动另外一个activity,如果它执行太久,就印象了用户体验。 Yes onResume() 
or
onStop()
onStop() 当activity不再被用户可见时会调用。原因是这个activity可能要被销毁,或者其他activity已经启动并完全覆盖了这个activity。 Yes onRestart()
or
onDestroy()
onDestroy() activity被销毁时调用。调用它要么是因为activity已经执行完成,要么是系统为了节约空间了暂时销毁它。 Yes nothing
上表中的“Killable after”的意思是系统是否能够在这个函数执行后直接杀死这个activity,而不执行下面的回调函数。有三个函数是yes,onPause()是第一个,因为在activity创建到被杀掉,onPause()是进程被杀死前能够被保证执行到的最后一个函数。而onStop()和onDestroy()可能会执行不到。所以你应该再onPause()方法中执行写入永久数据的操作。

保存activity状态
当activity处于paused或者stopped状态时,activity对象还处于系统内存中,所以activity中的信息是被保存着的。

不过,当系统为了恢复内存而销毁一个activity时,activity对象就会被销毁,所以当用户重新回到这个activity时,它需要被重新创建,为了让用户感觉不到这个activity已经被销毁并重启,你需要一个附加的回调函数来保存重要的数据:onSaveIntanceState()。

系统在activity被破坏前调用onSaveIntanceState()函数,传递一个Bundle来保存数据,当activity重新创建时,可以在onCreate()和onRestoreInstanceState()函数中取得Bundle恢复数据,如果Bundle为空的话,说明是第一次创建。
【Android API指南】App组件(二) - Activities
【Android API指南】App组件(二) - Activities
提示:不能保证onSaveIntanceState()函数在activity销毁前总会被调用到,在一些不需要保存数据的情况下就不会执行,比如用户按back键离开你的activity,这种情况下表明用户明确自己要离开这个activity了,所以就不会调用这个函数保存数据,这个方法在onStop()之前调用,也可能在onPause()之前调用。

不过,如果你没有实现onSaveInstanceState(),一些activity状态也会被默认的onSaveInstanceState()所保存。布局中设置了ID的View的信息都会被保存,如果没有设置ID就不会保存。

你也可以通过实现onSaveInstanceState()来保存一些你想附加保存的值。

因为默认的onSaveInstanceState()函数实现了保存UI的功能,所以你实现自己的onSaveInstanceState()时最好先调用父类的onSaveInstanceState()。

提示:由于onSaveInstanceState()不是一定会被执行,所以永久性的数据不要在这个方法中保存,应该在onPause()中实现。

测试程序是否保存了状态的好方法就是旋转你的屏幕,当屏幕方位改变时,会销毁activity,并重新创建。由于用户会经常旋转屏幕,所以保存状态就显示格外重要了。

处理配置更改
在程序运行中一些设备配置会被更改,比如屏幕旋转,键盘可用性,语言等,当这些配置改变时,系统会重新创建正在运行的activity(系统调用onDestroy()后马上调用onCreate()),以便适应新的配置。

使用onSaveInstanceState()和onRestoreInstanceState()是处理保存和恢复数据最好的办法。

协调activity的关系
当一个activity启动另外一个activity时,两个activity的生命周期都会发生变化。当另外一个activity被创建时,第一个activity暂停并停止。比较重要的一点是,在第二个activity被创建时,第一个activity并没有被完全停止,相反的,开启第二个的进程和停止第一个的进程是同时进行的。

如果两个activity在同一个进程中,比如activity A启动activity B,那么它们操作顺序如下:
  1. A的onPause()函数被执行。
  2. B的onCreate(), onStart(), onResume()按照顺序执行。
  3. 如果A在屏幕中已经不可见,那么A的onStop()被执行。
这些可预见的执行顺序可以让你更好的协调activity间的关系。比如,你需要在第二个activity中读取第一个activity保存到数据库中的数据,那么你必须在第一个activity的onPause()方法中执行保存操作,而不是在onStop()方法中。