Activity及其生命周期方法
△概述
Activity,是安卓里与用户交互的组件,通俗点说平时用手机的时候看到的一个个界面就是“Activity"所组成的(当然也有可能会是Fragment,这个不在本文讨论),Activity类似一个容器,可以装填布局文件,装填可显示的东西,但Activity本身不具备绘图功能。
△创建"Activity"
要创建一个可用的Activity,一共是有两个方式,在集成开发环境里(如Android Studio)在你想创建Activity的路径哪里右键→new→Activity,很简单的就完成创建了,如果你不想系统来帮你创建,想要自己一步一步来做,那么下面就是步骤:
(1)创建一个Java类,继承Activity,也可继承Activity子类(如FragmentActivity、ListActivity)。
(2)在"Manifest.xml"文件声明你的Activity。
(3)新建一个布局文件,该布局用作该Activity的界面。
(4)重写Activity里的onCreate()方法,调用"setContentView()"方法将布局文件与Activity绑定。
下面看看具体代码:
(1)创建一个Java类,类名叫做"TestActivity",继承Activity,比如这样:
public class TestActivity extends Activity{这里只是创建了一个类,其他代码稍后补上。
}
(2)在"Manifest.xml"文件里面声明你的Activity,像这样子:
<activity android:name=".TestActivity">
<!--<intent-filter>-->
<!--<action android:name="android.intent.action.MAIN" />-->
<!--<category android:name="android.intent.category.LAUNCHER" />-->
<!--</intent-filter>-->
</activity>
注意注释掉的部分,如果你的Activity是用作你应用启动时的入口(就是首个Activity),就要写上这几行了,否则的话不要写上。
(3)新建一个布局文件,将它作为Activity界面,这里布局文件的名字是:activity_test,像这样子:
<?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:textSize="24sp"
android:text="TestActivity"/>
</LinearLayout>
(4)重写Activity里的onCreate()方法,调用"setContentView()"方法将布局文件与Activity绑定。
public class TestActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
相比第一步的代码,就是重写一个生命周期方法,然后将布局文件给设置上去。
△启动"Activity"
前面讲了怎么创建一个Activity,那么创建好之后该怎么用呢,现在来看怎么启动Activity。启动Activity也有不同方式,如果一个Activity是应用的入口,那么不用你去启动,系统启动应用那时自动启动,如果一个Activity不是应用入口,那么根据是否需要被启动的"Activity"在结束后返回数据,可以调用两个不同方法启动"Activity",分别是:startActivity()、"startActivityForResult()",我们先来看第一个,关于启动"Activity"并且需要返回数据,我们会在后面谈论。
假设现在有一个"TestActivity",它由一个布局文件:activity_test,布局文件里面有个按钮,点击之后可以启动另外一个"Activity":SecondActivity,activity_test代码如下:
<?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/btn_open_second_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="启动SecondActivity"
android:textSize="24sp"/>
</LinearLayout>
很简单的只有一个按钮而已。
接下来在"TestActivity"的"onCreate()"方法里面,我们先要找到控件(就是按钮),然后设置一个点击事件响应,通过"Intent"对象指定要启动的Activity,然后调用"startActivity()"方法即可,像这样子:
public class TestActivity extends Activity{这样就能启动出"SecondActivity",当然前提是你已经正确创建"SecondActivity"。
private Button openSecondActivity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
openSecondActivity = (Button)findViewById(R.id.btn_open_second_activity);//调用方法找到按钮
openSecondActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(TestActivity.this, SecondActivity.class);
startActivity(intent);
}
});
}
}
△启动"Activity"并且需要返回值
有时候当我们启动另外一个"Activity"是为了让他去完成某个工作,工作完成之后会返回原来的"Activity",而原来的"Activity"需要根据工作完成状况给予用户不同反馈信息,那么此时就需要被启动那个"Activity"能够返回某些数据回来,此时不能再使用"startActivity()"方法,我们需要"startActivityForResult()"方法。
假设现在有两个"Activity":TestActivity,SecondActivity,从“TestActivity"启动"SecondActivity","SecondActivity"里面有一个文本输入框(严格来讲是他的布局文件里有个文本输入框),如果用户在里面有输入任何数据,当"SecondActivity"返回时,告诉“TestActivity"用户已经完成输入,并且将数据送回去,在"TestActivity"里面通过Log打印出输入内容,如果用户什么都没输入,Log里面将会打印"什么东西都没输入“。
这里主要涉及几个方法调用,现在这里简介一下:
(1)onActivityResult:这个方法将在TestActivity里面重写,当我们从"SecondActivity"返回时,系统将会回调这个方法,不论用户有没输入什么东西,都会回调,我们在这个方法里利用标志进行判断,同时也在这里进行数据获取。
(2)startActivityForResult():与"startActivity()"方法类似,也是用来启动一个新的"Activity",不过这个方法告诉系统,当被启动的那一个"Activity"返回时,需要带回一些数据,其实就是告诉系统需要回调"onActivityResult()"方法,如果使用"startActivity()"方法启动新的活动,活动返回时将不会回调"onActivityResult()"方法,这个方法将在"TestActivity"里面重写。
(3)setResult():这个方法就是"SecondActivity()"用来返回数据用的,调用该方法可标识任务是否完成,同时可以携带任务所需要返回的数据。
(4)finish():这个是"Activity"用来结束自己用的方法。
我们先来看看"SecondActivity"布局文件里的代码,文件名字:activity_second,如下:
<?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">
<EditText
android:id="@+id/et_user_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请你输入一些东西"/>
<Button
android:id="@+id/btn_return"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="返回"/>
</LinearLayout>
现在看看逻辑代码:SecondActivity文件,如下:
public class SecondActivity extends Activity{这里只设置了一个点击事件,当按下后,我们获取文本框的内容,然后判断是否有数据,如果没有任何文本,调用"setRdsult()“方法设置结果为"RESULT_CANCELED"标示的是任务取消,我们将没有数据当做取消了任务,当然即使你不调用,系统默认也会返回这个,这里只是为了提高可读性了。如果判断用户是有输入数据,就用Intent对象装着数据,同时设置"RESULT_OK"告诉系统任务已经处理完了,然后不论是否有输入过数据,我们都将"SecondActivity()"返回。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Button returnButton = (Button)findViewById(R.id.btn_return);
final EditText userInput = (EditText)findViewById(R.id.et_user_input);
returnButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userString = userInput.getText().toString();
// 如果用户有输入过什么东西
if(userString.equals("")){
setResult(RESULT_CANCELED);//其实这里不写也行,默认系统就会传入"RESULT_CANCELED"
finish();//关闭当前这个活动
}else {
// 到这说明用户什么都没输入
Intent data = new Intent();//Intent对象用来传递数据
data.putExtra("userInput", userString);
setResult(RESULT_OK, data);//注意这个必须调用,否则"TestActivity"会认为没有返回数据的
finish();//关闭当前这个活动
}
}
});
}
}
接下来看看"TestActivity"布局文件,文件名:activity_test,如下:
<?xml version="1.0" encoding="utf-8"?>代码也是非常简单,只有一个用来启动"SecondActivity"用的按钮。
<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/btn_start_second_activity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="启动SecondActivity"/>
</LinearLayout>
好了接下来就到了核心代码,我们来看"TestActivity"里的代码,尤其需要关注的是"onActivityResult()"方法,像这样子:
public class TestActivity extends Activity {首先在"onCreate()"生命周期方法里面,我们为按钮设置了点击事件,用来启动新的活动,这里需要注意的,和前面的启动不同,如果使用"startActivity()"方法启动,返回之后"onActivityResult()"方法将得不到回调,使用"startActivityForResult()"就是需要告诉系统需要回调"onActivityResult()"方法。然后在"onActivityResult()"方法里面,我们需要进行判断,这里面有三个参数都很重要:
private static int REQUEST_CODE = 0;
private static String TAG = "test";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
Button startSecondActivity = (Button)findViewById(R.id.btn_start_second_activity);
startSecondActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(TestActivity.this,SecondActivity.class);
startActivityForResult(intent,REQUEST_CODE);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(resultCode == RESULT_OK){//判断"resultCode"看看任务是否完成
if(requestCode == REQUEST_CODE){//判断请求码是否能对上
Log.i(TAG,data.getStringExtra("userInput"));
}
}
if(resultCode == RESULT_CANCELED){//如果任务被取消了
Log.i(TAG,"什么东西都没输入");
return;
}
}
}
(1)首先,resultCode表示被启动的活动是否完成任务,如果值为"Result_OK"表示任务顺利完成,如果值为"RESULT_CANCELED"表示任务被取消了,当然这个返回值由被启动的活动决定。
(2)其次就是requestCode参数了,这个参数代表了请求码,试想一下如果在"TestActivity"活动里面可以启动多个不同活动,"onActivityResult()"方法被回调的时候,怎么知道是由哪个活动返回所导致的回调,只有通过请求码来区分。
(3)一个Intent对象,还记得么,"SecondActivity"里面我们是在一个Intent对象里面,设置了要返回的数据,现在这个对象就传到了这里,我们在这获取数据就可以了。
所有参数介绍完后,来看一下逻辑代码,如果任务顺利完成,再判断下请求码是否能对上,如果对上,就该执行我们的操作了,打印数据。另外一单任务取消,我们也会打印提示。
到此为止,关于如何启动一个活动同时要求返回数据,也基本都介绍完了。
△关闭"Activity"
△生命周期
先来一张生命周期全图:
首先单独介绍一下各个生命周期方法:
→onCreate()
当且仅当Activity被创建的时候回调,从Java的角度去理解他,就是对象创建的时候来回调,你应当在这里对窗体做初始化,比如设置布局文件,以及其他一些初始化的设置,当然最重要的就是调用"setContentView()"方法设置布局文件。
→onStart()
→onResume()
→onPause()
一旦调用了该方法,“Activity"即将失去用户焦点(比如临时弹出了一个对话框、或者当你在操作时突然来了一个电话,或者手机进入休眠,都会导致"Activity"失去用户的焦点),需要注意的一点是,如果仅仅回调了该方法,没有回调"onStop()"方法此时,此时用户还是能够看见"Activity",只是不能预知交互而已,只要用户离开活动,他是第一个会被回调的方法。你可以在该方法里保存一些较重要的用户信息,比如用户操作到一半时突然来了一个电话,接完这个电话之后他就不再继续操作,那他之前如果输入某些有用信息,必须保存。
→onStop()
当窗体不再为用户所见(比如有一个新窗体已经完全覆盖原来那个窗体,或者用户回到桌面,或者用户按开机键锁屏),只要你再看不见窗体了,这个方法就会回调。对于那些只是用来更新UI用的资源(比如广播的监听,监听数据变化然后更新UI,像这样的广播就能在该方法里面注销),可以在该方法里面释放,因为用户已经看不到活动了。
→onDestroy()
当活动被销毁的时候,就会回调这个方法,一旦该方法被回调,如无意外(比如内存泄漏就是一个意外),活动对象就会销毁。你需要在这个方法里面释放资源,比如你的活动启动了一个下载任务,你必须在这里释放与下载有关的所有资源(如果你的下载任务需要保持后台下载,那就不用释放这个资源)。
→onRestart()
△几个操作所涉及的生命周期方法回调
我们重写所有生命周期方法,然后分别打印一下数据,然后执行几个操作,看看会有什么结果,就能感性体验一下"Activity“生命周期,代码很简单像这样:
public class TestActivity extends Activity {我们除了重写以及打印之外,什么事情都没有做。
private static String TAG = "test";
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
Log.i(TAG,"--onCreate--");
}
@Override
protected void onStart() {
super.onStart();
Log.i(TAG, "--onStart--");
}
@Override
protected void onResume() {
super.onResume();
Log.i(TAG,"--onResume--");
}
@Override
protected void onPause() {
super.onPause();
Log.i(TAG, "--onPause--");
}
@Override
protected void onStop() {
super.onStop();
Log.i(TAG,"--onStop--");
}
@Override
protected void onRestart() {
super.onRestart();
Log.i(TAG,"--onRestart--");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG,"--onDestroy--");
}
}
点击屏幕启动活动:我们看到如下信息:
一个活动完全运行起来,将会回调以下几个方法:onCreate()、onStart()、onResume()。
活动正常打开以后点回退键,我们看到如下信息:
即按下了返回键后,又会回调几个方法:onPause()、onStop()、onDestroy()。
点击屏幕启动活动,这次不点击回退键,点击Home键:
相比起点击回退键,少了一个onDestroy()周期。
让我们再回到最初始的时候,点击屏幕启动活动,这次不点击任何键,而是启动另外一个活动,我看到了如下信息
可以看到这和启动“Activity"然后按下”home"键的情况是一样的。
然后我再从第二个"Activity"回去,我们看到如下信息:
从第二个"Activity"返回,则原来的那个活动将会回调几个方法:onRestart()、onStart()、onResume()。
△生命周期图里面的几个循环
死记"Activity"生命周期方法没有很大意义,你注意到,生命周期图里面有几个循环,可以通过生命周期里的几个循环周期进行学习,同时,理解了这几个循环以后,你会明白我刚才的那些操作为什么会回调对应生命周期。
主要是有三个循环周期:
→entire lifetime(完整周期)
一个"Activity"的完整的生命周期,他是这样子的一个过程:onCreate()→onStart()→onResume()→onPause()→onStop()→onDestroy(),如何得到一个活动完整周期?点击屏幕打开一个活动,什么都不做然后就点返回键,就会回调上面几个生命周期方法,当你第二次再打开活动,又会重新调用onCreate()。可以认为,"onCreate()"方法以及"onDestroy()"方法就是一对的,通过处理这两方法里的代码,你就可以管理好"Activity"创建以及销毁时的任务。
→visible lifetime(可见周期)
该周期内活动对于用户可见,他是这样子的一个过程:
onStart()→onResume()→onPause()→onStop()→onRestart()→onStart(),当被调用onStop(),活动便不再可见了。如果活动又重新回到屏幕上端,会从onRestart()开始回调。什么时候会发生这样的循环?当有一个活动正在运行,你去启动另外一个活动,再从新的活动里面退回,就会发生上述循环。可以认为"onStart()"方法以及"onStop()"是一对的,他们标志着活动对用户可见及不可见。处理好了这两个方法的代码,就能管理好活动在可见和不可见间的逻辑。
→foreground lifetime(前台周期)
处在这个周期里面,当前"Activity"处于其它所有的"Activity"上方,而且获取用户交互焦点。他是这样子的一个过程:onPause()→onResume()。从onResume()到onPause()可能会被频繁调用,因为,当设备进入了休眠状态,或者弹出一个会话,都会调用onPause()。类似的,可以当做"onResume()"方法以及"onPause()"是一对的,他们标志着焦点的得失。
△两个Activity间的生命周期交替
两个"Activity“间的生命周期交替,其实就是当一个"Activity"启动另外的一个"Activity"时,他们两个生命周期执行顺序。这个事情比较简单,这里直接说出结果。例如"Activity"A启动"Activity"D,那么两者生命周期执行顺序:A的"onPause()"→D的"onCreate()"→D的"onStart()"→D的"onResume()"→A的"onStop()"。提出这个过程做什么呢,主要为了以下两点:
(1)如果D里面有一些数据显示,是由A来提交的,比如D要显示数据库的数据,或者"SharedPreferences"文件里的某些数据,这些数据是由A提交的,那么A必须要在"onPause()"方法提交,D才能在他启动时所经历的生命周期里面读取数据,如果A在"onStop()"里才提交,D就读取不到数据。
(2)如果你需要在A离开时进行一些耗时操作,只能将操作放到"onStop()"方法里面,不要放在"onPause()"方法里面,因为如果"onPause()"方法一直没执行完,新的"Activity"跟本就启动不起来。当然最好的情况是,别在活动任何一个生命周期方法里面执行耗时操作。
△Activity数据保存与恢复
为什么要进行数据保存以及恢复
数据保存与恢复的过程及其关键方法
<?xml version="1.0" encoding="utf-8"?>就是两个控件而已。我们在活动"onCreate()"方法里面找到控件并且设置点击事件。如下:
<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:id="@+id/tv_click_num"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:gravity="center_horizontal"
android:text="你点击了0次按钮"/>
<Button
android:id="@+id/btn_be_click"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="点我"/>
</LinearLayout>
private TextView clickNumTextView;//这个是界面里面的TextView
private Button clickBtn;//这个是界面里面的Button
private int num;//这个成员变量用来保存点击次数
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 找到项对应的控件
clickNumTextView = (TextView)findViewById(R.id.tv_click_num);
clickBtn = (Button)findViewById(R.id.btn_be_click);
// 设置点击事件监听
clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 记录并且显示点击次数
num = num+1;
clickNumTextView.setText("你点击了"+num+"次按钮");
}
});
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("num",num);//通过Bundle对象保存数据
}
@Override现在来看整个活动完整代码:
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 如果为空表示没有保存数据,直接返回
if(savedInstanceState==null){
return;
}
// 取出并且显示被保存的数据
clickNumTextView.setText("你点击了"+num+"次按钮");
}
public class MainActivity extends Activity {
private TextView clickNumTextView;//这个是界面里面的TextView
private Button clickBtn;//这个是界面里面的Button
private int num;//这个成员变量用来保存点击次数
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 找到项对应的控件
clickNumTextView = (TextView)findViewById(R.id.tv_click_num);
clickBtn = (Button)findViewById(R.id.btn_be_click);
// 设置点击事件监听
clickBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 记录并且显示点击次数
num = num+1;
clickNumTextView.setText("你点击了"+num+"次按钮");
}
});
}
// 将点击的次数保存到"Bundle"对象里面
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("num",num);//通过Bundle对象保存数据
}
// 取出被保存的数据
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// 如果为空表示没有保存数据,直接返回
if(savedInstanceState==null){
return;
}
// 取出并且显示被保存的数据
clickNumTextView.setText("你点击了"+num+"次按钮");
}
}