点赞关注,不再迷路,你的支持对我意义重大!
🔥 Hi,我是丑丑。本文 「Android 路线」| 导读 —— 从零到无穷大 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)
四大组件
说一下 Activity 启动模式
要想真正理解 Activity 启动模式,一定要先理解启动模式背后的 Task 工作模型,这样才能更好地记住每种启动模式的特点,以及为什么需要启动模式这种设计。
当我们点击最近任务键,可以查看最近任务列表并在多个应用之间进行切换。不过,更加准确的说法既不是在多个应用间切换,也不是在多个 Activity 之间切换,而是在多个 Task 之间切换。Task 工作模型主要有以下三个重要特点:
- 回退栈: 每个 Task 都有回退栈,它按照先进后出的顺序维护 Activity 的顺序,当回退栈清空时,Task 的生命周期就结束了。不过,已经消亡的 Task 依然会保留在最近任务列表中,目的是方便用户 “切回去”,不过此时 “切回去” 不是一般的任务切换,而是重新启动 App(可以观察到执行的是启动 App 的动画,而不是切换任务的动画)。
提示: 最近任务列表里的任务不一定是活着的。
-
Task 栈: Activity 可以叠成栈,Task 也可以叠成栈。不过 Task 叠加适用于前台 Task,当 Task 由前台进入后台,叠加的 Task 会在第一时间被拆开。Task 进入后台的场景有两种:
- 1、按 Home 键返回桌面;
- 2、按最近任务键查看最近任务列表。
注意: 查看最近任务列表的时候 Task 就已经进入后台,而不是在切换任务之后才进入后台。
- taskAffinity(任务相关性): 每个 Activity 都有 taskAffinity,Activity 默认取 Application,而 Application 默认取应用包名,每个 Task 的 taskAffinity 又取自栈底 Activity 的 taskAffinity。既然 Task 的 taskAffinity 取自栈底 Activity,那么说明可能会存在多个 taskAffinity 相同的Task。需要注意:taskAffinity 冲突的 Task 只会在最近任务列表里出现一个。
提示: 最近任务列表里的看不到的任务不一定是消亡的。
理解了 Task 工作模型的三个重要特点,现在我们来讨论不同启动模式的特点。
Standard(默认)的行为规则总结起来是 “在启动的 Task 创建”:
在 Task A 启动 Activity,Activity 会被直接进入到 Task A 的栈顶,在不同的 Task 里打开同一个 Activity,Activity 会被创建多个实例分别放进每一个 Task,互不干扰。这是符合大部分产品逻辑和用户心理的,所以这是默认的设置。当你的产品逻辑和默认规则不同时,你就需要使用其他的启动模式。-
SingleTask 的行为规则是总结起来是 “在固定的 Task 创建 + 在 Task 内唯一”:
在固定的 Task 创建:在 Task A 启动 Activity,系统会判断 Task A 的 taskAffinity 和新 Activity 的 taskAffinity 是否相同。如果相同,新 Activity 入栈,如果不同,新 Activity 会进入和自己 taskAffinity 相同的 Task(可以观察到执行了任务切换的动画,而不是一般的 Activity 入场动画)。这样就保证了 SingleTask Activity 一定会在固定的 taskAffinity Task 里创建。
在 Task 内唯一:如果要启动的 Acitivity 已经在 Task,那么会直接复用并回调 onNewIntent() 来刷新界面。另外,如果该 Activity 上方叠加了其他 Activity,那么其他 Activity 都会弹出栈。
在 SingleTask 的两条规则限定之下,SingleTask Activity 事实上在全局只有一个单例对象,那么名称为 “单例” 的 SingleInstance 的行为规则是怎样的呢?
-
SingleInstance 的行为规则与 singleTask 基本一致,但增加了更加严格独占性 “Task 里只有这一个 Activity”:
「Task A」打开「Task B」的 Activity,Activity 不会进入到 Task A 里,而是会创建一个新的 Task,然后随着整个 Task 一起压在 Task A 上面。随后用户继续启动 Activity,新的 Activity 并不会进入同一个 Task 里,而是会在另一个 Task 里创建,然后整个 Task 拿过来压在上面。
提示: 当我们使用 SingleInstance 打开 Activity 时,我们会发现最近任务列表里找不到这个任务,这是因为 taskAffinity 冲突了。
-
SingleTop 的行为规则和 Standard 基本一致,但增加了额外 “栈顶复用” 规则:
如果 Task 栈顶的 Activity 恰好就是要启动的 Activity,那么系统不会创建新的 Activity,而是会回调 onNewIntent()。
除了四种启动模式,还有其他方式可以影响 Activity 的行为规则:
-
Allow Reparenting 行为规则是 “在启动的 Task 创建 + 迁移”:
- 在启动的 Task 创建:「Task A」打开「Task B」的 Activity,Activity 会被直接进入到 Task A 的栈顶,这与 Standard 模式类似;
- 迁移:有两个时机会进行 Activity 迁移:
1、Task B 启动时;
2、Task A 进入后台并且 Task B 已经存在,该 Activity 会自动迁移到 Task B 中。
注意: android:allowTaskReparenting 属性 在 Android 9 和 Android 10 系统上是失效的。
启动模式 | 中文翻译 | 行为规则 |
---|---|---|
Standard | 默认 | 在启动的 Task 创建 |
SingleTop | 栈顶复用 | 与 Standard 基本一致,增加 “栈顶复用” 规则 |
SingleTask | 栈内复用 | 在固定的 Task 创建 + 在 Task 内唯一 |
SingleInstance | 栈内独占 | 与 SingleTask 基本一致,增加 “栈内独占” 规则 |
- 应用场景:
Standard 和 SingleTop 是 Task 内的启动规则,而 SingleTask 和 SingleInstance 是跨 Task 的启动规则(虽然不一定会跨 Task)。因此通常的选择是:用于 在 App 内部交互时使用 Standard 和 SingleTop,在开放给外部共享的 Activity 使用 SingleInstance,而 SingleTask 是内部交互和外部共享都可以用。
在更具体的场景上:SingleTop:推送消息(如果当前页面和推送消息点击跳转的页面相同,那么可以考虑复用这个 Activity);SingleTask:应用主页(因为无论从什么场景进入主页,都应该清空上面的 Activity);SingleInstance:开放给外部共享的页面,例如打电话,写短信等页面。
说一下 Activity 生命周期
说一下 Fragment 生命周期
Intent 和 PendingIntent 的区别?
在创建PendingIntent对象时是跟system_process(系统服务进程,AMS、PMS、WMS都在这个进程)有互动的
因为PendingIntent的相关数据都保存在【系统服务进程】,那么就算创建这个对象的进程已经被kill了,只要这个PendingIntent对象还存在,那它还是能够起到作用的。
PendingIntent 可以理解为延迟执行的 Intent,在某个特定条件下才会执行该 Intent。
Flags 的类型:
FLAG_ONE_SHOT:得到的 PendingIntent 只能使用一次,使用一次后自动调用 cancel 方法解除 PendingIntent 和 Intent 的关联
FLAG_NO_CREATE:当 PendingIntent 不存在时,不进行创建,直接返回 null
FLAG_CANCEL_CURRENT:当 PendingIntent 已存在时,执行 cancel 进行解除,再创建一个新的
FLAG_UPDATE_CURRENT:不存在时就进行创建,创建后每次使用会对数据进行更新
FLAG_IMMUTABLE:创建好 PendingIntent 后就保持一成不变
send 方法:
调用 send 方法时会启动包装的 Intent
cancel 方法:
调用 cancel 方法时会解除 PendingIntent 和被包装 Intent 之间的关联,只有创建该 PengdingIntent 的程序才有权解除
如何判断 App 位于前台还是位于后台,如何监听?
https://developer.android.google.cn/guide/topics/ui/notifiers/notifications
https://www.youtube.com/watch?v=nRcYhsDTyjo
https://www.youtube.com/watch?v=Bh9qg9NivtU
相关深入文章:
- ActivityA -> Activity B -> Activity A
- Activity A 启动模式为 singleTask
- Activity B 启动模式为常规模式
- 问 A 启动 B,B 又启动 A 的生命周期调用顺序?
2、数据存储
Serializable 和 Parcelable 的区别?
我们一定要分清楚什么是协议协议和什么是实现:举个例子,Json 是序列化协议,而 Gson、fastJson、jackJson 这些是对 Json 协议的实现 (序列化目的是将对象转换为可以存储或传输的形式,在将来,反序列化就是把数据重新创建为新的对象。为了保证序列化和反序列化前后的对象信息一致,就需要定义序列化协议,只要你遵循序列化协议,你也可以创建一个新的序列化实现)。现在,我们再回过头来讨论 Serializable([ˈsɪərɪəlaɪzəbl]) 和 Parcelable ([ˈpɑːsləbl]) 的区别:
- 协议:
先讨论两者的协议:
严格来说,Serializable 只是一个序列化实现,它背后是 JDK 序列化协议(另一个实现有 FST(fast-serialization),它是兼容 JDK 序列化协议的,但效率和产物优于 Serializable)。这种协议的序列化大体上是一种 “TLV(Type-Length-Value)” 编码,也就是说编码中会带有字段类型 / 长度等信息,有效载荷低。而 Parcelable 的编码更紧凑,大体上是通过一个游标指针 nativePtr 来操作序列化数据:序列化写入数据后,游标移动数据长度(超出空间时会扩展),反序列化读取数据,也移动数据长度,有效载荷高(牺牲了扩展性和便利性)。
- 实现:
再讨论两者的实现细节:
严格来说,虽然两者的目的都是序列化,但是它们的使用场景是不一样的,Serializable 做的事情就是把 JDK 序列化协议实现出来,再对外提供一套 API(ObjectInput/OutputStream),这和 Protobuf / Gson 等做的事情是类似的。而 Parcelable 除了序列化这个目的之外,还是对 Android 进程间数据传输做的针对性优化:通过共享内存(memcpy)作为进程间传输的媒介,Android 中的 IBinder 通信的数据载体,用的就是 Parcelable。
- Serializable 的其他实现细节:
- 1、Serializable / Externalizable 只是标记接口,真正的序列化实现在 ObjectInput/OutputStream;
- 2、序列化产物会带有当前类的 SerialVersionUID 版本,反序列化时会对比类的 SerialVersionUID 版本与序列化数据中的版本是否一致,如果不一致会抛出 InvalidClassException;
- 3、反序列化使用反射创建对象;
- 4、网上说
Serializable 序列化过程有文件 I/O,这个说法是不严谨的,应该说序列化本身是没有文件 I/O 的,但要进程间传输序列化数据,可能就需要文件 I/O。如果使用 Parcelable 就通过进程共享内存传输,不需要经过磁盘文件; - 5、序列化产物举例:
类定义:
class TestSerial implements Serializable {
public byte version = 100;
}
------------------------------------------------------------------
序列化产物:
AC ED (序列化协议)
00 05 (序列化版本)
73 (TC_OBJECT. 新的对象)
72 (TC_CLASSDESC. 这是一个新类描述)
00 0A (类名的长度)
53 65 72 69 61 6C 54 65 73 74 (类的名称)
05 52 81 5A AC 66 02 F6 (SerialVersionUID)
02 (Various flags,0x02代表这个对象支持序列化)
00 01 (类有几个字段)
49 (代表是int类型)
00 07 (字段名称的长度)
76 65 72 73 69 6F 6E (version, 字段的名称)
78 (TC_ENDBLOCKDATA, 描述的结束符)
70 (TC_NULL)
00 00 00 64 (version的值)
- Parcelable 的其他实现细节:
- 1、Parcelable 真正的序列化实现在 native 层 Parcel.cpp;
- 2、write 和 read 的顺序要保持一致;
- 3、反序列化未使用反射??
相关深入文章:
Parcelable vs Serializable
Parcelable 最强解析
Parcelable 为什么效率高于 Serializable ?
3. 资源
mipmap 和 drawable 的区别?
mipmap 文件夹存放应用启动图标,而其他类型的图片资源(如位图、点 9 图、矢量图)都应该存放在 drawable 文件夹。Launcher(桌面)在显示应用图标时,可以选择显示比当前屏幕更高密度的图片,以获取更高的清晰度和质量。
mipmap 和 drawable 的另一个区别体现在发布应用时的优化差异:在发布应用时,开发者可以向 Google Play 上传 App Bundle,Google Play 会创建优化的 Apk。比如对于 xhdpi 的设备,它会删除所有其他密度的 drawable 文件夹,只保留 xhdpi 的 drawable 文件夹和通用的 drawable 文件夹。因此用户下载的 Apk 包体积更小。mipmap 文件夹不同,优化 Apk 时所有的 mipmap 文件夹都会保留。