由于使用不通的事务方法,场景也是不通的,这里我们重点讨论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 viewId,int position, Bundle bundle) {
FragmentTransaction mCurTransaction = fragmentManager.beginTransaction();
try {
String name = makeFragmentName(viewId, 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是两者只相同