学习资料:
- 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()后执行
2.1.2 群英传知识梳理 <p>
在上图中,有6个生命周期状态,和形态有区别,但只有Resumed,Paused,Stopped
3个状态是稳定的,另外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
一次就退出整个APP
singleInstance
适合需要与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
每次初始化的时候,都只有一个Activity
finishOnTaskLaunch
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鲁班
算法:)
本人很菜,有错误请指出
共勉 :)