Activity生命周期的不同状态作为离开和返回你应用的通道,比如当你第一次启动时,Activity将处于最顶层并接收用户的焦点,在这个过程中android系统会调用activity的一系列生命周期的回调方法。当用户做了一个启动另一个窗口或者跳转到另一个程序的操作,系统将调用activity生命周期的其它回调方法将其置于后台(此时这个activity虽然处于不可见状态,但它仍然是存在的)。
通过activity的回调方法,你可以定义在离开和进入该窗口的一些行为。比如你做了一个音乐播放器,在离开播放窗口时你可能会暂停音频播放并中断网络连接,等到再次返回到这个窗口时,你又会再次连接网络来播放音乐。
本课将会对activity生命周期的各个回调方法做详细地讲解,让你知道如何在各个回调方法中如何处理来避免离开了窗口之后还占用系统资源。
启动一个窗口
与其它通过main方法来启动程序的编程语言不同的是,Android通过调用它生命周期的相关方法来实例化窗口,系统通过调用一系列的回调来启动和关闭窗口。
本课将向你介绍Activity的一些重要回调方法,通过正确地管理它们来创建你的第一个窗口实例。
理解Activity生命周期回调方法
在Activity的生命周期中,系统将调用一系列其生命周期回调方法一步一步地来实例化窗口。
当用户离开窗口时,系统会调用其它的回调方法将窗口置于后台,此时,这个窗口只有等到从其它窗口返回到这个界面时才能将其恢复。
需要实现什么回调方法根据你的具体实现情况而定,你并不是必须要实现所有的回调的。重要的是你要理解Activity的每个回调方法,在正确的回调里面做正确的事情。在管理activity的生命周期的时候你重点需要注意以下几点:
1、 当用户使用你的app的时候,突然来电话了或者是跳转到其它程序的时候不能崩溃掉;
2、 当窗口处于后台时不要占用系统资源;
3、 用户离开你的界面,回来时不能有资源丢失的情况;
4、 横竖屏切换时不能有程序崩溃或者进程被销毁的情况。
在接下来的课程中将介绍activity在不同状态间切换时回调,activity通常情况处于Resumed、Paused或者Stopped中的一种状态。
Resumed
该状态下,窗口处于前台并且用户是可以和窗口进行交互的(多数时候又称为“运行时”状态)。
Paused
该状态下,是当前窗口的上层有半透明或者是没有覆盖整个屏幕的窗口处于前台,此时此刻,处于Paused状态的窗口既不接收用户的输入,也不执行任何功能。
Stopped
该状态下,窗口处于完全不可见状态,但仍在后台运行,窗口的所有状态以及成员变量的的值都被保留,但不能执行任何功能。
有些暂态的生命周期回调方法(比如Created和Started)都是一闪而过的,也就是说,当系统调用onCreate()回调方法后,系统接着回调用onStart()方法,紧接着又调用了onResume()回调。
上面仅仅介绍了activity基本的生命周期回调,接下来将要学习一些特定的生命周期行为。
指定你程序的主入口
程序的主入口就是当用户在桌面点击你应用的图标,进入程序时调用onCreate()方法的那个窗口,它就是你程序的主界面。
主入口需要你去工程根目录下的清单文件AndroidManifest.xml中定义。
程序的主窗口必须在清单文件中的<intent-filter>标签下包含action为MAIN和category为LAUNCHER,比如:
<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>
注:当你通过AndroidSDK工具新建一个工程时,默认会在清单文件中定义一个程序的主入口Activity。
当然啦,如果你不在清单文件中的任何Activity的<intent-filter>标签下包含action为MAIN和category为LAUNCHER属性,那么你的应用将不会在桌面显示。
创建一个新实例
由于涉及到很多操作,很多程序都包含了若干个不同的窗口,无论是程序主入口还是用户通过触发某些事件进入的窗口也好,每个界面都是通过Activity的onCreate()方法来实例化的。
每个窗口你都需要实现只被系统调用一次的onCreate()回调方法来初始化窗口,比如你通过实现onCreate()回调来布局交互界面和初始化某些成员变量。
下例所示的代码中,通过onCreate()回调来布局窗口的交互、初始化成员变量和配置UI界面。
TextView mTextView; // Member variable for text view in the layout
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Set the user interface layout for this Activity
// The layout file is defined in the project res/layout/main_activity.xml file
setContentView(R.layout.main_activity);
// Initialize member TextView so we can manipulate it later
mTextView = (TextView) findViewById(R.id.text_message);
// Make sure we're running on Honeycomb or higher to use ActionBar APIs
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
// For the main activity, make sure the app icon in the action bar
// does not behave as a button
ActionBar actionBar = getActionBar();
actionBar.setHomeButtonEnabled(false);
}
}
注意:上例中使用SDK_INT的原因是阻止程序在API版本为5之前的系统中运行,旧版本的系统会报运行时异常。
一旦onCreate()方法执行完成后,系统会立即调用onStart()和onResume()方法,你的窗口永远不会是处于Created或者Started状态的,从技术角度来讲,系统在调用onStart()方法后程序就处于可见状态了,但紧接着就会继续调用onResume()并一直处于该状态直到类似于电话接入、跳转到其他程序或者待机等情况的出现。
注意:onCreate()方法中的参数savedInstanceState将会在后续“重新创建窗口”的课程中介绍。
销毁窗口
当你的窗口实例完全从系统内存中移除时系统会触发onDestory()回调方法。
很多程序根本不需要实现onDestory()回调,因为在系统调用onPause()或者onStop()方法时已经将窗口相关资源清理干净了。但是你的程序中涉及到一些通过线程加载资源的东西,你就应该实现onDestory()回调来完全清理内存了。
@Override
public void onDestroy() {
super.onDestroy(); // Always call the superclass
// Stop method tracing that the activity started during onCreate()
android.os.Debug.stopMethodTracing();
}
注意:系统通常在调用onPause()和onStop()方法之后才回调onDestory()方法,但有一种情况例外,那就是你在onCreate()中调用了窗口的finish()方法,比如这个界面是一个选择跳转到其它窗口的界面,在这种情况下,系统在销毁窗口时是不会调用除onDestory()方法以外的任何方法的。
暂停和恢复窗口
在应用运行的过程中,可能有各种可视组件跳到前台使当前活动窗口处于暂停状态,比如,当一个半透明的窗口被打开(比如一个对话框风格的窗口)时,之前的活动窗口就处于暂停状态了,在它获取到用户焦点之前它都一直处于暂停状态。
然后,一旦窗口被完全阻塞并且不可见,它将会停止(这个问题将在下面的章节中讨论)。
一旦你的窗口进入暂停状态,系统便会调用Activity的onPause()方法来让你停止正在运行但在暂停后就不会继续的动作(比如视频播放)或者保存一些需要持久化的数据。当再次返回到这个窗口时,你可以通过系统调用的onResume()方法来恢复之前保存的内容。
注意:当你的窗口收到onPause()回调时,可能预示者只是短暂的暂停一会,一会又会获得用户焦点。
暂停窗口
当系统调用你窗口的onPause()方法时,从技术角度上来讲你的窗口仍然部分可见,但更多时候预示者用户离开了你的窗口,系统将立即进入Stopped状态,在onPause()方法中你应该做如下事情:
1、 停止动画或者其它消耗CPU且正在运行的动作;
2、 提交用户希望保存但是还没保存的改变;
3、 释放系统资源,比如广播接收,传感器句柄或则其它耗电的东西。
比如,如果你的应用中使用到了相机,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密集型工作,比如保存数据库之类的,因为它可能影响你进入另一个窗口的速度,进而影响用户体验(你应该在onStop()方法中执行这些费时的操作)。
注意:当你的窗口处于暂停状态时,Activity实例仍然是在内存中驻留的,这样在窗口恢复时你就不需要重新初始化窗口的相关组件了。
恢复窗口
当窗口从暂停状态恢复时,系统将调用onResume()方法。
在你的窗口每次恢复到前台时都是要调用onResume()方法的,包括窗口被实例化的时候。所以说呢,你应该在onResume()方法中初始化在onPause()方法中释放掉的组件信息(比如启动动画或者初始化只有当窗口获取用户焦点时才需要使用的组件)。
下面这个例子就是与在窗口暂停实例中相对应的例子,在onResume()方法里重新初始化在onPause()方法中释放掉的相机资源。
@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
}
}
停止和重启窗口
暂停和重启窗口在窗口生命周期的管理中是一个非常重要的过程,在使用这两个方法的过程中有几点关键事项:
1、 当用户打开最近打开应用并且从你的应用跳转到其它应用的时候,你应用当前的活动窗口将会停止掉,当用户点击你应用的桌面图标或者通过最近打开应用返回你的应用时,这个窗口会被重启;
2、 当用户通过某种操作在当前窗口中启动了另一个窗口时,在下一个窗口被创建完成后当前窗口会被停止掉,当用户点击返回按钮返回到方才停止的窗口时,这个窗口将会被重启。
3、 在使用你应用的过程中收到来电时你应用的当前活动窗口也会被停止。
Activity针对停止和重启窗口提供了onStop()和onRestart()这两个回调方法,以便在窗口处于这两种状态时你做出相应的反馈。与暂停状态可以让部分UI组件可见不同的是,停止状态是在窗口处于不可见且用户焦点已经在另外一个窗口时才会被调用。当用户离开当前窗口时,系统会调用onStop()方法来停止当前活动窗口(1)。当用户从停止状态再次返回到这个窗口时,系统会调用onRestart()(2)方法,紧接着会调用onStart()(3)和onResume()(4)。不管是什么情况使得窗口停止,系统都会在会掉onStop()方法之前会掉onPause()方法的。
停止活动窗口
当窗口接收到系统回调的onStop()方法时,它将处于不可见状态并且应该释放掉程序不再使用的资源,一旦你的窗口被停止,系统可能会在内存不足的情况下销毁它,这种情况下系统是不会调用onDestory()方法的,所以为了避免这种情况的出现,在onStop()方法中就释放掉窗口的相关资源就显得尤为重要了。
尽管onPause()方法是在onStop()方法之前被调用,你还是应该在onStop()方法里面来做消耗CPU的操作,比如保存信息到数据库的操作之类的。
比如下面这段代码,就是在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.
);
}
当你的窗口被停止后,它仍在在内存中,一旦有某种操作将其唤醒,它又会从内存中恢复,这种情况下你就不需要再次初始化窗口的相关信息了,系统通常情况下都会在窗口被唤醒时恢复视图的布局和用户输入的相关信息。
注意:即使你的窗口在停止状态下被系统销毁掉,当它由于某种操作被系统唤醒时,系统还是能够通过保存到Bundle中的相关信息来恢复窗口的相关属性的。
启动/重启 窗口
当你的窗口从停止状态恢复时,系统将会回调onRestart()方法,不仅如此,在你的窗口从不可视状态变为可视状态时,系统都是会调用onRestart()的(不管你的重新创建窗口也好,第一次创建也好)。它是恢复窗口暂停状态的最好地方,但可不包含停止掉的东西额。
每个应用的具体情况都不相同,所以对于在onRestart()方法中做如何操作是没有统一的规范的。但是由于不管你是从停止状态恢复也好,或者说是第一次被创建也好(当系统不存在窗口的实例的时候),系统都是会调用onStart()方法的。哈,你也想到了是吧,onStart()方法就该干与onStop()方法相对应的事情,onStop()方法中停止掉什么,onStart()方法中就应该恢复什么。
比如说哈,有可能出现系统在离开你的应用很久后会再次返回的情况,此时onRestart()方法是一个非常适合于验证系统是否可以使能该窗口的地方。
@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
}
当你的窗口被销毁是,系统会调用你窗口的onDestory()方法。因为在调用onDestory()方法之前会调用onStop()方法来释放掉你程序中的大部分资源,所以没有多少程序是需要实现onDestory()方法的,不过onDestory()方法是处理内存释放的好地方,所以关于结束线程或者其它持久运行的东西都应该在onDestory()方法中来处理。
重新创建窗口
当你主动点击窗口的返回按钮或者调用finish()方法时,系统会在前台需要很多系统资源而系统内存又不足的情况下销毁掉你的窗口。
当用户点击返回按钮或者是窗口自身被结束时系统都会销毁当前活动窗口,此时窗口就会由于被销毁而消失。但如果是由于某种异常导致窗口消失时,这种情况下系统会通过Bundle来保存窗口的相关属性以便重启窗口时恢复数据信息的。
注意:窗口会在横竖屏转换时销毁和重建,当涉及到切换横竖屏时,系统会销毁并重建当前活动窗口,一是由于屏幕方向改变了,还有就是有可能你需要重新加载窗口的相关资源(比如布局之类的)。
默认情况下,系统会通过Bundle实例来保存窗口中每个视图的信息,所以你不需要担心窗口恢复时会有信息丢失的情况。
注意:为了能够保证系统保存每个视图的信息,你需要为他们指定独一无二的ID。
如果你需要在窗口停止时保存上面介绍之外的信息,你就必须要自己实现onSaveInstanceState()回调方法了。系统会在用户离开你的窗口时通过Bundle来保存你需要保存的信息,在这个方法里面保存后系统会在窗口被恢复时调用onRestoreInstanceState()以便你取出之前保存的信息。
保存窗口的状态
当你的窗口停止后,系统会调用onSaveInstanceState()方法通过键值对的方式来保存你需要保存的信息,该方法默认会保存窗口中所有视图的信息,比如编辑框中的输入信息或者是列表的滚动位置等。
为了能够保存除窗口默认会保存的信息外,你必须自己在onSaveInstanceState()方法中来保存你的数据,eg:。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);
}
恢复窗口状态
当窗口被销毁后再次进入时,系统会重建窗口,你可以在onRestoreInstanceState()方法中恢复之前保存的信息,然后通过onCreate()方法的savedInstance参数来判断是窗口是重建还是新建,并且可以通过saveInstance来获取之前保存的信息。
不管是新建还是重建窗口系统都会调用onCreate()方法,所以你需要通过onCreate()方法中的参数saveInstance来判断到底是新建还是重建。
比如,你可以照着下面这样来处理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
}
...
}
你同样可以通过实现onRestoreInstanceState()来恢复之前你保存的信息,它将会在onStart()方法之后紧接着被调用,系统只会在之前有过保存窗口信息操作的情况下才会调用该方法,所以你就不需要去判断它的参数是否为空啦。
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);
}