[Android] Android Activity详细解析

前言

在编程范式中,应用是通过 main() 方法启动的,而 Android 系统与此不同,它会调用与其生命周期特定阶段相对应的特定回调方法来启动 Activity 实例中的代码。

作为最重要的组件,Activity 充当了应用与用户互动的入口点。Activity 提供窗口供应用在其中绘制界面。此窗口通常会填满屏幕,但也可能比屏幕小,并浮动在其他窗口上面。通常,一个 Activity 实现应用中的一个屏幕。

通常,App会指定一个 Activity 为主 Activity,这是用户启动应用时出现的第一个屏幕。然后,每个 Activity 可以启动其他的 Activity,以执行不同的操作,从而形成了页面栈。

Intent 过滤器

Activity 通过 Intent 来启动,Intent 过滤器是 Android 平台的一项非常强大的功能。通过 Intent ,可以显式或隐式请求启动 Activity 。显式请求可以明确指定具体的 Activity ,隐式请求可能会先列举出所有符合条件的 Activity,再经过用户去选择具体用哪一个。

Intent 通过 action (必备)、 category(可选) 和 data(可选)来定义筛选条件。并且可以通过 putExtra(String, Any) 函数来传递一些数据。

putExtra 传递数据
 private Bundle myExtras;
    
    public Intent putExtra(String name, Object value) {
        if (myExtras == null) {
            myExtras = new Bundle();
        }
        myExtras.put(name, value);
        return this;
    } 

putExtra(String, Any)方法内部是通过将数据存入一个 Bundle 对象中,然后在 Activity 的 onCreate 生命周期中取出来供 Activity 使用。

public class Bundle(val mStr: String = "") {
		
    private final Map<String, Object> myMap = new HashMap<String, Object>();

    fun containsKey(String key): Boolean {
        return myMap.containsKey(key);
    }

    fun put(String key, Object value) {
        myMap.put(key, value);
    }

    fun <T: Parcelable> getParcelable(key: String): T {
        Object o = myMap.get(key)
        if (o == null) return null
        try {
            return (T) o;
        } catch (ClassCastException ex) {
            return null;
        }
    }
		// ...
} 

Bundle

Bundle 是一个 HashMap 的装饰类,并且要求 put 进来的元素是可以序列化的,当传入一个不可序列化的对象时,在编译期间会报错:None of the following functions can be called with the arguments supplied.

class Model1()
class Model2(): Serializable

val model1 = Model1()
val model2 = Model2()
activity.startActivity(Intent(activity, BActivity::class.java).apply {
    putExtra("A", model1) // error
    putExtra("B", model2)
}) 

Bundle 不适合保留大量数据,因为它需要在主线程上进行序列化处理并占用系统进程内存。

Bundle 传递参数的数据最大是 1M 左右,数据过大会导致 App 崩溃。

生命周期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x2M6cZ5Z-1656568990594)(https://developer.android.com/guide/components/images/activity_lifecycle.png)]

上面的是官方经典的生命周期流程图,由图中所示,可以看出 Activity 存在三种状态:

  • Activity Launched,启动
  • Activity Running,运行
  • Activity shutdown,关闭

启动过程会经过 onCreate、onStart、onResume ,经过 onResume 后,Activity 处于正常运行状态,直到 onDestroy 执行完成,Activity 关闭。

但是从与用户的交互来看,可以分为三种状态:

  • 可见并可以交互:这个一般指 onResume 到 onPause 之间的状态,也是常说的前台状态。
  • 可见但不可交互:这个是指 onStart 到 onResume 之间和 onPause 到 onStop 之间的状态。
  • 不可见不可交互:这个一般是指 onCreate 到 onStart 及 onStop 到 onDestroy 之间的状态。

用图像表示就是:

与用户交互的状态可以帮助我们来区分 Activity 可能处于的生命周期的状态。例如:在一个 Activity A 上打开了一个透明的新 Activity B,底部的 A 处于可见但不可交互的状态,所以生命周期只会走到 onPause ,不会 onStop。

生命周期方法
  • onCreate

    Activity 会在创建后进入“已创建”状态,该方法在 Activity 的整个生命周期中只应发生一次。

  • onStart

    Activity 进入“已开始”状态,Activity 对用户可见,此方法中的逻辑为 Activity 进入前台并支持交互做准备。

    该方法会非常快速地完成,结束会立刻进入 onResume。

  • onResume

    Activity 进入“已恢复”状态,应用会一直保持这种状态,直到某些事件发生,让焦点远离应用。

    当发生中断事件时,Activity 进入“已暂停”状态。

  • onPause

    Activity 进入“已暂停”状态。从该状态可以回到“已恢复”状态,此方法表示 Activity 不再位于前台(尽管在用户处于多窗口模式时 Activity 仍然可见)。

    处于“已暂停”状态时不应继续(或应有节制地继续)的操作,以及您希望很快恢复的操作。

    • 在 Android 7.0(API 级别 24)或更高版本中,有多个应用在多窗口模式下运行。无论何时,都只有一个应用(窗口)可以拥有焦点,因此系统会暂停所有其他应用。
    • 有新的半透明 Activity(例如对话框)处于开启状态。只要 Activity 仍然部分可见但并未处于焦点之中,它便会一直暂停。

    如果 Activity 变为完全不可见,系统会调用 onStop()

    进入“已停止”状态后,Activity 要么返回与用户互动,要么结束运行并消失。如果 Activity 返回,系统将调用 onRestart()。如果 Activity 结束运行,系统将调用 onDestroy()

  • onStop

    Activity 不再对用户可见,说明其已进入“已停止”状态。

  • onRestart

    当 Activity 从 onStop 返回与用户互动时,会先调用该周期,并继续调用 onStart,主要是为了在 onStart 之前供用户做一些拦截处理。

  • onDestroy

    销毁 Activity 之前,系统会先调用 onDestroy()。两种情况会导致:

    • 用户关闭了 Activity 或调用了 finish() 方法。
    • 由于配置更改,例如旋转屏幕或多窗口模式,系统暂时销毁 Activity。

经典问题:Activity A 启动 Activity B,两者生命周期是如何进行的?

A 启动 B:

A#onCreate >> A#onStart >> A#onResume >> A#onPause >> B#onCreate >> B#onStart >> B#onResume >> A#onSaveInstance >> A#onStop

A 先不可交互,然后 B开始进入前台,B进入前台后,A变为不可交互不可见。

B 返回 A:

B#onPause >> A#onRestart >>A#onStart >> A#onResume >> B#onStop >> B#onDestroy

Activity 状态和从内存中清除

系统会在需要释放内存时终止进程;系统终止给定进程的可能性取决于当时进程的状态。反之,进程状态取决于在进程中运行的 Activity 的状态。下面的表中展示了进程状态、Activity 状态以及系统终止进程的可能性之间的关系。

系统终止进程的可能性进程状态Activity 状态
较小前台(拥有或即将获得焦点)已创建
已开始
已恢复
较大后台(失去焦点)
最大后台(不可见)
已停止
已销毁
保存和恢复瞬时界面状态

用户期望 Activity 的界面状态在整个配置变更(例如旋转或切换到多窗口模式)期间保持不变。但是,默认情况下,系统会在发生此类配置更改时销毁 Activity,从而清除存储在 Activity 实例中的任何界面状态。同样,如果用户暂时从您的应用切换到其他应用,并在稍后返回您的应用,他们也希望界面状态保持不变。但是,当用户离开应用且您的 Activity 停止时,系统可能会销毁该应用的进程。

当 Activity 因系统限制而被销毁时,通过 onSaveInstanceState() 方法,来保存瞬时状态。

当您的 Activity 开始停止时(在执行 onStop 前),系统会调用 onSaveInstanceState()

然后,实现在 onStart 方法后执行的 onRestoreInstanceState 方法,而不是在 onCreate 期间恢复状态。

任务与返回栈

任务是用户在执行某项工作时与之互动的一系列 Activity 的集合。这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。

Android 7.0(API 级别 24)及更高版本支持多窗口环境,当应用在这种环境中同时运行时,系统会单独管理每个窗口的任务;而每个窗口可能包含多项任务。

这种 push-pop 结构同样适用于 Fragment 。

在当前 Activity 启动另一个 Activity 时,新的 Activity 将被 push 到堆栈顶部并获得焦点。上一个 Activity 仍保留在堆栈中,但会停止。

当 Activity 停止时,系统会保留其界面的当前状态。当用户按返回按钮时,当前 Activity 会从堆栈顶部退出(该 Activity 销毁),上一个 Activity 会恢复(界面会恢复到上一个状态)。

堆栈中的 Activity 永远不会重新排列,只会被送入和退出,在当前 Activity 启动时被送入堆栈,在用户使用返回按钮离开时从堆栈中退出。因此,返回堆栈按照“后进先出”的对象结构运作。下图借助一个时间轴直观地显示了这种行为。该时间轴显示了 Activity 之间的进展以及每个时间点的当前返回堆栈。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uGDf5KbK-1656568990596)(https://developer.android.com/images/fundamentals/diagram_backstack.png)]

如果用户继续按返回,则堆栈中的 Activity 会逐个退出,以显示前一个 Activity,直到用户返回到主屏幕(或任务开始时运行的 Activity)。移除堆栈中的所有 Activity 后,该任务将不复存在。

这样的栈结构,会导致一个任务的 Activity 序列顺序是固定的,这样一来,同一个 Activity 会多次创建,当然你也可以通过一些设置,来实现回到其中一个或者是重启一个新的任务栈的功能。

管理任务栈

可以通过一些属性来控制 Activity 在任务栈中的行为,包括:

主要的 Intent 的 Flag 有:

  • FLAG_ACTIVITY_NEW_TASK:在新任务中启动 Activity。如果您现在启动的 Activity 已经有任务在运行,则系统会将该任务转到前台并恢复其最后的状态,而 Activity 将在 onNewIntent() 中收到新的 intent。
  • FLAG_ACTIVITY_CLEAR_TOP:如果要启动的 Activity 已经在当前任务中运行,则不会启动该 Activity 的新实例,而是会销毁位于它之上的所有其他 Activity,并通过 onNewIntent() 将此 intent 传送给它的已恢复实例(现在位于堆栈顶部)。
  • FLAG_ACTIVITY_SINGLE_TOP:如果要启动的 Activity 是当前 Activity(即位于返回堆栈顶部的 Activity),则现有实例会收到对 onNewIntent() 的调用,而不会创建 Activity 的新实例。
启动模式

launchMode 有四种属性值:

  • "standard"(默认模式)

    系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。

  • "singleTop"

    如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其 onNewIntent() 方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。

    Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。

    需要注意的是,如果 Activity 不在栈顶,那么就会创建新的 Activity 实例;如果在栈顶,才会调用其 onNewIntent() 方法将 intent 交给栈顶的对象来处理。

  • "singleTask"

    系统会创建新任务,并实例化新任务的根 Activity。

    但是,如果另外的任务中已存在该 Activity 的实例,则系统会通过调用其 onNewIntent() 方法将 intent 转送到该现有实例,而不是创建新实例。Activity 一次只能有一个实例存在。

    虽然 Activity 在新任务中启动,但用户按返回按钮仍会返回到上一个 Activity。

  • "singleInstance"

    "singleTask" 相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开。

无论 Activity 是在新任务中启动的,还是在和启动它的 Activity 相同的任务中启动,用户按返回按钮都会回到上一个 Activity。但是,如果您启动了指定 singleTask 启动模式的 Activity,而后台任务中已存在该 Activity 的实例,则系统会将该后台任务整个转到前台运行。此时,返回堆栈包含了转到前台的任务中的所有 Activity,这些 Activity 都位于堆栈的顶部。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J2cIn6px-1656568990596)(https://developer.android.com/images/fundamentals/diagram_backstack_singletask_multiactivity.png)]

taskAffinity 亲和性

taskAffinity 表示 Activity 倾向于属于哪个任务。taskAffinity 属性采用字符串值,该值必须不同于 manifest 元素中声明的默认软件包名称,因为系统使用该名称来标识应用的默认任务亲和性。taskAffinity 实际上就是用来给 Activity 指定任务的标签。但是需要配合特殊场景使用。

taskAffinity 可在两种情况下发挥作用:

  • 当启动 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记时

    如果传递给 startActivity() 的 intent 包含 FLAG_ACTIVITY_NEW_TASK 标记,则系统会寻找其他任务来容纳新 Activity。通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同 taskAffinity 的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。

  • 当 Activity 的 allowTaskReparenting 属性设为 "true"

    在这种情况下,一旦和 Activity 有相同 taskAffinity 的任务进入前台运行,Activity 就可从其启动的任务转移到该任务。

使用场景

如果一个 APK 文件中包含了就用户角度而言的多个“应用”,(一个 App 安装后会在 launcher 中存在多个图标)您可能需要使用 taskAffinity 属性为每个“应用”所关联的 Activity 指定不同的亲和性。

清除返回栈

如果用户离开任务较长时间,系统会清除任务中除根 Activity 以外的所有 Activity。当用户再次返回到该任务时,只有根 Activity 会恢复。系统之所以采取这种行为方式是因为,经过一段时间后,用户可能已经放弃了之前执行的操作,现在返回任务是为了开始某项新的操作。

您可以使用一些 Activity 属性来修改此行为:

  • alwaysRetainTaskState

    如果在任务的根 Activity 中将该属性设为 "true",则不会发生上述默认行为。即使经过很长一段时间后,任务仍会在其堆栈中保留所有 Activity。

  • clearTaskOnLaunch

    如果在任务的根 Activity 中将该属性设为 "true",那么只要用户离开任务再返回,堆栈就会被清除到只剩根 Activity。也就是说,它与 alwaysRetainTaskState 正好相反。用户始终会返回到任务的初始状态,即便只是短暂离开任务也是如此。

  • finishOnTaskLaunch

    该属性与 clearTaskOnLaunch 类似,但它只会作用于单个 Activity 而非整个任务。它还可导致任何 Activity 消失,包括根 Activity。如果将该属性设为 "true",则 Activity 仅在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。

在当前会话中归属于任务。如果用户离开任务再返回,则该任务将不再存在。

最后

如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。

小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!

不论遇到什么困难,都不应该成为我们放弃的理由!

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,需要一份小编整理出来的学习资料的关注我主页或者点击扫描下方二维码免费领取~

这里是关于
我自己的Android 学习,面试文档,视频收集大整理**,有兴趣的伙伴们可以看看~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值