当前位置: 首页 > news >正文

【Android笔记】Activity不同状态间转换研究

在AndroidSDK发布的官方文档里,实际上已经对Activity所包含的各个重要状态的概念和相互之间转换机制作了较为详细的描述,但梨子的滋味总是自己尝了才能体会的深刻,笔者就在开发一些应用过程中出现过奇怪的错误,后来经过确定发现是由于没有对Activity状态转换的一些细节理解到位,从而导致应用本身的一些运行状态被打乱而出现各种错误。因此笔者找个时间特地基于AndroidSDK的Activity描述(AndroidSDK路径/docs/reference/android/app/Activity.html)做了一些实验,目的是搞清楚Activity各个重要状态的转换机制。

在做实验前,先依照官方文档的介绍理清一些重要状态的概念,笔者会依照自己开发的经验和理解加上一些解释,如果大家觉得有疑问和待商榷的地方也可以提出来探讨。

Android针对Activity的管理使用的是栈,就是说某一个时刻只有一个Activity处在栈顶,当这个Activity被销毁后,下面的Activity才有可能浮到栈顶,或者有一个新的Activity被创建出来,则旧的Activity就被压栈沉下去了。从这里我们可以看出Android似乎是依照一种层次管理所有的Activity的,为什么这么做,个人觉得原因在于Android对Activity的作用定位很重要的一点是考虑其管理与用户交互的作用,而谈到交互,首要的就是界面了,因此Activity是直接涉及到与用户交互的界面处理的,而任意时刻与用户处于交互状态的界面只能有一个,所以Android针对Activity的管理采用了具有层次感的栈的数据结构,理解这一点对于Activity一些状态转换的细节处理非常重要。

依据这种基于界面层次性的理解,官方文档主要提了两点:

1. 只有处于栈顶的Activity一定是出于运行状态的。当启动一个新的Activity时,系统会将它置于栈顶,同时使其运行,这个时候先前处于栈顶的Activity则被压栈,只有当栈顶的Activity退出时才可能重新处于栈顶。

2.一个Activity总的来说拥有四种状态,分别是

1)激活(运行)状态: 此时它一定是在屏幕的最前端的,对应Activity栈来说,它是在栈顶的;

2)暂停状态:此时它在屏幕上仍然是可见的,但是失去了焦点。这种情况会发生在当有一个不会占满整个屏幕或者拥有透明属性的Activity启动并获得了屏幕焦点时。一个暂停状态的Activity还是存活的(alive),它仍然维持着自己的各个内部状态和成员信息,仍然和window manager保持连接,但是系统可能会在手机内存极低的情况下杀掉该Activity;

3)终止状态:此时这个Activity在屏幕上完全不可见,它已经被其他的Activity挡住了。这时它维持着自己的各个内部状态和成员信息,但是由于用户已完全看不见它,其window也会被隐藏掉,也就是说window manager不再管理其window信息了。停止状态下的Activity会经常由于手机的内存征用问题被系统杀掉。

4)当Activity被暂停或者终止时,系统可以把它从内存中清除,这个过程也许会提示用户是否要结束该Activity,也许只会简单地杀掉其进程。当这个Activity重新被用户调出来显示在界面上时,用户自己必须保证Activity能够完全恢复先前维护的内部状态信息,以使其回到Activity内部所处先前的状态,以笔者的经验这种情况如果处理不好很容易出现问题。

这第二点实际上依据界面层次考虑就是3种情况:

a.active/running: 此时Activity管理的界面可见,且直接面对和用户的交互;

b.pause: 此时Activity管理的界面可见,但处于后端,类似变成了背景;

c.stop: 此时Activity管理的界面完全不可见。
澄清了上述重要的概念之后,再来看需要研究的Activity具体的函数,很自然可以知道最重要的函数一定就是针对上面三种情况加上Activity自身创建和消毁所涉及到的几个函数了:

public class Activity extends ApplicationContext {
protected void onCreate(Bundle savedInstanceState);

protected void onStart();
protected void onRestart();
protected void onResume();
protected void onPause();
protected void onStop();
protected void onDestroy();
}

笔者对这几个函数做了测试实验,以此研究不同状态间的转换,和这几个重要函数的调用顺序,实验非常简单,两个Activity之间的跳转:Activity1的界面上只有一个按钮,点击后转到到Activity2上,Activity2上同样一个按钮,点击后跳到Activity1上。我将上述的函数均打上log,便于观察,同时为了过滤其他的log,使用了Error级别,纯粹是为了方便显示。

大致代码如下:

----------------------------------------------------------------------------------------------

public class ALStudyActivity extends Activity {
private String LOG_TAG = "ALStudyActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
Log.e(LOG_TAG,"onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button01 = (Button)findViewById(R.id.Button01);
button01.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v){
Intent it = new Intent(ALStudyActivity.this,NewActivity.class);
startActivity(it);
}
});
}
@Override
protected void onDestroy() {

Log.e(LOG_TAG,"onDestroy");
super.onDestroy();
}

.... //其他几个测试函数,不再列出

}

public class NewActivity extends Activity {
private String LOG_TAG = "NewActivity";
@Override
public void onCreate(Bundle savedInstanceState) {
Log.e(LOG_TAG,"onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.test);
Button button02 = (Button)findViewById(R.id.Button02);
button02.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v){
Intent it = new Intent(NewActivity.this,ALStudyActivity.class);
startActivity(it);
}
});
}

... //省略,同上

}

----------------------------------------------------------------------------------------------

这里ALStudyActivity对应Activity1, NewActivity对应Activity2

实验1. Activity1->按手机(模拟器)Back键退出

输出结果:

test1

实验1 显示了Activity在正常情 况下从正常到退出的全部情形。

onDestory只有在Activity真正退出时才会被调用。

实验2. Activity1->手机Home键回主界面

输出结果:

test2

实验2 显示了一在按手机Home键退出时的函数调用时序。由

于不是退出,因此最终没有调用onDestroy(),但是应用变得不可见,

因此调用了onStop。可以看到系统还调用了保存实例状态的函数:

onSaveInstanceState(),也就是说按Home键退出到主界面系统

会帮助我们保持Activity的一些信息,同时给了我们保存额外需要维

护的信息的机会,这一点请注意,因为系统不是在任何时候都会调用onSaveInstanceState()的,下面的实验将可以看到。

实验3.Activity1->Activity2

输出结果:

test3

从实验3可以看到系统在Activity1的onPause()之后先将Activity2创

建并显示出来,然后在执行的Activity1后续的操作,onPause()之后Acti

vity1就不再是最前端显示了。

实验4.Activity1->Activity2->Activity1

输出结果: test4

实验4要注意的是,由于我们使用的是

Intent it = new Intent(...)
startActivity(it);

的方式,实际上Activity2->Activity1执行的是创建了一个新的

Activity1实例,这和一开始的Activity1不是同一个,也就是说,

这里Activity栈里已经有了3个Activity实例,这一点很容易被弄

错,可能界面上是好像回到了Activity1,但实际上完全不同。

实验5.Activity1->Activity2->Activity1->Back键

输出结果:

test5

实验5承接了实验4,只是多按了Back键,这时候系统销毁了

一个Activity1实例,栈里还有两个,栈顶的是Activity2,也就是

说界面上看见的是Activity2.

实验6.Activity1->Activity2->Home键->Activity1

输出结果: test7

实验6的特别地方在于,在按了Home键回到主界面后,Activity2也

被转到后台,程序走到了onStop(),然后我们重新启动Activity1应

用,可以看到执行onRestart()的是Activity2,而不是Activity1!!

这是因为此时Activity2处在栈顶,这一点也是很容易出错的地方。

实验7.Activity1->屏幕旋转90度

输出结果:

test8

实验7单独列出来也是因为代表了一类Activity被打断的情况,就是系统配置发

生了变化,官方文档里也对为什么一定要采用这种先销毁再重新创建的方式做了解

释,可以参考Configuration Changes一段,我就不再多介绍了,这里只是实

际跑了个实验验证。

实验就做这么多,其实还可以做更多一些情况的测试,但基本的转换情况这里都已提到。虽然运行,暂停,退出的基本概念大家比较清楚,但是从上面的实验也可以看到,一些细节仍然需要在设计时考虑清楚,因为毕竟我们无法估计用户的操作序列和系统本身的各种情况,如果要想使我们的应用够健壮,必须充分考虑到Activity不同状态间转换的问题。

此外,还有一些可以深入探讨的问题:

1. 关于finish()的使用,上面的实验没有用到它,但我们看到前面的实验只有在按Back键的时候系统会销毁当前的Activity,如果处理不当会产生大量Activity新的实例,而显然很多不是我们需要的,必须以合适的方式在恰当的时机消除,这时就需要finish(),函数使用虽然简单,但是要用在什么时机和地方是需要考虑的。

2. 关于onSaveInstanceState()的使用,我们前面的实验也说明了,不是任何时候系统都会调用onSaveInstanceState()的,原因官方文档也提了一些,请仔细阅读,官方举的一个例子是当一个Activity不再处于最前端时,如果系统由于内存原因需要关掉这个Activity,此时系统会调用onSaveInstanceState(),这样我们才有机会做些保存信息以便将来重启的工作。

3.关于onPause()函数,由于Activity从最前端显示到其他状态的转换会很频繁,这个函数也会很频繁的被调到,而它是一个同步函数,这个操作不完成,后面的操作无法进行,因此请不要在其中做太多操作,造成性能问题。

4.本文涉及的实验关于Activity的配置都是使用的standard属性,没有涉及"singleTop" /"singleTask" / "singleInstance",这些会影响实验结果,但是基本的状态转换通过标准已经体现出来,是其他的基础,搞清楚了这些再来研究多个task情况下可能涉及到的Activity跳转就容易理解了。

关于Activity的状态转换还有很多可以研究的地方,以后有机会再贴出来。

相关文章:

  • UVa 11988 悲剧文本(四种方法)
  • 旁观者看eBay技术发展
  • UVa12657 Boxes in a Line (数组模拟双向链表)
  • 网站架构相关PPT、文章整理(更新于2009-7-15)
  • UVa679 Dropping Balls (满二叉树+开关灯思想)
  • UVa 548 Tree(建树+DFS)
  • Android开发指南-框架主题-安全和许可
  • UVa 699 The Falling Leaves(建树+求竖直权值和)
  • Widget带来了真正的移动互联网
  • 2019 ICPC 徐州区域赛 - C <3 numbers(素数密度)
  • 2019 ICPC 徐州区域赛 - A Cat(异或性质)
  • 2019 ICPC 南昌区域赛 - C And and Pair(思维+组合数学)
  • Android开发指南-框架主题-清单文件
  • 2019 ICPC 南昌区域赛 - G Eating Plan(技巧+暴力)
  • 离职的日子
  • 【RocksDB】TransactionDB源码分析
  • 【译】理解JavaScript:new 关键字
  • css系列之关于字体的事
  • docker容器内的网络抓包
  • Java多线程(4):使用线程池执行定时任务
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • niucms就是以城市为分割单位,在上面 小区/乡村/同城论坛+58+团购
  • Odoo domain写法及运用
  • Selenium实战教程系列(二)---元素定位
  • Shell编程
  • 如何设计一个微型分布式架构?
  • 使用docker-compose进行多节点部署
  • 使用权重正则化较少模型过拟合
  • 我看到的前端
  • 消息队列系列二(IOT中消息队列的应用)
  • 新年再起“裁员潮”,“钢铁侠”马斯克要一举裁掉SpaceX 600余名员工 ...
  • ​ArcGIS Pro 如何批量删除字段
  • (2)关于RabbitMq 的 Topic Exchange 主题交换机
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (android 地图实战开发)3 在地图上显示当前位置和自定义银行位置
  • (poj1.2.1)1970(筛选法模拟)
  • (安全基本功)磁盘MBR,分区表,活动分区,引导扇区。。。详解与区别
  • (带教程)商业版SEO关键词按天计费系统:关键词排名优化、代理服务、手机自适应及搭建教程
  • (二十五)admin-boot项目之集成消息队列Rabbitmq
  • (三)mysql_MYSQL(三)
  • (十三)Maven插件解析运行机制
  • (一)Java算法:二分查找
  • (一)Thymeleaf用法——Thymeleaf简介
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • *1 计算机基础和操作系统基础及几大协议
  • .【机器学习】隐马尔可夫模型(Hidden Markov Model,HMM)
  • .Net - 类的介绍
  • .NET 实现 NTFS 文件系统的硬链接 mklink /J(Junction)
  • .net 使用$.ajax实现从前台调用后台方法(包含静态方法和非静态方法调用)
  • .NET/C# 使用 #if 和 Conditional 特性来按条件编译代码的不同原理和适用场景
  • @Autowired自动装配
  • @DataRedisTest测试redis从未如此丝滑
  • @ModelAttribute使用详解
  • [ JavaScript ] JSON方法
  • [ Linux ] Linux信号概述 信号的产生