Android Activities学习[Android Developers译作整理]

1、定义


Activity就是给用户提供可视化交互界面的应用程序组件。

通常一个应用程序有多个松散组合的Activity组成,应用程序需要指定一个mainActivity作为程序首次启动的入口点。一个Activity能够启动另外一个Activity,当一个新的Activity启动时,系统将它压入栈顶,先前的Activity就会停止,系统会将它保存到回退栈(back stack)中,当前的Activity完成后,用户通过按下返回按钮,当前的Activity就被销毁,先前的Activity就会从回退栈弹出并恢复它之前的状态。

Activity的这些动作都是通过它的生命周期回调方法实现的,开发者需要根据时机,在适当的回调方法里做一些相应的处理工作。

2、创建Activity


必须继承自Activity或者Activity的子类,必须实现相应的生命周期回调方法,其中OnCreate()方法必须实现

2.1 实现用户界面


定义一个布局,往布局中添加想要的部件。然后在OnCreate()方法中,使用setContentView()来将布局绑定到Activity。

2.2 在manifest文件中声明Activity

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

往<application>元素里添加<activity>子元素,并使用<android:name>属性来指定Activity的类名。

2.3 使用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>

在<activity>元素里添加一个<intent-filter>元素,<action>指定该Activity能够响应的动作,<category>指定该Activity可以响应的intent类型。

3.启动Activity


3.1 不需要返回结果

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

使用startActivity(Intent)方法来启动另外一个Activity,这个Intent指定了要启动的Activity,或者描述需要执行的动作类型(可以启动本应用程序的Activity,也可以是其他应用程序的)。

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

3.2 需要返回结果


有时候,我们需要获得新启动Activity的处理结果,这时候就需要使用startActivityForResult()来启动Activity,然后通过实现onActivityResult()来接收后续启动的Activity返回的结果:当后续的Activity完成后,它会返回一个Intent给onActivityResult()方法。

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...
        }
    }
}

4、关闭Activity


通过调用finish()方法可以关闭一个Activity,也可以通过调用finishActivity()方法来关闭一个之前启动的独立Activity。

注:Android系统会为我们管理Activity,所以在大多数情况下,我们不需要显式地去关闭一个Activity,因为这可能会对用户体验产生不利的影响,除非你可以确定用户确实不再需要使用这个Activity。

5、管理Activity的生命周期


5.1 实现生命周期函数

 
通过实现Activity生命周期回调方法来管理Activity生命周期,这对实现一个健壮而且灵活的应用程序很重要。一个Activity的生命周期是直接受与它相关联的其他Activity和它的任务和回退栈影响。

从本质上来说,一个Activity存在以下三种状态:
  • Resumed(Running):Activity运行在前台,并且拥有用户焦点;
  • Paused:另外一个Activity运行在前台并且拥有焦点,但是当前Activity仍然是可见的。(这可能是另外一个Activity在当前Activity的上方可见,或者是它部分透明或没有覆盖整个屏幕)。处于该状态的Activity仍是完全存活的(该Activity的对象保留在内存中,维持着所有状态和成员信息,而且还与窗口管理器相关联),当系统内存非常贫乏时,系统将把它杀死。
  • Stopped:此时Activity完全被另外一个Activity遮住(也就是说它现在在“后台”)。处于该状态的Activity也仍然是存活的(该Activity的对象保留在内存中,维持着所有状态和成员信息,但不与窗口管理器相关联)。此时它对于用户来说是不可见的,当其他地方需要内存空间的时候,系统将把它杀死。


说明:
  • Activity的整个生命周期是从调用onCreate()开始到调用onDestroy()为止。应该在onCreate()方法里设置一些全局的状态(比如定义布局),在onDestroy()方法里释放剩余的资源。
  • Activity的可见生命周期是从调用onStart()开始到调用onStop()为止。在此期间,用户能够看见Activity并与它交互。在这两个方法之间,我们可以保持需要展示给用户的资源。这个周期在整个Activity的生命周期里可能被调用多次,因为Activity可以交替出现和隐藏。
  • Activity的前台生命周期是从调用onResume()开始到调用onPause()为止。在此期间,Activity在所有其他Activities前面,并且拥有用户焦点。Activity可以频繁地在前后台切换,因此这两个方法里的代码应该尽量轻量,以免用户等待时间过长。

5.2 保存Activity状态


当Activity从paused或stopped状态回到resumed状态时,所有的Activity状态都能恢复。

但是当系统为了回收内存而销毁了一个Activity,内存中的Activity对象被销毁了,当重新返回到这个Activity的时候,系统就不能简单地将Activity恢复到销毁前的原样了。当用户导航回到这个Activity的时候,系统必须重新建立这个Activity对象,但是用户却意识不到这种变化,所以期望它能够恢复原样。在这种情形下,我们可以通过实现一个额外的回调函数onSaveInstanceState()来确保Activity的重要状态信息能够保存下来。



系统在Activity受到攻击被破坏之前,会去调用onSaveInstanceState(),系统会给这个方法传递一个能以键值对形式存储Activity状态信息的Bundle对象。然后,系统杀死了应用程序进程并且用户导航回这个Activity,系统重新建立Activity,并且同时给onCreate()和onRestoreInstanceState()方法传递带有状态信息的Bundle对象,因此,使用其中一个方法,我们就可以从这个Bundle对象中抽取出保存的状态信息并恢复Activity的状态。如果没有存储任何状态信息,那么这个传递过来的Bundle对象是null的,这就好像是Activity第一次被创建时的情形。

注意:在Activity被销毁之前,不保证onSaveInstanceState()一定会被调用,因为有些情形下是没有必要保存状态的(比如用户按下后退键离开Activity,因为用户确定需要关闭这个Activity)。如果系统会调用onSaveInstanceState()方法,则是在onStop()或者也可能是在onPause()之前。

然而,即使我们没有实现onSaveInstanceState(),Activity的onSaveInstanceState()默认实现也会保存Activity的一些状态,具体来说,默认实现会为布局中的每一个View调用相应的onSaveInstanceState()来保存它们本身的状态信息。几乎Android框架里的每一个组件都以适当的方式实现了这个方法,这样任意一个UI状态的改变都会自动保存,然后在Activity重建的时候恢复。(前提是这个控件有唯一的ID)

我们需要保存一些不属于UI状态的重要成员变量信息。当我们重写onSaveInstanceState()方法时,始终需要调用父类的onSaveInstanceState()实现。同样地,当重写onRestoreInstanceState(),也需要调用父类的实现。

注意:由于onSaveInstanceState()不保证会被调用,我们应该仅用它来记录Activity暂时性的状态(也就是UI的状态),而不应该用它来保存持久化的数据,而当用户离开Activity的时候,应该在onPause()中来保存持久化的数据(比如保存到数据库)。

测试应用程序是否能够保存状态信息的最简单办法是旋转设备,当设备旋转时,系统销毁并重建Activity。

5.3 处理配置改变


在运行期间,一些设备配置可以改变(比如屏幕方向,键盘可用性和语言),当这样的改变发生时,Android系统重新建立正在运行的Activity(系统调用onDestroy()后立即调用onCreate())。这种行为旨在帮助我们用我们所提供的(如不同的屏幕方向和大小不同的布局)替代资源进行应用程序自动重载,以适应新的配置。

如果按照如上所述的恢复Activity状态正确地设计Activity,处理由于屏幕方向的变化给Activity带来的重新建立,那么这个应用程序在Activity生命周期中对于其他突发事件的应对将更具弹性。

处理重新启动来保存和恢复Activity状态的一个的最佳方式是使用onSaveInstanceState()和onRestoreInstanceState()(或onCreate())。

当重新建立Activity需要保存大量数据的时候,会大大影响用户体验,此时,我们有两种选择:

a、当配置改变时保留一个对象

如果重新启动Activity需要恢复大量的数据,重新建立网络连接,或者执行更加深入的操作,这样由于配置变化带来的全面重启可能导致很差的用户体验。同样,利用系统使用onSaveInstanceState()保存的Bundle来重新建立Activity可能也不能完全恢复Activity的状态,因为Bundle本来就不是设计用来携带大对象(比如bitmaps)的,并且它携带的数据需要经过序列化和反序列化,这样会消耗大量的内存,使得配置的改变变得缓慢。当Activity由于配置的变化需要重新启动的时候,我们可以通过保留一个有状态的对象来减轻重新初始化Activity的负担。

当运行时配置发生改变时,保留一个对象需要做:

1、重写onRetainNonConfigurationInstance()方法,以返回我们想要保留的对象;
2、当Activity再次被建立时,调用getLastNonConfigurationInstance()来恢复对象。

当Android系统由于配置发生改变而关闭Activity时,它会在onStop()和onDestroy()方法之间调用onRetainNonConfigurationInstance()。在onRetainNonConfigurationInstance()的实现中,我们可以返回任意我们需要在配置改变后能够有效地恢复状态的对象。

比如,当我们的程序从网络中下载了大量的数据,如果用户旋转了设备,则当前Activity需要重建,应用程序就需要重新获取数据,这样会影响用户体验。此时,我们可以通过实现onRetainNonConfigurationInstance()来返回一个携带了这些数据的对象,然后当Activity重新启动的时候,使用getLastNonConfigurationInstance()来恢复这些数据。

返回对象:

@Override
public Object onRetainNonConfigurationInstance() {
    final MyDataObject data = collectMyLoadedData();
    return data;
}

注意:虽然我们可以返回任意对象,但是不应返回一个与Activity相关联的对象(比如说是一个Drawable,Adapter,View,或者是其他与Context相关的对象),因为这样做的话,会泄漏原Activity实例的所有views和resources。(泄漏资源是指应用程序一直维持这些资源而不能被垃圾回收,这样会损失大量内存)。

Activity重启时获取数据:

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

    final MyDataObject data = (MyDataObject) getLastNonConfigurationInstance();
    if (data == null) {
        data = loadMyData();
    }
    ...
}

getLastNonConfigurationInstance()返回了通过onRetainNonConfigurationInstance()返回的数据对象,如果这个对象是null(可能这个Activity的重启不仅仅是因为配置改变的缘故),则需要从原始数据源中重新加载数据。

b、自行处理配置改变(不推荐)

如果当一个特点的配置发生改变时,应用程序不需要更新资源,而且需要我们避免Activity重启的性能限制,那么我们就需要自行来处理这些配置的改变了。

注:自行处理配置改变使得我们使用替代资源变得更加困难,因为系统不会自动为我们去应用替代资源。这种技术应该作为我们必须避免因配置改变带来的Activity重启的最后一种手段。

为了声明Activity处理配置的改变,我们需要在manifest文件中的<activity>元素里添加一个属性 android:configChanges,这个属性的值指定了我们需要处理什么样的配置改变,我们可以使用“|”分隔符来给这个属性设置多个值。比如:

<activity android:name=".MyActivity"
          android:configChanges="orientation|keyboardHidden"
          android:label="@string/app_name">

这样,当它们之间的任一一个配置发生改变时,MyActivity不会重新启动。取而代之的是MyActivity会接收onConfigurationChanged()的调用,这个方法传递一个指定了新设备配置的Configuration对象,通过读取这个Configuration对象的域,我们可以确定新的配置,并通过更新界面中使用的资源来作出一些适当的改变。当这个方法被调用时,Activity的Resources对象被更新了,返回了基于新配置的资源,因此我们可以无需重启Activity而轻松地对界面元素进行重置。

注意:自Android3.2开始,当设备旋转时,“屏幕尺寸”也发生改变。因此,当我们开发基于Android3.2(API-13)或更高版本的应用程序时(通过minSdkVersion和targetSdkVersion声明的),如果我们想阻止在运行时由于屏幕旋转而带来的Activity重启,我们除了包含“orientation”这个属性值之外还必须包含“screenSize”这个值,也就是说我们必须声明android:configChanges="orientation|screenSize",如果是基于Android3.2或者更低版本时,则不需考虑这个因素(即使它运行在Android3.2或者更高版本的机器上)。

例如,下面的onConfigurationChanged()实现检查了当前的屏幕方向:

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    // Checks the orientation of the screen
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show();
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show();
    }
}

Configuration对象代表了所有的当前配置,不仅仅是那些改变了的配置。大部分情况下,我们不需要去关心这个配置是如何改变,是否可以简单地对我们正在处理的有替代资源的资源进行重新分配。

注意来自Configuration域的值都是与Configuration类中常量相匹配的整数值,使用的时候请注意对号入座。

记住:当声明Activity自行处理配置的改变时,我们需要负责重置提供了替代资源的元素。比如说,如果我们分别提供了横竖屏的背景图片,当我们在onConfigurationChanged()里应该根据不同的情形重新分配资源。

如果当配置改变时不需要更新应用程序,我们可以不实现onConfigurationChanged(),在这种情况下,当配置改变后,配置改变之前的资源仍然在使用,我们仅仅是避免了Activity的重建。然而,我们的应用程序应该能够在保持先前状态完整的情况下关闭和重启。所以我们不应该考虑使用这种技术来摆脱在Activity的正常生命周期中保留状态。不仅因为有其他使我们不能阻止应用程序重新启动的配置发生更改,而且我们也应该处理诸如用户离开应用程序,在用户回来之前这个Activity应该被销毁这样的事件。

5.4 协调Activities


当一个activity启动另外一个activity的时候,它们都经历了生命周期的转变:第一个activity暂停然后停止,而另外一个activity被创建。假定这些Activities共享磁盘或者别处的数据,理解到在第二个activity建立前第一个activity还没有完全停止是非常重要的。相反,启动第二个activity的进程和停止第一个activity的进程有重叠。

在同一进程中的两个Activity中,当Activity A 启动Acivity B,产生的操作顺序为:
1、Activity A执行onPause()方法;
2、Activity B按照onCreate(),OnStart(),onResume()的顺序执行。(ActivityB现在取得用户的焦点。)
3、如果ActivityA在屏幕上不再是可见的,它执行onStop()方法。

这个可预测的生命周期回调顺序,允许我们管理从一个Activity到另一个Activity的转换信息。例如,如果为了让之后的Activity能够读取最新数据,第一个Activity停止时必须进行写数据库的操作,那么我们应该在onPause()里写数据库,而不是在onStop()里写。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值