安卓四大组件 之 Activity

来源:互联网 发布:windows 查看定时任务 编辑:程序博客网 时间:2024/05/17 04:22

本文摘要 

|---Activity的创建
    |---用户界面的实现
    |---在配置文件中声明activity
|---Activity的开启
    |---可以返回结果的activity
|---关闭activity
|---管理Activity的生命周期
    |---生命周期回调方法的实现
    |---保存activity的状态
    |---如何处理设备的配置变化
    |---多个activity之间的协调


Activity是安卓四大组件之一,它给用户提供交互界面。在拨打电话、拍照片、发邮件或者查看地图时,用户看到的操作界面就是Activity提供的交互界面。举个形象些的例子, 想象一下电脑屏幕,老外管这个叫“window”,也就是“窗口”,这个“窗口”中“画”出了交互界面,我们就是通过这种“窗口”对电脑进行操作,而activity就类似于这种“窗口”。只不过,activity是用在安卓设备上的(手机/pad等)。通常,activity是占满整个屏幕的,但是有的activity也会小于屏幕并且“漂浮”在其他activity的上层(效果就类似于电脑屏幕上弹出的对话框)。

一个app通常由许多个activity组成,这些activity之间的结构关系很松散(相互之间的依赖性不是很强)。对于大多数app来说,在其众多的activity中,有一个是用来充当“首页”的。也就是说,当app首次被开启时,用户最先看到的就是这个用来充当“首页”的activity。app中的每个activity都可以开启另一个activity来帮助我们完成不同的任务。 每当开启一个新的activity时,这个activity就会被放入回收栈(back stack)并获得用户焦点。而且这个新的activity被开启的同时,在它之前的那个activity会被停止,但系统会在回收栈中继续保留那个被停止的activity。回收栈遵守的是“后进先出”的机制。所以,当用户完成对当前activity的操作并点击设备上的返回按钮时,当前activity就会被系统从回收栈中“拽”出来并销毁掉。这样,之前被停止的那个activity就又重新呈现在用户眼前了(也就是又重新展示在屏幕上了)!关于回收栈的详细介绍,请参阅《安卓四大组件 之 Activity 之 任务栈和回收栈(Tasks and Back Stack)

假设有两个activity,分别是activityA和activityB。最初,activityA显示在屏幕上(此时activityA处于“活动”状态)。后来,activityB被开启,activityA就被停止了(此时activityA处于“停止”状态)。这两个activity在状态上的转变都是由其自身的生命周期回调方法体现出来的。Activity的状态包括:被系统创建、停止、展示、销毁等等,每当activity的状态发生转变时都会收到相应的回调方法。每一个回调方法都让我们可以根据activity所处的不同生命周期阶段来安排最合适的工作内容。例如,当activity被停止时,我们就应该释放大型对象资源,如网络或数据库的链接;当一个处于停止状态的activity被重新展示到屏幕时,我们又可以重新获取必要的资源并重新开启被中断的任务。这些状态的转变就是activity生命周期的所涵盖的内容。
本文将探讨如何建立和使用activity,还将详细地阐述activity生命周期的工作原理,这些可以告诉你该如何对activity的状态转变进行合理的管理。


Activity的创建

---------------------------------------------------------------------------------------------

创建activity的方法是:首先,创建一个类来继承Activity类或Activity的子类;然后,实现(implement)类中的回调方法。当activity在不同的生命周期之间转变时(比如被创建、停止、展现或销毁等),系统会相应地调用这些回调方法。这些回调方法就叫做“生命周期回调方法”,简称“生命周期方法”。Activity总共有7个生命周期回调方法,其中最重要的两个是:onCreate()和onPause()。

onCreate()

当activity被创建时,系统就会调用此方法。举个例子,假设有一个activity,当它被另一个组件开启时,它就被创建了。那么,该activity的onCreate()方法就是系统在创建这个activity时首先要调用的生命周期回调方法。所以,我们在编写activity时,必须(注意!是必须哦!)实现(implement)onCreate()方法,而且我们还得在这个方法里将activity中的组件初始化出来。最重要的是,在这个方法中调用setContentView()方法可以给activity指定要显示的布局(layout)文件。

【小贴士】activity与layout(布局文件)的关系就类似于画框和画布之间的关系。Activity相当于画框,layout(布局文件)相当于画框里面的画布。如果不往画框里面放画布,那么画框就是空的。安卓中,所有的布局文件都是.xml格式的文件,专门有一个文件夹用来存放这些xml格式的布局文件。当activity被创建时,在其onCreate()方法中调用setContentView()来指定想要展示的“画布”的名字。如果不这么做,activity照样会被创建出来,只不过,创建出来的将是个“空画框”,你想展示的“画布”是无法显示在屏幕上的。忘记指定“画布”是新手常犯的错误,谨记!

onPause()

当用户离开某个activity时(比如点击返回键返回到上一个activity),系统会首先调用这个activity的onPause()方法(这种状态下,activity并不一定会被销毁)。在onPause()方法中,我们往往需要将activity所产生的变化都保存下来,比如用户可能在activity中输入了用户名和密码信息,那么这些信息就应该被保存下来,而保存这些信息的动作就应该在onPause()方法中来做。就算当前屏幕已经是另一个activity在与用户交互了,这依然不影响上一个activity的onPause()方法的数据保存动作。

Activity总共有7种生命周期回调方法,上面只介绍了两种,其余的5中也很有用。在多个activity切换时,这些回调方法可以使切换过程更流畅、用户体验更好;另外,当一个activity因为某些突发状况而被停止或销毁时,生命周期回调方法也可以用来处理这些突发状况。在本文的【管理Activity生命周期】部分,会逐一交代activity的所有生命周期回调方法。


用户界面的实现

Activity上展示出来布局文件(layout)其实是由一系列“view”组成的,这里说的“view”指的是安卓中View类的对象。每一个View对象在布局文件(layout)上都占据一块矩形区域,并且View对象可以响应用户操作。比如,它可以是一个按钮,当被用户点击时,就会触发按钮上绑定的事件。

其实安卓已经为我们准备了许多View对象,比如按钮(button)、文本显示区(text field)、复选框(checkbox)、图片(image)等等——这些“小部件(widget)”都可以显示在屏幕上,并且可以与用户进行交互——我们可以很方便地直接利用它们来设计自己想要的布局文件(layout)。当布局文件中包含多个“小部件(widget)”时,我们就需要考虑如何排列它们。安卓为我们提供多种排列小部件的方式,这里举几个例子,比如:线性布局(linear layout)——顾名思义,就是所有小部件都从上到下顺序排列,每个小部件都独占一行;表格布局(grid layout)——意思就是以表格的方式来排列小部件;相对布局(relative layout)——意思是每个小部件的摆放位置都是以其他小部件的位置作为参照, 比如A放在B的左边,C放在B的右边,B又放在D的下边,它们互相作为对方的参照物。安卓提供的这些排列小部件的方式,就叫做“布局”——线性布局、表格布局、相对布局等等。 “布局”本身是源自ViewGroup类的一种View对象,这种view只负责指定“画布”上的“小部件”的排列方式。如果安卓提供的这些现成的“小部件”和“布局”都不能满足你的需求,那么你也可以根据自己的需要来创建它们(也就是自定义控件)。与创建activity类似,想要自定义“小部件”或者“布局”,可以通过创建View类或者ViewGroup类的子类来完成。然后将你自己定义的这些“小部件”或“布局”用在你的activity的布局文件(layout)中。

通常,使用XML文件来定义布局文件(layout),这种XML布局文件要保存在安卓项目的资源文件中。这样,用户界面和业务逻辑代码就被分开管理。而将布局文件(layout)作为UI展示在activity上的方法,前面已经提到过,就是在onCreate()方法中调用setContentView()方法,然后把SDK为布局文件(layout)生成的资源ID作为参数传递到这个方法中(关于资源ID的介绍,请见《安卓应用程序的基础介绍》)。除此之外,我们还可以用代码来动态生成布局文件。方法是:创建activity时,在代码中创建新的View类对象——用于创建“小部件”,并且将创建出来的“小部件”插入到ViewGroup类的对象中——用来建立排列“小部件”的“布局”,然后,将这个ViewGroup对象传入setContentView()方法,这样就可以在当前activity中展示这个动态生成的布局文件了。


在配置文件中声明activity
Activity必须要在配置文件中声明出来。只有这样,系统才会知道有这么个activity存在。

声明方法:打开配置文件,在 <application> 元素中添加 <activity> 子元素,如:

<manifest ... >  <application ... >      <activity android:name=".ExampleActivity" />      ...  </application ... >  ...</manifest > 
在<activity> 子元素中,还可以添加其他属性来定义activity的其他特征,比如可以给activity定义标签、给activity定义图标、还可以给activity定义UI样式主题。上述众多可定义的属性中,只有“android:name”属性是不能缺省的,因为它指明了activity的类名。一旦activity被发布,这个类名就不能随意改动了,因为改动activity的类名会导致一些功能失效,比如应用程序的快捷方式。

Intent过滤器的使用
【小贴士】intent过滤器用来帮助activity过滤掉不符合条件的调用。举个拟人的例子,有个程序猿,技术特别牛,本职工作就是帮助别人改BUG。按理说,无论谁来求助,他都可以帮忙。但是这样他会累坏的,而且也不安全。所以他给自己定了个规矩:只有来自A公司和B公司的人找他,他才会帮忙,其余的一律不帮。那么他怎么识别谁是这两家公司的呢?好办!只要来求助的人带上自己公司的证明信就OK了。只有看到A公司和B公司的证明信,他才会帮忙。下面,角色还原一下,程序猿就相当于activity——有自己能处理的业务;程序猿给自己定规矩是在心里*暗下决心*,而activity给自己“定规矩”是在配置文件中用intent过滤器来*声明*;证明信就相当于intent过滤器的内容——调用者带过来的intent必须符合activity的要求。


下面说说如何给activity添加intent过滤器。我们可以通过向<activity> 元素中添加<intent-filter>子元素来指定各种各样的intent过滤器,过滤器的数量可以是0个,也可以是多个。Intent过滤器的作用是为其所属的activity声明启动条件——其他组件要想调用这个activity,就必须满足这个条件。

在使用安卓SDK工具创建新的应用程序时,该应用程序会附带一个“原装”activity(每个新创建的应用程序都会自动创建出一个activity,我们就形象的把它称为“原装”activity吧)。应用程序创建好之后,打开配置文件,你会发现“原装”activity也已经自动声明出来了:

<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></span>


“原装”activity会自带一个intent过滤器,它声明了两件事:

1-activity是应用程序的“主界面” (<action android:name="android.intent.action.MAIN" />);

2-activity会被放置在启动目录中(<category android:name="android.intent.category.LAUNCHER"/> )。

第一件事是使用<action> 元素来声明的,它的作用是:将自己所属的这个activity指定为应用程序的入口;

第二件事是使用<category> 元素来声明的,它的作用是:将自己所属的这个activity展示在系统的应用程序启动列表中(供用户开启)。

【小贴士】这里提到的“应用程序启动列表”指的是(以手机为例)桌面上能看得见的那些app的图标组成的列表。


如果你打算让应用程序独立存在,也就是让它的任何一个activity都不希望被其他应用程序启动,那么,请保持默认状态就好——既不要对“原装”activity的intent过滤器做任何改动,也不要给任何activity增添新的intent过滤器。注意,在应用程序中,只有一个activity可以在<action> 元素中把自己定义为“MAIN”以及在<category> 元素中把自己定义为“LAUNCHER”。也就是说,只有一个activity可以作为应用程序的入口并被展示在程序列表中供用户开启。正如刚刚说过的,对于那些不希望被别的应用程序开启的activity,是不应该设置intent过滤器的。难道这样的activity就永远不会被开启了?不是滴,你自己还是可以用显式意图来开启它们的,具体方法请往下看。

然而,如果希望activity可以对隐式意图做出响应(隐式意图既可以来自应用程序内部,也可以来自其他应用程序),那么我们就必须为它添加intent过滤器。希望activity响应哪种intent,就必须在<intent-filter>中添加一个可以描述这种intent类型的<action> 子元素 。除了<action> 之外,还需要<category> 子元素或者<data> 子元素(<category>和<data>可以同时存在,也可以二选一,只添加其中一个元素)。这些子元素定义了其所属的activity到底可以响应哪种intent。


Activity的开启

-------------------------------------------------------------------------------------------------------------------------------------
想要开启另一个activity,可以使用startActivity()方法, 并向该方法中传入一个Intent对象作为参数,用来描述你想要开启的activity——直接指出activity的名字或只描述activity的类型均可。如果Intent对象只对希望开启的activity的类型进行描述,那么系统会自动选择最适合的activity来开启,哪怕这个activity属于别的应用程序。除此之外,intent还可以携带少量的数据,这些数据供被开启的activity使用。

【显式意图】
在应用程序内部调用属于应用程序自身的activity时,直接指出activity的名字即可,这种做法叫做“使用显式意图”。注意,这里说的“activity的名字”指的是activity的类名。下面这段代码演示了一个activity(名字叫做“SignInActivity”)是如何被开启的:

Intent myintent = new Intent(this, SignInActivity.class); //先创建一个Intent对象——myintent startActivity(myintent); //将myintent 作为参数传入startActivity()方法。</span>

【隐式意图】
有时,应用程序需要完成一些功能,比如发送邮件、短信,或更新状态,而该应用程序里也许并不包含能够完成这些功能的activity。Intent真正发挥作用的时候到了——可以用intent对想要实现的功能进行描述,系统会自动对安装在设备上的所有app里的所有activity进行搜索,进而找到符合条件的activity并开启它。如果不止一个activity符合要求,系统会引导用户来选择一个activity,用于完成intent中描述的功能。这种做法就叫做“使用隐式意图”。举个例子,如果你希望在自己的应用程序中为用户提供发邮件的功能,而你又不想为此功能专门写一个activity,那么你就可以利用“隐式意图”来开启设备中其他应用程序里能够完成发邮件功能的activity。代码如下:

Intent myintent = new Intent(Intent.ACTION_SEND);//首先创建一个Intent对象——myintent,并描述动作类型——ACTION_SEND——发邮件。myintent.putExtra(Intent.EXTRA_EMAIL, recipientArray);//向myintent中添加额外的数据——EXTRA_EMAIL——让myintent带上这个数据,供被调用的activity使用。startActivity(myintent);//将有动作意图的、带有数据的myintent作为参数传给新调用的activity</span>

上段代码中,“EXTRA_EMAIL”被附加在myintent中,它是一个字符串数组——记录着收件人的邮件地址——指出邮件应该发送给谁。当邮件app中负责编辑邮件的activity响应这个intent时,它会读取这个字符串数组并将它们填在“收件人”一栏中。在这种情形下,当用户完成发邮件的动作后,你的activity——也就是“借用”邮件app中的activity来发送邮件的那个activity——就会重新显示在屏幕上。

【小贴士】EXTRA_EMAIL”数据是从哪来的?其实它来自于你的activity。想象以下场景:你的app为用户提供发邮件的功能,在你的app中,专门有一个activity用来让用户填写收件人邮件地址。注意,此页面只能填写收件人地址,不提供撰写邮件的功能,这个功能是需要借用邮件app中负责编辑邮件的activity来完成的。那么,用户在你的activity中填写的收件人地址就是EXTRA_EMAIL”,它可以由intent带给邮件app中负责编辑邮件的activity。也就是说,intent不仅可以描述意图动作,还可以携带一些小的数据供被调activity使用。


可以返回结果的activity

有时,你需要开启一个能够返回结果的activity。 那么,就不能用startActivity()方法了,而要用startActivityForResult()方法。光使用这个方法还不行,要想顺利接收到被你开启的activity返回来的数据,就必须实现onActivityResult()回调方法。当被开启的activity完成任务时,它就会将数据结果放入一个Intent对象中返回给onActivityResult()方法。

举个例子,假如你的activity在运行中需要用到联系人信息,那么该activity就会使用startActivityForResult()方法来开启电话本app中负责展示联系人信息的activity——引导用户从中选择一个联系人,用户做出选择之后,展示联系人信息的activity就关闭了。而用户选择的那条联系人信息,就被返回给你的activity了。下面这段示例代码演示了如何创建这样的intent并处理返回结果:

private void pickContact() {    // 创建一个intent,从content provider URI中定义的联系人中“挑选(PICK)”一个。    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI);    startActivityForResult(intent, PICK_CONTACT_REQUEST);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {    // 判断该请求进行的是否顺利(OK),并且判断请求的内容是否为“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()) { // True if the cursor is not empty            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME);            String name = cursor.getString(columnIndex);            // 拿到查询出来的名字后,可以进行下一步操作......         }    }}

这个例子演示了在onActivityResult()方法中处理activity的返回结果所需要用到的基本逻辑。外层If代码块中的第一个条件用来判断请求是否成功(如果成功了,resultCode将会是“RESULT_OK”),以及是否知晓这个结果正在响应该请求(判断方法就是用requestCode与第二个参数进行比对,该参数由startActivityForResult()方法传进来)。从这行往后的代码就是在处理activity的返回结果,处理的方法就是对Intent带回来的数据进行检索(带回来的数据就是startActivityForResult()方法的第三个参数——data)。

实际发生的事情是:ContentResolver对content provider进行检索。检索后,返回一个Cursor, 这个Cursor允许对检索出来的数据进行读取。当然,这属于Content Provider的内容了。这里不赘述。


关闭activity

--------------------------------------------------------------------------------------------------------------------
想要关闭activity,可以调用它的finish()方法。如果想要关闭此前开启的activity,可以调用finishActivity()方法。
【小贴士】除非你真的不想让用户再回到某个activity了,否则(同时也是在绝大多数情况下),就不要使用上面那两个方法显式地关闭activity。原因就在于activity的生命周期(接下来会讨论)——安卓系统已经在帮你管理activity的生命了,所以你就没必要自己手动地结束它啦。用这种方法来结束activity会影响用户体验的。


管理Activity的生命周期

--------------------------------------------------------------------------------------------------------------------
管理好activity的生命周期对于开发出一款强大而灵活的app来说至关重要。要管理activity的生命周期,就得实现生命周期回调方法。一个activity的生命周期不仅直接取决于这个activity是如何与其他activity关联的,而且还取决于这个activity的任务和回收栈。

从本质上说,一个activity的生命可以分为三个状态阶段:

  • 显示状态
在这个状态下,activity被展示在屏幕上并获得用户的焦点。也可以将此阶段的activity理解成正在运行的activity。
  • 暂停状态

一个activity在“运行”状态时,焦点被另一个activity抢走了——也就是另一个activity被显示在屏幕上并获得了用户焦点,但是,之前的activity并没有被后来的activity完全遮盖住(效果请见配图),之前的activity还依稀可见。此时,之前的activity就处于暂停状态,虽然它处于暂停状态,但它还依然“活着”——它依然被保存在内存中;它保留着自己被暂停时的所有状态和信息;它依然跟window manager(窗口管理器)保持着关联。尽管如此,如果出现内存资源极度紧张的情况,系统还是会终止它的。

  • 停止状态

当一个activity被另一个activity完完全全遮住时,被遮住的activity就进入停止状态了,此时,这个activity也就退到了“后台”。进入停止状态的

activity也依然是“活着”的,但是activity在此阶段的存活状态与其在暂停阶段的活状态有所不同——虽然它依然被保存在内存中 ;它依然保留着

自己被停止时的所有状态和信息;但它已经跟window manager(窗口管理器)关联不上了。处于停止状态的activity已经完全 不可见了,而且,当有

限的内存资源被其他任务所需要时,系统就会终止这个状态中的activity。


如果一个activity处于暂停状态或停止状态,系统都有可能将其终止,并将其从内存中“扔”出去——以便回收内存资源。系统终止activity一般有两种做法:要么调用activity自身的finish()方法;要么直接终止activity的进程。在activity被finish或被终止后,如果该activity需要再次被打开,那么它就要再被创建一遍——也就是重新走一遍onCreate()方法。这种场景也很多见,比如有A,B,C,D四个activity,用户在操作过程中,通过A开启了B,然后通过B由开启了C,再然后又通过C开启了D。这时,设备的内存不够用了,于是系统就终止了A,B,C三个activity,因为他们仨都已经处于暂停或停止状态了。用户对D操作完了之后,点击返回键,正常情况下,会依次返回C-B-A,但这三个activity已经被系统终止,并且从内存中被“扔”出去了。那怎么办?系统就得再分别依次运行这三个activity的onCreate()方法,目的就是把这三个activity重新创建出来展示在用户眼前。


生命周期回调方法的实现

前面已经介绍过一些生命周期回调方法了,activity正是通过这些不同的生命周期回调方法来了解自己所处的状态阶段。所有回调方法都是Activity类本身自带的(别忘了,创建activity需要继承安卓的Activity类)。你可以根据实际需要,有选择地覆写这些回调方法,以便在activity的状态发生变化时,能够顺利完成activity在该阶段需要完成的任务。下面这段示例代码中包含了主要的生命周期回调方法:

public class ExampleActivity extends Activity { //创建一个名为“ExampleActivity”的activity。注意,要继承Activity类。    @Override  //覆写第一个回调方法——onCreate()方法。此方法必须覆写。    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        // ExampleActivity 被创建出来    }    @Override    protected void onStart() {        super.onStart();        // ExampleActivity做好被显示的准备(不可见)。    }    @Override    protected void onResume() {        super.onResume();        // ExampleActivity被展示在屏幕上(可见)    }    @Override    protected void onPause() {        super.onPause();        // 另一个activity占领一部分屏幕并获得用户焦点(ExampleActivity将要被“暂停”)。    }    @Override    protected void onStop() {        super.onStop();        // ExampleActivity不可见了——此时,它被停止了    }    @Override    protected void onDestroy() {        super.onDestroy();        // ExampleActivity将要被销毁。    }}

【小贴士】正如示例代码中演示的一样,在实现回调方法时,必须先调用其父类(super)的实现类——如“super.onDestroy();”,然后再完成其他任务。


示例代码中的这些回调方法放在一起,就定义了一个完整的activity生命周期。Activity的生命周期共嵌套着三层回路。通过实现生命周期回调方法,我们可以对这三层生命周期回路进行监听:

  • 整体寿命(最外层生命周期回路)——activity的整体寿命起于onCreate()方法的调用,终于onDestroy()方法的调用。我们应该在onCreate()方法中为activity做全局性配置——比如定义布局(layout),并且应该在onDestroy()方法中将activity运行过程中所占用的资源全部释放。举个例子,假设我们创建的activity有一个用于从网上下载数据的后台线程。那么该线程就应该在onCreate()方法中被创建,并在onDestroy()方法中被停止。
  • 可见期寿命(次内层生命周期回路)——activity的可见期寿命起于onStart()方法的调用,终于onStop()方法的调用。在此期间,用户不仅可以在屏幕上看见activity,而且还可以与activity进行交互。比如,当一个新的activity开启并完全遮盖住当前的activity时,被遮盖住的activity的onStop()方法就被调用了。在activity的可见期——从onStart()方法到onStop()方法——向用户展示activity所要用到的资源是可以被一直占用的。比如,我们可以在onStart()方法中注册一个BroadcastReceiver(广播接受者)去监听那些对UI产生影响的变化;然后,我们在onStop()方法中——也就是当用户不再能看见activity的时候——将这个广播接收者注销掉。在activity的整体寿命期间,系统可能会多次调用onStart()和onStop()方法——因为activity可能会随着用户的操作,一会儿可见,一会儿不可见,过一会儿用户又回到这个activity(可见),再过一会儿用户又离开了(不可见),每次在“可见”和“不可见”这两种状态之间转换时,系统都会相应地调用activity的onStart()和onStop()方法。
  • 前台寿命(最内层生命周期回路)——activity的前台寿命起于onResume()方法的调用,终于onPause()方法的调用。处于前台寿命期的activity被显示在屏幕上(把其他所有activity都压在自己下面)并获得用户的输入焦点。activity可以频繁地转换于前台状态与非前台状态之间,比如在操作activity的过程中突然弹出一个对话框时,或者当设备进入睡眠状态时,那么刚刚在操作的activity的onPause()方法就被调用,这个activity也就失去焦点,并且从前台状态转变为暂停状态(非前台);如果用户将弹出的对话框关闭,或者将设备从睡眠状态中唤醒,那么之前操作的activity就又回到前台啦,此时系统又会调用它的onResume()方法。就是因为前台状态可以被如此频繁的转换,所以onResume()和onPause()方法中一定要使用轻量级代码,并且要避免过大过重的任务,目的就是为了提高响应速度,避免用户等的太久,影响用户体验。

下图演示了生命周期回路以及各个回路之间的循环路径。矩形块代表着我们可以覆写的回调方法,在activity的生命周期状态发生转变时,这些方法会被及时调用——以完成我们覆写进去的任务。



我们再用一张表格来展示生命周期方法。这张表格对每一个回调方法都做了详细的解释,并且精准的定位出每一个回调方法在整个生命周期中所处的位置,除此之外还说明了在每个回调方法执行完毕后,系统是否可以对activity强行终止。


表中,“该方法执行完毕后,系统是否可以强行终止activity”一列(以下简称“结束后是否可终止”)指的是:在该方法执行完毕并将结果返回之后, 系统是否可以随时终止该activity所在的进程,并且,在终止进程时无需执行该activity的任何代码。表格中列出的方法中,有三个方法标注为“是”,分别为onPause(), onStop(), 以及 onDestroy()。这三个方法中,onPause()排在最前,即一旦一个已经被创建出来的activity要被系统(因为要回收内存资源而)直接终止时,那么onPause()方法一定是该activity能被执行到的最后一个方法。这也就意味着,如果系统出于紧急情况而必须收回内存资源时,很有可能等不到onStop()和 onDestroy()方法被执行,activity就被终止了。由此可见,activity中的一些关键性数据——比如用户填写进来的用户名密码或者其它由用户编辑过的小数据——都要在onPause()方法中进行保存操作。但是,需要注意的是,不是所有数据都有必要在onPause()方法中进行保存的,因为在此方法中,任何阻塞式的程序都会影响下一个activity被展示的速度,从而影响用户体验。

“结束后是否可终止”一列中标注为“否”的方法会使其所在的activity进程避免被系统终止。也就是说,一个activity执行完onPause()方法后就可以被系统随时终止了。如果执行完onPause()方法后又返回到onResume()方法,那么此时activity就不能随时被终止,直到onPause()方法再次被执行完之后,系统才又可以随时终止activity。
【小贴士】其实,严格来说,“结束后是否可终止”一列标记为“否”的方法,在非常极端的情况下——也就是实在没有一丁点儿额外资源可用的情况下——还是有可能被系统终止的。


保存activity的状态
前面提到:当activity被暂停或停止时,它的状态会被保留。这是因为activity被暂停或停止时,它的状态(也就是activity上的所有的成员信息(1)和当前状态)会被保存在内存中。也就是说,当一个被暂停或停止了的activity被重新展现在屏幕上时(onResume()),该activity展现出来的样子仍然保持着之前它被暂停或关闭时的样子。
【注释】(1)-activity上的成员是指:activity中展示的layout上包含的view控件。每一个view控件都是一个成员。


然而,当系统为了回收内存而“消灭”activity时,那么这个Activity类的对象(请时刻记住,每一个具体的activity都是Activity类的对象)就会被销毁,所以在它需要被重新展示时,系统就无法使该activity回到之前的状态了。在这种情况下,系统就只能通过onCreate()方法来重新创建这个Activity对象了。然而用户并不知道系统都做了些啥(销毁了activity,后来又创建了它),他们只是期望刚刚离开的那个activity可以重新回到眼前,而且还保持着刚才的样子。如何能够让一个被销毁了的activity在重新被创建时还能保持它被销毁之前的样子呢?我们可以通过实现onSaveInstanceState()回调方法并在此方法中编写代码将activity的重要信息保存下来。


系统会在activity被销毁之前调用onSaveInstanceState()方法。系统向该方法中传入一个Bundle参数,在该参数中我们可以用键值对的方式来保存activity的状态信息(使用类似putString() 和 putInt()这样的方法)。这样,如果某个activity被销毁了(原因是其所在的进程被系统终止),而当用户想要回到这个activity时,系统就只好重新创建这个activity并将Bundle分别传给onCreate() 方法和 onRestoreInstanceState()方法。无论使用onCreate() 方法还是使用onRestoreInstanceState()方法,我们都可以从Bundle中获得被保存的activity状态,且可以将这些状态在重新创建的activity中重置。如果一个activity是首次被创建,那就没有任何状态信息需要重置,这时,Bundle将会传递null。


【关于上图】对于一个activity来说,重新回到用户眼前无非也就两种情况:一种是activity由于某种原因被系统销毁了,等到用户想要它再次展示出来时,系统再重新创建它,并将保留下来的状态重置于该activity中(使之与被关闭时状态一致);另一种是activity被停止(onStop())了,用户想要它再次展示出来时,只需将此activity展示在屏幕上即可(onResume()),而且,该activity是带着被停止时的状态重新展现在屏幕上的。


【小贴士】在activity被销毁前,也不是一定就会执行onSaveInstanceState()方法。因为有些情况下——比如当用户确定想要离开某个activity时(他可能会点击返回按钮,目的是离开这个activity)——根本没有必要保存activity的状态。如果系统需要调用onSaveInstanceState()方法,那么它会在onStop()方法或者onPause()方法之前调用。


然而,就算我们不实现onSaveInstanceState()方法,activity的某些状态还是会通过Activity类中默认实现的onSaveInstanceState()方法来重置。具体来说,默认实现的onSaveInstanceState()方法为layout中的每一个View对象调用相应的onSaveInstanceState()方法——允许每一个view保存与之相关的信息。几乎每一个安卓框架中的“小部件”都会根据实际情况来实现这个方法,这样,任何关于UI的视觉变化都会自动地被保存下来,并且在重新创建的activity中重置。举个例子,有一种小部件叫做“EditText”(文本编辑框),它会保存用户输入进来的任何文本;还有一种小部件叫做“CheckBox”(复选框),它会记录选项是否被选中。而唯一需要我们做的就是在layout布局文件中,使用“android:id ”属性为每一个需要保存状态的小部件提供一个独一无二的ID(言外之意就是:如果不给小部件指定ID,那么系统就不会自动帮我们记录它的状态哦,切记!)。


尽管默认实现的onSaveInstanceState()方法可以帮助我们将activity上有用的UI信息记录下来,但如果需要额外对一些信息或数据进行保存,我们仍需覆写此方法。比如,用户在activity活动期间(也就是被展现期间)对其成员做出修改,而我们可能需要将这些被修改后的成员的信息值记录下来,因为默认的onSaveInstanceState()方法不会帮我们保存这些成员的状态信息,而这些需要被重置在UI中的信息值可能会影响到activity的活动。如果我们不覆写onSaveInstanceState()方法,那么在成员被重置时,用户对它做出的修改并没有被记录下来——成员的值还是activity刚刚被“onCreate()”时的初始状态。

【小贴士】事实上,我们可以有意识地阻止layout中的view对其自身的状态信息进行保存。方法是:1-在布局文件中对具体的view使用“android:saveEnabled”属性,并将值设为“false”; 2-在代码中直接调用setSaveEnabled()方法。通常来说,不应该阻止成员对自身状态进行保存,除非我们需要在重置activityUI的成员信息时,将别的值(不同于先前被修改的成员值)放置进去。
正因为默认实现的onSaveInstanceState()方法帮助我们保存了UI的状态信息,所以,如果为了额外保存更多的信息而覆写了这个方法,我们就必须在覆写onSaveInstanceState()方法时,首先调用它的父类的实现方法,然后再用代码去完成其他数据的存储工作。 与之类似的还有onRestoreInstanceState()方法,如果我们覆写这个方法,也应该首先调用它的父类的实现方法,然后再去编写完成其他事情的代码。正是因为这样,默认的实现方法才能够将view的状态重置。

【注意】由于onSaveInstanceState()方法不保证一定会被系统执行到,所以我们只可用它来记录那些与activity的UI相关的临时信息。也就是说,需要长期保存的数据是不可以在此方法中进行存储的。那些需要长期保存的数据——比如需要写入数据库的数据——应该在onPause()方法中进行存储,因为当用户离开某个activity时,该activity的onPause()方法会最先被执行,所以在这个方法中对数据进行存储是比较保险的。


想要检验一下我们编写的应用程序是否能够很好地保存并重置activity的状态,只需将设备(手机)的屏幕方向改变一下即可——如果我们原先是竖着持有设备,那么就将它横过来,反之亦然,如果原先是横着持有设备,那就将它竖过来。因为当屏幕方向发生改变时,系统会将正显示在屏幕上的activity先销毁,然后再重新创建,目的是让activity更好的适应新的屏幕方向。仅仅因为这个原因,我们编写的activity是否能够很好地重置之前的状态就变得非常重要了,因为用户可能会在使用App时,频繁地转换屏幕方向。


如何处理设备的配置变化

设备的一些配置是可以在运行期间发生变化的,比如屏幕的方向、键盘的可用性、以及显示语言等。当这类变化发生时,安卓就会对正在运行的activity进行重新创建——也就是安卓系统会先调用onDestroy()方法将activity销毁,然后立刻调用onCreate()方法将activity创建出来。安卓为什么会采用这种“销毁—重建”的机制呢?通常,我们需要在应用程序中为设备的某些可变配置预先提供相应的备选资源(比如,我们需要为同一个activity准备出若干套布局文件——内容相同但排版布局不同——用以适应屏幕的不同方向以及尺寸),而系统就是通过自动重新加载带有这些备选资源的应用程序来帮助其更好的适应设备的新配置。

如果你能够对activity进行合理的设计,使之有能力妥善处理由于屏幕方向改变而导致的程序重启以及activity成员信息重置,那么相信你所编写的应用程序在处理activity生命周期中的其他突发事件时也会更加游刃有余。

而应对这种“重启”的最佳方法就是像前面介绍的那样:先用onSaveInstanceState()方法将activity的状态信息保存下来,然后再用onRestoreInstanceState()或onCreate()方法将这些信息重置到activity中。


多个activity之间的协调

当一个activity开启另一个activity时,它们都会经历生命周期的转变。第一个activity经历了onPause()和onStop(),也就是暂停和停止(需要注意的是,如果该activity没有被完全覆盖,它就只经历onPause()方法,不会经历onStop()方法)。这时,被开启的那个activity会经历onCreate(),也就是被创建。如果这些activity需要向同一块存储空间中(可以是硬盘,也可以是别的存储设备)存入和获取一些数据(即前面的activity存数据,后来的activity读取数据),那么请一定要记住:第二个activity被创建之前,第一个activity并没有被完全停止。相反,开启第二个activity的进程恰恰与停止第一个activity的进程重叠。

生命周期回调方法的执行顺序已经被规定好了,尤其是当两个activity在同一个进程里并且其中一个正在开启另一个时。下面是当ActivityA开启ActivityB时的执行顺序:
1.ActivityA的onPause()方法执行。
2.ActivityB的onCreate(), onStart(), 和onResume()方法依次执行(执行到onResume()方法,ActivityB就被展示在屏幕上并获得用户焦点)。
3.然后,如果ActivityA不再于屏幕可见,它的onStop()方法就执行了。

生命周期回调方法的执行顺序是固定的。正是因为这个特性,才使我们能够管理数据从一个activity过渡到另一个activity的过程。比如,如果我们必须在前一个activity停止时将数据写进数据库,那么我们就应该在onPause()方法中(而不是在onStop()中)进行写入操作,这样,才能确保接下来的activity可以从数据库中读取到这些信息。


本文总结

—应用程序中负责为用户提供交互界面的、展示在设备屏幕上的就是activity。

—Activity可以被移到后台(不可见),还可以保留其状态的变化并带着这些状态信息重新展示在屏幕上。








0 0
原创粉丝点击