android四大组件之Activity - (1)从源码中理解并巧用onWindowFocusChanged(boolean hasFocus)
这里开始到后面,想趁着有时间,将Android四大组件和一些系统组件做一些总结和记录.由于网上已经有很多写的很好并且总结也全面的文章.小弟我也囊中羞涩不敢献丑,就记录一些自己觉得重要的有用的知识点,顺便大家一起学习讨论啥的也好
Activity作为四大组件之一,对于整个Android开发有多重要就无需赘言了.关于它的生命周期,这里借用下官网的图,便一目了然:
那么它的生命周期和我们所说的onWindowFocusChanged(boolean hasFocus)方法有何关系?
Activity生命周期源于onCreate(),于是我们将很多数据的初始化放在这里,将数据的持久保存放在onStop() onPause()和onDestroy()等方法,将临时数据的一致性保存处理放在onSaveInstanceState()等等. 那提一个问题, Activity作为装在各种布局控件的组件容器,有没有试过当我们某一个组件的宽高肯定不为0, 但是对该组件使用getWidth() getHeight() 的时候,发现获得的宽高值为0? 因为这个时候在onCreate()那里setContentView() 的视图初始化以及各个组件的测量并没有完成, 而我们就已经调用getWidth() getHeight() 这两个方法了.
追溯下源码, 会发现 Activity在初始化视图的过程中, 会调用onMeasure() onLayout() onDraw()等方法,去绘制组件,测量组件 同时刷新当前的视图,直至完成整个视图的初始化过程.我们如何在适当时候知道Activity完成视图的初始化才去使用getWidth() getHeight()? 这里有一个方法, 使用onWindowFocusChanged(boolean hasFocus);
我们通过Activity源码去查看,发现onWindowFocusChanged(boolean hasFocus)出现在window$callback 和View.java这两个类中. 而window$callback 当中所使用的该方法实际上也是来自View.java. 那么我们看看这个方法在View中是怎样的:
/** * 当当前的window(窗口)获取或者失去焦点的时候会回调这个方法.请注意,这个焦点和view焦点 * 是分离的,为了获取按键事件,view和view所在的窗口都必须获得焦点.如果一个窗口处于你的输入 * 事件的最上层,那么该窗口将失去焦点而view的焦点会保持不变. * 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()) { //键盘有按下事件,则强制将该view包含的所有子控件全部setPressed()设置为false setPressed(false); } if (imm != null && (mPrivateFlags & FOCUSED) != 0) { //这是一个隐藏的方法(带@hide标签),当view失去焦点时会调用该方法 imm.focusOut(this); } //移除长按事件回调的接口方法 removeLongPressCallback(); //移除轻触探测器,源码中叫 "Remove the tap detection timer." removeTapCallback(); //当焦点(fucos)从按下变成取消的时候会调用,属于隐藏方法 onFocusLost(); } else if (imm != null && (mPrivateFlags & FOCUSED) != 0) { //当view获得焦点时调用该方法,属于隐藏方法 imm.focusIn(this); } //强制view刷新drawable state,并且会回调drawableStateChanged()方法 refreshDrawableState(); }
源码的注释已经写上,应该看出,窗口的焦点和view的焦点是分离的, 这个方法onWindowFocusChanged(boolean hasFocus) 被回调的触发时机是窗口获取或失去焦点的时候.
那么在一个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. * * 从下面这段官方注释中,这一段"Use {@link #onWindowFocusChanged} to know for certain that..." * 可知道,在onResume()方法之后会调用onWindowFocusChanged()方法~ * * <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> * * @see #onRestoreInstanceState * @see #onRestart * @see #onPostResume * @see #onPause */ protected void onResume() { getApplication().dispatchActivityResumed(this); mCalled = true; }
那么让我们来想想, 哪些情况是会导致窗口失去或者获取焦点的?
1, 首次进入一个Activity后会在onResume()方法后面调用
2, 从Activity 跳到另一个Activity. 新的窗口会获取焦点, 就的Activity的窗口会失去焦点.
3, 打开软键盘进行输入, 窗口失去焦点.
4, 软键盘输入完毕消失, 窗口重新获取焦点
5, 应用后台, 窗口失去焦点
6,应用从后台返回当前, 窗口重新获取焦点
... ...
上面这些情况都会Activity都会调用onWindowFocusChanged() 方法.
好了,上面从源码理解了为什么以及何时会调用这个方法, 那么再来看看如何巧用?举个栗子:
巧用一:
当Activity来到onResume()的时候,视图已经初始化完毕了. 在文章开始的时候提到,视图初始化还没完成就调用getwidth() getHeight()获取宽高值是为0的. 那么现在将获取宽高的方法放在onWindowFocusChanged() 中去,就会发现获得的宽高就是正确的了~
巧用二:
当应用被用户按Home键变为后台, 或者从后台中将应用变为前台时,如果要对某些数据进行保存和恢复, 也可以在这里做一些操作. 当然这并不是唯一的选择, 也可以放在onPause(), onResume()等方法中去.只是说, 当知道了onWindowFocusChanged() 的作用, 这里我们就能多一个选择,不是挺好嘛.
当然,还有其他一些用法,可以参考上面所提及的触发onWindowFocusChanged() 方法的时机.同时我们也不能为了用而用,知道多一些,理解深一些,我们才能做出更好的选择和实现.本文到此,晚安啦~