Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Activity: Lifecycle and Launch Mode #19

Open
yunshuipiao opened this issue May 17, 2019 · 1 comment
Open

Activity: Lifecycle and Launch Mode #19

yunshuipiao opened this issue May 17, 2019 · 1 comment
Assignees
Labels

Comments

@yunshuipiao
Copy link
Owner

yunshuipiao commented May 17, 2019

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)会在以下情况被调用:

  • 当用户按下HOME键时。
  • 从最近应用中选择运行其他的程序时。
  • 按下电源按键(关闭屏幕显示)时。
  • 从当前activity启动一个新的activity时。
  • 屏幕方向切换时(无论竖屏切横屏还是横屏切竖屏都会调用)。

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 yunshuipiao self-assigned this May 17, 2019
@yunshuipiao
Copy link
Owner Author

yunshuipiao commented Dec 25, 2019

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant