学习资料:
- Android群英传
- Android开发艺术探索
Activity是与用户交互的第一接口,感觉说是四大组件之首也不为过。基本上除了Window,Dialog,Toast外,屏幕能见到的界面也就是Activity。在onCreate()方法中,通过setContentView()给一个Activity指定一个显示的界面,并以此做为基础提供用于交互的接口。系统是利用Activity栈来管理Activity
本篇为上,主要学习Activity的生命周期,启动模式,任务栈,下学习Activity工作过程
1. Activity的形态 <p>
Activity的一大特点就是具有多种形态,一般就是在在4种形态间进行切换,以此来进行控制生命周期
Active/Running
Activity此时位于Activity的栈顶,能够与用户交互,是可见的Paused
Activity_1失去焦点,当一个新的非全屏的Acticty_2或者透明的Activity_3放置在栈顶,并不能完全遮盖住Activity_1,此时为Paused。这时Activity_1失去了与用户交互的能力,但所有的状态信息、成员变量都还保持着,只有在系统内存极低的情况下,才会被系统回收掉Stoped
Activity_1被Activity_2完全覆盖或者用户点击HOME健切换到了桌面,Activity_1便进入Stoped形态。此时,虽然Acticity_1不可见,但却依然保持了所有的状态信息和成员变量Killed
Activity被系统回收l,这时就处于Killed形态
用户的不同动作,会让Actiivty在这四种形态间切换。而开发者,虽然可以控制Activity如何“生”,却无法控制Activity何时“死”
2. 生命周期 <p>
正常情况下,一般认为Activity有7个生命周期方法。Activity正常时,生命周期比较容易理解,可一旦出现异常,生命周期就会开始微妙起来,尤其如果此时还搭配Fragment,那就。。。
2.1 正常情况下的生命周期 <p>
由于同时看的两本书进行的学习,《Android开发艺术探索》和《Android群英传》两本书对知识的描述方式还是有些不同的。学习过程中,就根据书的描述方式把知识点也梳理成两个部分,开发艺术探索知识梳理和群英传知识梳理,不过涉及的知识点的本质是一样的,两种描述方式都学习也可以加深些印象 :)
2.1.1开发艺术探索知识梳理 <p>
《Android开发艺术探索》是从经典的生命周期图出发

onCreate()
生命周期的第一个方法,Activity正在创建。在这个方法中一般会做一些初始化工作onStart()
标识Activity正在启动,此时Activity还没有出现在前台,无法和用户进行交互。可以理解为,Activity虽然已经显示了,但没有获得焦点,我们看不到onResume()
到了这个方法,Activity已经可见了,并且出现在前台已经开始活动。可以理解为此时Activity获得焦点onPause()
表示Activity_1正在停止,正常情况下,紧接着onStop()就会被调用。此时可以做一些存储数据,关闭动画等工作。但不能太耗时。由Activity_1跳转Activity_2时,只有Activity_1的onPause()方法执行完毕,Activity_2的onResume()方法才会执行。如果太耗时,会影响Activity_2的显示onStop()
表示Activity即将停止。同样可以做一些不耗时的稍微轻量级回收工作onRestart()
表示Activity_1正在重启。一般情况下,Activity_1由不可见重新变为可见时,onRestart()便会被调用onDestroy()
表示Activity即将被销毁。Activity生命周期的最后一个回调方法,可以做一些回收工作和最终的资源释放
注意
当由
Activity_1跳转Activity_2时,如果Activity_2是透明的,Activity_1只会走到onPause()方法,不会回调onStop()方法。弹出Dialog或者PopupWindow也是,只要Activity_1没有被完全遮盖看不到,就不会回调执行到onStop()生命周期方法从整个生命周期来说,
onCreate()和onDestroy()是配对的;从Activity是否可见来说,onStart()和onStop()是配对的;从Activity是否在前台获得焦点来说,onResume()和onPause()是配对的onDestroy()方法回调了,并不意味着Activity已经被完全销毁被系统回收了。只是即将被销毁,还是会在内存中存留一会,而这个即将到底需要多久,可能并不是每次都一样,这也是为啥说开发者,虽然可以控制Activity如何“生”,却无法控制Activity何时“死”的原因
问题
onStart()和onResume(),onPause()和onStop()有啥实质不同?
主要是从不同的回调时机角度来看。onStart()和onStop()是从是否可见的角度回调,而onResume()和onPause()从是否在前台获得焦点的角度回调的。除了这两种区别,实际并没有其他的明显区别-
由当前
Activity_A跳转到Activity_B时,A的onPause()和B的onResume()哪个先进行回调执行?
直接用代码测试,代码很简单,不再给出,Log信息
A.onPause和B.onResume执行顺序
结论:A的onPause()先只执行,B的onResume()后执行
2.1.2 群英传知识梳理 <p>

在上图中,有6个生命周期状态,和形态有区别,但只有Resumed,Paused,Stopped3个状态是稳定的,另外3个是过度状态,很快就过去了
Rusumed
这个状态对应Active/Ruuning形态,此时的Activity位于栈顶Paused
当Activity有一部分被挡住,就会进入这个状态,这个状态下的Activity不会接收用户的输入Stopped
当Activity完全被覆盖时,会进入此状态,此时Activity不可见,仅在后台运行Destroyed
当回调了onDestroy()方法时,Activity也就进入了Destroyed状态,对应于Killed形态
2.2 异常情况下的生命周期 <p>
主要考虑两种情况:
- 资源相关的系统配置发生改变导致Activity被杀死并重新创建
- 资源内存不足导致低优先级的Activity被杀死
2.2.1 资源相关的系统配置发生改变导致Activity被杀死并重新创建 <p>
这种情况,有个典型的情景便是,横竖屏切换
在默认情况下,Activity不做任何特殊处理,当系统配置发生改变后,Activity就会被销毁并重新创建

当系统配置改变后,Activity在异常情况下被销毁,onPause(),onStop(),onDestroy()便会被依次调用,由于是异常情况,在onStop()方法回调之前,会先回调onSaveInstanceState()方法来保存Acticity的状态
onSaveInstanceState()方法只有在Activity异常终止的情况下在onStop()前会被回调。但这个方法和onPause()没有特定的关系,可能在onPause()之前,也可能在其后。在onSaveInstanceState()方法中,Activity的信息保存在Bundle中
当Acticity重建时,系统在onStart()方法后会先调用onRestoreInstanceState()方法,并把onSaveInstanceState()保存信息的Bundle对象作为参数同时传递给onRestoreInstanceState()和onCreate()方法。
onRestoreInstanceState()配合onCreate()方法,可以用来判断Activity是否被重建。如果重建了,就可以取出Activity销毁前保存的数据,然后恢复
在onSaveInstanceState()和onRestoreInstanceState()方法中,系统会自动做一定量的保存和恢复工作。当Activity在异常情况下需要重建时,系统会默认保存当前Activity的视图结构,并且在Activity重启后恢复这些数据,例如文本框用户输入的数据,ListView滚动的位置等,这种类似的View的状态系统都能默认保存和恢复。不同的Vie,能保存和恢复的数据不同,具体要看特定的View的onSaveInstanceState()和onRestoreInstanceState()方法对哪些数据做了操作
关于保存和恢复View层次结构,系统的工作流程:
1.在Activity在被意外终止时,Activity调用onSaveInstance去保存数据
- 数据信息保存之后
Acticity会委托Window去保存数据 -
Winodw接到委托后,会再委托它上面的顶层容器去保存数据。顶层容器是一个ViewGroup,一般很可能就是DecorView - 顶层容器收到委托后,再一一去通知子元素保存数据
这是典型的委托思想
当Actvitiy重建的时候,onSaveInstanceState()中存储的特定View的状态信息或者自己存储的信息,可以在onCreate()或者onRestoreInstanceState()两个方法选择其一。官方的建议是使用onRestoreInstanceState()
两者的区别:
-
onRestoreInstanceState()一旦被回调,其参数Bundle saveInstanceState一定有值,不需要再去判null -
onCreate()当为正常启动时,其参数Bundle saveInstanceState是null;异常情况下,重建时不为null,需要增加额外的判断
2.2.2 资源内存不足导致低优先级的Activity被杀死 <p>
这种情况下的数据存储和恢复过程与上面的情况完全一致。
Activity优先级可以分为三种情况:
- 前台
Activity:正在与用户进行交互,优先级最高 - 可见但非前台
Activity:典型场景就是弹出一个对话框,Activity虽然可见但失去了焦点,但此时位于后台无法和用户进行交互 - 后台
Activity:已经被暂停的Activity,回调了onStop()方法,完全不能在屏幕看到,优先级最低
系统出现内存不足时,就会根据上面的优先级去杀死目标Activity所在的进程,并使用onSaveInstanceState()存储数据,使用onRestoreInstanceState()恢复数据。
如果一个进程中没有四大组件在执行,这个进程很快会被系统杀死。因此,一个后台工作不适合脱离四大组件而独自运行在后台中,这样进程很容易被杀死。比较好的一个方法是将后台工作放在Service中从而保证进程有一定的优先级,这样不会轻易的被系统杀死。
上面提到的方法只能起到不会轻易的被杀死,但最终还是会被杀死。系统进程保活,貌似是一个伪命题,没有办法能够做到绝对的保活,尤其到了7.0之后,具体的分析可以在
D_clock爱吃葱花同学的关于 Android 进程保活,你所需要知道的一切学习查看
2.2.3 横竖屏切换的影响 <p>
代码很简单不再贴出来,就是重写MainActivity的7个生命周期方法和onSaveInstanceState()以及onRestoreInstanceState()方法,加入了Log
默认情况下:
-
竖屏切换横屏时,Activity回调的生命周期方法?
竖屏切换横屏时 -
横屏切换竖屏时,Activity回调的生命周期方法?
横屏切换为竖屏
网上有人说,横屏切为竖屏时,生命周期方法会走两遍,感觉说的不对。我测试了多次,并把测试的app卸载重装,还是走了一遍
默认情况下,横竖屏切换测试打印的Log信息是一样的
在上面默认的情况下,横竖屏进行切换会重建Activity,如果不想重建,可以在AndroidManifest.xml文件中给Activity指定configChangs属性
代码:
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
同时给MainActivity加了两个confingChanges值orientation|screenSize,原因下面的表格给出
除了重写MainActivity的生命周期方法和onSaveInstanceState()以及onRestoreInstanceState()方法外,再重写一个onConfigurationChanged()方法
代码:
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.e("Activity","&&&&---Activity_A--->onConfigurationChanged");
}
运行后,横竖屏进行切换,都只会打印下面一条Log信息:
加入了configChanges后,Actiivty没有被重建,也没有回调onSaveInstanceState()以及onRestoreInstanceState()方法,只是回调了onConfigurationChanged()方法
configChnanges常用属性值含义
| 属性 | 含义 |
|---|---|
orientation |
屏幕方向发生改变,旋转了手机屏幕 |
sreenSize |
当屏幕的尺寸信息发生了改变。旋转手机屏幕时,屏幕尺寸会发生改变,API13添加,在13后想要Activity不重建,需要加入这个属性,否则还是会重建 |
locale |
设备的本地位置发生改变时,一般指切换了手机系统语言 |
keyboardHidden |
键盘的可访问性发生改变 |
uiMode |
用户界面模式发生改变,例如切换夜晚模式 |
经我测试,感觉网上有的说法有问题,
orientation|keyboardHidden组合并不能起到防止Activity重建;单独使用orientation时,无论是横屏还是竖屏切换,都没有调用onConfigurationChanged()方法,和默认情况下,回调方法是一样的。
3. Activity任务栈 <p>
栈:后进先出(Last In Fisrt Out)的的线性表
一个Adnroid应用通常会包含有多个Activity,各个Acticity通过Intent进行连接。Android系统通过栈结构来保存整个一个应用的Activity,栈底的元素是整个任务的栈的发起者
当一个APP启动后,如果当前环境中不存在该APP的任务栈,系统就会创建一个任务栈,这个栈也称为Task,表示若干个Activity的集合。这个Task就会对整个APP中启动的Activity及进行控制管理。
任务栈涉及到一个TaskAffinity参数,翻译为任务相关性。这个参数标识了一个任务栈的名字。默认情况下,所有Acticity需要的任务栈的名字为应用的包名也可以单独为每一个Activity指定TaskAffinity属性,但这个属性值不能和包名相同。TaskAffinity主要和singlTask启动模式或者allowTaskReparenting属性使用,在其他情况下,并没有意义
注意:一个Task中的Activity可以来自不同APP,同一个APP的Activity也可以不在同一个Task中
任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity处于暂停状态,用户通过切换将后台任务栈再次调到前台
前台任务栈和后台任务栈的实际场景没有查到,哪位同学知道,请告诉一声
根据不同Activity在Task中不同位置,来决定该Activity状态
在正常的和谐情况下,一个Actvity_A中启动了Activity_B,B就会置于Task的顶端,并处于活动状态。A依然保留Task中,处于停止状态。当用户按下返回键或者调用了B.onDestroy()方法,B就从栈顶移除,A重新位于栈顶,恢复活动状态
但这种和谐终究会被特权破坏掉,这种特权便是给Activity设置启动模式
4. 启动模式 <p>
Android中有4中启动模式,但有两种设置启动模式的方式,AndroidManifest.xml文件指定和Itent Flag两种方式
4.1 在AndroidManifest中的设置启动模式 <p>
通过使用android:launchMode标签可以给Actiivity来设置启动模式
四种模式:
- standard:标准模式,默认
- singeTop:栈顶复用模式
- singleTask:栈内复用模式
- singleInstance:单实例模式
4.1.1 standard标准模式 <p>
系统的默认模式,每次启动一个Activity都会重新创建一个新的Activity对象实例,无论这个Activity是否存在。被创建的Activity的生命周期符合典型情况下的Activity生命周期。
在一个任务栈中,可以有很多个实例,每个实例也可以属于不同的任务栈。
在这种模式下,谁启动了Activity_B,那么B就运行在启动的它的那个Activity所在的栈中。

A启动B,B启动C
在MainActivity中,有一个Button,点击按钮后,启动自身

这时,无论MainAcivity是否已经在任务栈存在,都会启动几次创建几次
注意:
当使用ApplicationContext去启动一个standard模式的Activity时,会报错:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.szlk.customview, PID: 8800
android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
standard模式下的Activity默认会进入到启动它的Activity栈中,但非Activity类型的Context并没有任务栈
解决的方式就是为Activity指定FLAG_ACTIVITY_NEW_TASK标记位,启动时就会创建一个新的任务栈,但这就是singleTask模式,而不再是standard模式
4.1.2 singleTop 栈顶复用模式 <p>
在Activity_A中启动指定为singleTop模式的Activity_B,在启动时,系统会先检查任务栈当前栈顶的Activity是不是B,如果不是B无论栈内是否已经有了B实例,就创建一个新的B出来;如果是,就引用这个已经存在的B。这就是栈顶复用。
这种模式通常用于接收到消息后显示的界面。例如QQ接到了10条xia消息,总不能一次就弹出10个Activity
singleTop模式虽然不会创建已经存在于栈顶的Activity_B,但会回调B的onNewIntent()方法,通过此方法可以拿到当前请求的信息。但此时B的onCreate()和onStart()不会再回调

例子:
A,B,C,D四个singleTop模式的Activity,A在栈底,D位于栈顶
如果再次启动D后,栈内仍然有4个Activity,ABCD;
如果再次启动A后,栈内有会5个Acitvity,ABCDA
4.1.3 singleTask栈内复用模式 <p>
是一种单实例模式,想要启动一个singleTask模式的Activity_D时,系统会先检测D所需要的任务栈是否存在:
若栈不存在,就创建一个任务栈并把
D压入栈中若栈存在,看
D的实例是否存在。只要D在一个栈中存在,即使是多次启用D,也不会重新创建实例,直接将D置于栈顶,系统只是回调onNewIntent()方法,并且在D所在的栈中,如果D上面有其他的Activity也会被销毁,这里是指的同一个APP启动这个D
例子:
- 目前任务栈
T1中的情况为ABC,启动singleTask模式的Activity_D,D所需的任务栈为T2,系统便会先创建T2,再创建D并将其压入T2栈中 - 目前任务栈
T1中的情况为ABC,启动singleTask模式的Activity_D,D所需的任务栈为T1,系统直接将D压入T1中 - 目前任务栈
T1中的情况为ADBC,再次启动singleTask模式的Activity_D,此时D不会再被重建,系统直接把D切换到栈顶,并回调D的onNewIntent()方法,由于singleTask默认具有clearTop的功能,最终T1栈内便是AD
注意:
如果要启动的singleTask模式的D已经存在于一个后台任务栈中了,那么D所在的整个后台任务栈,都是被切换到前台

上面是启动D,下面看启动C

退出整个应用的一种实现思路:
使用这个模式可以用来退出整个应用:将MainActivity设置为singleTask模式,在想要退出应用的某个Activity_某中启动MainActivity,从而将任务栈中MainActivity之上Activity都给清除,然后重写MainActivity的onNewIntent()方法,加入finish()
4.1.4 singleInstance 单实例模式 <p>
这是一种加强的singleTask模式,具有singleTask的特性外,使用这个模式的Activity只能单独位于一个任务栈内。例如,singleInstance模式的Activity_A启动后,系统为A创建一个新的任务栈,随后A单独存在于这个任务栈,由于栈内复用性,后续的请求,都不会再会创建新的Activity,除非这个独特的任务栈被系统销毁
singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题
注意:
在一个singleTask或者singleInstance的Activity_A中通过startActivityForResult()方法启动Activity_B,系统直接回返回给A一个Activity_RESULT_CANCELED而不会再等待B返回结果。
这是因为系统在FarmeWork层对两种启动模式做了限制,设计者认为不同的Task间,默认不能传递数据。如果要传,只能通过Intent来绑定数据
4.1.5 四种启动模式常用的场景 <p>
singleTop
适合用来接收到通知之后,打开Activity用来显示内容
例如,某个APP一次推送了10条新闻,不能每看一个新闻就打开一个singleTask
适合APP的MainActivity使用
可以用来在某个Activity一次就退出整个APPsingleInstance
适合需要与APP分离开的页面
例如闹铃提醒,将闹铃提醒与闹铃设置分离
关于四种模式可以看看Activity启动模式图文详解:standard, singleTop, singleTask以及singleInstance
还有一个TaskAffinity与allowTaskReparenting搭配使用,以后再深入学习
4.2 Intent Flag 标志位启动模式<p>
上面的方式都是通过在AndroidManifest.xml使用android:launchMode=""来指定启动模式,也可以通过Intent来设置标志位来指定启动模式
private void startActivity(Class<? extends Activity> activity) {
Intent intent = new Intent(MainActivity.this, activity);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
两种方式的区别:
- 使用
Flag标志位优先级比在AndroidManifest.xml清单文件指定高,两种都指定时,以Flag为准 - 两种方式在限定范围有不同,清单文件没有办法设定
FLAG_ACTIVITY_CLEAR_TOP标识,Flag标志位的方式无法指定singleInstance模式
常用的标记位:
FLAG_ACTIVITY_NEW_TASK
为Activity指定singleTask模式,和在清单文件中指定该启动模式效果一样FLAG_ACTIVITY_SINGLE_TOP
为Activity指定singleTop模式FLAG_ACTIVITY_CLEAR_TOP
使用此标记位Activity_A的启动时,在同一个任务栈中所有位于A上面的Activity都会被移出栈。这个标记位一般和singleTask一起出现
如果启动目标Activity_A采用的是singleTask模式,若目标A存在,会回调它的onNewIntent()方法。如果启动目标A采用的是standard标准模式,那么A连同A之上的Activity都要出栈,系统会创建新的A实例并压入栈顶FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
使用这个标记的Activity不会出现在历史Activity列表中。等同于在清单文件中指定Activity的属性android:excludeFromRecents="true"
还有很多,以后用到慢慢学习
5. 清空任务栈 <p>
系统提供了清空任务栈的方法。一般在AndroidManifest.xml清单文件中的<activity>标签使用几种属性来清理任务栈
clearTaskOnLaunch
每次返回使用这个属性的Activity_A都会将同一任务栈内A之上的其他Activity清除。通过这个属性,可以让这个Task每次初始化的时候,都只有一个ActivityfinishOnTaskLaunch
clearTaskOnLaunch是作用于别人身上,而finishOnTaskLaunch则作用于自己身上。当离开这个Activity所处的Task,用户再返回时,该Activity就会finish掉android:alwaysRetainTaskState
这个属性给Task一个免死金牌,将Activity_A这个属性设置为true后,A所在的Task将不接受任何清理命令,一直保持当前的Task状态
这几个属性,都没有用过。。。
6. 最后 <p>
Activity涉及到的知识点还有很多,以后需要慢慢再学习
Activity的学习计划分为上,下,本篇为上。下打算学习Activity工作过程,看了一下书,全是the fucking source code ,所以打算下一篇博客先记录学习一个关于图片压缩的Luban鲁班算法:)
本人很菜,有错误请指出
共勉 :)