可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I am getting a very puzzling bug that I have no idea how to even begin working through.
I have a simple app with one activity, the views are implemented with Fragments. One of the fragments has a ViewPager inside of it; so I decided I that I wanted to use the getChildFragmentManager class of the v4 support library. I also had to use ActionBarSherlock, which caused a problem, because it does not ship with the v11 of the v4 library.
I fixed this by replacing the v4 support library in ABS with the v11 library, and everything compiled and appeared to be working, including the ViewPager.
Here is the strange part:
The first time the fragment with the ViewPager opens, it works properly; but the SECOND time it is navigated to, the app crashes, giving a useless stack trace. From debugging, I discovered that the problem was with the FragmentManager returned by getChildFragmentManager; it throws the No Activity error.
Does anybody have any idea what could be causing this?
I will post code that you think is relevant.
Thank you,
David
回答1:
I followed the link in jeremyvillalobos answer (which was very helpful) that led me to this workaround.
public class CustomFragment extends Fragment {
private static final Field sChildFragmentManagerField;
static {
Field f = null;
try {
f = Fragment.class.getDeclaredField("mChildFragmentManager");
f.setAccessible(true);
} catch (NoSuchFieldException e) {
Log.e(LOGTAG, "Error getting mChildFragmentManager field", e);
}
sChildFragmentManagerField = f;
}
@Override
public void onDetach() {
super.onDetach();
if (sChildFragmentManagerField != null) {
try {
sChildFragmentManagerField.set(this, null);
} catch (Exception e) {
Log.e(LOGTAG, "Error setting mChildFragmentManager field", e);
}
}
}
...
}
It works for me well, without the need to reinstantiate the fragment.
回答2:
This appears to be a bug reported at
https://code.google.com/p/android/issues/detail?id=42601
The variable
FragmentManagerImpl mChildFragmentManager;
In Fragment.java is not set to null on detach. So the next time the fragment is loaded, the variable still points to the last parent.
As discussed on that thread, a workaround is to reinstantiate the Fragment.
In my case, I was switching between fragments in an ActionBar tab. The troubled Fragment has nested Fragments and was crashing the app when coming back to the file loader Fragment. So this is the work-around code:
class MainTabsListener implements ActionBar.TabListener {
public Fragment fragment;
public int TabPosition;
public MainTabsListener(Fragment fragment, int tab_position) {
this.fragment = fragment;
TabPosition = tab_position;
}
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
CurrentFragment = fragment;
CurrentTabSelectedPos = TabPosition;
/**
* This is a work-around for Issue 42601
* https://code.google.com/p/android/issues/detail?id=42601
*
* The method getChildFragmentManager() does not clear up
* when the Fragment is detached.
*/
if( fragment instanceof FileLoaderFragment ){
fragment = reinstatiateFileLoaderFragment();
}
ft.replace(R.id.fragment_container, fragment);
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
ft.remove(fragment);
}
}
回答3:
sadly, it's a bug of support v4, still there :(
When you choose other Fragment via Navigation Drawer or other thing like it, the fragment which has sub-fragments is detached. So those sub-fragments' fragmentManager(getChildFragmentManager()) is no longer exist. while those fragments return, error occurred. Bomb!
Obviously, support v4 should clean mChildFragmentManager in onDetach(), but it didn't, so we must depend on ourselves. such as following codes in the fragment which has sub-fragments:
@Override
public void onDetach() {
try {
Field childFragmentManager = Fragment.class.getDeclaredField("mChildFragmentManager");
childFragmentManager.setAccessible(true);
childFragmentManager.set(this, null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
super.onDetach();
}
Everything will be OK, have a good day :)
回答4:
may your error is android.view.InflateException?
if so,you should inflate Fragment dynamically ,don't use XML layout.
and, you should not target fragment which is defined XML Layout to Fragment Transaction.
回答5:
I have the same problem.
In an activity, i have 3 bouttons to switch fragment with transaction.replace(...)
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.layout_tablet_paneau, mLigneMessageFragment);
One of this fragment contain a ViewPage with a custom FragmentPagerAdapter. Therefore, i must do getChildFragmentManager(), to alowed nested Fragments.
the constructor is here:
public LignePagerAdapter(Fragment ligneMessageTabletFragment) {
super(ligneMessageTabletFragment.getChildFragmentManager());
}
So i have the same error: The first show of this fragment wrorks, but when i show other fragment and go back on this one, i get this exception:
02-26 11:57:50.798: D/ACRA(776): Wait for Toast + worker ended. Kill Application ? true
02-26 11:57:50.798: E/AndroidRuntime(776): FATAL EXCEPTION: main
02-26 11:57:50.798: E/AndroidRuntime(776): java.lang.IllegalStateException: No activity
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1075)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1070)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManager.java:1861)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:1474)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:931)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1088)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1444)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:429)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.os.Handler.handleCallback(Handler.java:587)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.os.Handler.dispatchMessage(Handler.java:92)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.os.Looper.loop(Looper.java:132)
02-26 11:57:50.798: E/AndroidRuntime(776): at android.app.ActivityThread.main(ActivityThread.java:4126)
02-26 11:57:50.798: E/AndroidRuntime(776): at java.lang.reflect.Method.invokeNative(Native Method)
02-26 11:57:50.798: E/AndroidRuntime(776): at java.lang.reflect.Method.invoke(Method.java:491)
02-26 11:57:50.798: E/AndroidRuntime(776): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
02-26 11:57:50.798: E/AndroidRuntime(776): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
02-26 11:57:50.798: E/AndroidRuntime(776): at dalvik.system.NativeStart.main(Native Method)
02-26 11:57:52.818: I/dalvikvm(776): threadid=4: reacting to signal 3
02-26 11:57:52.818: I/dalvikvm(776): Wrote stack traces to '/data/anr/traces.txt'
So instead of put the same instance of fragment, i can re-create this so it fix the problem, but i seem not efficient.
transaction.replace(R.id.layout_tablet_paneau, LigneMessageTabletFragment.newInstance());
回答6:
Refer @lopisan answer:
I use his solution for a long time.
BUT I figure out have a better way to do this!
If mChildFragmentManager.mActivity is null, then set mChildFragmentManager to null. When performActivityCreated method.
@Override
void performActivityCreated(Bundle savedInstanceState) {
if (getFragmentManagerActivity(mChildFragmentManager) == null) {
setChildFragmentManager(this, null);
}
super.performActivityCreated(savedInstanceState);
}
public static FragmentActivity getFragmentManagerActivity(FragmentManager fragmentManager) {
FragmentManagerImpl fm = (FragmentManagerImpl) fragmentManager;
return fm.mActivity;
}
private static final Field sChildFragmentManagerField;
static {
/**
* BUG : causing a java.IllegalStateException error, No Activity, only
* when navigating to Fragment for the SECOND time
* http://stackoverflow.com /questions/15207305/getting-the-error-java-lang-illegalstateexception-activity-has-been-destroyed
* http://stackoverflow.com/questions/14929907/causing-a-java-illegalstateexception-error-no-activity-only-when-navigating-to
*/
Field f = null;
try {
f = Fragment.class.getDeclaredField("mChildFragmentManager");
f.setAccessible(true);
} catch (NoSuchFieldException e) {
Log.e(TAG, "Error getting mChildFragmentManager field", e);
}
sChildFragmentManagerField = f;
}
public static void setChildFragmentManager(Fragment fragment, FragmentManager fragmentManager) {
if (sChildFragmentManagerField != null) {
try {
sChildFragmentManagerField.set(fragment, fragmentManager);
} catch (Exception e) {
Log.e(TAG, "Error setting mChildFragmentManager field", e);
}
}
}
回答7:
I am stuck with the same bug using BottomNavigationView switch and replacement of fragment causing App crash. With normal speed it was working perfectly fine, if the user do fast switch it used to crash saying
IllegalStateException No Activity
in Logs
Before I have
fragmentA = new FragmentA();
fragmentB = new FragmentB();
fragmentC = new FragmentC();
mBottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.fragmentA:
replaceFragment(fragmentA);
case R.id.fragmentB:
repalceFragment(fragmentB);
case R.id.fragmentC:
repalceFragment(fragmentC);
}
};
I think this pre-creation of fragment and using them was for replace is creating the issue, now I made it to re-create everytime. It started working fine
mBottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.fragmentA:
fragment = new FragmentA();
replaceFragment(fragment);
case R.id.fragmentB:
fragment = new FragmentB();
repalceFragment(fragment);
case R.id.fragmentC:
fragment = new FragmentC();
repalceFragment(fragment);
}
};
public void repalceFragment(Fragment fragment) {
if (!this.isFinishing()) {
if (!fragment.isAdded()) {
if (!this.mDisplayedFragment.isRemoving()) {
getFragmentManager().beginTransaction().replace(R.id.layout_fragment_container, fragment).commit();
this.mDisplayedFragment = fragment;
}
}
}
}
Hope its helpful for some, I did too much of defensive coding but it solved my issue of fast switching of fragments
回答8:
Please check number 32 of this link :
Child FragmentManager retained after detaching parent Fragment
Steps:
- Download and copy this class to your project
- extend every Fragment with the support v4 one(best practice)
- use that class instead of FragmentStatePagerAdapter