碎片MyFragment不附加到活动

我创build了一个小型testing应用程序,代表我的问题。 我使用ActionBarSherlock来实现(Sherlock)片段的选项卡。

我的代码: TestActivity.java

 public class TestActivity extends SherlockFragmentActivity { private ActionBar actionBar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setupTabs(savedInstanceState); } private void setupTabs(Bundle savedInstanceState) { actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); addTab1(); addTab2(); } private void addTab1() { Tab tab1 = actionBar.newTab(); tab1.setTag("1"); String tabText = "1"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "1", MyFragment.class)); actionBar.addTab(tab1); } private void addTab2() { Tab tab1 = actionBar.newTab(); tab1.setTag("2"); String tabText = "2"; tab1.setText(tabText); tab1.setTabListener(new TabListener<MyFragment>(TestActivity.this, "2", MyFragment.class)); actionBar.addTab(tab1); } } 

TabListener.java

 public class TabListener<T extends SherlockFragment> implements com.actionbarsherlock.app.ActionBar.TabListener { private final SherlockFragmentActivity mActivity; private final String mTag; private final Class<T> mClass; public TabListener(SherlockFragmentActivity activity, String tag, Class<T> clz) { mActivity = activity; mTag = tag; mClass = clz; } /* The following are each of the ActionBar.TabListener callbacks */ public void onTabSelected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); // Check if the fragment is already initialized if (preInitializedFragment == null) { // If not, instantiate and add it to the activity SherlockFragment mFragment = (SherlockFragment) SherlockFragment.instantiate(mActivity, mClass.getName()); ft.add(android.R.id.content, mFragment, mTag); } else { ft.attach(preInitializedFragment); } } public void onTabUnselected(Tab tab, FragmentTransaction ft) { SherlockFragment preInitializedFragment = (SherlockFragment) mActivity.getSupportFragmentManager().findFragmentByTag(mTag); if (preInitializedFragment != null) { // Detach the fragment, because another one is being attached ft.detach(preInitializedFragment); } } public void onTabReselected(Tab tab, FragmentTransaction ft) { // User selected the already selected tab. Usually do nothing. } } 

MyFragment.java

 public class MyFragment extends SherlockFragment { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) { } return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); } }.execute(); } } 

我已经添加了Thread.sleep部分来模拟下载数据。 onPostExecute的代码是模拟Fragment使用。

当我在风景和肖像之间非常快地旋转屏幕时,我在onPostExecute代码中得到一个exception:

java.lang.IllegalStateException:片段MyFragment {410f6060}未附加到活动

我认为这是因为同时创build了一个新的MyFragment ,并且在AsyncTask完成之前附加到Activity。 onPostExecute的代码调用一个未连接的MyFragment

但是我怎样才能解决这个问题?

我发现了非常简单的答案: isAdded()

如果片段当前被添加到其活动,则返回true

 @Override protected void onPostExecute(Void result){ if(isAdded()){ getResources().getString(R.string.app_name); } } 

为避免在Fragment未附加到Activity时调用onPostExecute ,在暂停或停止Fragment时取消AsyncTask 。 然后isAdded()将不再是必要的。 但是,最好保持这个检查。

我在这里遇到了两种不同的情况:

1)当我想要完成asynchronous任务时:假设我的onPostExecute存储了接收到的数据,然后调用一个监听器来更新视图,为了更高效,我希望任务完成,所以我有数据准备好,当用户来了背部。 在这种情况下,我通常这样做:

 @Override protected void onPostExecute(void result) { // do whatever you do to save data if (this.getView() != null) { // update views } } 

2)当我只想要asynchronous任务完成时,可以更新视图:你在这里提出的情况下,任务只更新视图,没有数据存储需要,所以没有任何线索完成任务如果意见是不再显示。 我这样做:

 @Override protected void onStop() { // notice here that I keep a reference to the task being executed as a class member: if (this.myTask != null && this.myTask.getStatus() == Status.RUNNING) this.myTask.cancel(true); super.onStop(); } 

我发现没有问题,虽然我也使用(也许)更复杂的方式,包括从活动而不是片段启动任务。

希望这可以帮助别人! 🙂

您的代码的问题是您使用AsyncTask的方式,因为当您在睡眠线程中旋转屏幕时:

 Thread.sleep(2000) 

AsyncTask仍然在工作,这是因为你没有在片段重build(当你旋转)之前在onDestroy()中正确地取消AsyncTask实例,并且当这个相同的AsyncTask实例(在rotate之后)运行onPostExecute()时,它试图find带有旧片段实例(无效实例)的getResources()的资源:

 getResources().getString(R.string.app_name) 

相当于:

 MyFragment.this.getResources().getString(R.string.app_name) 

所以最终的解决scheme是在你旋转屏幕的时候重新生成片段之前pipe理AsyncTask实例(取消如果仍然有效的话),如果在转换过程中被取消,在重build之后借助一个布尔标志重启AsyncTask:

 public class MyFragment extends SherlockFragment { private MyAsyncTask myAsyncTask = null; private boolean myAsyncTaskIsRunning = true; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(savedInstanceState!=null) { myAsyncTaskIsRunning = savedInstanceState.getBoolean("myAsyncTaskIsRunning"); } if(myAsyncTaskIsRunning) { myAsyncTask = new MyAsyncTask(); myAsyncTask.execute(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean("myAsyncTaskIsRunning",myAsyncTaskIsRunning); } @Override public void onDestroy() { super.onDestroy(); if(myAsyncTask!=null) myAsyncTask.cancel(true); myAsyncTask = null; } public class MyAsyncTask extends AsyncTask<Void, Void, Void>() { public MyAsyncTask(){} @Override protected void onPreExecute() { super.onPreExecute(); myAsyncTaskIsRunning = true; } @Override protected Void doInBackground(Void... params) { try { Thread.sleep(2000); } catch (InterruptedException ex) {} return null; } @Override protected void onPostExecute(Void result){ getResources().getString(R.string.app_name); myAsyncTaskIsRunning = false; myAsyncTask = null; } } } 

问题是你试图使用getResources()。getString()来访问资源(在这个例子中是string),它将尝试从Activity获取资源。 看到这个Fragment类的源代码:

  /** * Return <code>getActivity().getResources()</code>. */ final public Resources getResources() { if (mHost == null) { throw new IllegalStateException("Fragment " + this + " not attached to Activity"); } return mHost.getContext().getResources(); } 

mHost是持有你的活动的对象。

由于该活动可能未被附加,您的getResources()调用将抛出一个exception。

恕我直言,恕不接受的解决scheme,因为你只是隐藏问题。 正确的方法是从其他地方获取始终保证存在的资源,如应用程序上下文:

 youApplicationObject.getResources().getString(...) 

他们对于这个和活动的片段泄漏都是非常诡计的解决scheme。

所以在getResource的情况下或任何一个取决于从Fragment访问的活动上下文的情况下,总是检查活动状态和片段状态如下

  Activity activity = getActivity(); if(activity != null && isAdded()) getResources().getString(R.string.no_internet_error_msg); //Or any other depends on activity context to be live like dailog } } 

我遇到了同样的问题,我只是添加单个实例来获取由Erick引用的资源

 MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name); 

你也可以使用

 getActivity().getResources().getString(R.string.app_name); 

我希望这会有所帮助。

加载首选项的应用程序设置活动可见时,我面临类似的问题。 如果我要更改其中一个首选项,然后再旋转显示内容并再次更改该首选项,它将会崩溃,并显示片段(我的首选项类)未附加到活动。

debugging时,看起来像显示内容旋转时,PreferencesFragment的onCreate()方法被调用两次。 这已经够奇怪了。 然后,我在块的外部添加了isAdded()检查,它将指示崩溃并解决了问题。

以下是更新首选项摘要以显示新条目的侦听器的代码。 它位于我的Preferences类的onCreate()方法中,它扩展了PreferenceFragment类:

 public static class Preferences extends PreferenceFragment { SharedPreferences.OnSharedPreferenceChangeListener listener; @Override public void onCreate(Bundle savedInstanceState) { // ... listener = new SharedPreferences.OnSharedPreferenceChangeListener() { @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // check if the fragment has been added to the activity yet (necessary to avoid crashes) if (isAdded()) { // for the preferences of type "list" set the summary to be the entry of the selected item if (key.equals(getString(R.string.pref_fileviewer_textsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Display file content with a text size of " + listPref.getEntry()); } else if (key.equals(getString(R.string.pref_fileviewer_segmentsize))) { ListPreference listPref = (ListPreference) findPreference(key); listPref.setSummary("Show " + listPref.getEntry() + " bytes of a file at once"); } } } }; // ... } 

我希望这会帮助别人!

 if (getActivity() == null) return; 

在某些情况下也有效。 只要打破它的代码执行,并确保应用程序不会崩溃

在我的情况下,碎片方法已被调用

 getActivity().onBackPressed(); 

一个旧的post,但我最惊讶的是最广泛的答案。

适当的解决scheme应该是取消onStop中的asynctask(或在片段中的任何适当的地方)。 这样你就不会引入内存泄漏(一个asynctask保存对被破坏片段的引用),并且可以更好地控制片段中发生的事情。

 @Override public void onStop() { super.onStop(); mYourAsyncTask.cancel(true); } 

如果扩展Application类并维护一个静态的“全局”Context对象,如下所示,那么可以使用它来代替该活动来加载String资源。

 public class MyApplication extends Application { public static Context GLOBAL_APP_CONTEXT; @Override public void onCreate() { super.onCreate(); GLOBAL_APP_CONTEXT = this; } } 

如果你使用这个,你可以摆脱Toast和资源加载而不用担心生命周期。