草庐IT

温习Activty的生命周期

generallizhong 2023-03-28 原文
子曰:溫故而知新,可以為師矣。《論語》

学习技术也一样,对于技术文档或者经典的技术书籍来说,指望看一遍就完全掌握,那基本不大可能,所以我们需要经常回过头再仔细研读几遍,以领悟到作者的思想精髓。

近来回顾了一下关于Activity的生命周期,参看了相关书籍和官方文档,也有了不小的收获,对于以前的认知有了很大程度上的改善,在这里和大家分享一下。

熟悉javaEE的朋友们都了解servlet技术,我们想要实现一个自己的servlet,需要继承相应的基类,重写它的方法,这些方法会在合适的时间被servlet容器调用。其实android中的Activity运行机制跟servlet有些相似之处,Android系统相当于servlet容器,Activity相当于一个servlet,我们的Activity处在这个容器中,一切创建实例、初始化、销毁实例等过程都是容器来调用的,这也就是所谓的“Don't call me, I'll call you.”机制。

我们来看一下这一张经典的生命周期流程图:

相信不少朋友也已经看过这个流程图了,也基本了解了Activity生命周期的几个过程,我们就来说一说这几个过程。

1.启动Activity:系统会先调用onCreate方法,然后调用onStart方法,最后调用onResume,Activity进入运行状态。

2.当前Activity被其他Activity覆盖其上或被锁屏:系统会调用onPause方法,暂停当前Activity的执行。

3.当前Activity由被覆盖状态回到前台或解锁屏:系统会调用onResume方法,再次进入运行状态。

4.当前Activity转到新的Activity界面或按Home键回到主屏,自身退居后台:系统会先调用onPause方法,然后调用onStop方法,进入停滞状态。

5.用户后退回到此Activity:系统会先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,再次进入运行状态。

6.当前Activity处于被覆盖状态或者后台不可见状态,即第2步和第4步,系统内存不足,杀死当前Activity,而后用户退回当前Activity:再次调用onCreate方法、onStart方法、onResume方法,进入运行状态。

7.用户退出当前Activity:系统先调用onPause方法,然后调用onStop方法,最后调用onDestory方法,结束当前Activity。

但是知道这些还不够,我们必须亲自试验一下才能深刻体会,融会贯通。

下面我们就结合实例,来演示一下生命周期的几个过程的详细情况。我们新建一个名为lifecycle的项目,创建一个名为LifeCycleActivity的Activity,如下:

 
package com.scott.lifecycle; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; public class LifeCycleActivity extends Activity { private static final String TAG = "LifeCycleActivity"; private Context context = this; private int param = 1; //Activity创建时被调用 @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i(TAG, "onCreate called."); setContentView(R.layout.lifecycle); Button btn = (Button) findViewById(R.id.btn); btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(context, TargetActivity.class); startActivity(intent); } }); } //Activity创建或者从后台重新回到前台时被调用 @Override protected void onStart() { super.onStart(); Log.i(TAG, "onStart called."); } //Activity从后台重新回到前台时被调用 @Override protected void onRestart() { super.onRestart(); Log.i(TAG, "onRestart called."); } //Activity创建或者从被覆盖、后台重新回到前台时被调用 @Override protected void onResume() { super.onResume(); Log.i(TAG, "onResume called."); } //Activity窗口获得或失去焦点时被调用,在onResume之后或onPause之后 /*@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); Log.i(TAG, "onWindowFocusChanged called."); }*/ //Activity被覆盖到下面或者锁屏时被调用 @Override protected void onPause() { super.onPause(); Log.i(TAG, "onPause called."); //有可能在执行完onPause或onStop后,系统资源紧张将Activity杀死,所以有必要在此保存持久数据 } //退出当前Activity或者跳转到新Activity时被调用 @Override protected void onStop() { super.onStop(); Log.i(TAG, "onStop called."); } //退出当前Activity时被调用,调用之后Activity就结束了 @Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestory called."); } /** * Activity被系统杀死时被调用. * 例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死. * 另外,当跳转到其他Activity或者按Home键回到主屏时该方法也会被调用,系统是为了保存当前View组件的状态. * 在onPause之前被调用. */ @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt("param", param); Log.i(TAG, "onSaveInstanceState called. put param: " + param); super.onSaveInstanceState(outState); } /** * Activity被系统杀死后再重建时被调用. * 例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死,用户又启动该Activity. * 这两种情况下onRestoreInstanceState都会被调用,在onStart之后. */ @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { param = savedInstanceState.getInt("param"); Log.i(TAG, "onRestoreInstanceState called. get param: " + param); super.onRestoreInstanceState(savedInstanceState); } }
大家注意到,除了几个常见的方法外,我们还添加了onWindowFocusChanged、onSaveInstanceState、onRestoreInstanceState方法:

 

1.onWindowFocusChanged方法:在Activity窗口获得或失去焦点时被调用,例如创建时首次呈现在用户面前;当前Activity被其他Activity覆盖;当前Activity转到其他Activity或按Home键回到主屏,自身退居后台;用户退出当前Activity。以上几种情况都会调用onWindowFocusChanged,并且当Activity被创建时是在onResume之后被调用,当Activity被覆盖或者退居后台或者当前Activity退出时,它是在onPause之后被调用,如图所示:

这个方法在某种场合下还是很有用的,例如程序启动时想要获取视特定视图组件的尺寸大小,在onCreate中可能无法取到,因为窗口Window对象还没创建完成,这个时候我们就需要在onWindowFocusChanged里获取;如果大家已经看过我写的Android动画之Frame Animation这篇文章就会知道,当时试图在onCreate里加载frame动画失败的原因就是因为窗口Window对象没有初始化完成,所以最后我将加载动画的代码放到了onWindowFocusChanged中,问题迎刃而解。不过大家也许会有疑惑,为什么我在代码里将它注释掉了,因为对当前Activity每一个操作都有它的执行log,我担心这会影响到整个流程的清晰度,所以将它注掉,大家只要了解它应用的场合和执行的顺序就可以了。

2.onSaveInstanceState:(1)在Activity被覆盖或退居后台之后,系统资源不足将其杀死,此方法会被调用;(2)在用户改变屏幕方向时,此方法会被调用;(3)在当前Activity跳转到其他Activity或者按Home键回到主屏,自身退居后台时,此方法会被调用。第一种情况我们无法保证什么时候发生,系统根据资源紧张程度去调度;第二种是屏幕翻转方向时,系统先销毁当前的Activity,然后再重建一个新的,调用此方法时,我们可以保存一些临时数据;第三种情况系统调用此方法是为了保存当前窗口各个View组件的状态。onSaveInstanceState的调用顺序是在onPause之前。

3.onRestoreInstanceState:(1)在Activity被覆盖或退居后台之后,系统资源不足将其杀死,然后用户又回到了此Activity,此方法会被调用;(2)在用户改变屏幕方向时,重建的过程中,此方法会被调用。我们可以重写此方法,以便可以恢复一些临时数据。onRestoreInstanceState的调用顺序是在onStart之后。

以上着重介绍了三个相对陌生方法之后,下面我们就来操作一下这个Activity,看看它的生命周期到底是个什么样的过程:

1.启动Activity:

在系统调用了onCreate和onStart之后,调用了onResume,自此,Activity进入了运行状态。

2.跳转到其他Activity,或按下Home键回到主屏:

我们看到,此时onSaveInstanceState方法在onPause之前被调用了,并且注意,退居后台时,onPause后onStop相继被调用。

3.从后台回到前台:

当从后台会到前台时,系统先调用onRestart方法,然后调用onStart方法,最后调用onResume方法,Activity又进入了运行状态。

4.修改TargetActivity在AndroidManifest.xml中的配置,将android:theme属性设置为@android:style/Theme.Dialog,然后再点击LifeCycleActivity中的按钮,跳转行为就变为了TargetActivity覆盖到LifeCycleActivity之上了,此时调用的方法为:

注意还有一种情况就是,我们点击按钮,只是按下锁屏键,执行的效果也是如上。

我们注意到,此时LifeCycleActivity的OnPause方法被调用,并没有调用onStop方法,因为此时的LifeCycleActivity没有退居后台,只是被覆盖或被锁屏;onSaveInstanceState会在onPause之前被调用。

5.按回退键使LifeCycleActivity从被覆盖回到前面,或者按解锁键解锁屏幕:

此时只有onResume方法被调用,直接再次进入运行状态。

6.退出:

最后onDestory方法被调用,标志着LifeCycleActivity的终结。

大家似乎注意到,在所有的过程中,并没有onRestoreInstanceState的出现,这个并不奇怪,因为之前我们就说过,onRestoreInstanceState只有在杀死不在前台的Activity之后用户回到此Activity,或者用户改变屏幕方向的这两个重建过程中被调用。我们要演示第一种情况比较困难,我们可以结合第二种情况演示一下具体过程。顺便也向大家讲解一下屏幕方向改变的应对策略。

首先介绍一下关于Activity屏幕方向的相关知识。

我们可以为一个Activity指定一个特定的方向,指定之后即使转动屏幕方向,显示方向也不会跟着改变:

1.指定为竖屏:在AndroidManifest.xml中对指定的Activity设置android:screenOrientation="portrait",或者在onCreate方法中指定:

 

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); //竖屏

2.指定为横屏:在AndroidManifest.xml中对指定的Activity设置android:screenOrientation="landscape",或者在onCreate方法中指定:

 

setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //横屏
为应用中的Activity设置特定的方向是经常用到的办法,可以为我们省去不少不必要的麻烦。不过,我们今天讲的是屏幕方向改变时的生命周期,所以我们并不采用固定屏幕方向这种办法。

 

下面我们就结合实例讲解一下屏幕转换的生命周期,我们新建一个Activity命名为OrientationActivity,如下:

package com.scott.lifecycle; import android.app.Activity; import android.content.res.Configuration; import android.os.Bundle; import android.util.Log; public class OrientationActivity extends Activity { private static final String TAG = "OrientationActivity"; private int param = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.orientation_portrait); Log.i(TAG, "onCreate called."); } @Override protected void onStart() { super.onStart(); Log.i(TAG, "onStart called."); } @Override protected void onRestart() { super.onRestart(); Log.i(TAG, "onRestart called."); } @Override protected void onResume() { super.onResume(); Log.i(TAG, "onResume called."); } @Override protected void onPause() { super.onPause(); Log.i(TAG, "onPause called."); } @Override protected void onStop() { super.onStop(); Log.i(TAG, "onStop called."); } @Override protected void onDestroy() { super.onDestroy(); Log.i(TAG, "onDestory called."); } @Override protected void onSaveInstanceState(Bundle outState) { outState.putInt("param", param); Log.i(TAG, "onSaveInstanceState called. put param: " + param); super.onSaveInstanceState(outState); } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { param = savedInstanceState.getInt("param"); Log.i(TAG, "onRestoreInstanceState called. get param: " + param); super.onRestoreInstanceState(savedInstanceState); } //当指定了android:configChanges="orientation"后,方向改变时onConfigurationChanged被调用 @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); Log.i(TAG, "onConfigurationChanged called."); switch (newConfig.orientation) { case Configuration.ORIENTATION_PORTRAIT: setContentView(R.layout.orientation_portrait); break; case Configuration.ORIENTATION_LANDSCAPE: setContentView(R.layout.orientation_landscape); break; } } }
首先我们需要进入“Settings->Display”中,将“Auto-rotate Screen”一项选中,表明可以自动根据方向旋转屏幕,然后我们就可以测试流程了,当我们旋转屏幕时,我们发现系统会先将当前Activity销毁,然后重建一个新的:

 

 

系统先是调用onSaveInstanceState方法,我们保存了一个临时参数到Bundle对象里面,然后当Activity重建之后我们又成功的取出了这个参数。

为了避免这样销毁重建的过程,我们需要在AndroidMainfest.xml中对OrientationActivity对应的<activity>配置android:configChanges="orientation",然后我们再测试一下,我试着做了四次的旋转,打印如下:

可以看到,每次旋转方向时,只有onConfigurationChanged方法被调用,没有了销毁重建的过程。

以下是需要注意的几点:

1.如果<activity>配置了android:screenOrientation属性,则会使android:configChanges="orientation"失效。

2.模拟器与真机差别很大:模拟器中如果不配置android:configChanges属性或配置值为orientation,切到横屏执行一次销毁->重建,切到竖屏执行两次。真机均为一次。模拟器中如果配置android:configChanges="orientation|keyboardHidden"(如果是Android4.0,则是"orientation|keyboardHidden|screenSize"),切竖屏执行一次onConfigurationChanged,切横屏执行两次。真机均为一次。

Activity的生命周期与程序的健壮性有着密不可分的关系,希望朋友们能够认真体会、熟练应用。

有关温习Activty的生命周期的更多相关文章

  1. ruby - 在 Ruby 中查找周期和范围集差异的有效方法 - 2

    我在Ruby中有很多时间范围:period=Time.parse('8:00am')..Time.parse('8:00pm')incidents=[Time.parse('7:00am')..Time.parse('9:00am'),Time.parse('1:00pm')..Time.parse('3:00pm'),Time.parse('1:30pm')..Time.parse('3:30pm'),Time.parse('7:00pm')..Time.parse('9:00pm'),]我正试图在这段时间内获得一系列无事件block。对于以上内容:[Time.parse('9:00

  2. ruby-on-rails - 有没有一种简单的方法可以在 Passenger 的请求周期之外运行垃圾收集? - 2

    unicorn有OobGC可用于在一定数量的请求后运行GC.start的机架中间件。PhusionPassenger中有类似的东西吗? 最佳答案 PhusionPassenger4正式引入了带外垃圾回收机制。它比Unicorn更灵活,允许任意工作,而不仅仅是垃圾收集。http://blog.phusion.nl/2013/01/22/phusion-passenger-4-technology-preview-out-of-band-work/ 关于ruby-on-rails-有没有一种

  3. ruby-on-rails - rails 周期性任务 - 2

    我有一个ruby​​onrails应用程序,我试图在其中找到每隔几秒运行一些代码的方法。我发现了很多使用cron或类似cron的实现的信息和想法,但这些只是准确到分钟,并且/或需要外部工具。我想每15秒左右启动一次任务,并且我希望它完全独立于应用程序中(如果应用程序停止,任务也停止,并且没有外部设置)。这用于缓存数据的后台生成。每隔几秒,任务就会收集一些数据,然后将其存储在缓存中,供所有客户端请求使用。该任务非常慢,因此需要在后台运行并且不阻塞客户端请求。我是ruby​​的新手,但有很强的perl背景,我解决这个问题的方法是创建一个间隔计时器和处理程序,它fork、运行代码,然后在完成

  4. ruby-on-rails - Rails 应用程序的生命周期 - 2

    我正在尝试了解Rails应用程序的生命周期。application_controller.rb什么时候运行?是每次更改时只执行一次,还是每次请求时都执行一次?我想了解以下文件:config/environments/*.rb(开发、生产或测试,取决于当前模式)boot.rb环境.rb路线.rb我问这个的原因之一是,我想知道放在哪里比较好初始化代码自定义配置数据编辑:@Gdeglin的回答很好,但我实际上很想知道这些文件中的每一个何时运行。 最佳答案 应用程序Controller.rbApplicationController是所有C

  5. ruby - 在 Sinatra(Ruby) 中,我应该如何创建在应用程序生命周期中只赋值一次的全局变量? - 2

    在Sinatra中,我无法创建在应用程序生命周期中仅分配一次值的全局变量。我错过了什么吗?我的简化代码如下所示:require'rubygems'ifRUBY_VERSION这导致nil2在终端和,2在浏览器中。如果我尝试将@a=1放入initialize方法中,我会在WebApp.run!中遇到错误线。我觉得我错过了一些东西,因为如果我不能有全局变量,那么我如何在应用程序实例化期间加载大数据?beforedo似乎每次有来自客户端的请求时都会被调用。 最佳答案 classWebApp请注意,如果您使用Shotgun或其他在每次请求时

  6. javascript - 使用 ajax 调用 react 组件 - 生命周期 - 2

    所以我有一个使用React和Ajax调用的有趣案例。在上下文中,我有一个带有3个选项卡的Accordion。初始化Accordionreact组件后,我首先打开第一个选项卡,其余选项卡关闭。每个选项卡的主体中都有所谓的DictionaryCall组件,如下所示:returnclassDictionaryCallextendsReact.Component{constructor(props){super();this.state={word:'',data:[],error:false,nodata:false,initialLoaded:props.load}}componentDi

  7. javascript - 为什么 setInterval() 周期每次都变快? - 2

    我正在Javascript上构建自定义slider,我希望每次用户单击slider的div时,slider都应停止X秒。我的代码是:$(document).ready(function(){varciclo;varindex_slide=1;functionstartSlidercicle(){ciclo=setInterval(function(){//Slidercodegoeshere},3000);}//HereIstarttheslideranimationstartSlidercicle();//Whentheuserclicksonadivcalled'slide',st

  8. javascript - 如何从 Vue.js 中的组件生命周期方法访问 mixin 方法内部的函数 - 2

    这是一个例子:混入.jsexportdefault{methods:{aFunction(){//Somefunctionalityhere}}}组件.vueimportmixinfrom'./mixin'exportdefault{mixins:[mixin]created(){//CallaFunctiondefinedinthemixinhere}}我想从组件内部的created()生命周期方法访问在mixin方法内部定义的aFunction。 最佳答案 mixin方法与组件的当前实例合并,所以它只是:created(){th

  9. javascript - 如何知道脚本 block 或 JavaScript 代码的生命周期? - 2

    我想知道javascriptblock/函数是否在加载后始终可用。因为我已经测试了一些东西,现在我有点困惑。我将一个脚本block定义到一个div中。脚本block有一个事件处理函数,用于元素使用ajax重新加载div。ajax调用返回div的纯html并将其替换为当前的html。但这意味着替换执行的脚本。我认为脚本会在替换语句后停止执行。但它没有。执行替换语句后的代码行那么这些东西是如何工作的。您如何描述脚本block的生命周期? 最佳答案 当代码包含在script中时元素被求值时,代码求值的结果成为页面运行时环境的一部分。删除s

  10. javascript - Google Chrome 标签页 ID 在标签页的生命周期内发生变化 - 2

    我正在实现处理选项卡的GoogleChrome扩展程序。这包括我获取onCreated、onUpdated和onActivated。每次,我都在我的逻辑中使用tabId,它最初在处理onCreated事件期间存储在一个数组中。原则上,一切正常。但是,我注意到了一个小故障。有时,现有选项卡的ID会更改。因此,一个选项卡有一个不在我的数组中的ID,这自然会导致错误。我可以在以下用例中重现此问题:使用不同的URL打开2个或更多标签在一个选项卡中加载一个已在另一个选项卡中加载的URL在这种情况下,会发生两件事:首先,触发T的onActivated事件,而不是onUpdated事件。其次,T现在

随机推荐