Activity生命周期的学习和验证
1.引子
Activity就像是英文词汇本的Abandon,开篇就能碰到,关于Activity生命周期的博客教程非常多,侧面说明Activity非常重要,其中的Activity的生命周期又是重中之重,面试官非常喜欢拿这个作为考点,因为掌握Activity生命周期对提高应用的健壮性具有很大的帮助。因为只看教程不自己总结实践的话,对其理解总会差点意思,自己动手总结了印象才深刻。
2.Activity生命周期
一个Activity具有四个状态:
- 活动(active)或运行(running)状态:此时Activity位于栈顶,用户可以与之交互;
- 可见(visible)状态:该状态下Activity对用户可见,但失去了焦点,例如弹出一个对话框,对话框变成前景,原Activity变成背景板,进入可见状态,此时Activity还是活着的,一旦对话框消失,Activity立刻回到活动状态;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q97Zcg70-1586575759198)(./assets/可见.png)] - 停止(stopped)或隐藏(hidden)状态:如果一个Activity完全被另一个Activity遮盖,则进入该状态,该状态下Activity仍保留着原先的状态信息,再次回到前台直接使用这些保留的信息,例如你从知乎的某个答案跳转到微博界面,此时知乎界面是停止状态,当你再次回到知乎界面时,因为保留了跳转时的状态,再次回到知乎界面还是你之前浏览的答案,注意,在停止状态下,如果系统资源紧张的话,则会销毁Activity,当然会把所有Activity保留的状态信息也清除了,这个时候再次回到知乎界面,只能是看一遍开屏广告再从首页开始吧;
- 销毁(destroyed)状态:系统终止Activity进程,在内存层面删除Activity。现在安卓手机的清理缓存功能其中一个功能是销毁堆栈中的Activity回收资源。当这个Activity再次向用户展示时,是重新创建后的实例了。就像她还是她,但已经不是你认识的她。
Activity生命周期其实是一个有限状态机,描述Activity对象如何在以上4种状态之间转换的,生命周期如图所示,图中矩形表示当Activity状态跳转时执行的回调方法。彩色椭圆矩形是Activity可以进入的主要状态,当然细心的同学会疑惑,彩色椭圆矩形里写的主要状态和4种状态对不上了,例如没见可见状态啊,个人认为彩色椭圆矩形指的是Activity动作状态,例如启动,运行,销毁,而上述4种状态是所处的静态状态,在周期图里用文字描述里,例如Activity进入可见状态,用The Activity is no longer visible来描述了。
这个图我们也许见过多次,跟词汇本老朋友Abandon一样,这个图非常重要,掌握了这个图,才能叫掌握Activity。
Activity生命周期有三种类型,其一是Activity的整个生命周期(entire lifetime),从onCreate()
开始,到onDestroy()
结束,也就是从出生到死亡,贯穿了Activity平庸的一生,Activity在onCreate()
中完成所有全局状态设置,在onDestroy()
中释放所有Activity资源;
其二是可见生命周期(visible lifetime),发生在onStart()
到onStop()
之间,如图所示,为什么叫可见生命周期,这是因为周期开始onStart()
和结束onStop()
Activity处于可见状态,不过处于可见状态Activity还不能和用户进行交互,只能远观。我们发现其实可见生命周期大多数步骤还是和整个生命周期步骤是重合的,为何还单独提出来呢?因为周期的侧重点不同,在可见生命周期内可重点管理给用户显示所需的资源,例如UI的更改,而整个生命周期则可重点管理维护贯穿整个生命周期的资源。
最后是前台生命周期(foreground lifetime),发生在onResume()
到onPause
之间在此期间,Activity处于运行状态,能够与用户进行交互。Activity频繁在onResume
和onPause
之间进行切换-例如设备锁屏和解锁,Activity被其他Activity覆盖和恢复等,因为频繁切换,所以应避免在这个生命周期内执行过多的代码,避免界面卡顿。
我们注意到,这三个生命周期发生的频度是逐渐增加的,就像时钟的三根针,整个生命周期是时针,步骤最长最多,发生的频度较低,可见生命周期是分针,步骤少一点,前台生命周期是秒针,步骤最少,发生的频度最高,开发者应根据这三类生命周期的特点,合理安排Activity所需资源的加载。
3.生命周期回调方法
3.1 生命周期回调方法介绍
我们来看一下生命周期里每一个环节的回调方法
-
onCreate()
Activity第一次创建时调用该方法,可在该方法中完成Activity初始化操作,例如创建视图,绑定按钮事件或其他全局性的资源加载。这是Activity整个生命周期的开始。 -
onStart()
当活动对用户可见时调用,是Activity可见生命周期的开始。 -
onResume()
当Activity开始与用户互动时调用。 此时Activity位于堆栈的顶部并处于运行状态。是Activity前台生命周期的开始。 -
onPause()
当Activity失去焦点或者即将隐藏/停止或销毁时调用,执行该方法期间Activity对用户仍可见,直到另一个Activity覆盖,一个比较相似的例子是游戏加载,在加载完新场景之前原场景一直对玩家显示,不过为了不能让玩家觉得是死机了,加载场景会切换成过场片段或者过场画面例如展示一下游戏技巧等给玩家知道游戏没有死机,同样道理,onPause()
一样是执行过场的动作,官方推荐调用这个方法时让UI保持活动,同时方法内要执行的操作尽可能轻量化,别做太多的耗时操作。 -
onStop()
当Activity对用户完全不可见时调用,例如另一个Activity在栈顶,完全覆盖了当前的Activity,或者该Activity正在被销毁。该回调方法和onPause()
方法的主要区别在于,如果启动的新Activity是一个对话框式的activity,那么,onPause()方法会得到执行,而onStop()方法并不会执行。 -
onDestory()
这个回调方法在Activity被销毁之前调用。 -
onRestart()
在Activity停止之后,再次启动之前调用,始终跟随onStart()
。
3.2 生命周期回调方法示例
我们在每个回调方法里添加log监控每个回调方法的运行情况,代码如下,然后再来执行各种操作看Activity生命周期。
package com.test.activitydemo;
import androidx.appcompat.app.AppCompatActivity;
import android.app.AlertDialog;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
Button bclick;
TextView tv;
String TAG="Activity Life";
//
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bclick=findViewById(R.id.button);
tv=findViewById(R.id.textView);
tv.setText("活动状态");
Log.e(TAG, "start onCreate");
bclick.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv.setText("可见状态");
AlertDialog alertDialog1 = new AlertDialog.Builder(MainActivity.this)
.setTitle("弹框")//标题
.setMessage("弹框变成前景,验证onPaus是否执行")//内容
.setIcon(R.mipmap.ic_launcher)//图标
.create();
alertDialog1.show();
}
});
}
// 当活动对用户可见时调用,Activity可见生命周期的开始。
@Override
protected void onStart()
{
super.onStart();
Log.e(TAG, "start onStart");
}
// 在Activity停止之后,再次启动之前调用,其后是onStart(),也就是可见生命周期开始.
@Override
protected void onRestart()
{
super.onRestart();
Log.e(TAG, "start onRestart");
}
// 前台生命周期开始
@Override
protected void onResume()
{
super.onResume();
Log.e(TAG, "start onResume");
}
// 前台生命周期结束前调用
@Override
protected void onPause()
{
super.onPause();
Log.e(TAG, "start onPause");
}
// 可见生命周期结束即将结束前调用
@Override
protected void onStop()
{
super.onStop();
Log.e(TAG, "start onStop");
}
// 销毁前调用.
@Override
protected void onDestroy()
{
super.onDestroy();
Log.e(TAG, "start onDestroy");
}
}
- 首先是启动demo,我们看到Activity逐一调用了
onCreate()
,onStart()
,onResume()
,然后进入Activity界面,对应Activity整个生命周期的前三个回调函数;
- 然后我们点击按钮,在Activity上弹个框,看看是否回调
onPause()
,结束前台生命周期。我们看到,有弹框在Activity之上,但并没有回调onPause()
,也就是说,Activity没有结束前台生命周期,当前Activity还是在栈顶,不是说有弹框部分覆盖Activity,Activity失去焦点后结束前台生命周期吗,这是怎么回事?这是因为创建的AlertDialog对话框有一个context类型的参数,该参数表示AlertDialog是在Activity的上下文创建的,表示弹出的AlertDialog仍属于当前Activity,Activity没退居幕后,所以不会执行onPause()
.那怎样的弹框才能进入onPause()
呢?只能是伪装成弹框的Activity了。为什么需要是弹框式Activity,普通的Activity是否可行?普通的Activity完全覆盖了当前的Activity,当前Activity不可见了,弹框式Activity没有完全覆盖当前Activity,Activity结束前台生命周期,但仍然可见。
将普通Activity变成弹框式,只需要修改主题(Theme)成弹框主题即可,弹框主题在style.xml里设置,代码如下
<style name="dialogstyle">
<!--设置dialog的背景-->
<item name="android:windowBackground">@android:color/transparent</item>
<!--设置Dialog的windowFrame框为无-->
<item name="android:windowFrame">@null</item>
<!--设置无标题-->
<item name="android:windowNoTitle">true</item>
<!--是否浮现在activity之上-->
<item name="android:windowIsFloating">true</item>
<!--是否半透明-->
<item name="android:windowIsTranslucent">true</item>
<!--设置窗口内容不覆盖-->
<item name="android:windowContentOverlay">@null</item>
<!--设置动画,在这里使用让它继承系统的Animation.Dialog-->
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
<!--背景是否模糊显示-->
<item name="android:backgroundDimEnabled">true</item>
</style>
然后在AndroidManifest.xml里修改Activity主题
<activity
android:name=".AlertDialogActiviy"
android:theme="@style/dialogstyle" />
最后在代码中实现跳转
Intent intent = new Intent(MainActivity.this, AlertDialogActiviy.class);
startActivity(intent);
这样我们打开Activity弹框时,原Activity仍可见,我们看logcat,可看到已经调用onPause()
,表示结束了前台生命周期。我们点导航栏的返回键,回到原Activity界面,则回调onResume
方法,重新进入前台生命周期。
3. 现在来看下可见生命周期的示例。这个比较简单,只要调用另外一个Activity或者回调桌面(桌面其实也是一个Activity)即可,只要让我们的Activity不可见就行。为了方便(偷懒),我用回到桌面来验证,验证的logcat截图如下,可以看到,首先回调onPause()
,随着Activity消失,桌面完全显示,最后回调onStop()
,正式结束可见生命周期。
然后我们再从桌面回到Activity,logcat截图如下,首先回调onRestart()
,宣布复活,然后是onStart()
,进入可见生命周期,最后调用onResume()
,进入前台生命周期。
4. 最后看销毁Activity的回调方法,操作方法很简单,杀掉Activity即可调用,不再赘述。
wa_520: 很多容易的地方都写得很详细
wa_520: 很多容易的地方都写得很详细
m0_60387895: 接口中的void方法应该更换String的
象屁火箭: RSA?
象屁火箭: 写的超好