对活动的生命周期有了充分的了解才能对程序作出最好的优化,给用户最好的使用体验。
Android官方给出了活动的生命周期图,不过是英文的,翻译后如下所示(作图真考验耐心):
Activity类中有7个回调方法,覆盖了活动生命周期的每一个环节。
1、onCreate()。活动第一次被创建时调用。
2、onStart()。活动由不可见变为可见时调用。
3、onResume()。在活动准备好和用户进行交互时调用,也被称为获得输入焦点时调用。
4、onPause()。在系统准备去启动或者恢复另一个活动的时候调用,也被称为失去输入焦点时调用。
5、onStop()。在活动完全不可见时调用,若A活动启动的B活动为对话框式活动时,A活动此时不是完全不可见,则onPause()会得到执行,onStop()不会被执行。
6、onDestory()。活动被销毁之前调用。
7、onRestart()。活动被重新启动时调用,只能在onStop()方法之后调用。停止态 -> 运行态之前。
体验活动生命周期:
准备,新建一个项目ActivityLifeCycleTest,包含一个活动(MainActivity),再新建两个子活动,一个是正常的覆盖屏幕的活动(NormalActivity),另一个是对话框式活动(DialogActivity),并让两个子活动布局拥有一个TextView,用于明示这是哪一个活动。在主活动添加两个按钮,分别用于启动两个活动,重写上述7个方法,分别打印Log日志,通过观察MainActivity的方法调用情况,来理解活动的生命周期。
将DialogActivity设置为对话框式活动可以在AndroidManifest文件中,在该活动的<activity>标签中加入:
<activity android:name=".DialogActivity"
android:theme ="@style/Theme.AppCompat.Dialog" >
</activity>
修改activity_normal.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is NormalActivity " />
</LinearLayout>
修改activity_dialog.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="This is DialogActivity " />
</LinearLayout>
修改activity_main.xml文件。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/start_normal_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start NormalActivity" />
<Button
android:id="@+id/start_dialog_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Start DialogActivity" />
</LinearLayout>
修改MainActivity中的代码。
package com.my.activitylifecycletest;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
//tag说明,用于log日志,简化书写
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate"); //输出onCreate()方法执行语句
setContentView(R.layout.activity_main);
Button startNormalActivity = (Button)findViewById(R.id.start_normal_activity);
Button startDialogActivity = (Button)findViewById(R.id.start_dialog_activity);
//注册监听事件,启动NormalActivity
startNormalActivity.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,NormalActivity.class);
startActivity(intent);
}
});
//注册监听事件,启动NormalActivity
startDialogActivity.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this,DialogActivity.class);
startActivity(intent);
}
});
}
//重写活动生命周期的7个方法
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
}
1、运行程序:
由于原图太大,所以截取了一部分,省去了空白。
Logcat图:
主活动从创建、开始、获得输入焦点。经历了三个过程。
2、启动NormalActivity,将主活动完全覆盖。
Logcat图:
启动了NormalActivity,主活动经历了暂停、停止两个过程。
3、按下返回键,返回主活动。
Logcat图:
返回主活动时,由于是从停止态到运行态的过程,所以onRestart()方法得到执行,活动重新启动,并经历开始、获得输入焦点的过程。
4、启动DialogActivity。此时主活动并不是完全不可见。
Logcat图:
由于主活动并不是完全不可见,所以onStop()方法不会得到执行,所以只会执行onPause()方法。
5、按下返回键,回到主活动。
Logcat图:
同样的,返回主活动时,由于不是从停止态向运行态过度,所以只执行onResume()方法。重新获得输入焦点。
6、在主活动上,按下返回键结束程序。
Logcat图:
从运行态到结束活动,会依次执行暂停、停止、销毁方法。
另外的,活动被回收了怎么办。例:活动A启动了活动B,活动A进入了停止态,但是由于系统内存不足将A活动回收了,此时按下返回键,从B活动回到A活动会经历什么?
同样的道理,A活动被回收,从停止态直接销毁。执行onDestory()方法。回到A活动的过程中,A活动会被重新创建,从onCreate()方法开始执行,而不会执行到onRestart()
方法,所以,理解活动生命周期很重要,一定要避免重要数据未保留,而活动却被系统回收。
了解了活动的生命周期后,还有一些重点需要掌握。即活动的异常的生命周期。
导致异常的情况有两种:
1、系统配置发生变化导致活动被杀死并重新创建(多指横竖屏切换)。
2、由于内存不足导致处于停止态的低优先级的活动被杀死。
首先,从第一种情况开始说明,我们可以利用上面讲到的程序进行分析,首先在重写的7个方法上面再重写两个方法。如下:
//保存数据方法
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Log.d(TAG,"onSaveInstanceState");
}
//恢复数据方法
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
Log.d(TAG,"onRestoreInstanceState");
}
这次执行程序需要模拟器模拟横竖屏切换。我使用的是Genymotion模拟器,同时按下Ctrl + F11即可实现横竖屏切换。
运行程序,并将竖屏切换为横屏。注意Loacat输出。
运行图:
此时Logcat图:
按下Ctrl + F11,切换为横屏。
此时Logcat图:
可以看到,活动被杀死后重新创建。暂停后执行保存数据方法,然后执行停止、销毁方法,然后创建活动,执行创建、开始方法,并执行恢复数据方法,然后执行onResume()方法获得输入焦点。
我们的预期是将横竖屏切换不会影响到活动的生命周期,而是从什么状态切换,到什么状态结束。上面的例子就是异常的活动生命周期。
然而我们绝对不希望活动的运行是这样的,到底要怎么避免旋转屏幕后活动销毁重建呢?那就是为活动设置configChange属性。即configChange:"orientation",在SDK>13时还要增加一个screenSize属性,要怎么使用呢?还是以上面的程序为例,在AndroidManifest.xml文件的主活动定义标签内加入下面一句话:
<activity android:name=".MainActivity"
android:configChanges="orientation|screenSize" >
这些还不够,光一条语句不能实现预期,它的意义就是:横竖屏切换时不会让活动销毁重建,而是回调onConfigurationChanged()方法。在MainActivity中重写该方法。
//重写的配置改变方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.d(TAG,"onConfigurationChanged");
}
然后再次运行程序,Logcat中的输出如下(运行结果图及屏幕旋转过程略):
可以看到活动不会再销毁重建了,而是运行了onConfigurationChanged()方法。
第二种情况,系统由于内存不足而将处于停止态的低优先级活动杀死,这个其实就是活动生命周期被以外终止,依次执行暂停、保存数据、停止、销毁方法。我们要做的就是尽量保证活动的优先级高。
进程优先级分5种,优先级从高到低分别为:1、前台进程。2、可视进程。3、服务进程。4、后台进程。5、空进程。
尽量避免停止态中的活动不会被系统杀死的做法就是,尽量将活动所在的进程设置为前台进程,或者通过service绑定,也可以使其成为一个单独的进程。
本文总结参考自郭神(郭霖)的《第一行代码 第2版》以及zejian的活动解析博客。