从零学Android(五)、Activity的生命周期

本文详细介绍了Android Activity的生命周期,包括打开、暂停、恢复、停止和重新创建Activity时的回调函数,强调了生命周期函数在控制Activity行为和资源管理中的重要性,以及如何处理屏幕旋转等场景下的状态保存和恢复。通过示例演示了如何使用关键的生命周期方法,如onCreate(), onPause(), onResume(), onSaveInstanceState()等。" 83584579,5889726,Spark机器学习库:mllib与ml的对比与优势,"['Spark', '机器学习', 'mllib', 'ml', '数据处理']
摘要由CSDN通过智能技术生成

当用户退出、进入或者在使用我们的app的时候,我们app中的Activity页面的实例对象会在它们的生命周期函数中转换。比如说:当我们的Activity是第一次启动的时候,它会展示在系统的最前端,并且获取到用户焦点。在这个过程中,当我们在建立Activity的用户界面和组件的时候,系统会调用Activity的一系列的生命周期函数。这个时候,如果用户打开了另外一个Activity或者进入了其它的APP 应用,这个时候Activity会进入到后台(Activity页面不可见,但是对象实例还在),那么系统又会执行另外一系列生命周期函数。

我们可以通过这些生命周期回调函数来控制当用户进入或者退出Activity时的行为。比如说,在Activity中我们实现了一个视频播放的功能,那么当用户退出app的时候,我们需要停止视频的播放,并且断开它的网络连接,而当用户再次进入这个Activity页面的时候,我们则需要建立网络连接并继续播放视频。

掌握Activity的生命周期函数,是Android开发最基础也是最重要的。下面我们从4个场景来学习它的生命周期函数:


1.打开一个Activity

和其它的通过main()方法作为程序入口的编程范式不同,Android系统中关于Activity的初始化的代码是通过在它不同的阶段下调用其特定的生命周期函数来完成的。创建一个Activity会有一系列有顺序的生命周期回调函数,而销毁一个Activity同样有一系列有序的生命周期回调函数。这个场景我们主要了解重要生命周期函数的概述和当我们创建一个Activity实例时候的第一个生命周期回调函数的处理。

我们可以将Activity的生命周期回调函数理解为一个金字塔形,如下图:

如上图,每一个Activity的状态都是金字塔的一个台阶,当创建一个新的Activity的实例时,每一个回调方法都会随着Activity的状态改变一步步向金字塔的顶部移动,也就是说,创建一个新的Activity的实例时,生命周期回调方法按顺序调用为:onCreate() → onStart() → onResume(),金字塔的最顶部即为Resumed状态,即当前的Activity运行在最前端,用户能与之交互。

当用户离开当前的Activity的时候,随着Activity状态的改变,系统会回调金字塔往下走的这些回调方法,以便销毁当前的Activity对象。当然,在一些情况下,Activity页面可能仅仅只往下走一个台阶或者部分台阶,比如在当前的Activity中,用户直接跳转到其它的app中,这种情况Activity只会往下走两个台阶到Stopped状态,即onPause() → onStop(),在这种状态下,用户如果再次返回到我们的app,那么Activity又会恢复到用户离开前的状态,即从Stopped状态回到金字塔的顶端Resumed状态,根据图中描述,这个时候会回调onRestart() → onResumed()。

根据Activity页面的复杂度,我们可能并不需要去实现所有的生命周期回调方法。但是,理解和实现合适的生命周期回调方法,以便于让我们app的行为与用户所期待的一致却很重要。我们一般从下面几个重要点来保证APP行为和用户期待的效果一致:

1.在使用我们app的时候,如果用户这时接到一个来点或者切换到其它的APP中,app不要崩溃。

2.当用户没有去使用的时候,不要消耗系统资源。

3.当用户离开我们app再返回的时候,不要丢失掉用户之前的操作进度。

4.当用户切换横竖屏的时候,APP不要崩溃或者丢失掉用户进度。

从上面的生命周期回调函数金字塔图中,我们不难发现,当Activity在不同状态间转变的时候,会有很多个情形,然后只有三种状态能让Activity长时间停留:

一、Resumed状态(运行中状态)

这种状态下,当前的Activity运行在最前端,能够和用户交互

二、Paused状态(暂停状态)

这种状态下,可能是打开了一个新的Activity页面,同时这个新的Activity并没有占据整个屏幕,导致我们还能看到前一个Activity页面,虽然这个时候我们无法和之前的Activity页面进行交互。

三、Stopped状态(停止状态)

这种状态下,Activity页面已经完全被隐藏了,用户无法看到也无法与之交互。一般被认为是运行在后台,当处于Stopped状态时,Activity的实例以及它的状态信息(比如一些成员变量之类)还是被保留的,只是无法执行任何的代码。

另外的两个状态Created和Started状态都是属于短暂性的状态,即Activity页面无法长时间处于这种状态下,在这些短暂性状态下,系统会快速调用下一个生命周期回调函数进入到一个新的状态中。也就是说,当系统回调完onCreate()之后会立即回调onStart(),然后立即回调onResume()方法。

以上就是Activity的基本生命周期方法。下面我们通过一个Demo学习生命周期方法的具体用法:

1.1 指定App的Launcher Activity(启动页面)

当用户从手机主屏幕上点击我们的App图标的时候,系统会找到我们的"Launcher Activity"并且调用它的onCreate()方法。这个Activity就是我们的应用入口了。在我们的应用配置文件AndroidManifest.xml中,我们可以指定一个Activity作为我们的应用入口。这个Launcher Activity必须在配置文件中通过一个包含MAIN 行为(action)和LAUNCHER策略(category)的<intent-filter>标签来声明注册。代码如下:

<activity android:name=".MainActivity" android:label="@string/app_name">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
其中的 MAIN行为(action)和 LAUNCHER策略(category)缺一不可。


1.2 创建一个新的Activity实例

大部分的App都会包含多个不用的Activity页面,以便用户执行不同的操作。不管是用户点击App图标打开的Launcher Activity页面还是用来响应用户行为打开的普通的Activity页面,每次创建新的实例的时候,系统都会调用Activity页面的onCreate()方法。我们必须实现onCreate()方法,用来执行应用开始或者打开Activity新页面的基本逻辑,onCreate()方法只会在Activity创建时执行一次。

比如说:在下面的代码中,我们在onCreate()方法中执行了一些Activity页面创建的基本操作,例如声明引用的用户界面(layout布局文件),定义一些成员变量,或者配置UI信息之类。

public class MainActivity extends AppCompatActivity {
    /**
     * 界面中展示一个文本的文本框
     */
    private TextView tv_message;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //指定当前Activity对应的用户界面(即layout布局文件)
        setContentView(R.layout.activity_main);
        //初始化文本框
        tv_message = (TextView) findViewById(R.id.tv_message);
    }

}
onCreate()回调方法执行完成,系统会马上调用 onStart()onResume()方法。我的Activity永远不会驻留在Created或Started状态。从技术上来讲,当调用 onStart()的时候,这个Activity就已经可以被用户看见了,但是 onResume()会紧跟着被执行,并且Activity会停留在Resumed状态,直到发生一些其它的状况,比如说接收到一个来电或用户进入到另外一个Activity或手机屏幕被关闭。

在接下来的学习中,我们会看到当用户从Paused状态或者Stopped状态进入到Resumed状态时(即重新进入这个Activity页面),onStart()onResume()这两个方法的重要性。

注意:onCreate()方法包含一个名为savedInstanceState的参数,这个会在下面的第四点重新创建一个Activity的小节中讲到。


1.3 销毁Activity

如果说onCreate()Activity的第一个生命周期回调方法,那么onDestroy()就是Activity的最后一个生命周期回调方法,从方法名上就能看出来,这个方法会在Activity被销毁的时候回调(也就是完全从系统内存中移除的时候)。

许多的app不需要去实现这个方法,因为Activity页面的本地类的引用会随着Activity的销毁而一起清理掉,我们app应该将大部分的清理工作放在onPause()onStop()方法中。当然,如果我们的Activity页面中包含一个在onCreate()时创建的后台线程,或者其它长时间运行的资源,如果不正确关闭可能导致内存泄露的,我们就应该在onDestroy()中将它们关闭。

一般来说,在调用onDestroy()之前,系统会先调用onPause()onStop()方法,但是有一种情况除外:就是我们在onCreate()中调用finish()方法来关闭我们的Activity页面。在某些情况下,我们需要一个Activity来作为临时的中间人去打开另外一个Activity页面,那么我们就需要在这个Activity的onCreate()中调用finish()方法,这个时候,系统在调用完onCreate()方法后会立即调用onDestroy()方法,而不会去调用其它的任何生命周期回调方法。


2.暂停和恢复一个Activity

在App的使用过程中,最前端的Activity页面总是可能被一些可见的组件给遮挡,以致于Activity页面失去焦点,进入到Paused状态,这个时候,系统会回调Activity的onPause()方法,以便于我们可以在这个方法中停止当前正在执行的一些动作(比如停止播放视频)或者保存一些必要的信息。如果用户再次返回到这个Activity页面,系统会回调onResume()方法。如图:

2.1 暂停Activity

当系统回调onPause()方法,从技术上来说,这意味着我们的Activity页面是部分可见的,但是更多的时候这暗示着用户将要离开这个页面,页面很快会进入到Stooped状态。一般情况下,我们会在onPause()方法中做如下一些操作:

1.停止一些消耗CPU的操作,比如动画之类的。

2.提交一些没有被保存的信息(当然这些信息必须是用户希望被保存的,例如:草稿电子邮箱)

3.释放系统资源,比如广播接收器,传感器或者任何一些用户不需要用到却又耗费电池生命的资源。

比如,我们的应用程序有用到Camera,那么onPause()方法就是一个比较好的释放它的地方。

@Override
public void onPause() {
    super.onPause();  // Always call the superclass method first

    // Release the Camera because we don't need it when paused
    // and other activities might need to use it.
    if (mCamera != null) {
        mCamera.release();
        mCamera = null;
    }
}
一般来说,我们不应该在 onPause()方法中保存用户改变的信息(如个人信息)到内存中,唯一需要这样做的情况就是:你确定用户希望将这些信息自动保存(比如:草稿电子邮箱)。另外,我们需要避免在 onPause()方法中执行一些消耗CPU大的工作,例如:写数据库操作,因为 onPause()方法中的高CPU消耗会导致跳转到下一个Activity的可见过渡变得缓慢(我们应该将这些高负载的操作放到 onStop()方法中)。同时,我们还应该 onPause()方法中

2.2 恢复Activity

当用户从Paused状态再次回到我们原来的Activity页面的时候,系统会回调onResume()方法。注意!每次我们的Activity回到前台的时候,都会调用onResume()方法,包括它第一次被创建的时候。因此,我们需要实现onResume()方法来做一些初始化的操作,这些初始化的内容包括每次Activity进入前台要初始化的数据或组件(比如说开始一个动画或者当获取到用户焦点时才需要去初始化的组件等),同时也包括在onPause()方法中释放掉的资源。

下面的例子就是上面我们讲onPause()方法时,释放掉的资源,当进入Resumed状态时,我们需要初始化这些资源:

@Override
public void onResume() {
    super.onResume();  // Always call the superclass method first

    // Get the Camera instance as the activity achieves full user focus
    if (mCamera == null) {
        initializeCamera(); // Local method to handle camera init
    }
}


3.停止和再启动一个Activity

停止和再启动Activity可能是最重要的一个生命周期过程了,在这个过程中,我们必须确保我们的App没有崩溃掉,同时也没有丢失掉用户的进度。Activity的停止和再启动主要有下面几个场景:

1.用户通过最近使用的App列表打开了另外一个App。我们App的Activity就会退到后台,进入Stopped状态。如果用户通过主屏幕我们应用的图标或者最近使用App列表再进入我们的App,我们的Activity就会被再启动。

2.用户通过在Activity中的某个操作,打开了一个新的Activity,当第二个Activity创建好后,第一个Activity就会进入stopped状态。这个时候如果用户点击返回按钮,那么第一个Activity就又会再启动。

3.用户在使用我们App的时候,收到了一个来电,那么我们的Activity就会进入stopped,当用户接听完来电后,又会回到我们的App,并再启动之前的Activity。


        Activity类提供了onStop()onRestart()这两个生命周期回调方法以便我们处理Activity停止和再启动的事件。和部分UI可见的paused状态不同,stopped状态表明这个Activity已经完全不可见,而且用户已经进入了另外一个Activity或者另外一个App。

注意:当Activity处理stopped状态时,系统内存中还保留着这个Activity的实例对象,也就是说我们可能并不需要去实现onStop()onRestart()这两个方法,甚至onStart()也不用实现,因为大多数的Activity都比较简单,暂停和再启动Activity,系统就能帮我们完成,我们要做的可能仅仅就是在onPause()方法中将当前正在和系统资源交互的动作给断开。下图虚线框部分是暂停和再启动的流程图:


3.1 暂停Activity

当我们Activity回调onStop()方法的时候,代表我们的Activity已经完全不可见,这个时候我们应该释放掉那些用户不会再用到的资源。在系统内存紧缺的时候,一旦我们的Activity进入stopped状态,系统就有可能将它的实例回收掉。在一些极端的情况下,系统甚至不会调用Activity的最后一个生命周期方法onDestroy(),而是直接杀掉我们的App进程。所以我们应该在onStop()方法中释放掉一些资源,以免造成内存泄露。

虽然onPause()方法在onStop()方法之前被调用,但是我们应该在onStop()中去执行那些消耗CPU大的操作,比如写数据到数据库中(因为这个时候另外一个Activity已经被创建好,并且进入到前台,不会造成上面提到的Activity过渡卡顿的情况)。

如下,在onStop()方法中进行写数据操作:

@Override
protected void onStop() {
    super.onStop();  // Always call the superclass method first

    // Save the note's current draft, because the activity is stopping
    // and we want to be sure the current note progress isn't lost.
    ContentValues values = new ContentValues();
    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());

    getContentResolver().update(
            mUri,    // The URI for the note to update.
            values,  // The map of column names and new values to apply to them.
            null,    // No SELECT criteria are used.
            null     // No WHERE columns are used.
            );
}
当Activity处于stopped状态时,在系统内存中还保留有它的实例,当Activity恢复时,这个对象会被调用。当再启动时,我们不用再去初始化那些之前已经初始化过的组件。系统同样会保存之前Activity布局中所有View的踪迹状态,比如说我们之前在文本域 EditText中输出了一些内容,当再启动时,这些内容会被保留,我们不用去

手动保存然后恢复数据。

注意:即使系统将我们处于stopped状态的Activity直接销毁掉,它仍然会将我们View对象(比如一个文本域EditText)的状态给保存下来,放到一个Bundle(一种key-value形式的二进制数据)中,并在用户再次打开(重新创建)这个Activity的时候,会恢复这些数据。(下面第四个场景会更加详细地了解在Activity被销毁后再创建时候Bundle保存状态数据的行为)


3.2 启动或再启动Activity

当我们的Activity从stopped状态再回到前台时,系统会回调onRestart()方法。在Activity变得可见的时候,系统同样会回调onStart()方法(不管是再启动Activity还是第一次创建Activity)。由于onRestart()方法仅仅是从stopped状态再启动时被调用,所以当我们确定Activity是进入stopped状态而没有被销毁掉时,我们可以在onRestart()方法中进行一些必要的恢复操作,但是一般情况下我们并不会需要在onRestart()方法中去恢复一些数据。但是,由于我们在onStop()方法中基本上清理了Activity中的资源数据,所以在Activity再启动时,我们需要去重新初始化这些资源(注意:初始化不是恢复)。当然,第一创建这个Activity实例的时候,我们也需要去初始化这些资源,基于这一点,我们一般将初始化的操作放在onStart()方法中(因为onStart()不管是第一次创建还是再启动,都会被调用)。

比如,由于在用户再启动Activity之前,已经过了很长一段时间,所以我们需要再次确保系统功能当前是否可用:

@Override
protected void onStart() {
    super.onStart();  // Always call the superclass method first
    
    // The activity is either being restarted or started for the first time
    // so this is where we should make sure that GPS is enabled
    LocationManager locationManager = 
            (LocationManager) getSystemService(Context.LOCATION_SERVICE);
    boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
    
    if (!gpsEnabled) {
        // Create a dialog here that requests the user to enable GPS, and use an intent
        // with the android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS action
        // to take the user to the Settings screen to enable GPS when they click "OK"
    }
}

@Override
protected void onRestart() {
    super.onRestart();  // Always call the superclass method first
    
    // Activity being restarted from stopped state    
}
当系统销毁掉我们的Activity的时候,会回调 onDestroy()方法。由于我们在 onStop()中释放了大部分的资源,在 onDestroy()方法中我们就没有太多事情需要去做。同样这个方法是我们最后一次机会去释放那些可能导致内存泄露的资源的机会,所以我们需要确保那些长时间运行任务和子线程被停止了。 


4.重新创建一个Activity

Activity被销毁正常情形有很多,比如用户按返回键或者我们的Activity主动调用finish()关闭Activity页面。同样的,如果我们的Activity运行在后台而且很长时间没有被打开,当系统资源短缺的时候,系统就可能会主动销毁掉我们的后台Activity。

当我们的Activity因为用户按下返回键或者主动调用finish()而被销毁的时候,系统将会把这个Activity的实例对象从内存中永远移除,因为用户按下返回键或者主动调用finish()的行为暗示着这个Activity不再需要了。然而,如果是因为系统约束导致系统主动销毁我们的Activity,那么虽然实际的Activity实例同样已经不存在了,但是系统会通过一系列的数据来保留当我们Activity被销毁时候的Activity的状态信息,当用户再次进入这个Activity的时候,系统会通过这些数据来重新创建Activity的实例。系统用来恢复之前Activity状态的这系列数据被称之为“instance state(实例状态)”,它是一个key-value数据对的集合,并且保存在Bundle对象中。

特别注意:当用户旋转屏幕的时候,我们的Activity会被销毁和重建。当屏幕方向改变时(横竖屏切换),系统会销毁重建我们最前端的Activity页面,因为系统配置已经改变,所以我们的Activity可能需要加载新的layout资源(比如横竖屏时加载不同的layout资源,这部分我们在学习Android适配不同屏幕时提到过)。

为了保存Activity状态的数据,我们必须重写onSaveInstanceState()方法。这个方法在用户离开我们的Activity时候被调用,它会在onStop()之前被调用,但是不能确定是在onPause()之前还是之后,另外只有当Activity对象被异常销毁时,才会保留数据到Bundle对象中,正常的销毁不会触发该方法,同时在重写该方法的时候,我们必须调用super.onSaveInstanceState(),否则我们就需要自己手动去保存所有的View的状态。当系统必须去重建我们这个被异常销毁的Activity的时候,它会将这个保留了状态实例数据的Bundle对象传递到onRestoreInstanceState()onCreate()方法中。如下图:


4.1 保存Activity的状态

当Activity被异常销毁时,系统会在Activity的onStop()之前之前调用onSaveInstanceState()方法,以便我们保留数据信息到key-value的集合中。这个onSaveInstanceState()方法的默认实现保存了Activity布局中每一个View的状态信息,比如一个文本域EditText的文本内容或者一个可滑动列表ListView当前的滑动位置等。

为了保存一些附加信息,我们必须实现onSaveInstanceState()方法,并且将它们以key-value键值对的形式加入到Bundle对象中。如下:

static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // Save the user's current game state
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
    // Always call the superclass so it can save the view hierarchy state
    super.onSaveInstanceState(savedInstanceState);
}

4.2 恢复Activity的状态

当我们的Activity被异常销毁然后重建时,我们可以通过系统传递给我们的保留之前Activity状态的Bundle对象来恢复我们的Activity状态。onRestoreInstanceState()onCreate()方法都会收到这个保留着实例状态信息的Bundle对象。由于不管是第一次创建还是再次重建Activity页面,onCreate()方法都会被执行,所以当我们从Bundle对象取出对象来重建Activity时,我们必须先去判断它是否为null,如果这个Bundle对象等于null,那么说明这次是第一次创建这个Activity页面,而不是恢复之前被异常销毁的Activity。

如下例子,告诉我们要怎么在onCreate()方法中去恢复之前的状态信息:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState); // Always call the superclass first
   
    // Check whether we're recreating a previously destroyed instance
    if (savedInstanceState != null) {
        // Restore value of members from saved state
        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
    } else {
        // Probably initialize members with default values for a new instance
    }
    ...
}
有时候,我们可能并不会在 onCreate()方法中恢复数据,而是选择去实现 onRestoreInstanceState(),这个方法会在 onStart()方法之后被调用。而且,相较于在 onCreate()方法中恢复数据,实现该方法我们不用去判断 Bundle对象是否为null,因为只有在重建Activity的时候,系统才会去调用这个方法。比如:
public void onRestoreInstanceState(Bundle savedInstanceState) {
    // Always call the superclass so it can restore the view hierarchy
    super.onRestoreInstanceState(savedInstanceState);
   
    // Restore state members from saved instance
    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
}
同样的,在 onRestoreInstanceState()方法中,我们必须去调用super. onRestoreInstanceState()方法,以便系统帮我们恢复Activity布局中所有View的状态,而不用我们手动去恢复!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值