Android 开发艺术探索 学习 day01

0 阅读10分钟

Android 开发艺术探索 学习 day01

Activity

1.1.1典型情况下的生命周期分析

(1)onCreate:

表示Activity正在被创建,这是生命周期的第一个方法。在这个方法中,我们可以做一些初始化工作,比如调用setContentView去加载界面布局资源、初始化Activity所需数据等。

(2)onRestart:

表示Activity正在重新启动。一般情况下,当当前Activity从不可见重新变为可见状态时,onRestart就会被调用。这种情形一般是用户行为所导致的,比如用户按Home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会暂停,也就是onPause和onStop被执行了,接着用户又回到了这个Activity,就会出现这种情况。

(3)onStart:

(角度 :是否可见)

表示Activity正在被启动,即将开始,这时Activity已经可见了,但是还没有出现在前台,还无法和用户交互。这个时候其实可以理解为Activity已经显示出来了,但是我们还看不到。 (可见不代表立马显示出来)

(4)onResume:

(角度 :是否位于前台)

表示Activity已经可见了,并且出现在前台并开始活动。要注意这个和onStart的对比,onStart和onResume都表示Activity已经可见,但是onStart的时候Activity还在后台,onResume的时候Activity才显示到前台。

(5)onPause:

(角度 :是否位于前台)

表示Activity正在停止,正常情况下,紧接着onStop就会被调用。此时可以做一些存储数据、停止动画等工作,但是注意不能太耗时,因为这会影响到新Activity的显示

onPause必须先执行完,新Activity的 onCreate onStart onResume才会执行。

(6)onStop:

(角度 :是否可见)

表示Activity即将停止,可以做一些稍微重量级的回收工作,同样不能太耗时。

(7)onDestroy:

表示Activity即将被销毁,这是Activity生命周期中的最后一个回调,在这里,我们可以做一些回收工作和最终的资源释放。

activity启动流程

image.png

1.1.2异常情况下的生命周期分析

我们知道,Activity除了受用户操作所导致的正常的生命周期方法 调度,还有一些异常情况,比如当资源相关的系统配置发生改变以及系统内存不足时,Activity就可能被杀死。

1.情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建

image.png

当系统配置发生改变后,Activity会被销毁,其onPause、onStop、onDestroy均会被调用,同时由于Activity是在异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态。这个方法的调用时机是在onStop之前,需要强调的一点是,这个方法只会出现在Activity被异常终止的情况下,正常情况下系统不会回调这个方法。

当Activity被重新创建后,系统会调用onRestoreInstanceState,并且把Activity销毁时onSaveInstanceState方法所保存的Bundle对象作为参数同时传递给onRestoreInstanceStateonCreate方法

因此,我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建了,如果被重建了,那么我们就可以取出之前保存的数据并恢复,从时序上来说,onRestoreInstanceState的调用时机在onStart之后。

image.png

image.png

如图所示,Activity被销毁了以后调用了onSavelnstanceState来保存数据,重新创建以后在onCreate和onRestorelnstanceState中都能够正确地恢复我们之前存储的字符串。

这个例子很好地证明了上面我们的分析结论。

2.情况2:资源内存不足导致低优先级的Activity被杀死

Activity的优先级情况。

Activity按照优先级从高到低,可以分为如下三种:

  1. 前台Activity--正在和用户交互的Activity,优先级最高。
  2. 可见但非前台Activity—--比如Activity中弹出了一个对话框,导致Activity可见但是位于后台无法和用户直接交互。
  3. 后台Activity-y—已经被暂停的Activity,比如执行了onStop,优先级最低。

当系统内存不足时,系统就会按照上述优先级去杀死目标Activity所在的进程,并在后 续通过onSavelnstanceState和onRestorelnstanceState来存储和恢复数数据。

当系统配置发生改变后,有没有办法不重新创建呢?

系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建Activity,可以给Activity指定configChanges属性。比如不想让Activity在主题模式发生改变的时候重建,就可以给configChanges属性添加uiMode这个值,如下所示。

android:configChanges="uiMode′
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="19" />
<activity
    android:name="com.ryg.chapter_1.MainActivity"
    android:configChanges="orientation|uiMode"
    android:label="@string/app_name"
        <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
</activity>

image.png

1.2.1 Activity的LaunchMode

1. taskAffinity

taskAffinity 决定 Activity 更“倾向”进入哪个 Task

  • 默认:包名
  • singleTask / NEW_TASK 时特别重要
<activity
    android:name=".HomeActivity"
    android:launchMode="singleTask"
    android:taskAffinity="com.yaxon.home" />

效果:

  • 可能被拉到 指定 Task
  • 即使从 Service / 系统进程启动
2. 任务栈
任务栈(Task)到底是什么?
  • 用来“装 Activity 的栈结构”, Activity 永远运行在某一个 Task 里
  • 一个后进先出(LIFO)的 Activity 栈

特点:

  • 栈顶 Activity 是 当前正在显示的页面
  • Back 键 → 栈顶 Activity 出栈
  • 栈空了 → Task 结束(App 回到后台或桌面)
Activity 和 Task 的基本规则(重点)

1️⃣ Activity 必须属于某一个 Task

  • 不存在“游离的 Activity”
  • 启动 Activity 时,系统要先决定: 进哪个 Task?要不要新建 Task?

2️⃣ 一个 Task 可以包含多个 Activity

典型结构:

Task1:
A → B → C

Back 行为:

C → BA → 桌面

3️⃣ 一个 Activity 可以被创建多次,存在于多个 Task

比如 standard

Task1: A → B → A
Task2: A

👉 所以 Activity 是类,实例是可以有多个的

为什么不能“一个进程一个栈”?

你说得非常对: “我一个进程里有很多 Activity,为啥还要多个任务栈?”

因为 Activity 的来源不同、用户意图不同


举个最真实的例子

用户正在:

  • 微信 聊天(Task A)
  • 点击一个 网页链接 → 打开浏览器(Task B)
  • 从浏览器点 分享 → 又回微信(Task A)

如果 只有一个栈

微信 → 浏览器 → 微信

Back 键会变成:

微信 ← 浏览器 ← 微信 ← …

完全不符合用户直觉。

Task 的真实语义 : “一次完整的用户操作链”

Task ≈ 用户的一次操作任务

  • 点桌面图标 → 一个 Task
  • 点通知 → 可能是另一个 Task
  • 从别的 App 分享进来 → 新 Task 或已有 Task

👉 Task 是从“用户视角”设计的,不是程序员视角

系统选择 Task 的决策流程(重点)**

当你 startActivity() 时,系统会依次判断:


第一步:Intent 里有没有明确要求?

1️⃣ FLAG_ACTIVITY_NEW_TASK

含义

  • 不进当前 Task
  • 去找一个“合适的 Task”
  • 找不到就新建

合适 = taskAffinity 匹配

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

Service / Broadcast 启 Activity 必须用它


第二步:Activity 的 launchMode 是什么?

singleInstance

  • 直接去它独占的 Task
  • 不用纠结了

singleTask

  • 系统先找有没有包含它的 Task
  • 找到就复用
  • 找不到就新建 Task

第三步:taskAffinity(任务亲和性)

这是 系统选 Task 的“名字标签”

  • 每个 Task 都有 affinity
  • 每个 Activity 也有 affinity

系统规则

  • affinity 相同 → 倾向进同一个 Task
  • 不同 → 分栈
android:taskAffinity="com.yaxon.home"

第四步:默认行为(最常见)

如果以上都没命中:

👉 进当前 Task

也就是:

startActivity()
3. Activity 四个启动模式
一、一句话总览

standard 👉 不管在哪、有没有,永远新建一个 Activity,放进当前任务栈

singleTop 👉 只有当它已经在当前任务栈栈顶时才复用,否则照样新建

singleTask 👉 系统中只允许存在一个实例, 👉 如果已存在,整个任务栈被拉到前台,并清掉它上面的页面

singleInstance 👉 系统中唯一实例 + 独占一个任务栈, 👉 这个任务栈里永远只有它自己

二、从“系统到底干了什么”来总结(本质版)

standard

  • 系统行为: new Activity → 压入当前 Task
  • 从不检查历史
  • 不会触发 onNewIntent

singleTop

  • 系统行为:

    • 如果 栈顶就是它 → 复用
    • 否则 → new
  • 只关心当前 Task 的栈顶

  • 可能存在多个实例


singleTask (重要)
  • 系统行为:

    • 全系统查找该 Activity
    • 找到 → 清栈 + bring Task to front
    • 找不到 → new(可能新建 Task)
  • 该 Activity 在系统中只有一个实例

  • 一定会触发 onNewIntent(复用时)


singleInstance

  • 系统行为:

    • 全系统查找
    • 有就直接切到它的 Task
    • 没有就新建 独占 Task
  • 不允许其他 Activity 和它共栈

  • 强制打断当前任务

三、怎么使用?

如何给Activity指定启动模式呢?

方式一:xml配置

第一种是通过AndroidMenifest为Activity指定启动模式

        <activity
            android:name="com.ryg.chapter_1.SecondActivity"
            android:configChanges="screenLayout"
            android:launchMode="singleTask"
            android:label="@string/app_name" />

方式二:intent.addFlags

另一种情况是通过在lntent中设置标志位来为Activity指定启动模式

        Intent intent = new Intent();
        intent.setClass(MainActivity.this, SecondActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        startActivity(intent);

这两种方式都可以为Activity指定启动模式,但是二者还是有区别的。

  • 第二种方式的优先级要高于第一种,当两种同时存在时,以第二种方式为准;
  • 第一种方式无法直接为Activity设定FLAG_ACTIVITY_CLEAR_TOP标识,
  • 而第二种方式无法为Activity指定singlelnstance模式。
4. Activity的Flags
一、车机里最核心的 5 个 Activity Flag(必须记)

1️⃣ FLAG_ACTIVITY_NEW_TASK(车机第一重要)

作用

  • 允许在“没有任务栈的上下文”里启动 Activity
  • 会让系统去 选择 / 新建一个 Task

为什么车机必须用

  • MCU / Service / SystemServer 没有 Activity 栈
  • 不加它 → 直接 crash

典型场景

  • MCU 按键拉起 Home
  • Service 拉起 Settings / Bluetooth
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

⚠️ 车机铁律

Service 启 Activity,不加 NEW_TASK = 必死

2️⃣ FLAG_ACTIVITY_CLEAR_TOP(回到已有页面)

作用

  • 如果目标 Activity 已存在于当前 Task
  • 清掉它上面的所有 Activity
  • 自身不会重建 常见组合(配合 CLEAR_TOP + singleTop (activity 启动模式))
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);

真实场景: 返回设置首页

Task:
Home → Settings → Bluetooth → DeviceDetail

现在你在 DeviceDetail,想 “回到 Settings 首页”

目标 Activity:Settings

只用 CLEAR_TOP(很多人以为这样就够了)

intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);

系统真实行为

  1. 在当前 Task 里找到 Settings
  2. 清掉它上面的 Activity 👉 BluetoothDeviceDetail 出栈

此时栈变成:

Home → Settings

⚠️ 关键问题来了

Settings 会不会重建?

👉 不确定,取决于它的 launchMode

  • 如果是 standard

    • Settings 会被销毁再重新创建
    • 走完整生命周期
  • 如果是 singleTop

    • 不会重建
    • 直接复用,走 onNewIntent()
3️⃣ FLAG_ACTIVITY_SINGLE_TOP(Flag 版 singleTop)

作用

  • 和 launchMode singleTop 行为一致
  • 只是“临时规则”

典型场景

  • 通知 / 物理按键 / 重复点击
  • 不想改 Manifest
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);

一般可以配合FLAG_ACTIVITY_CLEAR_TOP 一起使用

intent.addFlags(
    Intent.FLAG_ACTIVITY_NEW_TASK |
    Intent.FLAG_ACTIVITY_CLEAR_TASK
);
4️⃣ FLAG_ACTIVITY_CLEAR_TASK(清空整个 Task)

作用

  • 清空目标 Task 里的所有 Activity
  • 通常和 NEW_TASK 一起用

典型车机场景

  • 用户切换账号
  • 系统恢复出厂
  • 模式切换(司机 / 乘客)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

⚠️ 极度危险

用之前一定想清楚 Back / Recent 行为

5️⃣ FLAG_ACTIVITY_RESET_TASK_IF_NEEDED(Launcher 专用)

作用

  • 系统决定是否重置 Task
  • 常见于 Launcher 启动 App

车机场景

  • Launcher 点第三方 App

一般你不用手动写,系统会加。

Launcher 启 App,本质是:

“把这个 App 的 主任务栈 恢复成一个干净、可预期的状态


举个真实例子

App 上次退出时任务栈是:

Main → Detail → SubDetail

用户现在点 Launcher 图标,期望看到的是

Main

而不是:

SubDetail

RESET_TASK_IF_NEEDED 干了什么

当系统发现:

  • 这个 Task 是从 Launcher 启动
  • 且 Task 状态“不干净”

系统就会:

  • 清理非 root Activity
  • 或重建 root Activity

👉 让用户“感觉像刚打开 App”

二、车机里常见“黄金组合”(直接照抄)

🔹 MCU / Service 拉起 Home

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

(Home 本身用 singleTask + affinity)


🔹 回到设置首页(不新建)

intent.addFlags(
    Intent.FLAG_ACTIVITY_CLEAR_TOP |
    Intent.FLAG_ACTIVITY_SINGLE_TOP
);

🔹 强制回 Home(清干净)

intent.addFlags(
    Intent.FLAG_ACTIVITY_NEW_TASK |
    Intent.FLAG_ACTIVITY_CLEAR_TASK
);

🔹 通知 / 物理键重复打开同一页面

intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);