Android 基础之 Activity 面面观
一、生命周期
生命周期上图是 Activity 和 Fragment 的完整的生命周期函数调用过程,Activity 常规的生命周期回调函数有七个:
- onCreate:Activity 第一次创建时调用,一般在该函数中做一些初始化操作,比如创建 View,绑定数据到 View 等。该函数有一个 Bundle 类型的参数 onSaveInstanceState 用于 Activity 被系统销毁后重建;
- onStart:Activity 变为可见状态。可以在该方法中注册 BroadcastReceiver;
- onResume:Activity 变为可交互(前台)状态。此时 Activity 处于所有 Activivty 的最前端,一个 Activity 可能会频繁的在前台状态和可见状态切换,比如弹出 Dialog 和锁屏;
- onPause:Activity 由可交互状态变为部分可见状态。因为在系统资源不足的情况下,会直接杀死 Activity 而不会调用后面的生命周期方法,所以可以在该方法中存储一些变化的数据,或者停止一些不该在可见状态执行的操作,如停止动画,暂停视频等,但是为了保证及时切换到下一个 Activity,不要在该方法内做重量级的操作;
- onStop:Activity 变为不可见(后台)状态。可以在该方法中反注册 BroadcastReceiver 以及一些不适合在 onPause 执行的重量级操作;
- onDestroy:销毁 Activity。在该方法中执行释放资源,终止线程等操作;即使一个 Activity 被销毁其中的 static 变量还是存在于内存中的,因为 static 是全局变量且生命周期在应用进程的生命周期结束时才结束;
- onRestart:Activity 调用 onStop 方法切换到后台后再重新启动不需要调用 onCreate 方法,而是调用 onRestart 方法;
常见场景的生命周期
- Activity 正常启动
onCreate --> onStart --> onResume
- 按 Back 键退出
onPause --> onStop --> onDestroy
- 从 A Activity 跳转到 B Activity
A#onPause -- > B#onCreate -- > B#onStart --> B#onResume --> A#onStop
- 从 B Activity 返回 A Activity
B#onPause -- > A#onRestart --> A#onStart --> A#onResume --> B#onStop --> B#onDestroy
- 按 Home 键回到主屏
onPause --> onStop
- 从主屏返回
onRestart --> onStart --> onResume
- 弹出 Dialog 或锁屏
onPause
- 关闭 Dialog 或解锁屏
onResume
二、状态恢复和保存
Activity 的销毁份两种情况:一种是调用 finish()
方法主动退出,另一种是被系统销毁,在系统销毁 Activity 时会调用 onSaveInstanceState(Bundle outState)
方法保存状态,并在重建 Activity 时调用 onRestoreInstanceState(Bundle savedInstanceState)
方法恢复状态,主动退出 Activity 则不会。
而系统销毁 Activity 的情景可以分两种:
- 系统配置发生变化
当系统配置发生变化时,会销毁 Activity 并立即重启:回调函数调用如下:
onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onStart --> onRetainInstanceState --> onResume
如果不想让 Activity 重建,可以在 AndroidManifest.xml 的 <activity>
标签在属性 android:configChanges
中声明,属性取值如下:
然后在
Activity#onConfigurationChanged()
方法中处理配置的变化。比较常处理的配置有:
- locale:系统语言切换
- fontScale:字号发生改变
- keyboard:键盘类型发生改变
- orientation:屏幕方向发生变化,API13以上要和 screenSize 一起使用
- 处于后台(调用 onStop 方法)或者暂停(调用 onPause 方法)状态的 Activity 因为系统资源不足而被杀死。
这种情况下,在 onStop 方法前会调用 onSaveInstanceState 方法保存状态,待系统资源充足后会重新创建 Activity,并在 onStart 方法之后调用onRetainInstanceState
方法恢复状态,也可在 onCreate 方法中调用参数 onSaveInstanceState 恢复状态。
在 Activity#onSaveInstanceState 被调用时,Activity 将会从 View 层次(View Hierachy)中自动搜集每一个 View 的状态,所搜集的 View 需要满足两个条件:
- View 实现了
onSaveInstanceState
和onRetainInstanceState
方法; - View 设置了
android:id
属性;
所以我们保存和恢复 Activity 状态时一般不必对 View 进行处理,但是 Activity 的成员变量会和 Activity 一起销毁,需要手动恢复和保存。
Android 提供的标准 View 组件基本上都实现了状态的保存和恢复,只是有些需要我们手动让它生效。比如为 TextView 设置 android:freezeText="true"
。
三、运行模式
Android 中引入了 Task 的概念,由一组为了完成某项工作而聚集在一起的 Activity 对象共同组成,它不受应用和进程的约束,Task 中的 Activity 是按照 Stack 的形式组织的,成为 Activity Stack。栈底 Activity 是整个任务的发起者,栈顶 Activity 是该任务与用户正在交互的 Activity,栈顶 Activity 执行完成后会退栈销毁。
运行模式
开发者可以通过 android:launchMode
属性来改变该 Activity 的运行模式,在不同的运行模式下,Activity 的任务组件栈会有所变化:
- standard(默认模式)
每次启动 Activity 都会创建一个新的 Activity 实例。 - singTop(栈顶复用模式)
如果要启动的 Activity 实例已经存在并且位于栈顶,而是直接复用,将调用者发出的 Intent 对象通过 Acitvity#onNewIntent 方法传递给栈顶的 Activity 对象。
singTop 模式适用于与用户交互时保持信息更新的 Activity; - singTask(栈内复用)
如果要启动的 Activity 已存在于栈内,那么不再创建新的实例,而是将该 Acitivity 上面所有的 Activity 出栈销毁,并调用 onNewIntent 方法。
栈内只存在一个 Activity 的实例,并且可能会发生任务栈的切换,一定会跳转到目标 Activity 所在的栈中进行,与调用者没有任何关系,而 standard 和 singTop 模式不会。 - singInstance(单例模式)
该模式也在内存中也只有一个 Activity 实例存在,通过 onNewIntent 方法处理 Intent 对象,与 singTask 不同的是,其所在的任务栈中只有一个 Activity 对象。
singTask 和 singInstance 模式适用于消耗内存较多的单实例 Activity,比如浏览器界面,音乐播放器界面等。
Intent flags 设置启动模式
- FLAG_ACTIVITY_SINGLE_TOP:相当于 singTop 启动模式;
- FLAG_ACTIVITY_CLEAR_TOP:相当于 singTask 模式;
- FLAG_ACTIVITY_NEW_TASK:在一个新的任务栈中启动 Activity,如果 Activity 的
android:taskAffinity
属性已被设置,会先寻找具有同样任务名的 task 是否已存在,如果已存在就不再构造新的任务栈。
具有相同
android:taskAffinity
属性值的 Activity 属于同一个任务栈,Activity 默认在以应用包名为名字的任务栈中
四、Intent 和 IntentFilter
Intent 的作用
- 启动 Activity
- 启动 Service
- 发送 Broadcast
Intent 对象的分类
- 显式 Intent: 指定组件的类名,通常用于启动自己应用内的组件;
- 隐式 Intent:通过指定 Action 来指定要启动的组件,通常用于启动其它应用的组件;
当使用隐式 Intent 时,Android 系统通过比较 Intent 的内容和 AndroidManifest.xml 文件中声明的<intent-filter>
来寻找合适的组件,如果匹配则启动该组件,如果存在多个满足条件的组件,则弹出 Dialog 进行选择。
Intent 对象的构成
1. ComponentName
组件名字,可选项。如果没有 ComponentName 则只能通过其它 Intent 信息(action、data、category)来隐式启动组件。
启动 Service 必须显式启动。
Intent 的该属性是一个指定了目标组件类名的 ComponentName 对象,可以通过 setComponent
,setClass
,setClassName
或者 Intent 的构造方法来设置;
2. Action
指定了要执行动作的字符串,Intent 类里面定义了很多 Action 常量,我们也可以自定义 Action,需要注意但是要以包名作为前缀,可以通过 setAction
或者 Intent 构造方法设置;
预定义的发送 Broadcast 的 Action
3.Data
指定 action 要操作的 data 的 URI,URI 能够表达存储在任何地方的数据,比如
- 位于本地目录/sdcard/下的 example.data 文件
file:///sdcard/sample.data
- 数据源组件 com.duguhome.providers.sample 中 id 为 1 的数据
content://com.duguhome.providers.sample/1
- 存放在 Web 的数据
http://flyvenus.net/sample.data
可以通过 setData
或者 setDataAndType
进行设置
4. Type
MIME 格式的字符串,用于描述组件能够处理的处理的请求类型,或者补充说明 Data 数据的类型,它可以通过通配符来表示整个类别的信息,比如 image/*
也可以更具体地指定子类别 image/jpg
,可以通过 setType
设置, setData
和 setType
是互相排斥的,如果需要同时指定,需要使用 setDataAndType
方法;
5. Category
包含了要处理该 Intent 的组件类别的额外信息,Intent 类里面也预定义了很多的 Category 常量,比如 CATEGORY_DEFAULT
,CATEGORY_LAUNCHER
, 也可自定义 Category 项,同样需要依赖于包名,可以通过 addCategory 方法为 Intent 添加 Category 项;
6. Extras
用于组件间传输数据的 Bundle 类型的键值对,实现了 Parcelable 接口,通过 putExtra(key,value)
系列方法或者 putExtras(Bundle)
方法设置 Extra,通过 getXXXExtra
方法获取;
一般少量的数据用 Extras 传输,大量的数据用 Data 传输;
7.Flags
Intent 类里定义的整型常量,通过 setFlags
方法设置;
IntentFilter
通过 manifest 文件中包含在 <activity>
,<activity-alias>
,<service>
,<receiver>
中的 <intent-filter>
标签定义:
Intent 的组成
-
<action>
:必须项,可以包含多个。可以通过IntentFilter#addAction
动态添加; -
<category>
:可选项,可以包含多个。只要 Intent 中的 Category 满足其中一个,就可以接受该 Intent 的请求,可以通过IntentFilter#addCategory
动态添加; -
<data>
:可选项,描述可接受的数据范围和类型
<data android:scheme="string"// URI 的 scheme 部分
android:host="string" //URI 对应的域名信息
android:port="string"
android:path="string" //完整路径信息
android:pathPattern="string" //路径信息前缀
android:pathPrefix="string" //模糊匹配
android:mimeType="string" //对应 Intent 的 Type,表示可以接受的数据类型
/>
Intent 匹配流程
1)比较 Action:如果 Intent 包含 Action 信息,就必须要求该 Action 项在 IntentFilter 的 Action 列表中;
2)比较 Data 和 Type:如果 Intent 中不包含 Data 项和 Type 项,IntentFilter 也不能包含相关信息,如果包含 Type 项,则要求 IntentFilter 的 Type 信息基于通配符 * 比较下相等,如果 Intent 包含 Data 项,则拆分成 Scheme 和 Authority 逐一比较,必须完全匹配;
3)比较 Category:如果 Category 不包含任何 Category 项,则直接匹配成功,如果包含 Category 项,则要求 Intent 的所有 Category 项都出现在 IntentFilter 的 Category 列表中;
在进行 Activity 调用时,如果 Intent 对象没有添加 Category 项,系统会为其添加上
Intent.CATEGORY_DEFAULT
类别,所以 Activity 要想作为通用的功能组件被调用,必须显性地添加Intent.CATEHORY_DEFAULT
;
如果有多个 IntentFilter 与 Intent 相匹配,会基于优先级进行选择,每个 IntentFIlter 都有一个优先级,其范围从-1000到1000,默认为0,可以通过<action>
标签中的android:priority
属性或者setPriority
方法设置,如果优先级一致,按照组件的名字字母排序进行调用
五、转场动画
Activity 有默认的切换效果,也可以进行自定义,一般有如下几种方式:
- Activity#overridePendingTransition(int enterAnim, int exitAnim)
该方法必须在 startActivity 或者 finish 方法之后调用才能生效,
- exterAnim:Activity 进入时的动画资源 id;
- exitAnim:Activity 退出时的动画资源 id;
- 在 Activity 的 theme 里面定义:
在 style 中定义android:windowAnimationStyle
属性:
<item name="android:windowAnimationStyle">@style/activityAnim</item>
windowAnimationStyle 中包含4中动画:
- android:activityOpenEnterAnimation
- android:activityOpenExitAnimation
- android:activityCloseEnterAnimation
- android:activityCloseExitAnimation
- 使用
windowEnterAnimation
和windowExitAnimation
方法使用的是 activityXXX 属性,这里使用的是 windowXXX 属性 - 使用 Window 的 windowXXXTransition 属性
- android:windowEnterTransition:第一次进入时的动画,可以调用方法
Window#setEnterTransition(Transition)
方法设置; - android:windowExitTransition:退出时的动画,可以调用方法
Window#setEixtTransition(Transition)
方法设置; - android:windowReenterTransition:再次进入时的动画,可以调用方法
Window#setReeterTransition(Transition)
方法设置; - android:windowReturnTransition:返回时的动画,可以调用方法
Window#setReturnTransition(Transition)
方法设置;
Transition 类
Transiton 可以通过 xml 文件定义,放在 res/transition 目录下,然后通过上述属性 <item>在 style 文件中设置。
在代码中设置需要一下几步:
1)在
setContentView 之前设置
getWindow().requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
告诉 Window 页面切换需要使用动画;
2)使用 TransitionInflater 加载动画 Transition explode = TransitionInflater.from(this).inflateTransition(R.transition.explode);
;
3)getWindow().setWindowXXXTransition 方法
4)调用 startActivity(intent,ActivityOptions.makeSceneTransitionAnimation(this).toBundle())
方法跳转,第二个参数是一个 Bundle 对象,
这里简单列一下几个间接子类
- Explode: 爆炸效果,xml 中使用
<explode>
标签; - Slide:滑动效果,xml 中使用
<slide>
标签; - Fade:淡入淡出效果, xml 中使用
<fade>
标签;