近期在修改页面结构过程中,遇到了一个特殊的问题,现在做个总结。 先说下Feed首页的页面结构,为了满足运营多种动态化的需求,Feed首页采用了以下图所示的页面结构,从页面到最小粒度的控件可以分成5个层级 再介绍下修改的原由:应用原来的使用的主框架是tabhost + activity,最近android架构升级要求替换为FragmentTabHost,为了同时满足并行开发和尽量保证代码逻辑改变不大, 因此计划将原FeedHomePageActivity中的业务再次封装为一个FeedHomeFragment给框架团队,其内部逻辑和FeedHomePageActivity保持一致。 其中一些细节调整不再赘述,我们进入本篇的正题: 在将Activity替换为Fragment后,我们发现原来Viepager中的TabFragment所设计的基于setMenuVisibility的可视生命周期监听失效了,而该生命周期承载了我们重要的业务功能,由此引发了我们对原因的分析。 TabItemFragment 原来使用了 setMenuVisibility 来判断页面是否对用户可见 当顶级容器为Activity时该判断表现良好,但是当在新框架中FragmentTabHost中时,发现再次回到Feed频道后,TabItemFragment#onResume方法中的isMenuVisible竟然变为了false, 这就引发了onPageStart()方法没有调用! 通过初步分析,我们发现新老结构的差异在于,新框架下重回Feed渠道时会触发FragmentStatePagerAdapter.restoreState,该函数调用了setMenuVisibility(false),哪怕页面时处于显示状态的 那么这个函数是什么作用,为什么会被新框架调用呢? 我们从新框架引发的变化开始分析 我们知道Fragment的常见生命周期如下: 我们对fragment进行管理时,往往使用的是FragmentManager, 其中有两对关键函数: 那么 FragmentTabHost 使用的是哪种方式呢?我们进入源码分析,入口从 onTabChanged(String tabId) 开始: 其中会调用doTabChanged(@Nullable String tag, @Nullable FragmentTransaction ft)去获取一个FragmentTransaction,并commit提交 我们看到其中: 我们吧目光转向FragmentTransaction的实现类BackStackRecord: 内部维护一个操作栈队列( ArrayList<BackStackRecord.Op> mOps = new ArrayList();),执行了对应的入队操纵。 真正的执行是在commit函数中 我们现在把目光转移到回调BackStackRecord#executeOps(): 可以看到最终调用了 FragmentManagerImpl的attach|detach, 由此我们也确认了: 这里解释了,重新回到FeedHomeFragment时它的生命周期是正常被调用的。 由于FeedHomeFragment直接包裹了 FeedTabContainerFragment,那FeedTabContainerFragment 的生命周期也是正常的 那么为什么ViewPage里的TabItemFragment会出现 setMenuVisibility 异常呢? ViewPager有一个方法setOffscreenPageLimit(int limit),该方法设置保存当前页面两侧各limit个页面,已经超出limit的部分会被销毁,该值默认为1。 假设我们有0,1,2,3四个页面,一开始ViewPager的currentItem为0,此时会预加载1页面;滑动到1页面时候,因为0已经加载过了,此时会预加载2页面;当滑动到2时候,就会销毁0,预加载3。其中加载时会调用PagerAdapter的instantiateItem(ViewGroup container, int position)方法,销毁时会调用destroyItem(ViewGroup container, int position, Object object)方法。 我们看下日志记录: 我们可以看到当Fragment遇到ViewPager时,通过生命周期直接判断 界面是否对用户可见已经不可靠了,业界常用的进行真正判断的函数是借助setMenuVisibility 或者 setUserVisibleHint 进行实现的 我们在原框架结构中是使用这种方式去实现的,经过验证也是真实可靠的,但是为什么在新架构中就失效了呢? 进一步的,我们联想到了可能是Fragment的状态保存和恢复引发的异常 先上大招,看下整体的时序图分析: 我们知道FragmentTabHost最终是通过FragmentManager的attachFragment|detachFragment来管理的Fragment的(会触发onattach和ondeatch,会触发viewcreate和destory),就是说ShopStreeMainTabFragment的视图会经历销毁和重建。 内部会走到 void moveFragmentToExpectedState(final Fragment f) 对fragment进行操作,其中有几个关键生命周期阶段: 那么“进入feed,切到其他tab,再重新切回feed时”,重建过程会依次触发 ShopStreeMainTabFragment的生命周期(ShopStreeMainTabFragment当前状态为1) start resume 我们先来看restoreViewState中的源码: 我们使用了FragmentStatePagerAdapter,它缓存的Fragment是放在mFragments集合中的,当调用destroyItem时候会调用 mFragments.set(position, null)移除对应的实例 需要注意的是当视图重建时: 其中restoreState会强制把 setMenuVisibility 置为false 新框架中TabItemFragment的setMenuVisibility异常是由于: 问题查明后,我们的解决方案其实也出来了,FragmentStatePagerAdapter恢复视图状态时只强制调用了setMenuVisibility(false),并没有影响setUserVisibleHint,因此将相关的判断改为setUserVisibleHint即可一、背景介绍
二、问题描述
private boolean isExitViewpager = false; protected boolean isViewCreated = false; protected boolean isMenuVisible = false; @Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); LLog.i(TAG, String.format("%s : %s : %s : %s", pageTagMark(), "setUserVisibleHint", menuVisible, isViewCreated)); isMenuVisible = menuVisible; if (!menuVisible && isExitViewpager && isViewCreated) { onPagePause(); } if (menuVisible && isExitViewpager && isViewCreated) { onPageStart(); } } @Override public void onResume() { super.onResume(); LLog.i(TAG, String.format("%s : %s : %s : %s", pageTagMark(), "onResume", isViewCreated, isMenuVisible)); if (isExitViewpager) { if (isViewCreated && isMenuVisible) { onPageStart(); } } else { onPageStart(); } } @Override public void onPause() { super.onPause(); LLog.i(TAG, String.format("%s : %s", pageTagMark(), "onPause")); if (isExitViewpager) { if (isMenuVisible) { onPagePause(); } } else { onPagePause(); } }
三、FragmentTabHost 对FeedHomeFragment生命周期的影响
public void onTabChanged(String tabId) { if (this.mAttached) { FragmentTransaction ft = this.doTabChanged(tabId, (FragmentTransaction)null); if (ft != null) { ft.commit(); } } if (this.mOnTabChangeListener != null) { this.mOnTabChangeListener.onTabChanged(tabId); } }
@Nullable private FragmentTransaction doTabChanged(@Nullable String tag, @Nullable FragmentTransaction ft) { FragmentTabHost.TabInfo newTab = this.getTabInfoForTag(tag); if (this.mLastTab != newTab) { if (ft == null) { ft = this.mFragmentManager.beginTransaction(); } if (this.mLastTab != null && this.mLastTab.fragment != null) { ft.detach(this.mLastTab.fragment); } if (newTab != null) { if (newTab.fragment == null) { newTab.fragment = Fragment.instantiate(this.mContext, newTab.clss.getName(), newTab.args); ft.add(this.mContainerId, newTab.fragment, newTab.tag); } else { ft.attach(newTab.fragment); } } this.mLastTab = newTab; } return ft; }
public FragmentTransaction attach(Fragment fragment) { this.addOp(new BackStackRecord.Op(7, fragment)); return this; } public FragmentTransaction detach(Fragment fragment) { this.addOp(new BackStackRecord.Op(6, fragment)); return this; }
public int commit() { return this.commitInternal(false); } int commitInternal(boolean allowStateLoss) { if (this.mCommitted) { throw new IllegalStateException("commit already called"); } else { if (FragmentManagerImpl.DEBUG) { Log.v("FragmentManager", "Commit: " + this); LogWriter logw = new LogWriter("FragmentManager"); PrintWriter pw = new PrintWriter(logw); this.dump(" ", (FileDescriptor)null, pw, (String[])null); pw.close(); } this.mCommitted = true; if (this.mAddToBackStack) { this.mIndex = this.mManager.allocBackStackIndex(this); } else { this.mIndex = -1; } this.mManager.enqueueAction(this, allowStateLoss); return this.mIndex; } }
void executeOps() { int numOps = this.mOps.size(); for(int opNum = 0; opNum < numOps; ++opNum) { BackStackRecord.Op op = (BackStackRecord.Op)this.mOps.get(opNum); Fragment f = op.fragment; if (f != null) { f.setNextTransition(this.mTransition, this.mTransitionStyle); } switch(op.cmd) { case 1: f.setNextAnim(op.enterAnim); this.mManager.addFragment(f, false); break; case 2: default: throw new IllegalArgumentException("Unknown cmd: " + op.cmd); case 3: f.setNextAnim(op.exitAnim); this.mManager.removeFragment(f); break; case 4: f.setNextAnim(op.exitAnim); this.mManager.hideFragment(f); break; case 5: f.setNextAnim(op.enterAnim); this.mManager.showFragment(f); break; case 6: f.setNextAnim(op.exitAnim); this.mManager.detachFragment(f); break; case 7: f.setNextAnim(op.enterAnim); this.mManager.attachFragment(f); break; case 8: this.mManager.setPrimaryNavigationFragment(f); break; case 9: this.mManager.setPrimaryNavigationFragment((Fragment)null); } if (!this.mReorderingAllowed && op.cmd != 1 && f != null) { this.mManager.moveFragmentToExpectedState(f); } } if (!this.mReorderingAllowed) { this.mManager.moveToState(this.mManager.mCurState, true); } }
四、ViewPager遇上Fragment
@Override public void setMenuVisibility(boolean menuVisible) { super.setMenuVisibility(menuVisible); if (menuVisible) { //相当于Fragment的onResume } else { //相当于Fragment的onPause } }
五、Fragment的状态保存和恢复
static final int INVALID_STATE = -1; // Invalid state used as a null value. 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.
void moveToState(Fragment f, int newState, int transit, int transitionStyle, boolean keepActive) { if (f.mState <= newState) { switch (f.mState) { ... case Fragment.CREATED: container = mContainer.onFindViewById(f.mContainerId); f.mContainer = container; f.mView = f.performCreateView(f.performGetLayoutInflater( f.mSavedFragmentState), container, f.mSavedFragmentState); if (f.mView != null) { if (container != null) { container.addView(f.mView); } if (f.mHidden) { f.mView.setVisibility(View.GONE); } f.onViewCreated(f.mView, f.mSavedFragmentState); dispatchOnFragmentViewCreated(f, f.mView, f.mSavedFragmentState, false); } f.performActivityCreated(f.mSavedFragmentState); dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false); if (f.mView != null) { f.restoreViewState(f.mSavedFragmentState); } f.mSavedFragmentState = null; } }else{ switch (f.mState) { ... case Fragment.STOPPED: case Fragment.ACTIVITY_CREATED: saveFragmentViewState(f); f.performDestroyView(); dispatchOnFragmentViewDestroyed(f, false); } }
4.1 Fragment#restoreViewState(f.mSavedFragmentState)的参数
final void restoreViewState(Bundle savedInstanceState) { if (mSavedViewState != null) { mView.restoreHierarchyState(mSavedViewState); mSavedViewState = null; } mCalled = false; onViewStateRestored(savedInstanceState); if (!mCalled) { throw new SuperNotCalledException("Fragment " + this + " did not call through to super.onViewStateRestored()"); } }
void saveFragmentViewState(Fragment f) { if (f.mView == null) { return; } if (mStateArray == null) { mStateArray = new SparseArray<Parcelable>(); } else { mStateArray.clear(); } f.mView.saveHierarchyState(mStateArray); if (mStateArray.size() > 0) { f.mSavedViewState = mStateArray; mStateArray = null; } }
public void saveHierarchyState(SparseArray<Parcelable> container) { dispatchSaveInstanceState(container); } protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; Parcelable state = onSaveInstanceState(); if ((mPrivateFlags & PFLAG_SAVE_STATE_CALLED) == 0) { throw new IllegalStateException( "Derived class did not call super.onSaveInstanceState()"); } if (state != null) { // Log.i("View", "Freezing #" + Integer.toHexString(mID) // + ": " + state); container.put(mID, state); } } }
@Override protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { super.dispatchSaveInstanceState(container); final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { View c = children[i]; if ((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) != PARENT_SAVE_DISABLED) { c.onSaveInstanceState(container); } } }
public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); ViewPager.SavedState ss = new ViewPager.SavedState(superState); ss.position = this.mCurItem; if (this.mAdapter != null) { ss.adapterState = this.mAdapter.saveState(); } return ss; }
4.2 遍历嵌套Fragment中
void performActivityCreated(Bundle savedInstanceState) { if (mChildFragmentManager != null) { mChildFragmentManager.noteStateNotSaved(); } mState = ACTIVITY_CREATED; mCalled = false; onActivityCreated(savedInstanceState); if (!mCalled) { throw new SuperNotCalledException("Fragment " + this + " did not call through to super.onActivityCreated()"); } if (mChildFragmentManager != null) { mChildFragmentManager.dispatchActivityCreated(); } }
public void dispatchActivityCreated() { mStateSaved = false; dispatchMoveToState(Fragment.ACTIVITY_CREATED); } private void dispatchMoveToState(int state) { if (mAllowOldReentrantBehavior) { moveToState(state, false); } else { try { mExecutingActions = true; moveToState(state, false); } finally { mExecutingActions = false; } } execPendingActions(); } void moveToState(int newState, boolean always) { for (int i = 0; i < numAdded; i++) { Fragment f = mAdded.get(i); moveFragmentToExpectedState(f); if (f.mLoaderManager != null) { loadersRunning |= f.mLoaderManager.hasRunningLoaders(); } } }
4.3 FragmentStatePagerAdapter
@Override public Object instantiateItem(ViewGroup container, int position) { //mFragments中保存ViewPager缓存的页面对应的Fragment实例,如果在缓存中就直接返回啦 if (mFragments.size() > position) { Fragment f = mFragments.get(position); if (f != null) { return f; } } if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } Fragment fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment); if (mSavedState.size() > position) { //是否之前保存过该页面的状态,保存过就恢复 Fragment.SavedState fss = mSavedState.get(position); if (fss != null) { fragment.setInitialSavedState(fss); } } while (mFragments.size() <= position) { mFragments.add(null); } fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); mFragments.set(position, fragment); mCurTransaction.add(container.getId(), fragment); return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment) object; if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object + " v=" + ((Fragment)object).getView()); while (mSavedState.size() <= position) { mSavedState.add(null); } //保存该Fragment的状态 mSavedState.set(position, fragment.isAdded() ? mFragmentManager.saveFragmentInstanceState(fragment) : null); //将缓存对应位置置空 mFragments.set(position, null); //销毁实例 mCurTransaction.remove(fragment); } public Parcelable saveState() { } public void restoreState(Parcelable state, ClassLoader loader) { ... f.setMenuVisibility(false); ... }
结论
@Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (getUserVisibleHint()) { //界面可见 } else { //界面不可见 相当于onpause } }
参考文献
本网页所有视频内容由 imoviebox边看边下-网页视频下载, iurlBox网页地址收藏管理器 下载并得到。
ImovieBox网页视频下载器 下载地址: ImovieBox网页视频下载器-最新版本下载
本文章由: imapbox邮箱云存储,邮箱网盘,ImageBox 图片批量下载器,网页图片批量下载专家,网页图片批量下载器,获取到文章图片,imoviebox网页视频批量下载器,下载视频内容,为您提供.
阅读和此文章类似的: 全球云计算