生命周期和状态
Lifecycle状态
具体场景:
ActivityA启动ActivityB时
- 先调用A的onPause
- 然后调用B的onCreate -> onStart -> onResume
- 然后调用A的onSavedInstanceState -> onStop
back返回的时候
- 调用B的onPause
- 再调用A的onRestart -> onStart -> onResume
- 然后调用B的onStop->onDestroy
总结:要等后一个Activity完全显示出来onResume之后,才会触发前一个Activity的onStop
Dialog和生命周期
- 当前Activity弹出的Dialog不会触发Activity生命周期
- 其它Activity弹出的Dialog才会使当前的Activity执行
onPause()
onSavedInstanceState
onSavedInstanceState
:通过Bundle保存Activity的实例,用于恢复页面意外销毁前的状态(备忘录模式)
调用场景:当activity有可能被系统回收的情况下,在onStop之前执行。即Activity在后台(不在当前活动栈的栈顶,可能是在当前活动Task栈底,或者在其他Task中),内存不足时可能被回收。
如果必定被销毁,例如按下返回键或者调用finish方法手动销毁Activity,则不会调用
onSavedInstanceState
调用时机:
- Android 3之前:在onPause之前调用
- Android 3-9:在onStop之前调用
- Android 9及之后:在onStop之后调用
举例
- 当按下Home键退回桌面时
- 打开新的Activity时(在当前活动Task栈底)
- 打开其他程序时,如跳到电话界面,或者从通知栏进入其他程序,或者打开后台显示切换到另一个程序(在其他Task中)
- 按下电源键关闭屏幕时
- 横竖屏切换时
onRestoreInstanceState
onRestoreInstanceState
:获取onSavedInstanceState
保存的数据,恢复页面状态。
调用场景:只有在Activity确定是被系统回收后,重新创建Activity的时候才会被调用。
调用时机:在onStart之后。
注:
- 如果是手动销毁Activity,则不会被调用
- 如果
onRestoreInstanceState
被调用了,则页面必然被系统回收过,onSavedInstanceState
、onDestroy
必然被调用过。因此重建的时候必然调用onCreate
onRestoreInstanceState
中的Bundle不为空,onCreate
中的Bundle可能为空,需要进行判空。
进行横竖屏切换时,生命周期如下
onPause->onSavedInstanceState->onStop->onDestroy->onCreate->onStart->onRestoreInstanceState->onResume
如果在清单文件中设置了android:configChanges="orientation|keyboardHidden|screenSize"
,则不会调用各个生命周期,而是调用onConfigurationChanged
方法
四种启动方式
- standard模式:默认的启动模式。每次启动Activity都会创建Activity实例,并放入任务栈。
- singleTop模式:栈顶复用。如果任务栈的栈顶正好存在该Activity的实例,就重用该实例,否则创建新的实例并放入栈顶。例如:点击通知打开页面
- singleTask模式:栈内复用。如果任务栈中存在该实例,则不需要创建,会把该Activity以上的实例都pop销毁,一般用于应用首页
- singleInstance模式:新建一个栈并把Activity加入栈中,该Activity独占该栈(被它开启的Activity会加入其他栈)。整个系统中只有一个这样的实例,再次启动时(即使是另一个app)会重用该实例。例如Launcher、拨号页面等
onNewIntent
当Activity被重用时,会调用onNewIntent()
方法,在onResume之前调用
注:在onNewIntent
中获取到了Intent参数,需要使用setIntent(intent)
保存下来,否则getIntent
拿到的是老的Intent
配置LaunchMode
- 在
AndroidManifest.xml
中配置android:launchMode
属性。 startActivity
设置Intent的Flag
任务栈(Task)、回退栈
- 用于存储当前程序的Activity。
- 当前运行的Task叫当前活动栈。当前运行的Activity在当前活动栈的栈顶。
- 退出应用时,把任务栈中的activity都销毁出栈,然后销毁任务栈
- 一个应用程序至少有一个任务栈,还可能有多个
- Activity在栈内的顺序是可以控制的,通过启动方式和Intent Flag
场景1:
- B设置为singleInstance,A打开B,B打开C:此时A和C在Task1中,B在Task2中。如果A和C的taskAffinity不相同的话,B会新建一个Task3来存放C
- 点击返回:C出栈,回到A页面
- 再点一次返回:A出栈,Task1销毁,回到B页面
场景2:
B设置为singleInstance,A启动B,按下Home键,点击图标进入app,回到页面A。从launcher打开应用的时候会启动默认的栈。
场景3:
B设置为singleInstance,A启动C,C启动B,从B页面打开A,结果显示的是C,而不是A。
注:
- 如果想让两个页面处于同一个栈,可以设置taskAffinity属性(任务归属),设置同样的栈名
starActivityForResult
在5.0之后无论目标Activity设置什么模式,启动后都会强制放在当前栈顶
taskAffinity属性
- Activity包含一个taskAffinity属性,表示该Activity归属的栈的名称。
- 具有相同taskAffinity的两个Activity处于同一个任务栈(即使是两个应用)
- 默认情况下,一个应用的所有Activity具有相同的taskAffinity,即应用程序包名。可以通过设置不同的taskAffinity进行分组,也可以把不同应用的Activity设置为相同的值
- 使用singleTask方式启动页面时,会检查名为taskAffinity的任务栈中是否存在该实例,如果存在,则重用并弹出在其上的所有Activity,如果不存在,则新建一个实例到名为taskAffinity的任务栈中
Intent的Flag属性
Intent.FLAG_ACTIVITY_NEW_TASK
:如果taskAffinity和当前栈相同,则在当前栈新建一个Activity实例,如果不同,则新建一个栈存放Activity- 注:在非Activity的Context中启动Activity需要添加这个属性,否则ContextImpl中会抛异常,Activity重写了startActivity方法,因此不会抛异常
FLAG_ACTIVITY_SINGLE_TOP
:类似singleTopFLAG_ACTIVITY_CLEAR_TOP
:类似singleTask,将其上的Activity清除- 和SingleTask不一样的是,
FLAG_ACTIVITY_CLEAR_TOP
会把自身也销毁,然后重新实例化该Activity。而SingleTask会复用该Activity,只会触发onNewIntent,不触发onCreate。
- 和SingleTask不一样的是,
FLAG_ACTIVITY_REORDER_TO_FRONT
:将栈内的Activity移到栈顶,不销毁其他ActivityFLAG_ACTIVITY_BROUGHT_TO_FRONT
:将栈内的Activity移到栈顶,销毁其他Activity
Intent
Intent是一种在不同组件之间传递的请求消息,是应用程序发出的请求和意图。作为一个完整的消息传递机制,Intent不仅需要发送端,还需要接收端。
显式Intent
明确指出了组件名称
一般用于应用程序内部传递消息(因为我们无法知道其他应用程序的组件名称)
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
隐式Intent
组件在清单文件中声明<Intent-Filter>
(包括Action、Category、Data),注册到AMS中,由系统(AMS)来找到对应的组件。
Extra和Flag在解析Intent找到组件时是不起作用的
//过滤出电话程序
Intent intent = new Intent(Intent.ACTION_CALL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
//打开浏览器,并访问网址
Intent intent = new Intent(Intent.ACTION_VIEW,Uri.parse("http://www.baidu.com"));
startActivity(intent);
常用Action
动作 | 说明 |
---|---|
ACTION_PICK | 启动一个子Activity,从提供的数据列表中选取一项 |
ACTION_SEARCH | 启动一个Activity,执行搜索动作 |
ACTION_SENDTO | 启动一个Activity,向数据提供的联系人发送信息 |
ACTION_SEND | 启动一个可以发送数据的Activity,短信 |
ACTION_VIEW | 最常用的动作,对以Uri方式传送的数据,根据Uri协议部分以最佳方式启动相应的Activity进行处理。对于http:address将打开浏览器查看;对于tel:address将打开拨号界面并呼叫指定的电话号码 |
ACTION_WEB_SEARCH | 打开一个Activity,对提供的数据进行Web搜索 |
ACTION_ANSWER | 打开接听电话的Activity,默认为Android内置的拨号界面 |
ACTION_CALL | 打开拨号盘界面并拨打电话,使用Uri中的数字部分作为电话号码 |
ACTION_DIAL | 打开内置拨号界面,显示Uri中提供的电话号码 |
ACTION_EDIT | 打开一个Activity,对所提供的数据进行编辑操作 |
ACTION_DELETE | 打开一个Activity,对所提供的数据进行删除操作 |
ACTION_INSERT | 打开一个Activity,在提供数据的当前位置插入新项 |
Intent和Bundle
为什么有了Intent还需要Bundle?
- Intent内部也是使用Bundle存储源码,并且在
getExtras
构造新的Bundle对象,避免破坏Intent - 假设数据需要从页面A经过B再传到C,如果使用Intent,需要一个一个取出来放到新的Intent中,使用Bundle只需要取一次即可
- 使用场景不同:
- Intent用于组件间传值,包含Action、Flags、Data、Component、Extras等,不仅仅是数据。
- 有些场景只需要Bundle不需要Intent:例如Handler中的Message,Binder建立连接后,客户端和服务端通信数据传递
public class Intent implements Parcelable, Cloneable {
private Bundle mExtras;
...
public @NonNull Intent putExtra(String name, int value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putInt(name, value);
return this;
}
public int getIntExtra(String name, int defaultValue) {
return mExtras == null ? defaultValue : mExtras.getInt(name, defaultValue);
}
public @Nullable Bundle getExtras() {
//外部获取的时候构造一个新的Bundle对象,避免破坏Intent
return (mExtras != null) ? new Bundle(mExtras) : null;
}
...
}
为什么Bundle不使用HashMap替代?
- Bundle内部使用ArrayMap实现,在数据量较小的情况下,比HashMap更省内存
- HashMap使用Serializable序列化,不支持Parcelable