Android 基础之 Activity 面面观

一、生命周期

生命周期

上图是 Activity 和 Fragment 的完整的生命周期函数调用过程,Activity 常规的生命周期回调函数有七个:

  1. onCreate:Activity 第一次创建时调用,一般在该函数中做一些初始化操作,比如创建 View,绑定数据到 View 等。该函数有一个 Bundle 类型的参数 onSaveInstanceState 用于 Activity 被系统销毁后重建;
  2. onStart:Activity 变为可见状态。可以在该方法中注册 BroadcastReceiver;
  3. onResume:Activity 变为可交互(前台)状态。此时 Activity 处于所有 Activivty 的最前端,一个 Activity 可能会频繁的在前台状态和可见状态切换,比如弹出 Dialog 和锁屏;
  4. onPause:Activity 由可交互状态变为部分可见状态。因为在系统资源不足的情况下,会直接杀死 Activity 而不会调用后面的生命周期方法,所以可以在该方法中存储一些变化的数据,或者停止一些不该在可见状态执行的操作,如停止动画,暂停视频等,但是为了保证及时切换到下一个 Activity,不要在该方法内做重量级的操作;
  5. onStop:Activity 变为不可见(后台)状态。可以在该方法中反注册 BroadcastReceiver 以及一些不适合在 onPause 执行的重量级操作;
  6. onDestroy:销毁 Activity。在该方法中执行释放资源,终止线程等操作;即使一个 Activity 被销毁其中的 static 变量还是存在于内存中的,因为 static 是全局变量且生命周期在应用进程的生命周期结束时才结束;
  7. onRestart:Activity 调用 onStop 方法切换到后台后再重新启动不需要调用 onCreate 方法,而是调用 onRestart 方法;

常见场景的生命周期

  1. Activity 正常启动
onCreate --> onStart --> onResume
  1. 按 Back 键退出
onPause --> onStop --> onDestroy
  1. 从 A Activity 跳转到 B Activity
A#onPause -- > B#onCreate -- > B#onStart --> B#onResume --> A#onStop
  1. 从 B Activity 返回 A Activity
B#onPause -- > A#onRestart --> A#onStart --> A#onResume --> B#onStop --> B#onDestroy
  1. 按 Home 键回到主屏
onPause --> onStop
  1. 从主屏返回
onRestart --> onStart --> onResume
  1. 弹出 Dialog 或锁屏
onPause 
  1. 关闭 Dialog 或解锁屏
onResume

二、状态恢复和保存

Activity 的销毁份两种情况:一种是调用 finish() 方法主动退出,另一种是被系统销毁,在系统销毁 Activity 时会调用 onSaveInstanceState(Bundle outState) 方法保存状态,并在重建 Activity 时调用 onRestoreInstanceState(Bundle savedInstanceState) 方法恢复状态,主动退出 Activity 则不会。
而系统销毁 Activity 的情景可以分两种:

  1. 系统配置发生变化
    当系统配置发生变化时,会销毁 Activity 并立即重启:回调函数调用如下:
onPause --> onSaveInstanceState --> onStop --> onDestroy --> onCreate --> onStart --> onRetainInstanceState --> onResume

如果不想让 Activity 重建,可以在 AndroidManifest.xml 的 <activity> 标签在属性 android:configChanges 中声明,属性取值如下:


然后在 Activity#onConfigurationChanged() 方法中处理配置的变化。
比较常处理的配置有:

  • locale:系统语言切换
  • fontScale:字号发生改变
  • keyboard:键盘类型发生改变
  • orientation:屏幕方向发生变化,API13以上要和 screenSize 一起使用
  1. 处于后台(调用 onStop 方法)或者暂停(调用 onPause 方法)状态的 Activity 因为系统资源不足而被杀死。
    这种情况下,在 onStop 方法前会调用 onSaveInstanceState 方法保存状态,待系统资源充足后会重新创建 Activity,并在 onStart 方法之后调用 onRetainInstanceState 方法恢复状态,也可在 onCreate 方法中调用参数 onSaveInstanceState 恢复状态。

在 Activity#onSaveInstanceState 被调用时,Activity 将会从 View 层次(View Hierachy)中自动搜集每一个 View 的状态,所搜集的 View 需要满足两个条件:

  • View 实现了 onSaveInstanceStateonRetainInstanceState 方法;
  • 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 的任务组件栈会有所变化:

  1. standard(默认模式)
    每次启动 Activity 都会创建一个新的 Activity 实例。
  2. singTop(栈顶复用模式)
    如果要启动的 Activity 实例已经存在并且位于栈顶,而是直接复用,将调用者发出的 Intent 对象通过 Acitvity#onNewIntent 方法传递给栈顶的 Activity 对象。
    singTop 模式适用于与用户交互时保持信息更新的 Activity;
  3. singTask(栈内复用)
    如果要启动的 Activity 已存在于栈内,那么不再创建新的实例,而是将该 Acitivity 上面所有的 Activity 出栈销毁,并调用 onNewIntent 方法。
    栈内只存在一个 Activity 的实例,并且可能会发生任务栈的切换,一定会跳转到目标 Activity 所在的栈中进行,与调用者没有任何关系,而 standard 和 singTop 模式不会。
  4. 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 的作用

  1. 启动 Activity
  2. 启动 Service
  3. 发送 Broadcast

Intent 对象的分类

  1. 显式 Intent: 指定组件的类名,通常用于启动自己应用内的组件;
  2. 隐式 Intent:通过指定 Action 来指定要启动的组件,通常用于启动其它应用的组件;
    当使用隐式 Intent 时,Android 系统通过比较 Intent 的内容和 AndroidManifest.xml 文件中声明的 <intent-filter> 来寻找合适的组件,如果匹配则启动该组件,如果存在多个满足条件的组件,则弹出 Dialog 进行选择。

Intent 对象的构成

1. ComponentName

组件名字,可选项。如果没有 ComponentName 则只能通过其它 Intent 信息(action、data、category)来隐式启动组件。

启动 Service 必须显式启动。

Intent 的该属性是一个指定了目标组件类名的 ComponentName 对象,可以通过 setComponentsetClasssetClassName 或者 Intent 的构造方法来设置;

2. Action

指定了要执行动作的字符串,Intent 类里面定义了很多 Action 常量,我们也可以自定义 Action,需要注意但是要以包名作为前缀,可以通过 setAction 或者 Intent 构造方法设置;

预定义的启动 Activity 的 Action

预定义的发送 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 设置, setDatasetType 是互相排斥的,如果需要同时指定,需要使用 setDataAndType 方法;

5. Category

包含了要处理该 Intent 的组件类别的额外信息,Intent 类里面也预定义了很多的 Category 常量,比如 CATEGORY_DEFAULTCATEGORY_LAUNCHER, 也可自定义 Category 项,同样需要依赖于包名,可以通过 addCategory 方法为 Intent 添加 Category 项;

预定义 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 的组成
  1. <action>:必须项,可以包含多个。可以通过 IntentFilter#addAction 动态添加;
  2. <category>:可选项,可以包含多个。只要 Intent 中的 Category 满足其中一个,就可以接受该 Intent 的请求,可以通过 IntentFilter#addCategory 动态添加;
  3. <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 有默认的切换效果,也可以进行自定义,一般有如下几种方式:

  1. Activity#overridePendingTransition(int enterAnim, int exitAnim)
    该方法必须在 startActivity 或者 finish 方法之后调用才能生效,
  • exterAnim:Activity 进入时的动画资源 id;
  • exitAnim:Activity 退出时的动画资源 id;
  1. 在 Activity 的 theme 里面定义:
    在 style 中定义 android:windowAnimationStyle 属性:
    <item name="android:windowAnimationStyle">@style/activityAnim</item>
    windowAnimationStyle 中包含4中动画:
  • android:activityOpenEnterAnimation
  • android:activityOpenExitAnimation
  • android:activityCloseEnterAnimation
  • android:activityCloseExitAnimation
  1. 使用 windowEnterAnimationwindowExitAnimation
    方法使用的是 activityXXX 属性,这里使用的是 windowXXX 属性
  2. 使用 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 对象,

Transition 直接子类和间接子类

这里简单列一下几个间接子类

  • Explode: 爆炸效果,xml 中使用 <explode> 标签;
  • Slide:滑动效果,xml 中使用 <slide> 标签;
  • Fade:淡入淡出效果, xml 中使用 <fade> 标签;

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 153,506评论 22 673
  • 此系列文章是我在毕业求职期间,对Android面试相关的基础知识做的一个整理,内容还比较全面,现在将其发布出来,希...
    thinkChao阅读 3,344评论 4 61
  • Activity 一、四种形态 运行状态: 当 Activity 处于栈的顶层,可见,并可与用户进行交互 onRe...
    任教主来也阅读 938评论 1 9
  • 本文参加#我的故城,我的故事#活动,本人承诺,所发的内容为原创,且未在其他平台发表过。 大一暑假期间的某天...
    大慧慧慧慧啊阅读 87评论 0 2
  • 五一假期。早上,和一位几乎不认识的微信朋友聊天。 对方:五一快乐,你假期干嘛呢? 我:没干啥,就是和朋友晚上吃吃夜...
    阿银老师阅读 259评论 0 3