ÚLTIMAS INFORMAÇÕES:
Limitei meu problema a ser um problema com o fragmentManager retendo instâncias de fragmentos antigos e meu viewpager estando fora de sincronia com meu FragmentManager. Veja este problema ... http://code.google.com/p/android/issues/detail?id=19211#makechanges . Ainda não tenho ideia de como resolver isso. Alguma sugestão...
Eu tentei depurar isso por um longo tempo e qualquer ajuda seria muito apreciada. Estou usando um FragmentPagerAdapter que aceita uma lista de fragmentos assim:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
A implementação é padrão. Estou usando a biblioteca de computabilidade ActionBarSherlock e v4 para Fragments.
Meu problema é que depois de sair do aplicativo e abrir vários outros aplicativos e voltar, os fragmentos perdem sua referência de volta para a FragmentActivity (ou seja. getActivity() == null
). Não consigo entender por que isso está acontecendo. Tentei definir manualmente, setRetainInstance(true);
mas isso não ajuda. Percebi que isso acontece quando minha FragmentActivity é destruída, no entanto, isso ainda acontece se eu abrir o aplicativo antes de receber a mensagem de log. Existe alguma ideia?
@Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
O adaptador:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
@Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
@Override
public int getCount() {
return this.fragments.size();
}
}
Um dos meus fragmentos foi removido, mas eu expus tudo que foi removido e ainda não funciona ...
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
@Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
@Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
@Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
@Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
@Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
O método de atualização é chamado quando um usuário interage com um fragmento diferente e getActivity está retornando null. Aqui está o método que o outro fragmento está chamando ...
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
Eu acredito que quando o aplicativo é destruído e criado novamente, em vez de apenas iniciar o aplicativo até o fragmento padrão, o aplicativo é iniciado e o viewpager navega para a última página conhecida. Isso parece estranho, o aplicativo não deveria apenas carregar no fragmento padrão?
fonte
Respostas:
Você está enfrentando um problema porque está instanciando e mantendo referências a seus fragmentos fora de
PagerAdapter.getItem
e tentando usar essas referências independentemente do ViewPager. Como o Seraph diz, você tem garantias de que um fragmento foi instanciado / adicionado em um ViewPager em um determinado momento - isso deve ser considerado um detalhe de implementação. Um ViewPager carrega lentamente suas páginas; por padrão, ele carrega apenas a página atual e a página à esquerda e à direita.Se você colocar seu aplicativo em segundo plano, os fragmentos que foram adicionados ao gerenciador de fragmentos serão salvos automaticamente. Mesmo se seu aplicativo for encerrado, essas informações serão restauradas quando você reiniciá-lo.
Agora, considere que você visualizou algumas páginas, Fragmentos A, B e C. Você sabe que eles foram adicionados ao gerenciador de fragmentos. Como você está usando
FragmentPagerAdapter
e nãoFragmentStatePagerAdapter
, esses fragmentos ainda serão adicionados (mas potencialmente destacados) quando você rolar para outras páginas.Considere que você coloca seu aplicativo em segundo plano e ele é encerrado. Quando você voltar, o Android se lembrará de que você costumava ter os fragmentos A, B e C no gerenciador de fragmentos e, portanto, ele os recria para você e depois os adiciona. No entanto, aqueles que são adicionados ao gerenciador de fragmentos agora NÃO são os que você tem em sua lista de fragmentos em sua atividade.
O FragmentPagerAdapter não tentará chamar
getPosition
se já houver um fragmento adicionado para essa posição de página específica. Na verdade, como o fragmento recriado pelo Android nunca será removido, você não tem esperança de substituí-lo por uma chamada paragetPosition
. Também é muito difícil obter uma referência a ele, porque foi adicionado com uma tag que você não conhece. Isso ocorre por design; você é desencorajado a mexer com os fragmentos que o view pager está gerenciando. Você deve realizar todas as suas ações dentro de um fragmento, comunicando-se com a atividade e solicitando a mudança para uma página específica, se necessário.Agora, de volta ao seu problema com a atividade ausente. Chamar
pagerAdapter.getItem(1)).update(id, name)
depois que tudo isso aconteceu retorna o fragmento em sua lista, que ainda não foi adicionado ao gerenciador de fragmentos e, portanto, não terá uma referência de atividade. Eu sugeriria que seu método de atualização deve modificar alguma estrutura de dados compartilhada (possivelmente gerenciada pela atividade) e, em seguida, quando você mover para uma página específica, ele pode desenhar a si mesmo com base nesses dados atualizados.fonte
instantiateItem
e você deve fazê-lo naonCreate
prática. veja os detalhes aqui: stackoverflow.com/questions/14035090/…Encontrei uma solução simples que funcionou para mim.
Faça com que seu adaptador de fragmento estenda FragmentStatePagerAdapter em vez de FragmentPagerAdapter e substitua o método onSave para retornar nulo
Isso evita que o Android recrie o fragmento
Um dia depois, encontrei outra solução melhor.
Peça
setRetainInstance(true)
todos os seus fragmentos e salve as referências a eles em algum lugar. Fiz isso em uma variável estática em minha atividade, porque ela é declarada como singleTask e os fragmentos podem permanecer os mesmos o tempo todo.Dessa forma, o Android não recriará fragmentos, mas usará as mesmas instâncias.
fonte
setRetainInstance(true)
comFragmentPagerAdapter
. Tudo funciona bem. Mas quando giro o dispositivo, o adaptador ainda tem os fragmentos, mas os fragmentos não são mostrados. Os métodos de ciclo de vida dos fragmentos também não são chamados. Alguém pode ajudar?Resolvi esse problema acessando meus fragmentos diretamente por meio do FragmentManager em vez de por meio do FragmentPagerAdapter. Primeiro, preciso descobrir a tag do fragmento gerado automaticamente pelo FragmentPagerAdapter ...
Então, simplesmente obtenho uma referência a esse fragmento e faço o que preciso, então ...
Dentro de meus fragmentos, defino o
setRetainInstance(false);
para que possa adicionar manualmente valores ao pacote savedInstanceState.e então no OnCreate eu pego aquela chave e restauro o estado do fragmento conforme necessário. Uma solução fácil que foi difícil (pelo menos para mim) de descobrir.
fonte
FragmentByTag
no ViewPager.Solução testada de trabalho global.
getSupportFragmentManager()
mantém a referência nula algumas vezes e o visualizador de pager não cria novos, pois encontra a referência para o mesmo fragmento. Então, para superar, esse usogetChildFragmentManager()
resolve o problema de maneira simples.Não faça isso:
new PagerAdapter(getSupportFragmentManager(), fragments);
Faça isso:
new PagerAdapter(getChildFragmentManager() , fragments);
fonte
FragmentStatePagerAdapter(activity!!.supportFragmentManager)
uma mais fácil de olharFragmentStatePagerAdapter(childFragmentManager)
:)Não tente interagir entre fragmentos no ViewPager. Você não pode garantir que outro fragmento anexado ou mesmo exista. Em vez de alterar o título da barra de ação do fragmento, você pode fazer isso a partir de sua atividade. Use o padrão de interface padrão para isso:
fonte
Você pode remover os fragmentos ao destruir o viewpager, no meu caso, removi-os
onDestroyView()
do meu fragmento:fonte
ViewPager
também seja baseado nochildFragmentManager
adaptador (nãofragmentManager
). Outra variante também funciona: não usaronDestroyView
, mas remover fragmentos filhos antes daViewPager
criação do adaptador.Depois de algumas horas procurando por um problema semelhante, acho que tenho outra solução. Este pelo menos funcionou para mim e eu só tenho que mudar algumas linhas.
Este é o problema que eu tive, eu tenho uma atividade com um pager de exibição que usa um FragmentStatePagerAdapter com dois Fragments. Tudo funciona bem até que eu force a atividade a ser destruída (opções do desenvolvedor) ou gire a tela. Eu mantenho uma referência aos dois fragmentos depois que eles são criados dentro do método getItem.
Nesse ponto, a atividade será criada novamente e tudo funciona bem neste ponto, mas perdi a referência aos meus fragmetns porque getItem não é chamado novamente.
Foi assim que resolvi esse problema, dentro do FragmentStatePagerAdapter:
Você não receberá uma chamada em getItem novamente se o adaptador já tiver uma referência a ele internamente e você não deve alterar isso. Em vez disso, você pode obter o fragmento que está sendo usado observando este outro método instantiateItem (), que será chamado para cada um de seus fragmentos.
Espero que ajude alguém.
fonte
Como o FragmentManager se encarregará de restaurar seus Fragments para você assim que o método onResume () for chamado, faço com que o fragmento chame a atividade e o adicione a uma lista. No meu caso, estou armazenando tudo isso na minha implementação do PagerAdapter. Cada fragmento conhece sua posição porque é adicionado aos argumentos do fragmento na criação. Agora, sempre que preciso manipular um fragmento em um índice específico, tudo o que tenho a fazer é usar a lista do meu adaptador.
A seguir está um exemplo de um adaptador para um ViewPager customizado que aumentará o fragmento conforme ele se move para o foco e o redimensionará conforme ele sai de foco. Além das classes Adaptador e Fragmento, tenho aqui tudo que você precisa é que a atividade pai seja capaz de fazer referência à variável do adaptador e pronto.
Adaptador
Fragmento
fonte
Minha solução: configurei quase todas as visualizações como
static
. Agora meu aplicativo interage perfeitamente. Poder chamar os métodos estáticos de qualquer lugar talvez não seja um bom estilo, mas por que brincar com um código que não funciona? Li muitas perguntas e suas respostas aqui no SO e nenhuma solução trouxe sucesso (para mim).Eu sei que ele pode vazar a memória e desperdiçar pilha, e meu código não caberá em outros projetos, mas não tenho medo disso - testei o aplicativo em diferentes dispositivos e condições, sem nenhum problema, o Android A plataforma parece ser capaz de lidar com isso. A IU é atualizada a cada segundo e mesmo em um dispositivo S2 ICS (4.0.3), o aplicativo é capaz de lidar com milhares de marcadores geográficos.
fonte
Eu enfrentei o mesmo problema, mas meu ViewPager estava dentro de um TopFragment que criou e configurou um adaptador usando
setAdapter(new FragmentPagerAdapter(getChildFragmentManager()))
.onAttachFragment(Fragment childFragment)
Corrigi esse problema substituindo no TopFragment desta forma:Como já se sabe (veja as respostas acima), quando o childFragmentManager se recria, ele também cria os fragmentos que estavam dentro do viewPager.
O importante é que depois disso, ele chama onAttachFragment e agora temos uma referência ao novo fragmento recriado!
Espero que isso ajude alguém a conseguir esse Q antigo como eu :)
fonte
Resolvi o problema salvando os fragmentos no SparceArray:
fonte
Só para você saber ...
Adicionando à ladainha de desgraças com essas classes, há um bug bastante interessante que vale a pena compartilhar.
Estou usando um ViewPager para navegar em uma árvore de itens (selecione um item e o pager de visualização anima a rolagem para a direita e o próximo ramo aparece, navegue de volta, e o ViewPager rola na direção oposta para retornar ao nó anterior) .
O problema surge quando empurro e retiro fragmentos do final do FragmentStatePagerAdapter. É inteligente o suficiente para notar que os itens mudam e inteligente o suficiente para criar e substituir um fragmento quando o item é alterado. Mas não é inteligente o suficiente para descartar o estado do fragmento, ou inteligente o suficiente para cortar os estados do fragmento salvos internamente quando o tamanho do adaptador muda. Portanto, quando você abre um item e empurra um novo para o final, o fragmento do novo item obtém o estado salvo do fragmento do item antigo, o que causou estragos absolutos em meu código. Meus fragmentos carregam dados que podem exigir muito trabalho para recuperá-los da Internet, então não salvar o estado realmente não era uma opção.
Não tenho uma solução alternativa limpa. Eu usei algo assim:
Uma solução imperfeita porque a nova instância do fragmento ainda obtém um SavedState Bundle, mas pelo menos não carrega dados obsoletos.
fonte
Como as pessoas não costumam ler comentários, aqui está uma resposta que em grande parte duplica o que escrevi aqui :
a causa raiz do problema é o fato de que o sistema Android não chama
getItem
para obter fragmentos que são realmente exibidos, masinstantiateItem
. Este método primeiro tenta pesquisar e reutilizar uma instância de fragmento para uma determinada guia emFragmentManager
. Somente se essa pesquisa falhar (o que acontece apenas na primeira vez, quandoFragmentManager
é criado),getItem
é chamado. É por motivos óbvios não recriar fragmentos (que podem ser pesados), por exemplo, cada vez que um usuário gira seu dispositivo.Para resolver isso, em vez de criar fragmentos com
Fragment.instantiate
em sua atividade, você deve fazê-lo compagerAdapter.instantiateItem
e todas essas chamadas devem ser cercadas porstartUpdate/finishUpdate
chamadas de método que iniciam / confirmam transações de fragmentos respectivamente.getItem
deve ser o local onde os fragmentos são realmente criados usando seus respectivos construtores.fonte