前言
之前遇到一个问题,与Fragment的Pause生命周期有关,所以就研究了一下Fragment的Pause生命周期特点。就有关这篇笔记。
我们知道Fragment的生命周期是依赖Activity的,所以想探究Fragment的Pause过程需要从Activity的Pause下手。
Pause过程
在FragmentActivity的onPause可以看到相关代码,如下:
@Override protected void onPause() { super.onPause(); mResumed = false; if (mHandler.hasMessages(MSG_RESUME_PENDING)) { mHandler.removeMessages(MSG_RESUME_PENDING); onResumeFragments(); } mFragments.dispatchPause(); } 复制代码
可以看到最后一行代码启动了Fragment的Pause过程,mFragments是一个FragmentControler对象,它的dispatchPause方法只有一行代码
public void dispatchPause() { mHost.mFragmentManager.dispatchPause(); } 复制代码
它调用了FragmentManager的dispatchPause方法
public void dispatchPause() { moveToState(Fragment.STARTED, false); } 复制代码
也只有一行代码,调用moveToState。在FragmentManager中,这个方法最终会调用另外一个重载的moveToState方法
void moveToState(int newState, boolean always) { moveToState(newState, 0, 0, always); } void moveToState(int newState, int transit, int transitStyle, boolean always) { if (mHost == null && newState != Fragment.INITIALIZING) { throw new IllegalStateException("No host"); } if (!always && mCurState == newState) { return; } mCurState = newState; if (mActive != null) { boolean loadersRunning = false; for (int i=0; i<mActive.size(); i++) { Fragment f = mActive.get(i); if (f != null) { moveToState(f, newState, transit, transitStyle, false); if (f.mLoaderManager != null) { loadersRunning |= f.mLoaderManager.hasRunningLoaders(); } } } if (!loadersRunning) { startPendingDeferredFragments(); } if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) { mHost.onSupportInvalidateOptionsMenu(); mNeedMenuInvalidate = false; } } } 复制代码
先判断是否有fragment,如果有则最终调用下面的函数
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) 复制代码
这个方法很复杂,将近300行,就不将所有源码都贴出来了。
重点关注这些代码
} else if (f.mState > newState) { switch (f.mState) { case Fragment.RESUMED: if (newState < Fragment.RESUMED) { if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); f.performPause(); } case Fragment.STARTED: if (newState < Fragment.STARTED) { if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); f.performStop(); } case Fragment.STOPPED: if (newState < Fragment.STOPPED) { if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f); f.performReallyStop(); } case Fragment.ACTIVITY_CREATED: if (newState < Fragment.ACTIVITY_CREATED) { if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f); if (f.mView != null) { // Need to save the current view state if not // done already. if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { saveFragmentViewState(f); } } f.performDestroyView(); //destroy view if (f.mView != null && f.mContainer != null) { Animation anim = null; if (mCurState > Fragment.INITIALIZING && !mDestroyed) { anim = loadAnimation(f, transit, false, transitionStyle); } if (anim != null) { final Fragment fragment = f; f.mAnimatingAway = f.mView; f.mStateAfterAnimating = newState; final View viewToAnimate = f.mView; anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener( viewToAnimate, anim) { @Override public void onAnimationEnd(Animation animation) { super.onAnimationEnd(animation); if (fragment.mAnimatingAway != null) { fragment.mAnimatingAway = null; moveToState(fragment, fragment.mStateAfterAnimating, 0, 0, false); } } }); f.mView.startAnimation(anim); } f.mContainer.removeView(f.mView); //remove view } f.mContainer = null; f.mView = null; f.mInnerView = null; } case Fragment.CREATED: 复制代码
几个state如下
static final int INITIALIZING = 0; // Not yet created. static final int CREATED = 1; // Created. static final int ACTIVITY_CREATED = 2; // The activity has finished its creation. static final int STOPPED = 3; // Fully created, not started. static final int STARTED = 4; // Created and started, not resumed. static final int RESUMED = 5; // Created started and resumed. 复制代码
可以看到RESUMED最大
当一个页面已经展示,那么当前状态就是RESUMED,Puase时可以在dispathPause方法中看到newState是STARTED,那么上面代码的第一行判断就成立,进入这层逻辑中。
由于mState是RESUMED,会依次执行每一个case(因为switch中每个case都没有break,所以会顺序执行下去)。但是当执行到第一个case时,调用了Fragment的performPause方法,
在Fragment源码中可以看到performPause这个方法
void performPause() { if (mChildFragmentManager != null) { mChildFragmentManager.dispatchPause(); } mState = STARTED; mCalled = false; onPause(); if (!mCalled) { throw new SuperNotCalledException("Fragment " + this + " did not call through to super.onPause()"); } } 复制代码
如果没有childFragment,那么直接调用onPause。
如果有childFragment,会先调用它的fragmentManager的dispatchPause方法,这样就进入了childFragment的Pause过程。这个过程与上面的一致,其实就是一个递归的过程。
总结起来,调用顺序如图
以上就是Fragment的Pause整个过程。是从Activity的onPause开始的,而Fragment的onPause是最后被调用的。
问题出现
回到最初,我们遇到的问题到底是什么呢?
首先注意在最后的moveToState函数中有如下代码
f.performDestroyView(); //destroy view if (f.mView != null && f.mContainer != null) { ... f.mContainer.removeView(f.mView); //remove view } f.mContainer = null; f.mView = null; f.mInnerView = null; 复制代码
表示当执行Pause时fragment会destory页面上的view。
这里就会存在一个问题!!!在Activity中切换Fragment时,在Fragment的Pause周期完全结束之前view就已经被remove了, 而这时Fragment还显示在屏幕上。如果activity的theme是transparent,且Fragment里有SurfaceView或者MapView这类view,在remove时就会发生透视现象(遮盖在下面的Activity的内容会显示出来)。而且为装载Fragment的容器设置背景色没有任何用处。
注意这个现象虽然发生在Pause阶段,但是由于返回桌面这个操作会瞬间完成,所以这时没有问题。主要发生在Fragment切换的时候,即getFragmentManager().beginTransaction().replace(...)
如果只是TextView这类普通的就不会存在上面的现象。经过反复测试发现,remove时(在第二个划线的代码处)如果TextView这类的view虽然remove了但是还会留存在页面上,并且会参与转场动画(如果有转场动画);而SurfaceView这类,remove的时候那块区域内容会立刻消失!这样就导致了一瞬间的透视现象。
剖析解决
这是由于SurfaceView的特殊性,实际上remove时SurfaceView与其他类型的View一样留存,但是它的绘制内容被全部回收了(包括SurfaceView的默认背景 - 黑色),没有内容则呈现出了一种透明现象。
解决SurfaceView透明的方法,可以为其设置一下format,如
view.surfaceview.setZOrderOnTop(true) view.surfaceview.holder.setFormat(PixelFormat.RGBA_8888) 复制代码
而MapView(百度)没有找到相关方法,但是有一个取巧的办法,在切换前,即getFragmentManager().beginTransaction().replace(...)
之前前将MapView或其容器设为INVISIBLE,这样由于没有MapView的影响,背景色就可以正常显示了,不会出现透视现象。注意恢复时要记得重新设为VISIBLE。
另外一个彻底解决透视方法是将activity的theme设置为不透明,但是要保证不影响这个activity其他的显示效果。
深层原因
至于SurfaceView为何没有内容呈现透明状态,则容器背景也无效,这个与SurfaceView的绘制有关,引用网上的一段解释:
用来描述SurfaceView的Layer或者LayerBuffer的Z轴位置是小于用来其宿主Activity窗口的Layer的Z轴位置的,但是前者会在后者的上面挖一个“洞”出来,以便它的UI可以对用户可见。实际上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不过是在其宿主Activity窗口上设置了一块透明区域。
所以说当SurfaceView内容完全消失后,这个“洞”就露出了下面页面的内容,这样就导致了问题。