Fragment 页面切换状态监控
由于使用不通的事务方法,场景也是不通的,这里我们重点讨论show/hide与attach/dettach两类问题。当然,我们绕不开的是add/remove和replace。
一、replace事务
replace相对简单,对应的是Fragment最简单的生命周期,因此页面的切换在onResume中即可。
二、add事务
实际上add和remove虽然是【添加】和【移除】,但是实际上这俩个事务很少同时使用。常见的使用情况反而是attach/dettach+add和show/hide+add事务的组合相对常见。本质上,add+remove的事务组合和replace类似,因此也没有必要去remove。
单独的add事务无法实现页面切换,这里我们主要说明attach/detach+add和show/hide+add。
2.1 attach/detach+add事务组合
参考android.support.v4.app.FragmentPagerAdapter源码:
public Object instantiateItem(ViewGroup container, int position) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } final long itemId = getItemId(position); // Do we already have this fragment? String name = makeFragmentName(container.getId(), itemId); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment != null) { if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment); mCurTransaction.attach(fragment); } else { fragment = getItem(position); if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment); mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId)); } if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); } return fragment; } @Override public void destroyItem(ViewGroup container, int position, Object object) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction(); } if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object + " v=" + ((Fragment)object).getView()); mCurTransaction.detach((Fragment)object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { Fragment fragment = (Fragment)object; if (fragment != mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem.setMenuVisibility(false); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment != null) { fragment.setMenuVisibility(true); fragment.setUserVisibleHint(true); } mCurrentPrimaryItem = fragment; } } @Override public void finishUpdate(ViewGroup container) { if (mCurTransaction != null) { mCurTransaction.commitNowAllowingStateLoss(); mCurTransaction = null; } }
这种场景下,使用add只是简单的添加,attach和dettach负责页面的切换。Fragment在ViewPager中一般是提前重建的,因此,传统的Fragment生命周期已经不适合,这里我们看到setUserVisibleHint被调用,因此,我们可以使用,setUserVisibleHint机制。
@Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if(getUserVisibleHint()) { onVisible(); } else { onInvisible(); } } protected void onVisible(){ } protected void onInvisible(){ }
但是这里有个问题,在初始化方法instantiateItem中,我们如果在没有判断的情况下,强行setUserVisibleHint,可能在onCreate之前执行,造成生命周期混乱。
if (fragment != mCurrentPrimaryItem) { fragment.setMenuVisibility(false); fragment.setUserVisibleHint(false); }
实际上在setPrimaryItem比较合理,因为提前在ViewPager提前创建了Fragment并且调用了onAttach->onCreate。对于当前的问题,我们的解决方法通过flag去控制。
protected boolean isCreated = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); isCreated = true; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if(!isCreated) return; if(getUserVisibleHint()) { onShow(); } else { onHide(); } } protected void onShow(){ } protected void onHide(){ }
此外,如果不仅仅对tab实现控制,还要实现打开/退出新的activity时UI的监控,我们在上述代码的基础上实现如下方法即可
@Override public void onResume() { super.onResume(); if(getUserVisibleHint() ){ onShow(); } } @Override public void onStop() { super.onStop(); if(getUserVisibleHint()){ onHide(); } }
2.1 show/hide+add事务组合
这类主要运用于FragmentManager自行管理的页面
public Fragment showFragment(FragmentManager fragmentManager,int position, Bundle bundle) { FragmentTransaction mCurTransaction = fragmentManager.beginTransaction(); try { String name = makeFragmentName(mViewContainer.getId(), position); Fragment fragment = mFragmentManager.findFragmentByTag(name); if (fragment == null) { fragment = instantiateItem(position); fragment.setUserVisibleHint(false); } if (mCurrentPrimaryItem != fragment) { if (mCurrentPrimaryItem != null) { mCurTransaction.hide(mCurrentPrimaryItem); mCurrentPrimaryItem.setUserVisibleHint(false); } if (fragment.isAdded()) { mCurTransaction.show(fragment); } else if(fragment.getFragmentManager()!=null){ mCurTransaction.show(fragment); }else { mCurTransaction.add(viewId, fragment, makeFragmentName(mViewContainer.getId(), position)); } mCurrentPrimaryItem = fragment; } if(bundle!=null) mCurrentPrimaryItem.setArguments(bundle); if (!mCurrentPrimaryItem.getUserVisibleHint()) { mCurrentPrimaryItem.setUserVisibleHint(true); } mCurTransaction.commit(); mCurTransaction = null; return fragment; } catch (Exception e) { e.printStackTrace(); } return null; }
同样,对于这类生命周期,我们也可以参考attach/detach+add的处理方式,但是我们这里存在更好的方法,而且配合onResume一起调用,那就是onHiddenChanged,他的优点是在add时不会调用,在hide/show时才会调用。因此,在fragment之间切换时会调用onHiddenChanged,在Activity之间切换时调用onResume,这种更新更好解决问题,而且可以肯定的是,onHiddenChanged在执行过一次onResume之后才会被调用。
@override public void onResume(){ if( getUserVisibleHint()){ //onResume在Fragment不可见时也会调用,为了防止此情况发生,需要做判断 onShow(); } } @override public void onStop(){ //onstop被调用,Fragment页面必然隐藏 onHide(); } @override public void onHiddenChanged(boolean hidd) { if (hidd) { //隐藏时所作的事情 onHide(); } else { //显示时所作的事情 onShow(); } } protected void onShow(){ } protected void onHide(){ }
三、FragmentTabHost事务
FragmentTabHost相对来说没有就没有那么简单了,内部通过了attach/detach+add的事务模式,对于这种更新,我们只能通过手动方式来实现了。
当然,我们最好定义一个接口,这里不再赘述了。
四、常见问题
@Fragment已经被加载的状态异常,在一个FragmentManager中,同一个Fragment的实例只允许被add一次。那么。这个问题是怎么产生的呢?
java.lang.IllegalStateException: Fragment already added: Fragment
答:FragmentManager添加Fragment的过程中使用了事务,而在commit的时候讲add任务放到了队列中,由于任何队列具有阻塞和延时问题,因此可能导致消息执行过程虽然同步,但也产生了延时。
BackStackRecord.java源码如下
int commitInternal(boolean allowStateLoss) { if (mCommitted) throw new IllegalStateException("commit already called"); if (FragmentManagerImpl.DEBUG) { Log.v(TAG, "Commit: " + this); LogWriter logw = new LogWriter(TAG); PrintWriter pw = new PrintWriter(logw); dump(" ", null, pw, null); } mCommitted = true; if (mAddToBackStack) { mIndex = mManager.allocBackStackIndex(this); } else { mIndex = -1; } mManager.enqueueAction(this, allowStateLoss); return mIndex; }
FragmentManager.java源码
public void enqueueAction(Runnable action, boolean allowStateLoss) { if (!allowStateLoss) { checkStateLoss(); } synchronized (this) { if (mDestroyed || mHost == null) { throw new IllegalStateException("Activity has been destroyed"); } if (mPendingActions == null) { mPendingActions = new ArrayList<Runnable>(); } mPendingActions.add(action); if (mPendingActions.size() == 1) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); } } }
解决方案:我们不能通过Fragment.isAdd()或者FragmentManager.findFragmentByTag(tag)去判断是否已经add,因此,我们可以利用FragmentTransaction,当add之后,Fragment会自动引用到FragmentManager或者tag,因此,我们add之前也可以通过 Fragment.getTag()来判断是否已经处于add状态了。
②FragmentTabHost+ViewPager组合是否合理
答:此组合是误用,FragmentTabHost并不推荐ViewPager一起使用。首先是FragmentTabHost可以自动添加Fragment到布局上,并且有自己管理Fragment生命周期的能力,但是ViewPager只能通过PagerAdapter添加页面,某种意义上,FragmentTabHost的作用被削弱了,同时做了无用功,因此非常消耗性能。
正确方案:
<1>. FragmentTabHost + 【TabWidget,可选,如果不添加时会自动内部生产】 +Fragment + 【Layout,可选,如果不添加时会自动内部生产】
<2>. TabWidget + ViewPager +Fragment + Layout
@FragmentId和布局containerId的区别
答案:大多数情况下Fragmentid是两者只相同
原文地址:https://my.oschina.net/ososchina/blog/1649341
相关推荐
-
说说 Go 中的变量(附粗制滥造面试题) Java基础
2020-6-16
-
深入剖析神秘的“零拷贝” Java基础
2019-7-4
-
DAS解决访问数据库延时突高的案例分享 Java基础
2020-6-13
-
源码分析Mybatis MapperProxy初始化之Mapper对象的扫描与构建(图文并茂) Java基础
2020-7-7
-
Java线程池解析 Java基础
2019-2-23
-
离开 FB 两年后,Instagram 创始人干嘛去了? Java基础
2020-7-2
-
【开发经验】flutter实现按键监听 Java基础
2019-10-3
-
开发小哥的困惑:为何要用第三方推送? Java基础
2019-6-26
-
类加载器 Java基础
2019-9-17
-
Java 在PDF中添加水印——文本/图片水印 Java基础
2019-4-19