参考:
崩溃1:
throw new IllegalStateException("Fragment does not have a view");
崩溃分析:
- 从崩溃栈我们可以发现这个崩溃是在Fragment,initChildFragmentManager 中的回掉中发生的。(mContainer.onFindViewById 有两个实现,一个是在FragmnetActivity 中,这个不会导致这个崩溃。)
- 崩溃时机:ParentFragment 还没有调用 onCreateView 的时候。
崩溃代码调用位置:(state : fragment crated :)container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
case Fragment.CREATED:
// This is outside the if statement below on purpose; we want this to run
// even if we do a moveToState from CREATED => *, CREATED => CREATED, and
// * => CREATED as part of the case fallthrough above.
ensureInflatedFragmentView(f);
if (newState > Fragment.CREATED) {
if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f);
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(
"Cannot create fragment "
+ f
+ " for a container view with no id"));
}
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
String resName;
try {
resName = f.getResources().getResourceName(f.mContainerId);
} catch (NotFoundException e) {
resName = "unknown";
}
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
+ resName
+ ") for fragment " + f));
}
}
f.mContainer = container;
f.mView = f.performCreateView(f.performGetLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mInnerView = f.mView;
f.mView.setSaveFromParentEnabled(false);
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);
// Only animate the view if it is visible. This is done after
// dispatchOnFragmentViewCreated in case visibility is changed
f.mIsNewlyAdded = (f.mView.getVisibility() == View.VISIBLE)
&& f.mContainer != null;
} else {
f.mInnerView = null;
}
}
f.performActivityCreated(f.mSavedFragmentState);
dispatchOnFragmentActivityCreated(f, f.mSavedFragmentState, false);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
// fall through
界面构成信息: Activity 中存在Fragment , Fragment 中使用了ViewPager , ViedwPager 中嵌套了ViewPager with fragmnets ;
相关知识:关于Fragment state 的 保存与恢复:
- child fragment 的状态保存时机:
Fragment.java called in FragmentManager. when performSaveInstanceState;
void performSaveInstanceState(Bundle outState) { onSaveInstanceState(outState); if (mChildFragmentManager != null) { Parcelable p = mChildFragmentManager.saveAllState(); if (p != null) { outState.putParcelable(FragmentActivity.FRAGMENTS_TAG, p); } } }
- child fragment 的恢复时机:
-
Fragment.java @CallSuper public void onCreate(@Nullable Bundle savedInstanceState) { mCalled = true; restoreChildFragmentState(savedInstanceState); if (mChildFragmentManager != null && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) { mChildFragmentManager.dispatchCreate(); } }
- Fragment 状态恢复的时机:
-
FragmentActivity.onCreate(); ...
mFragments.restoreAllState(p, nc != null ? nc.fragments : null); ...
mFragments.dispatchCreate();
- Fragment onCreate 调用的时机:
-
(mv to sate : )Fragment.INITIALIZING in FragmentManager
- Fragment.CRREATED: 状态执行,是FragmentManager add fragment 后通过commit 触发, 或者在界面恢复的时候通过mFragmentManager.dispatchCreate();触发。
崩溃原因:
- Parante Fragment 在界面恢复的时候,也恢复了childFragments。
- childFragment 在恢复的时候,进入了 fragment crated 状态。崩溃在childFragment 中。
- Parent Fragment 的initChildFragmentManager在Parent 的onCreateView 之前被调用到。从而导致child fragment 去查找自己的容器添加自己视图的时候,崩溃。
解决方案: (work around)
在ParentFragment onCreate 的时候, 手动移除所有的child fragment;
崩溃场景2: 当为程序设置不保留后台活动的情况下。 重新回到 ViewPager + fragments 的界面中程序崩溃。
崩溃日志:
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Object android.util.SparseArray.get(int)' on a null object reference
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:902)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:216)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1453)
at android.view.View.dispatchRestoreInstanceState(View.java:15784)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3274)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:3282)
at android.view.View.restoreHierarchyState(View.java:15762)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:510)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445)
at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1740)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1809)
at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:799)
at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2580)
at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2367)
at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2322)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:2229)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:700)
at android.os.Handler.handleCallback(Handler.java:761)
界面构成信息: Activity 中存在Fragment , Fragment 中使用了ViewPager , ViedwPager 中嵌套了ViewPager with fragmnets 。而FragmentViewPager 的Adapter 则使用了StateFragmentAdapter。
public Fragment getFragment(Bundle bundle, String key) {
int index = bundle.getInt(key, -1);
if (index == -1) {
return null;
} else {
Fragment f = (Fragment)this.mActive.get(index);
if (f == null) {
this.throwException(new IllegalStateException("Fragment no longer exists for key " + key + ": index " + index));
}
return f;
}
}
mActive的恢复代码:在FragmentManager中。对Activity 的FRM 在ActivityOnCreate 的时候就调用恢复了。对于childFragmentManager。在Fragment的onCreate中调用mChildFragmentManager.restoreAllState(p, mChildNonConfig);
void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// If there is no saved state at all, then there can not be
// any nonConfig fragments either, so that is that.
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
List<FragmentManagerNonConfig> childNonConfigs = null;
// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
......
// Build the full list of active fragments, instantiating them from
// their saved state.
mActive = new SparseArray<>(fms.mActive.length);
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
FragmentManagerNonConfig childNonConfig = null;
if (childNonConfigs != null && i < childNonConfigs.size()) {
childNonConfig = childNonConfigs.get(i);
}
Fragment f = fs.instantiate(mHost, mContainer, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.put(f.mIndex, f);
// Now that the fragment is instantiated (or came from being
// retained above), clear mInstance in case we end up re-restoring
// from this FragmentState again.
fs.mInstance = null;
}
}
// Update the target of all retained fragments.
...
// Build the list of currently added fragments.
mAdded.clear();
if (fms.mAdded != null) {
for (int i=0; i<fms.mAdded.length; i++) {
Fragment f = mActive.get(fms.mAdded[i]);
if (f == null) {
throwException(new IllegalStateException(
"No instantiated fragment for index #" + fms.mAdded[i]));
}
f.mAdded = true;
if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f);
if (mAdded.contains(f)) {
throw new IllegalStateException("Already added!");
}
synchronized (mAdded) {
mAdded.add(f);
}
}
}
....
this.mNextFragmentIndex = fms.mNextFragmentIndex;
}
崩溃分析:由下面两行可以确定是在Fragment ,case Fragment.CREATED, 执行视图恢复的时候崩溃的。
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:510)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1445)
崩溃原因:
- 界面恢复。恢复保存的Fragment。所有的一级fragemnt在恢复的时候,如果有child fragment ,也会恢复。
- FragmentMangerImpl.mActive is null ; 在ParentFragment恢复childFragmentManager的时候,其中activie 为空。
- ParentFragment 已经OnViewCreated了, 并且在开始执行f.restoreViewSate.
- 使用了FragmentStatePagerAdapter(getSurpportedFragmentManager()) , 在销毁的时候,会保存当前pager fragment 中的状态。
- 当界面重新创建的时候,会爱OnCreate 的时候,恢复Fragments 。当重新复这个ViewPager 的时候,恢复数据导致崩溃。
崩溃代码位置:
解决办法: 使用FragmentPagerAdapter 替代FragmentStatePagerAdapter。 FragmentPagerAdapter 不会保留状态。但是又引发了刷新问题。
后续问题: 页面不刷新。换成FragmentStatePagerAdapter,是可以刷新的。
问题原因: 历史代码中对adatper 设置新的fragment 数组后,再要求刷新。因为使用的是PagerFragmentAdapter。 在切换页面的时候,使用的是detach方式。而fragmentTag 又和 posotion 相关,导致notify 后,fragment 其实使用的是之前的fragment。(Fragment 复用了) 因此界面不刷新。
解决方案:自定义新的Fragment Pager Adapter。 在destroy view 的时候,采用 remove 而不是 detach 方式。当然这样做存在效率问题。
2, 知识点: FragmentStatePagerAdapter 与 FragmentPagerAdapter 的区别:
- 相同点: 构造函数都需要传入FragmentManager。 都在内部维护了fragments 列表信息。
- 不同点:FragmentStatePagerAdapter 会在destroy 的时候saveState 。 执行remove 。而FragmentPagerAdapter 执行detach。
1) FragmentStatePagerAdapter : destroy 的时候, 先移除所有的adapter 并且保存状态。执行的是remove 在initiate 的时候,执行的addFragment 操作。
instantiateItem:的时候
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);
destroy 的时候:
@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);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
而 FragmentPagerAdapter 执行的只是detach attach 操作。 因此界面是不会刷新的(与Adapter 的具体实现相关)。
@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);
}
3, commitNowAllowLossState 与 commitAllowLossState区别。
commitNowAllowLossState 如果前面调用了addToBackStack 就会导致崩溃。见源码。
4, onSaveInstanceState (View 中也有这个方法,用于保存视图状态) onRestoreInstanceState.
调用顺序: onSaveInstanceState 未必会调用。 先调用onSaveInstanceState 在onStop 前执行。
如果我们没有覆写onSaveInstanceState()方法, 此方法的默认实现会自动保存activity中的某些状态数据, 比如activity中各种UI控件的状态.。android应用框架中定义的几乎所有UI控件都恰当的实现了onSaveInstanceState()方法,因此当activity被摧毁和重建时, 这些UI控件会自动保存和恢复状态数据. 比如EditText控件会自动保存和恢复输入的数据,而CheckBox控件会自动保存和恢复选中状态.开发者只需要为这些控件指定一个唯一的ID(通过设置android:id属性即可), 剩余的事情就可以自动完成了.如果没有为控件指定ID, 则这个控件就不会进行自动的数据保存和恢复操作。
5, fragment 恢复的时机:
当Activity 被以外销毁后,系统会保留当前界面中的Fragment 数据。 在Activity 被系统重新建立的时候, 从新恢复Activity。
- 恢复Fragment 的时机: FragmentActivity OnCreate 的时候,先调用super 。然后再使用FragmentController 执行Fragment 的恢复工作。会先后恢复active ,added , backStack 中的数据。
6, fragment 的addToBackStack 操作,只添加了transaction 事件到栈中。 在pop 的时候,执行对应的逆操作。因此界面中在执行某些添加与某些不添加到栈中这样的操作的时候,就很可能导致问题。参考文章3;