Android Fragment MyFragment 未附加到 Activity
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10919240/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Fragment MyFragment not attached to Activity
提问by nhaarman
I've created a small test app which represents my problem. I'm using ActionBarSherlock to implement tabs with (Sherlock)Fragments.
我创建了一个代表我的问题的小型测试应用程序。我正在使用 ActionBarSherlock 来实现带有 (Sherlock)Fragment 的选项卡。
My code:
TestActivity.java
我的代码:
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
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
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();
}
}
I've added the Thread.sleep
part to simulate downloading data. The code in the onPostExecute
is to simulate use of the Fragment
.
我已经添加了Thread.sleep
部分来模拟下载数据。中的代码onPostExecute
是模拟使用Fragment
.
When I rotate the screen very fast between landscape and portrait, I get an Exception at the onPostExecute
code:
当我在横向和纵向之间非常快速地旋转屏幕时,我在onPostExecute
代码中得到一个异常:
java.lang.IllegalStateException: Fragment MyFragment{410f6060} not attached to Activity
java.lang.IllegalStateException: Fragment MyFragment{410f6060} 未附加到 Activity
I think it's because a new MyFragment
has been created in the meantime, and was attached to the Activity before the AsyncTask
finished. The code in onPostExecute
calls upon a unattached MyFragment
.
我认为这是因为MyFragment
在此期间创建了一个新的,并且在AsyncTask
完成之前附加到了 Activity 。中的代码onPostExecute
调用一个独立的MyFragment
.
But how can I fix this?
但是我该如何解决这个问题?
回答by nhaarman
I've found the very simple answer: isAdded()
:
我找到了非常简单的答案isAdded()
:
Return
true
if the fragment is currently added to its activity.
返回
true
如果片段正在增加其活性。
@Override
protected void onPostExecute(Void result){
if(isAdded()){
getResources().getString(R.string.app_name);
}
}
To avoid onPostExecute
from being called when the Fragment
is not attached to the Activity
is to cancel the AsyncTask
when pausing or stopping the Fragment
. Then isAdded()
would not be necessary anymore. However, it is advisable to keep this check in place.
为避免onPostExecute
在Fragment
未附加到时被调用是在暂停或停止时Activity
取消。那么就没有必要了。但是,建议保留此检查。AsyncTask
Fragment
isAdded()
回答by Tiago
The problem is that you are trying to access resources (in this case, strings) using getResources().getString(), which will try to get the resources from the Activity. See this source code of the Fragment class:
问题是您正在尝试使用 getResources().getString() 访问资源(在本例中为字符串),它将尝试从 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
is the object that holds your Activity.
mHost
是保存您的 Activity 的对象。
Because the Activity might not be attached, your getResources() call will throw an Exception.
由于可能未附加 Activity,因此您的 getResources() 调用将引发异常。
The accepted solution IMHO is not the way to go as you are just hiding the problem. The correct way is just to get the resources from somewhere else that is always guaranteed to exist, like the application context:
恕我直言,接受的解决方案不是可行的方法,因为您只是在隐藏问题。正确的方法是从其他地方获取资源,这些资源总是保证存在,比如应用程序上下文:
youApplicationObject.getResources().getString(...)
回答by luixal
I've faced two different scenarios here:
我在这里遇到了两种不同的情况:
1) When I want the asynchronous task to finish anyway: imagine my onPostExecute does store data received and then call a listener to update views so, to be more efficient, I want the task to finish anyway so I have the data ready when user cames back. In this case I usually do this:
1)当我希望异步任务无论如何完成时:想象一下我的 onPostExecute 确实存储了接收到的数据,然后调用侦听器来更新视图,因此,为了更有效,我希望任务无论如何完成,以便在用户到来时准备好数据背部。在这种情况下,我通常这样做:
@Override
protected void onPostExecute(void result) {
// do whatever you do to save data
if (this.getView() != null) {
// update views
}
}
2) When I want the asynchronous task only to finish when views can be updated: the case you're proposing here, the task only updates the views, no data storage needed, so it has no clue for the task to finish if views are not longer being showed. I do this:
2)当我希望异步任务只在视图可以更新时完成:在你在这里提出的情况下,任务只更新视图,不需要数据存储,所以如果视图是,它没有完成任务的线索不再显示。我这样做:
@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();
}
I've found no problem with this, although I also use a (maybe) more complex way that includes launching tasks from the activity instead of the fragments.
我发现这没有问题,尽管我也使用了一种(可能)更复杂的方法,包括从活动而不是片段启动任务。
Wish this helps someone! :)
希望这对某人有所帮助!:)
回答by Erick Reátegui Diaz
The problem with your code is the way the you are using the AsyncTask, because when you rotate the screen during your sleep thread:
您的代码的问题在于您使用 AsyncTask 的方式,因为当您在睡眠线程期间旋转屏幕时:
Thread.sleep(2000)
the AsyncTask is still working, it is because you didn't cancel the AsyncTask instance properly in onDestroy() before the fragment rebuilds (when you rotate) and when this same AsyncTask instance (after rotate) runs onPostExecute(), this tries to find the resources with getResources() with the old fragment instance(an invalid instance):
AsyncTask 仍在工作,这是因为在片段重建之前(旋转时)并且当同一个 AsyncTask 实例(旋转后)运行 onPostExecute() 之前,您没有在 onDestroy() 中正确取消 AsyncTask 实例,这会尝试找到带有旧片段实例(无效实例)的 getResources() 资源:
getResources().getString(R.string.app_name)
which is equivalent to:
这相当于:
MyFragment.this.getResources().getString(R.string.app_name)
So the final solution is manage the AsyncTask instance (to cancel if this is still working) before the fragment rebuilds when you rotate the screen, and if canceled during the transition, restart the AsyncTask after reconstruction by the aid of a boolean flag:
因此,最终的解决方案是在旋转屏幕时片段重建之前管理 AsyncTask 实例(如果这仍然有效,则取消),如果在过渡期间取消,则在重建后借助布尔标志重新启动 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;
}
}
}
回答by Vinayak
Their are quite trick solution for this and leak of fragment from activity.
他们是非常巧妙的解决方案,并且从活动中泄漏了片段。
So in case of getResource or anything one which is depending on activity context accessing from Fragment it is always check activity status and fragments status as follows
因此,在 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
}
}
回答by superUser
if (getActivity() == null) return;
works also in some cases. Just breaks the code execution from it and make sure the app not crash
在某些情况下也有效。只是中断代码执行并确保应用程序不会崩溃
回答by Aristo Michael
I faced the same problem i just add the singletone instance to get resource as referred by Erick
我遇到了同样的问题,我只是添加了单音实例来获取 Erick 引用的资源
MainFragmentActivity.defaultInstance().getResources().getString(R.string.app_name);
you can also use
你也可以使用
getActivity().getResources().getString(R.string.app_name);
I hope this will help.
我希望这将有所帮助。
回答by ohgodnotanotherone
I faced similar issues when the application settings activity with the loaded preferences was visible. If I would change one of the preferences and then make the display content rotate and change the preference again, it would crash with a message that the fragment (my Preferences class) was not attached to an activity.
当加载首选项的应用程序设置活动可见时,我遇到了类似的问题。如果我更改其中一个首选项,然后使显示内容旋转并再次更改首选项,它会崩溃并显示片段(我的首选项类)未附加到活动的消息。
When debugging it looked like the onCreate() Method of the PreferencesFragment was being called twice when the display content rotated. That was strange enough already. Then I added the isAdded() check outside of the block where it would indicate the crash and it solved the issue.
调试时看起来好像在旋转显示内容时 PreferencesFragment 的 onCreate() 方法被调用了两次。这已经够奇怪了。然后我在块之外添加了 isAdded() 检查,它表明崩溃并解决了问题。
Here is the code of the listener that updates the preferences summary to show the new entry. It is located in the onCreate() method of my Preferences class which extends the PreferenceFragment class:
这是更新首选项摘要以显示新条目的侦听器的代码。它位于我的 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");
}
}
}
};
// ...
}
I hope this will help others!
我希望这会帮助其他人!
回答by Anthony Chuinard
If you extend the Application
class and maintain a static 'global' Context object, as follows, then you can use that instead of the activity to load a String resource.
如果您扩展Application
类并维护静态“全局” Context 对象,如下所示,那么您可以使用它而不是活动来加载字符串资源。
public class MyApplication extends Application {
public static Context GLOBAL_APP_CONTEXT;
@Override
public void onCreate() {
super.onCreate();
GLOBAL_APP_CONTEXT = this;
}
}
If you use this, you can get away with Toast
and resource loading without worrying about lifecycles.
如果你使用它,你可以摆脱Toast
资源加载而不必担心生命周期。
回答by CoolMind
In my case fragment methods have been called after
在我的情况下片段方法已被调用
getActivity().onBackPressed();