Android系统编程入门系列之加载界面Activity

2021年7月3日 5点热度 0条评论

上回说到应用初始化加载及其生命周期,在Android系统调用Applicaiton.onCreate()之后,继续创建并加载清单文件中注册的首个界面即主Activity,也可称之为入口界面。主Activity的确定规则在Android系统编程入门系列之清单文件有介绍,本文主要介绍Android系统创建Activity之后的生命周期流程。
在清单文件中所注册的界面均为自定义Activity,其父类往上追溯,必须继承自android.content.Activity

生命周期

Activity作为四大组件之首,主要负责与系统使用者的可视化交互响应。只有深刻掌握Activity的生命周期及相关概念,才能在开发设计时游刃有余。注意,这里的生命周期介绍,与官方生命周期定义有所区别,本文中的范围更加宽泛。

Android组件的生命周期,均是由Android系统主线程调用,如果在调用的生命周期方法内出现耗时操作,将会导致后续的生命周期方法无法被及时调用,反应到交互界面上就是应用程序卡顿甚至操作无响应。为了防止这种情况的发生,Android系统定义当应用程序超时无响应时间超过一定时长(界面Activity默认5秒),会触发应用无响应错误ANR,同时界面弹出提示对话框,以供用户选择退出停止响应或继续等待。所以在生命周期方法内不允许耗时操作。

(调用构造方法)启动实例化

界面Activity的启动需要通过android.content.Intent意图来操作,意图的创建分显示意图和隐示意图两种类型。顾名思义,显示意图要指定Activity的具体包名和类名,而隐示意图只需要指定Activity在清单文件中注册时所嵌入的actioncategory标签信息。创建的意图作为参数才能启动界面Activity。显示意图常用于当前应用内的界面Activity之间启动,而隐示意图多用于不同应用间的界面启动。

回想下,在Android系统编程入门系列之清单文件文章中说到,主Activity的注册时,必须在其标签内部嵌入<intent-filter></intent-filter>标签,并在该标签内固定且唯一<action><category>标签的内容。这种写法的原因,就在于Android系统是创建的隐示意图启动应用内的主Activity。举一反三,留个问题思考下,如果一个应用的清单文件中有多个上述主Activity的固定<intent-filter></intent-filter>标签内容,那Android系统在启动这个应用后会怎么调用这些Activity呢?(答案将在相应视频教程中揭晓)

在Android系统通过清单文件找到意图指定的界面Activity之后,会实例化该界面Activity对象,并将其放入当前应用程序的指定任务栈中。任务栈,正如其名,是采用先进后出的模式管理当前应用程序启动的所有界面Activity对象的数据结构。

当界面Activity启动时放入任务栈中,退出界面Activity时则从任务栈中取出其对象并销毁。然而如果一个应用程序只对应一个任务栈,在有些频繁启动退出单个界面的场景中,就会频繁创建销毁该界面实例,浪费了很多cpu耗时。为了优化界面的实例化过程,Android系统允许一个应用程序使用多个任务栈。这也就引出了两个问题,一是如何从多个任务栈中指定具体一个作为界面启动管理的任务栈?二是界面在启动时放入不同任务栈的规则是什么?

对于第一个问题,在清单文件中静态注册界面Activity时,可以在<activity></activity>标签中对属性名taskAffinity赋值,该属性值默认为当前应用包名,从而指定当前界面Activity所属的任务栈。而<application></application>标签中也可以使用该属性,表示当前应用程序内的所有界面启动时都使用该属性值对应的任务栈管理。同时在界面Activity中,可以使用getTaskId()来获取当前界面Activity所属任务栈对应的id值。

对于第二个问题,可以根据界面Activity启动模式来确定放入任务栈的规则。启动模式可以在清单文件中静态注册<activity></activity>标签时,指定其属性launchMode赋值;也可以在创建意图Intent操作之后,通过调用其setFlags(int flags)方法动态设置。如果动态设置方式会优先覆盖静态设置方式,如果不设置启动模式,则使用默认模式启动。启动模式主要有四种,其具体方式和对应的属性值可参考下表。

启动模式 静态设置 动态设置 功能含义
标准模式(默认模式) standard - 启动时会在内存空间中实例化对象,并将该对象放入指定任务栈中
栈顶单例模式 singleTop Intent.FLAG_ACTIVITY_SINGLE_TOP 启动时先检查指定任务栈顶部界面对象,
如果与该界面信息一致,则使用任务栈顶层的对象;
否则实例化对象并放入指定任务栈顶部
栈内单例模式 singleTask Intent.FLAG_ACTIVITY_CLEAR_TOP 启动时检查指定任务栈内的所有界面对象,
如果存在与该界面信息一致的对象,则将其顶部界面对象移出任务栈并销毁,最终保留一致对象在任务栈顶部;
否则实例化对象并放入指定任务栈顶部
全局单例模式 singleInstance Intent.FLAG_ACTIVITY_NEW_TASK 启动时检查当前应用程序使用的所有任务栈内的所有界面对象,
如果存在与该界面信息一致的对象,则将其所在任务栈顶部的界面对象移出任务栈并销毁,最终保留一致对象在任务栈顶部;
否则实例化对象并放入指定任务栈顶部

在界面`Activity`启动实例化阶段,只是创建实例,并未加载Android系统相关环境,故该阶段一般不需要重写构造方法。

(调用attachBaseContext(Context base))加载运行环境

Application一样,Activity也继承自android.content.ContentWrapper,使用时绑定上下文环境,尤为注意的是,在AndroidSDK定义的所有android.content.ContentWrapper子类中,只有android.content.Activity类中重写了attachBaseContext(Context base)方法。故界面Activity实例化之后,系统会最终调用android.content.Activity.attachBaseContext(Context base)方法,在该方法中以完成将当前界面与上下文环境绑定的任务。

同样的,在界面Activity加载运行环境阶段,也不推荐重写该方法,界面内所有的操作都应在该方法之后完成。

(调用onCreate())界面创建

在界面加载运行环境之后,Android系统对界面的创建工作就完成了。之后系统回调onCreate()方法,官方称之为已创建状态。此时当前Activity还处在后台未展示给用户,因此可以重写该方法,以完成界面内相关变量的初始化操作。

(调用onStart())界面展示

在界面创建之后,或界面重新展示之后,系统会回调onStart()方法,将当前Activity绘制给用户可见,官方称之为已开始状态。如果重写该方法,可以完成展示给用户的界面初始化操作。

(调用onResume())界面运行

当界面展示之后,系统会回调onResume()方法,使得界面可以与用户进行交互,官方称之为已恢复状态。此时系统开始响应该界面内的用户交互,这也表明当前界面Activity已经处于运行了。

系统调用该生命周期方法后会一直处于界面运行中,直到用户的一些操作改变该界面的状态。这些用户操作包括但不限于:

  1. 在该界面Activity启动另一个界面Activity
  2. 点击back返回按键,退出该界面Activity,返回上一个界面Activity
  3. 点击home主页按键,该应用Application切换到后台。
  4. 点击menu菜单按键,该应用的当前界面Activity在最近任务栏展示。
  5. 点击分屏按键,该应用Application作为多窗口模式之一显示。
  6. 点击息屏按键,该应用Application在后台休眠。
  7. 点击电源按键,强制关机,该应用Application被杀死。

界面暂停(调用onPause()

当界面运行状态被改变后,系统都会首先调用onPause()方法,此时系统已停止当前界面Activity与用户的交互响应操作,官方称之为已暂停状态。可以重写该方法,在其中做释放一些不需要但却很耗电的系统资源,以防止无效持有消耗电量。但是此时该界面仍然对用户可见,所以不建议在该方法内执行耗时的释放操作,以免给用户带来卡顿的假象。

在该状态之后,Android系统根据之前的用户操作类型判断后续生命周期流程。
操作1、2、3、6、7可能会继续令当前界面Activity停止展示
而操作4、5将可能在用户重新点击该界面返回,此时将重新回到界面运行的生命周期中。

界面停止展示(调用onStop()

当系统令当前界面Activiy停止展示时,会调用onStop()方法,将当前界面对用户不可见,官方称之为已停止状态。重写该方法时,可以释放界面资源和不需要的CPU耗时资源,以供其他地方及时获得。

在该状态之后,Android系统继续根据之前的用户操作类型判断后续的生命周期流程。
操作1会将新启动的界面Activity放入当前Activity所在任务栈中,等待新启动的界面返回时,令当前界面重新展示
操作2则是将当前界面从任务栈中取出并销毁
操作3则可能在手机内存不足时,将当前应用的所有界面包括当前界面从任务栈中取出并销毁
操作6会将当前应用休眠,等设备重新亮屏后,令当前界面重新展示;也可能有的设备在息屏休眠后保持低消耗电源模式时,当前应用超过一定时间或内存占用的休眠期后,被系统杀死,即当前应用的所有界面包括当前界面从任务栈汇总取出并销毁
操作7则会将当前应用的所有界面全部销毁,不会再调用当前界面的任何生命周期。

界面重新展示(调用onRestart()

当界面由用户不可见转为可见时,如果之前已经界面创建了,则会调用onRestart()方法,以表明当前界面将会重新展示。重写该方法可以处理对用户重新可见后的操作。

该方法调用之后,系统将会继续调用界面展示运行,以重新运行界面与用户的交互操作。

界面销毁(调用onDestroy()

当界面Activity被移除任务栈后,如果应用程序还处在Android系统的控制下,系统将在调用onDestroy()方法之后销毁界面,官方称之为已销毁状态。重写此方法可以释放所有不需要的资源,以防止发生内存泄漏OOM的问题。

上述内容是针对单个界面Activity的加载流程,那么其中涉及到的两个界面Activity的相互交流方式是怎样的,以及界面运行之后如何处理与用户的交互操作?详情请关注后续文章。