yunshuipiao / Potato

Read the fucking source code for the Android interview
Apache License 2.0
80 stars 12 forks source link

Activity: Lifecycle and Launch Mode #19

Open yunshuipiao opened 5 years ago

yunshuipiao commented 5 years ago

Activity: Lifecycle and Launch Mode

[TOC]

关于 Activty 相关的东西一直以来都是看别人总结的二手资料,从未正真的看过源码,包括官方文档的相关东西都能在源码中找到,所以 多看源码,永远不会错。

继承关系

由于 Activity 的源码太多,一个类就有 8000 行代码,所以找感兴趣,当然,每个类前面的注释特别重要,属于必读内容。

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback, WindowControllerCallback,
        AutofillManager.AutofillClient {
        }

这里先不管接口,继承了 ContextThemeWrapper 类,一直往上走,继承关系如下:

// 一定遇到的上下文:提供一个程序环境的全局信息,Android 系统去实现它。它允许访问特定于应用程序的资源和类。
public abstract class Context {}

// 使用代理模式,将所有调用委托给 base。其子类可以通过修改 attachBaseContext(base) 来修改其实现,而不同其改变原来 Context 的行为。 
public class ContextWrapper extends Context {
    Context mBase;
}

// 有属性 Theme,允许修改或者替换主题设置。
public class ContextThemeWrapper extends ContextWrapper {
    private int mThemeResource;
    private Resources.Theme mTheme;
    private LayoutInflater mInflater; // 将布局XML文件实例化为对应的View对象
    private Configuration mOverrideConfiguration;  // 应用程序的相关设置
    private Resources mResources;  // 获取程序的所有资源
}

Activity 是什么

英文是这样描述的:

An activity is a single, focused thing that the user can do.

几乎所有的 Activity 都与用户交互,所以几乎都是全屏幕,也有少量的 浮动 window 。

其子类有两个需要实现的方法

// 创建UI界面
onCreate()

// 当离开 Activity 时调用,维护状态;更重要的是在这时保存数据。
onPause()

生命周期

当一个新 Activity 启动时,位于当前 Activity Stack 的顶部并且正在运行,之前的 Activity 保存在栈顶下面。

屏幕上可能会有一个或者多个可见的 Activity Stack 。

一个 Activity 只可能会有四种状态:

  1. 位于屏幕前台:正在运行,位于栈顶部, 正在与用户交互。
  2. 失去焦点但可见:非全屏或者透明的 Activity 在上面,此时依然存活,仍然与窗口管理器绑定
  3. 被其他 Activty 覆盖不可见:由于不可见,其界面窗口消失,经常会因为内存不足而被系统杀死。
  4. 系统调用 finish 将其从内存移除,或者直接杀掉进程:重新显示时,会重启动,需要从之前的状态恢复。

image

上面有3个关键流程需要注意以下:

  1. 完整的生命周期:start — destroy, 需要在 destory 是释放资源,停止线程的操作等。

  2. 可见生命周期:start — stop ,可见但不可交互,此时可以向用户显示 Activity 所需的资源。

    比如注册 BroadcastReceiver来监视界面的改变, stop 是取消注册。随着可见和不可见,会被调用多次。

  3. 前台周期:resume — pause。

在整个生命周期中,需要实现 onPause() 来提交对数据的更改,并准备暂停与用户的交互;onStop()来处理屏幕不可见时的问题。

两点高版本的改动:

  1. 从 Android 3.0 开始, 知道onStop()方法返回之前,应用程序都不会因为任何情况被杀死。所以在 onPause() 之后肯定会安全调用 onStop() , 做数据保存的操作。
  2. 从 Android P 开始,onSaveInstanceState(android.os.Bundle) 将会在 onStop() 之后执行。

此外,还有两个生命周期:

  1. onRestart(): onStop()之后,但 Activity 重新可见之后调用
  2. onNewIntent(): 当启动模式为 SingleTop, 并且位于栈顶重新启动时,一定在 onPause之后调用。此时 getIntent()为 旧的 intent, 可以使用 setIntent()修改。

onSaveInstanceState和onRestoreInstanceState基本作用

Android系统的回收机制会在未经用户主动操作的情况下销毁activity,而为了避免系统回收activity导致数据丢失,Android为我们提供了onSaveInstanceState(Bundle outState)和onRestoreInstanceState(Bundle savedInstanceState)用于保存和恢复数据。

当activity有可能被系统回收的情况下,而且是在onStop()之前。注意是有可能,如果是已经确定会被销毁,比如用户按下了返回键,或者调用了finish()方法销毁activity,则onSaveInstanceState不会被调用。 或者也可以说,此方法只有在activity被异常终止的情况下会被调用。

onSaveInstanceState(Bundle outState)会在以下情况被调用:

onPause -> onSaveInstanceState -> onStop。

onRestoreInstanceState(Bundle savedInstanceState)只有在activity确实是被系统回收,重新创建activity的情况下才会被调用。

第5种情况屏幕方向切换时,activity生命周期如下: onPause -> onSaveInstanceState -> onStop -> onDestroy -> onCreate -> onStart -> onRestoreInstanceState -> onResume

onCreate()里也有Bundle参数,可以用来恢复数据,它和onRestoreInstanceState有什么区别?

因为onSaveInstanceState 不一定会被调用,所以onCreate()里的Bundle参数可能为空,如果使用onCreate()来恢复数据,一定要做非空判断。

而onRestoreInstanceState的Bundle参数一定不会是空值,因为它只有在上次activity被回收了才会调用。

而且onRestoreInstanceState是在onStart()之后被调用的。有时候我们需要onCreate()中做的一些初始化完成之后再恢复数据,用onRestoreInstanceState会比较方便。

启动模式

启动模式允许您定义一个 Activity 的新实例如何与当前任务关联。你可以用两种方式定义不同的启动模式:

Using the manifest file and use intent flags.

standord: 默认模式

singleTop: 栈顶复用模式, 常用于接收通知页面

singleTask: 新开一个返回栈并创建,或者当返回栈存在时,调用 onNewIntent()。 统一时间只能存在一个该实例。虽然在不同任务栈,但返回时还是会返回之前的 Activity 。, 比如 浏览器, 多次进入走 onNewIntent(), 并且清空上面的页面。

singleInstance, 与“singleTask”相同,只是系统不向包含实例的任务启动任何其他活动。活动总是其任务的唯一成员;由这个启动的任何活动都在一个单独的任务中打开。系统应用, 比如 Launcher, 锁屏应用。

yunshuipiao commented 4 years ago

Viewpager 中两中不同的 Adapter 下,fragment 生命周期的变化:

假设有10个Fragment,每个fragment都打印其位置信息,从位置0开始:

FragmentStatePagerAdapter

但进入页面,停留在 0 位置处,打印日志如下:

D/Logger: setUserVisible0, false
D/Logger: setUserVisible1, false
D/Logger: setUserVisible0, true
D/Logger: onAttach0
D/Logger: onCreate0
D/Logger: onAttach1
D/Logger: onCreate1
D/Logger: onCreateView0
D/Logger: onActivityCreated0
D/Logger: onCreateView1
D/Logger: onActivityCreated1

得到的信息如下:

setUserVisible 方法在所有方法前调用,之后设置可见的 fragment 为 true。

重要的生命周期为:onAttach -- onCreate -- onCreateView -- onActivityCreated。此时 Fragment 变为活跃状态。

会提前加载下一个Fragment。

此时往后滑动 1 个,打印日志如下:

D/Logger: setUserVisible2, false
D/Logger: setUserVisible0, false
D/Logger: setUserVisible1, true
D/Logger: onAttach2
D/Logger: onCreate2
D/Logger: onCreateView2
D/Logger: onActivityCreated2

此时 Fragment 2 创建,共有 3 个framgent实例存在。当前为 Fragment 1, 左右各一个。

继续往后滑动一个:

D/Logger: setUserVisible3, false
D/Logger: setUserVisible1, false
D/Logger: setUserVisible2, true
D/Logger: onAttach3
D/Logger: onCreate3
D/Logger: onDestoryView0
D/Logger: onDestroy0
D/Logger: onDetach0
D/Logger: onCreateView3
D/Logger: onActivityCreated3

当前为Fragment 2, 此时 Fragment 0 被销毁,Fragment 3 创建。

滑动tab,直接点击fragment 6, 打印日志如下:

D/Logger: setUserVisible6, false
D/Logger: setUserVisible5, false
D/Logger: setUserVisible7, false
D/Logger: setUserVisible2, false
D/Logger: setUserVisible6, true
D/Logger: onAttach6
D/Logger: onCreate6
D/Logger: onAttach5
D/Logger: onCreate5
D/Logger: onAttach7
D/Logger: onCreate7
D/Logger: onCreateView6
D/Logger: onActivityCreated6
D/Logger: onCreateView5
D/Logger: onActivityCreated5
D/Logger: onCreateView7
D/Logger: onActivityCreated7
D/Logger: onDestoryView3
D/Logger: onDestroy3
D/Logger: onDetach3
D/Logger: onDestoryView2
D/Logger: onDestroy2
D/Logger: onDetach2
D/Logger: onDestoryView1
D/Logger: onDestroy1
D/Logger: onDetach1

此时变化较大,可见性方面没有变化。

Fragment 5,6,7 被创建。1,2,3被销毁。共计Fragment 实例存在。

viewpager.offscreenPageLimit = 4

可以通过上述方法修改不可见屏幕外的 fragment 的数量。

FragmentPagerAdapter

在上述的基础上,此 Adapter 在滑动过程中,其余生命周期方法一致,只是不会调用

D/Logger: onDestroy0
D/Logger: onDetach0

fragment 不会销毁。所以重新创建时:

D/Logger: setUserVisible0, false
D/Logger: setUserVisible2, false
D/Logger: setUserVisible1, true
D/Logger: onCreateView0
D/Logger: onActivityCreated0
D/Logger: onDestoryView3

只会重新调用创建view的相关方法,从而保留 fragment 的实例在内存中。

因此可以根据不同的 Fragment 数量来决定使用哪种 adapter。

嵌套 ViewPager

在嵌套多个ViewPager的情况下, 从外层开始进行初始化, 其 fragment 实例个数与上述分析保持一致。

D/Logger: onCreateView 0
D/Logger: onCreateView 1
D/Logger: onCreateView 0-0
D/Logger: onCreateView 0-1
D/Logger: onCreateView 1-0
D/Logger: onCreateView 1-1