浅析Activity中View的生命周期方法回调

0 前言

Activity有生命周期,同样的,View从添加到界面到从界面中移除也有一个生命周期,在官方文档中介绍了自定义View需要重写的一些方法,可以认为这些方法就是View的生命周期方法。

View的生命周期方法.png

本文就介绍一下这些方法调用的顺序流程以及与Activity的生命周期结合之后的顺序流程。

1 View的生命周期

从Activity启动到退出,这个View 的过程是这样的。


D/ViewLifeTestView: ViewLifeTestView: construct 
D/ViewLifeTestView: onFinishInflate: 
D/ViewLifeTestView: onAttachedToWindow: 
D/ViewLifeTestView: onWindowVisibilityChanged: visiable
D/ViewLifeTestView: onMeasure: 
D/ViewLifeTestView: onMeasure: 
D/ViewLifeTestView: onSizeChanged: 
D/ViewLifeTestView: onLayout: 
D/ViewLifeTestView: onDraw: 
D/ViewLifeTestView: onWindowFocusChanged: true
D/ViewLifeTestView: onWindowVisibilityChanged: gone
D/ViewLifeTestView: onWindowFocusChanged: false
D/ViewLifeTestView: onDetachedFromWindow: 

Paste_Image.png

在Activity的onCreate()方法中调用setContentView方法,Activity显示到界面时的View的回调

  1. 构造方法,这是肯定的,View也是一个Java类。
  2. onFinishInflate,这个一般是通过LayoutInflater进行填充的时候会走这个方法。如果我们是直接在代码中new出来的View进行添加,是不会走这个方法的。
  3. onAttachedToWindow,这个方法表明现在这个View已经跟它对应的Window已经绑定了
  4. onWindowVisibilitChanged(int visibility) ,这个值等于 View.VISIBLE,代表View所在的Window已经可见了。
  5. onMeasure,开始测量。我们发现,这个measure过程是在Window可见的情况下才会去调用了,仔细想想这个也不难理解,如果你都不准备显示,我何必去花精力测量你呢。这个测量过程可能会多次调用。
  6. onSizeChanged ,测量之后会回调这个方法。onSizeChanged,顾名思义就是当尺寸发生变化的时候会调用。一般是第一次测量之后调用,后面再测量,如果尺寸没变化就不会再去调用了。
  7. onLayout,测量时候就进行布局,这个时候如果是View的话一般不用去管,因为具体放在哪个位置是由父控件去控制的,如果是ViewGroup,就需要去确定子View的位置。
  8. onDraw,确定完位置和宽高,就可以进行绘制了。
  9. onWindowFocusChanged(boolean hasWindowFocus),为true这个说明View所绑定的Window开始获取焦点

当按back键退出当前Activity后,走下面几个方法

  1. onWindowVisibilitChanged(int visibility) ,这个值等于 View.GONE,此时Window已经不可见了
  2. onWindowFocusChanged(boolean hasWindowFocus),这个也变为false,说明已经没有焦点了。有一点比较奇怪,为什么是先不可见才是没有焦点的呢?
  3. onDetachedFromWindow, 当前View与它对应的Window解除绑定。

2 Activity和View的生命周期结合

当Activity和View的生命周期结合,我们会发现一些有意思的东西。


D/ViewLifeTestActivity: onCreate: 
D/ViewLifeTestActivity: onWindowAttributesChanged: 
...
D/ViewLifeTestView: ViewLifeTestView: construct 
D/ViewLifeTestView: onFinishInflate: 
D/ViewLifeTestActivity: onStart: 
D/ViewLifeTestActivity: onWindowAttributesChanged: 
D/ViewLifeTestActivity: onResume: 
D/ViewLifeTestActivity: onAttachedToWindow: 
D/ViewLifeTestView: onAttachedToWindow: 
D/ViewLifeTestView: onWindowVisibilityChanged: visiable
D/ViewLifeTestView: onMeasure: 
D/ViewLifeTestView: onMeasure: 
D/ViewLifeTestView: onSizeChanged: 
D/ViewLifeTestView: onLayout: 
D/ViewLifeTestView: onDraw: 
D/ViewLifeTestActivity: onWindowFocusChanged hashFocus: true
D/ViewLifeTestView: onWindowFocusChanged: true
D/ViewLifeTestView: onTouchEvent: 
D/ViewLifeTestActivity: onPause: 
D/ViewLifeTestView: onWindowVisibilityChanged: gone
D/ViewLifeTestActivity: onWindowFocusChanged hashFocus: false
D/ViewLifeTestView: onWindowFocusChanged: false
D/ViewLifeTestActivity: onStop: 
D/ViewLifeTestActivity: onDestroy: 
D/ViewLifeTestView: onDetachedFromWindow: 
D/ViewLifeTestActivity: onDetachedFromWindow: 

结合与Activity的启动过程可以看到

  1. Activity 调用onCreate方法,这个时候我们setContentView加载了带View的布局
  2. Activity 调用onWindowAttributesChanged 方法,而且这个方法连续调用多次
  3. View 调用构造方法
  4. View 调用onFinishInflate方法,说明这个时候View已经填充完毕,但是这个时候还没开始触发绘制过程
  5. Activity 调用onstart方法
  6. Activity 再次调用 onWindowAttributesChanged 方法,说明这个方法在onResume之前会多次调用
  7. Activity 调用onResume,我们一般认为当Activity调用onResume的时候,整个Activit已经可以和用户进行交互了,但事实上可能并不是这样,后面解释原因。
  8. Activity 调用onAttachedToWindow,说明跟Window进行了绑定。发现了吗,Activity在onResume之后才跟Window进行了绑定。
  9. View 调用onAttachedToWindow,View开始跟Window进行绑定,这个过程肯定是在Activity绑定之后才进行的。
  10. View 调用 onWindowVisibilityChanged(int visibility),参数变为 View.VISIABLE,说明Window已经可见了,这个时候我们发现一个问题就是其实onResume的时候似乎并不代表Activity中的View已经可见了。
  11. View 调用onMeasure,开始测量
  12. View 调用onSizeChanged,表示测量完成,尺寸发生了变化
  13. View 调用onLayout,开始摆放位置
  14. View 调用 onDraw,开始绘制
  15. Activity 调用onWindowFocusChanged(boolean hasFocus),此时为true,代表窗体已经获取了焦点
  16. View 调用 onWindowFocusChanged(boolean hasWindowFocus),此时为true,代表当前的控件获取了Window焦点,当调用这个方法后说明当前Activity中的View才是真正的可见了。

当退出当前的Activity的时候

  1. Activity 调用 onPause
  2. View 调用 onWindowVisibilityChanged(int visibility),参数变为 View.GONE,View中对应的Window隐藏
  3. Activity 调用onWindowFocusChanged(boolean hasFocus),此时为false,说明Actvity所在的Window已经失去焦点
  4. Activity 调用 onStop,此时Activity已经切换到后台
  5. Activity 调用 onDestory,此时Activity开始准备销毁,实际上调用onDestory并不代表Activity已经销毁了。
  6. View 调用 onDetachedFromWindow,此时View 与Window解除绑定
  7. Activity 调用 onDetathedFromWindow ,此时Activity 与Window 解除绑定

当View进行与Window解除绑定之后,View即将被销毁。我们可以在 View 的 onDetachedFromWindow 方法中可以做一些资源的释放,防止内存泄漏。

3 Activity的onWindowFocusChanged(boolean hasFocus)

从上面的分析我们可以知道,一个Activity启动后onCreate、onStart、onResume等过程后,Activity并不是真正可见的,只有当 onWindowFocusChanged 方法最后调用并且参数为true的时候Activity才是真正的可见,这个时候才可以和用户进行交互。

我们可以这 onWindowFocusChanged 可以做一些事情。比如, 获取布局中的控件的尺寸。

从 Activity 中的 onWindowFoucusChanged 方法介绍来看,当包含 View的 Window 获得或者失去焦点就会调用这个方法。而且要注意,它和View的焦点是有区别的。为了接收键盘事件,View和Window都必须获得焦点。而当一个显示在你的Window上面的Window获取输入焦点的时候,你自己的Window失去了焦点,但是这个View本身的焦点不会改变。例如,弹出一个PopopWindow。

下面是 Activity 的 onWindowFocusChanged 方法介绍。

# Activity
/**
 * Called when the window containing this view gains or loses focus.  Note
 * that this is separate from view focus: to receive key events, both
 * your view and its window must have focus.  If a window is displayed
 * on top of yours that takes input focus, then your own window will lose
 * focus but the view focus will remain unchanged.
 *
 * @param hasWindowFocus True if the window containing this view now has
 *        focus, false otherwise.
 */
public void onWindowFocusChanged(boolean hasWindowFocus) {
    InputMethodManager imm = InputMethodManager.peekInstance();
    if (!hasWindowFocus) {
        if (isPressed()) {
            setPressed(false);
        }
        if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
            imm.focusOut(this);
        }
        removeLongPressCallback();
        removeTapCallback();
        onFocusLost();
    } else if (imm != null && (mPrivateFlags & PFLAG_FOCUSED) != 0) {
        imm.focusIn(this);
    }
    refreshDrawableState();
}

在 Activity 的 onResume 方法中,可以看到有这么一段注释。意思就是提醒大家 这个onResume 并不是提醒你这个Activty对用户可见的最佳指示器。例如一个系统Window如键盘可能是处在前面。采用onWindowFocusChanged可以确定当前的Activity对用户可见并且是可交互的。

/**
 * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
 * {@link #onPause}, for your activity to start interacting with the user.
 * This is a good place to begin animations, open exclusive-access devices
 * (such as the camera), etc.
 *
 * <p>Keep in mind that onResume is not the best indicator that your activity
 * is visible to the user; a system window such as the keyguard may be in
 * front.  Use {@link #onWindowFocusChanged} to know for certain that your
 * activity is visible to the user (for example, to resume a game).
 *
 * <p><em>Derived classes must call through to the super class's
 * implementation of this method.  If they do not, an exception will be
 * thrown.</em></p>

4 总结

  1. 熟知 View 的生命周期方法,可加深我们对于View的理解,在开发中也可以根据这个生命周期方法的回调时机来写出更加合理高效的自定义View,例如在 View 的 onDetachedFromWindow 方法中释放资源。
  2. Activity还有其它的生命周期方法,比如 onSaveInstanceState(Bundle outState)onRestoreInstanceState(Bundle savedInstanceState) 等,限于篇幅和主题,本文并没有介绍这些方法,但是其实这些方法在 Activity 的生命周期方法中也是很重要的。
  3. 通常认为Activity的 onResume 方法调用之后,就可以与用户交互,通过本文的分析可知,这种说法并不准确。只有在 Activity 的 onWindowFocusChanged(boolean hasFocus) 调用,并且参数为true的时候,才是真正可以和用户交互的时机。
  4. Activity 和 View 的很多生命周期回调方法都牵涉到 Window,这个Window 是Android 系统中的一个抽象类,它有一个唯一的子类叫作 PhoneWindow, 它具体有什么作用,在以后的文章中会介绍到它。

推荐阅读更多精彩内容