Android入门——Activity生命周期和横竖屏切换和解锁屏详尽总结

引言

好久没写博客了,最近一直忙公司的一个人工智能应用的项目,项目中许多流程需要智能自动开启和随时控制流程的状态,自然地想到了在Activity的生命周期方法中去控制,由于业务的自身的特殊性,在生命周期这一块产生了很多偶发性的Bug,后面细细排查才找到祸根所在,于是就把很多人以为很简单的生命周期的相关知识总结一遍,毕竟有时候记忆不好,同时也加深下印象,接下来以MainActivity 跳转到ToActivity,再由ToActivity返回到MainActivity的过程为例。

一、Activity完整生命周期

这里写图片描述

  • onCreate(Bundle savedInstanceState):创建activity时调用。Bundle中可以提出用于创建该 Activity 所需的信息。当Activity被创建(Activity对象从无到有,但如果只是被暂时隐藏,并没有被销毁时,重新变成激活状态的不会触发)时,就会执行,且在一个生命周期内只执行一次。所以在这里完成一些初始化工作,比如说调用setContentView(id)设置在资源文件的id;使用findViewById(id) 获得所有的组件;绑定注册对应的监听器、数据绑定等等。

  • onStart():activity变为在屏幕上对用户可见时,处于可见状态,即获得焦点时,会调用,Activity处于栈顶。

  • onRestart():重新启动activity时调用。该活动仍在栈中,而不是启动新的活动。即当Activity由完全被覆盖但没有被销毁时,重新回到前台

  • onResume():activity开始与用户交互时调用(无论是启动还是重新启动一个活动,该方法总是被调用的)。当Activity**可以得到用户焦点时,此时Activity获得输入焦点。这个方法内部仍然比较适合获取运行所需资源,尤其适用于启动音频、视频和动画**。

  • onPause():activity被暂停或收回cpu和其他资源时调用,activity未被完全遮盖时,该方法可用于保存活动状态的。当Activity 部分被遮盖时,此时的Activity不能获得输入焦点,但是部分可见。一般来说,在onResume方法中获取的资源,比如手动管理的Cursor对象,均应该在onPause里释放,否则如果线程被终止,就有可能造成有些资源是放不完全。

  • onStop():activity被停止并转为不可见阶段及后续的生命周期事件时,当activity被完全摭盖时被调用,即失去焦点时调用。

  • onDestroy():activity被完全从系统内存中移除时调用,该方法被调用可能是因为有人直接调用 finish()方法 或者系统决定停止该活动以释放资源,在activity销毁之前被调用。这是activity能收到的最后一个调用。调用的原因可能是别人在这个activity上调用了finish(),也可能是系统为了更多的内存空间而把它所在的进程处死了。在这个方法中,可以调用isFinishing()来判断自己属于哪一种死法。

二、Activity的四种启动模式

通过Acitivty的xml标签来改变任务栈的默认行为,使用android:launchMode=”standard|singleInstance|singleTask|singleTop”来控制Acivity任务栈。(任务栈是一种后进先出的结构。位于栈顶的Activity处于焦点状态,当按下back按钮的时候,栈内的Activity会一个一个的出栈,并且调用其onDestory()方法。如果栈内没有Activity,那么系统就会回收这个栈,每个APP默认只有一个栈,以APP的包名来命名.)Activity的堆栈管理以ActivityRecord为单位,所有的ActivityRecord都放在一个List里面.可以认为一个ActivityRecord就是一个Activity栈,除了可以通过清单配置外,还可以使用Intent Flagss的东西设置更多的行为

  • standard(默认)—— 标准模式,每次启动Activity都会创建一个新的Activity实例,并且将其压入任务栈栈顶且和发送intent的Activity处于同一个任务栈中,而不管这个Activity是否已经存在。Activity的启动三回调(onCreate()->onStart()->onResume())都会执行,系统中的自带发送邮件的Activity或者发布社交网络状态的Activity等就是采用这种模式的,简而言之,如果你希望Activity单独服务于一个Intent,就像邮件发送Activity一样发送给,就可以考虑standard启动模式。

  • singleTop——栈顶复用模式,如果新Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,所以它的启动三回调就不会执行,同时Activity的onNewIntent()方法会被回调来重用,否则创建新的实例并放入栈顶(即使栈中已经存在该实例,只要不在栈顶都会创建新的实例),换言之,它的表现几乎和standard模式一模一样,一个singleTop Activity 的实例可以无限多,唯一的区别是如果在栈顶已经有一个相同类型的Activity实例,Intent不会再创建一个Activity,而是通过onNewIntent()被发送到现有的Activity,所以在singleTop模式下我们需要同时在onCreate() 和 onNewIntent()中处理发来的intent,以满足不同情况。

  • singleTask——栈内复用模式,创建这样的Activity的时候,系统会先确认它所需任务栈已经创建,否则先创建任务(系统会创建一个新的任务(task)并将目标activity作为这个任务的根元素).然后放入Activity,如果栈中已经有一个Activity实例,那么这个Activity就会被调到栈顶,onNewIntent(),并且singleTask会清理在当前Activity上面的所有Activity,。如果任务栈中正好存在Activity该实例就重用(会调用实例的onNewIntent())重用时会让该实例重回栈顶,因此在他上面的实例就会被移出栈,如果栈中不存在该实例,则创建新的实例并放入栈中,举个例子假设有两个Activity 分别是A(standard)和 B (singleTask),当A启动B 时,通过adb shell dumpsys activity activities可以发现 A 、B的Task Id是相同的,说明A和B运行在同一个Task里面;接着B启动A, 此时由于A是standard模式的,现在这个栈里从栈底到栈顶的顺序依次是 A—B—A,然后通过A再次启动B,此时候B的lanchMode就起作用了,由于栈里已经存在的B的实例,那么B上面的所有的Activity都被清理,最终结果是 A—B。再举个例子推送烦人的广告,接收到推送以后,单击通知栏中推送项目后的跳转问题。当需求是当浏览完广告页时,按back键要求返回程序栈,当然如果程序此时未启动,则需要启动程序。同样,如果APP正在前台运行则不做任何处理,再比如一个栈中有四个活动,分别是A,B,C,B。其中活动B可以接收用户进行参数设置。然后一个用户在栈顶的B中对程序进行了设置,然后他按back返回了C,再back返回了B。他发现此时B中显示的状态并非他刚才设置好的状态,没错,此时我们需要将活动B做成一个”singleTask”,使用户对B的操作能够及时”同步”

  • singleInstance——加强版的singleTask模式,这种模式的Activity只能单独位于一个任务栈内,由于栈内复用的特性,后续请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了,并让多个应用共享该Activity的实例,一旦该模式的Activity实例已存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例,其效果相当于多个应用共享一个应用,不管谁激活该Activity都会进入同一个应用中。在这个模式下的Activity实例所处的task中,只能有这个activity实例,不能有其他的实例。举个例子假设有两个Activity 分别是A(standard)和 B (singleInstance),当A启动B 时,通过adb shell dumpsys activity activities可以发现 A 、B的Task Id是不同的,说明A和B运行在两个Task里面,而singleInstance所在的Task,只能有它这一个Acitivity。

三、Activity 的跳转生命周期不受android:configChanges 和 android:lunchMode 影响

例:MainActivity 跳转至 ToActivity,再从ToActivity跳转至MainActivity的完整生命周期,首先看看标准模式下

1、配置了android:configChanges=”orientation|screenSize”

首先新建MainActivity,

onCreate()——>onStart()——>onResume()——>

这里写图片描述
跳转至 ToActivity(跳转可以分为两个步骤,第一个步骤先隐藏MainActivity,再创建ToActivity),

MainActivity的onPause()——>ToActivity的onCreate()——>
ToActivity的onRestart()——>ToActivity的onResume()——>
MainActivity的onSaveInstanceState()——>MainActivity的onStop()——>

这里写图片描述
从ToActivity跳转回MainActivity,

ToActivity的onPause()——>MainActivity的onCreate()——>
MainActivity的onRestart()——>MainActivity的onResume()——>
ToActivity的onSaveInstanceState()——>ToActivity的onStop()——>

这里写图片描述
从ToActivity按返回键返回MainActivity(与跳转不同的是返回是会把ToActivity finish掉),

ToActivity的onPause()——>MainActivity的onCreate()——>
MainActivity的onRestart()——>MainActivity的onResume()——>
ToActivity的onSaveInstanceState()——>ToActivity的onStop()——>
ToActivity的onDestory()

这里写图片描述
在MainActivity锁屏时(按Home键也是一样),

onPause()——>onSaveInstanceState()——>onStop()

这里写图片描述
解锁返回到MainActivity

onRestart()——>onStart()——>onResume()

这里写图片描述
经验证Activity跳转时的生命周期变化,与android:configChangesandroid:lunchMode的取值无关(由于篇幅问题截图不一一贴出)。

2、在Activity 里未配置android:configChanges=”orientation|screenSize”

第一次新建MainActivity
这里写图片描述
跳转至 ToActivity
这里写图片描述
从ToActivity跳转回MainActivity
这里写图片描述
从ToActivity按返回键返回MainActivity
这里写图片描述
在MainActivity锁屏时(按Home键也是一样)
这里写图片描述
解锁返回到MainActivity
这里写图片描述

四、横竖屏切换与Activity的生命周期

onConfigurationChanged事件是在改变屏幕方向、弹出软件盘和隐藏软键盘时,不再去重新触发Activity的onCreate()方法,而是直接执行onConfigurationChanged()方法;有些情况下,当横、竖屏转换时,程序会报错或异常终止,往往是由于重新触发了Activity的生命周期方法。进而造成重复的初始化工作,影响程序效率等等。所以我们合理地在代码中捕获相关事件并进行逻辑处理提高我们的程序的健壮性和高效性。

@Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
            Toast.makeText(MainActivity.this, "现在是竖屏", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "竖屏"+"onConfigurationChanged by MainActivity");
        }
        if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
            Toast.makeText(MainActivity.this, "现在是横屏", Toast.LENGTH_SHORT).show();
            Log.d(TAG, "横屏"+"onConfigurationChanged by MainActivity");
        }
    }

1、配置了Activity节点下的android:configChanges=”orientation|screenSize”

在MainActivity 由竖屏切换为横屏时
这里写图片描述
在MainActivity 由横屏切换为竖屏时
这里写图片描述
在低版本中想捕获横竖屏切换的事件,得先申请权限( Android:name=”android.permission.CHANGE_CONFIGURATION”)
在高版本里直接在清单文件里配置orientation即可,需要注意的是从Android 3.2(API 13)起,ScreenSize也随着设备的横竖切换而改变。
所以,在AndroidManifest.xml里设置的MiniSdkVersion和 TargetSdkVersion属性大于等于13的情况下,如果你想阻止程序在运行时重新加载Activity,除了设置”orientation”,你还必须设置”ScreenSize”,否则还是无法在onConfigurationChanged里捕捉到相应事件。

2、未配置Activity节点下的android:configChanges属性

在MainActivity 由竖屏切换为横屏时

onPause()——>onSaveInstanceState()——>onStop()——>
onDestory()——>onCreate()——>onStart()——>
onRestoreInstanceState()——>onResume()

这里写图片描述
在MainActivity 由横屏切换为竖屏时

onPause()——>onSaveInstanceState()——>onStop()——>
onDestory()——>onCreate()——>onStart()——>
onRestoreInstanceState()——>onResume()

这里写图片描述

3、小结

  1. 不设置Activity的android:configChanges和只设置android:configChanges=”orientation”时,切屏会重新调用各个生命周期。
  2. 设置Activityandroid:configChanges=”orientation|screenSize”的,切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法。
  3. 切屏时Activity的生命周期不受lunchMode影响。
  4. 当前Activity产生事件弹出Toast、AlertDialog和PopupWindow的时候不会触发相关生命周期方法。
  5. activity在有“被销毁的可能”时,就会调用onSaveInstanceState方法,给你一个机会去保存activity中的数据,之后横竖屏切换时activity确实在“未经你许可的情况下被销毁了”,所以系统又会调用onRestoreInstanceState方法,让你把之前保存的数据恢复

五、横竖屏切换及其对应布局加载

1、横竖屏动态切换连带横竖屏布局

假如软件在横竖屏之间切换,由于横竖屏的高宽会发生变化,有可能会要求不同的布局。可通过以下两种方法来切换布局:

1.1、在res目录下建立对应的资源文件

在res目录下建立layout-land和layout-port目录,相应的layout文件名不变,比如:layout-land是横屏的layout,layout-port是竖屏的layout,其他的不用管,横竖屏切换时程序调用Activity的onCreate方法中的setOnContent(xxx),系统会自动加载相应的布局。

1.2、通过java代码来判断当前是横屏还是竖屏然后来加载相应的xml布局文件

因为当屏幕变为横屏的时候,系统会重新加载当前Activity的onCreate方法(如果不设置Activity的android:configChanges属性的话),可以把代码判断屏幕方向,再设置调入不同的layout。

/** 1:竖屏   2:横屏 判断屏幕以旋转的方向 */
private int orientation;
orientation=getResources().getConfiguration().orientation;

2、强制设定屏幕的横、竖屏方向

2.1、在清单文件里设置Activity 的android:screenOrientation
//横屏显示设置
android:screenOrientation="lanscape"
//竖屏显示设置
android:screenOrientation="portrait"
2.2 代码调用setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)

在没有配置android:configChanges时,android屏幕的切换会重启Activity,所以在Activity销毁前保存当前活动的状态,并在Activity再次Create的时候载入配置

3、自适应切换屏幕

1、首先,在清单文件对应的Activity下配置
android:screenOrientation="sensor" android:configChanges="orientation|screenSize|keyboardHidden"
2、接着,取得屏幕的长和宽,比较设置横竖屏的变量
Display display = getWindowManager().getDefaultDisplay();
int width = display.getWidth();
int height = display.getHeight();
if (width > height) {
    orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; // 横屏
} else {
    orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; // 竖屏
}
3、最后,在onConfigurationChanged()函数中追加this.setRequestedOrientation(orientation)
@Override
public void onConfigurationChanged(Configuration newConfig) {  
     super.onConfigurationChanged(newConfig);  
     this.setRequestedOrientation(orientation);  
 }

但是这样的话你切到别的画面的时再返回到原画面,它仍然是横的或竖的。怎么让它从别的屏幕回来后,又重新横竖屏布局呢?
只要在onResume()中在设定下就行了(但是这个仅仅只是支持横竖屏只有一个layout的)

@Override
protected void onResume() {
    orientation = ActivityInfo.SCREEN_ORIENTATION_USER;
    this.setRequestedOrientation(orientation);
    Display display = getWindowManager().getDefaultDisplay();
    int width = display.getWidth();
    int height = display.getHeight();
    if (width > height) {
        orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
    } else {
        orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
    }
    super.onResume();
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值