Eu criei um pequeno aplicativo de teste que representa meu problema. Estou usando o ActionBarSherlock para implementar guias com fragmentos (Sherlock).
Meu código:
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();
}
}
Eu adicionei a Thread.sleep
peça para simular o download de dados. O código no onPostExecute
é para simular o uso do Fragment
.
Quando eu giro a tela muito rapidamente entre paisagem e retrato, recebo uma exceção no onPostExecute
código:
java.lang.IllegalStateException: fragmento MyFragment {410f6060} não anexado à atividade
Eu acho que é porque um novo MyFragment
foi criado nesse meio tempo e foi anexado à Atividade antes do AsyncTask
término. O código onPostExecute
chama um desanexado MyFragment
.
Mas como posso corrigir isso?
android
android-fragments
actionbarsherlock
nhaarman
fonte
fonte
mView = inflater.inflate(R.layout.my_layout, container, false)
E agora usar este ponto de vista quando você deseja obter recursos:mView.getResources().***
. Isso me ajudou a corrigir esse bug.Context
que está anexado ao seu `mView`.mView
no onDestroy?Respostas:
Eu encontrei a resposta muito simples
isAdded()
:Para evitar
onPostExecute
ser chamado quando oFragment
não estiver conectado ao,Activity
é para cancelar oAsyncTask
quando pausar ou parar oFragment
. EntãoisAdded()
não seria mais necessário. No entanto, é aconselhável manter essa verificação no local.fonte
isDetached()
, que foi adicionado no nível 13 da APIO problema é que você está tentando acessar recursos (nesse caso, strings) usando getResources (). GetString (), que tentará obter os recursos da Activity. Veja este código fonte da classe Fragment:
mHost
é o objeto que contém sua atividade.Como a atividade pode não estar anexada, sua chamada getResources () lançará uma exceção.
A solução aceita IMHO não é o caminho a seguir, pois você está apenas escondendo o problema. A maneira correta é apenas obter os recursos de outro lugar que é sempre garantido que existe, como o contexto do aplicativo:
fonte
getString()
quando meu fragmento foi pausado. ObrigadoEu enfrentei dois cenários diferentes aqui:
1) Quando eu quero que a tarefa assíncrona termine de qualquer maneira: imagine que o meu onPostExecute armazene dados recebidos e chame um ouvinte para atualizar as visualizações, para que, para ser mais eficiente, eu queira que a tarefa termine assim mesmo, para que eu tenha os dados prontos quando o usuário acessar de volta. Neste caso, eu costumo fazer isso:
2) Quando eu quero que a tarefa assíncrona termine apenas quando as visualizações puderem ser atualizadas: no caso que você está propondo aqui, a tarefa atualiza apenas as visualizações, não é necessário armazenamento de dados, portanto não há nenhuma pista para a tarefa terminar se as visualizações forem não está mais sendo mostrado. Eu faço isso:
Não encontrei nenhum problema com isso, embora eu também use uma maneira (talvez) mais complexa que inclua iniciar tarefas da atividade em vez de fragmentos.
Desejo que isso ajude alguém! :)
fonte
O problema com o seu código é o modo como você está usando o AsyncTask, porque quando você gira a tela durante o encadeamento de suspensão:
o AsyncTask ainda está funcionando, é porque você não cancelou a instância do AsyncTask corretamente em onDestroy () antes da reconstrução do fragmento (quando você gira) e quando essa mesma instância do AsyncTask (após a rotação) é executada emPostExecute (), isso tenta encontrar os recursos com getResources () com a instância antiga do fragmento (uma instância inválida):
que é equivalente a:
Portanto, a solução final é gerenciar a instância do AsyncTask (para cancelar se ainda estiver funcionando) antes que o fragmento seja reconstruído quando você girar a tela e, se cancelado durante a transição, reinicie o AsyncTask após a reconstrução com o auxílio de um sinalizador booleano:
fonte
getResources().***
usandoFragments.this.getResource().***
ajudouSão uma solução bastante complicada para isso e vazamento de fragmento de atividade.
Portanto, no caso de getResource ou qualquer coisa que dependa do contexto de atividade acessado pelo Fragment, verifique sempre o status da atividade e o status dos fragmentos da seguinte maneira
fonte
isAdded
é o suficiente. Eu nunca vi uma situação quandogetString()
havia caído seisAdded == true
. Tem certeza de que uma atividade foi mostrada e um fragmento foi anexado?funciona também em alguns casos. Apenas interrompe a execução do código e verifique se o aplicativo não falha
fonte
Enfrentei o mesmo problema: basta adicionar a instância singletone para obter o recurso, conforme referido por Erick
você também pode usar
Espero que isso ajude.
fonte
Eu enfrentei problemas semelhantes quando a atividade de configurações do aplicativo com as preferências carregadas estava visível. Se eu alterasse uma das preferências e, em seguida, fizesse o conteúdo de exibição girar e alterasse a preferência novamente, haveria uma falha na mensagem de que o fragmento (minha classe Preferences) não estava anexado a uma atividade.
Ao depurar, parecia que o método onCreate () do PreferencesFragment estava sendo chamado duas vezes quando o conteúdo da exibição girou. Isso já era estranho o suficiente. Em seguida, adicionei a verificação isAdded () fora do bloco, onde indicava a falha e resolvia o problema.
Aqui está o código do ouvinte que atualiza o resumo das preferências para mostrar a nova entrada. Está localizado no método onCreate () da minha classe Preferences, que estende a classe PreferenceFragment:
Espero que isso ajude os outros!
fonte
Se você estender a
Application
classe e manter um objeto de Contexto 'global' estático, conforme a seguir, poderá usá-lo em vez da atividade para carregar um recurso String.Se você usar isso, poderá se safar
Toast
e carregar recursos sem se preocupar com os ciclos de vida.fonte
No meu caso, métodos de fragmento foram chamados após
fonte
Um post antigo, mas fiquei surpreso com a resposta mais votada.
A solução adequada para isso deve ser cancelar a asynctask no onStop (ou onde for apropriado no seu fragmento). Dessa forma, você não introduz um vazamento de memória (uma assíncrona mantém uma referência ao seu fragmento destruído) e tem um controle melhor do que está acontecendo no seu fragmento.
fonte
cancel
pode não impedir aonPostExecute
invocação.