Ciclo de vida do Android Fragment sobre alterações de orientação

120

Usando o pacote de compatibilidade para o destino 2.2 usando Fragmentos.

Depois de recodificar uma atividade para usar fragmentos em um aplicativo, não consegui que as mudanças de orientação / gerenciamento de estado funcionassem, por isso criei um pequeno aplicativo de teste com uma única FragmentActivity e um único fragmento.

Os logs das alterações de orientação são estranhos, com várias chamadas para os fragmentos OnCreateView.

Obviamente, estou perdendo algo - como desvendar o fragmento e anexá-lo novamente, em vez de criar uma nova instância, mas não consigo ver nenhuma documentação que indique onde estou errado.

Alguém pode esclarecer o que estou fazendo de errado aqui, por favor. obrigado

O log é o seguinte após as alterações de orientação.

Initial creation
12-04 11:57:15.808: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:15.945: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:16.081: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 1
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:57:39.031: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:57:39.031: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:57:39.167: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null


Orientation Change 2
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.162: D/FragmentTest.FragmentOne(3143): onSaveInstanceState
12-04 11:58:32.361: D/FragmentTest.FragmentTestActivity(3143): onCreate
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.361: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState not null
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView
12-04 11:58:32.498: D/FragmentTest.FragmentOne(3143): OnCreateView->SavedInstanceState null

Atividade principal (FragmentActivity)

public class FragmentTestActivity extends FragmentActivity {
/** Called when the activity is first created. */

private static final String TAG = "FragmentTest.FragmentTestActivity";


FragmentManager mFragmentManager;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Log.d(TAG, "onCreate");

    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
}

E o fragmento

public class FragmentOne extends Fragment {

private static final String TAG = "FragmentTest.FragmentOne";

EditText mEditText;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {

    Log.d(TAG, "OnCreateView");

    View v = inflater.inflate(R.layout.fragmentonelayout, container, false);

    // Retrieve the text editor, and restore the last saved state if needed.
    mEditText = (EditText)v.findViewById(R.id.editText1);

    if (savedInstanceState != null) {

        Log.d(TAG, "OnCreateView->SavedInstanceState not null");

        mEditText.setText(savedInstanceState.getCharSequence("text"));
    }
    else {
        Log.d(TAG,"OnCreateView->SavedInstanceState null");
    }
    return v;
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    Log.d(TAG, "FragmentOne.onSaveInstanceState");

    // Remember the current text, to restore if we later restart.
    outState.putCharSequence("text", mEditText.getText());
}

Manifesto

<uses-sdk android:minSdkVersion="8" />

<application
    android:icon="@drawable/ic_launcher"
    android:label="@string/app_name" >
    <activity
        android:label="@string/app_name"
        android:name=".activities.FragmentTestActivity" 
        android:configChanges="orientation">
        <intent-filter >
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
</application>
MartinS
fonte
Não sei se é uma resposta adequada, mas tente usar uma tag ao adicionar o fragmento, adicionar (R.id.fragment_container, fragmento, "MYTAG") ou, na sua falta, substitua (R.id.fragment_container, fragmento ", MYTAG ")
Jason
2
Fazendo algumas investigações. Quando a Atividade Principal (FragmentTestActivity) é reiniciada na alteração de orientação e eu obtenho uma nova instância do FragmentManager, em seguida, realizo um FindFragmentByTag para localizar o fragmento que ainda existe, para que o fragmento esteja sendo retido durante a recreação da atividade principal. Se eu encontrar o fragmento e não fizer nada, ele será exibido novamente com a MainActivity.
Martins

Respostas:

189

Você está colocando seus fragmentos em camadas um sobre o outro.

Quando ocorre uma alteração na configuração, o fragmento antigo se adiciona à nova atividade quando é recriada. É uma dor enorme na retaguarda na maioria das vezes.

Você pode interromper a ocorrência de erros usando o mesmo fragmento em vez de recriar um novo. Basta adicionar este código:

if (savedInstanceState == null) {
    // only create fragment if activity is started for the first time
    mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment);
    fragmentTransaction.commit();
} else {        
    // do nothing - fragment is recreated automatically
}

Entretanto, esteja avisado: ocorrerão problemas se você tentar acessar as Exibições de Atividade de dentro do Fragmento, pois os ciclos de vida serão alterados sutilmente. (Obter visualizações de um pai A atividade de um fragmento não é fácil).

Graeme
fonte
54
"É uma dor enorme na retaguarda na maioria das vezes" (thumbs up)
rushinge 29/04
1
Como o mesmo cenário em caso de ViewPage pode ser usado com o FragmentStatePagerAdapter ... alguma sugestão?
CoDe
5
Existe uma afirmação semelhante na documentação oficial? Isso não é uma contradição com o que está indicado no guia "when the activity is destroyed, so are all fragments":? Desde então "When the screen orientation changes, the system destroys and recreates the activity [...]".
CYrus
4
Cyrus - Não, a atividade é realmente destruída, os fragmentos que ele contém são referenciados no FragmentManager, não apenas na atividade, portanto ela permanece e é lida.
Graeme
4
registrar os métodos onCreate e onDestroy dos fragmentos, bem como seu código hash depois de encontrar no FragmentManager, mostra claramente que o fragmento está destruído. é apenas recriado e recolocado automaticamente. somente se você colocar setRetainInstance (true) em fragmentos método onCreate ele realmente não serei destruído
Lemao1981
87

Para citar este livro , "para garantir uma experiência consistente do usuário, o Android persiste no layout do Fragmento e na pilha traseira associada quando uma Atividade é reiniciada devido a uma alteração na configuração". (p. 124)

E a maneira de abordar isso é primeiro verificar se a pilha de trás do fragmento já foi preenchida e criar a nova instância de fragmento somente se não tiver:

@Override
public void onCreate(Bundle savedInstanceState) {

        ...    

    FragmentOne fragment = (FragmentOne) mFragmentManager.findFragmentById(R.id.fragment_container); 

    if (fragment == null) {
        FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();
        fragmentTransaction.add(R.id.fragment_container, new FragmentOne());
        fragmentTransaction.commit();
    }
}
k29
fonte
2
Você provavelmente me salvou muito tempo com este ... muito obrigado. Você pode combinar esta resposta com a do Graeme para obter uma solução perfeita para lidar com alterações e fragmentos de configuração.
azpublic
10
Esta é realmente a resposta certa, não a marcada. Muito obrigado!
precisa
como lidar com o mesmo cenário no caso da implementação do ViewPager Fragment.
CoDe
Esta pequena jóia ajudou em um problema que eu estava procurando por vários dias. Obrigado! Esta é definitivamente a solução.
Quem
1
@SharpEdge Se você tiver vários fragmentos, dê a eles tags ao adicioná-los ao contêiner e use mFragmentManager.findFragmentByTag (em vez de findFragmentById) para obter referências a eles - assim você conhecerá a classe de cada fragmento e poderá elenco corretamente
k29 14/10
10

O método onCreate () da sua atividade é chamado após a mudança de orientação, como você viu. Portanto, não execute a FragmentTransaction que adiciona o Fragment após a mudança de orientação em sua atividade.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (savedInstanceState==null) {
        //do your stuff
    }
}

Os fragmentos devem e devem ser inalterados.

Αλέκος
fonte
Sabemos que a instância será salva depois que o fragmento for criado e adicionado? Quero dizer, chapéu, se um usuário gira pouco antes de o fragmento ser adicionado? Nós ainda terá não nulo savedInstanceState que não contenha estado fragmento
Farid
4

Você pode @Overrideusar o FragmentActivity onSaveInstanceState(). Certifique-se de não chamar super.onSaveInstanceState()o método.

Victor.Chan
fonte
2
Isso provavelmente interromperia o ciclo de vida das atividades, introduzindo mais problemas em potencial nesse processo já bastante confuso. Veja o código-fonte do FragmentActivity: ele está salvando os estados de todos os fragmentos.
21713 Brian
Eu tive o problema de ter uma contagem de adaptadores diferente para uma orientação diferente. Então, eu sempre tive uma situação estranha depois de ligar o dispositivo e deslizar algumas páginas, peguei a antiga e a errada. com o giro do savedInstance ele funciona melhor sem vazamentos de memória (eu usei setSavedEnabled (false) antes da sua e acabou com grandes vazamentos de memória em cada mudança de orientação)
Informatic0re
0

Devemos sempre tentar evitar a exceção nullpointer, portanto, precisamos verificar primeiro no método saveinstance as informações do pacote. para uma breve explicação para verificar este link do blog

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        if (getResources().getConfiguration().orientation
            == Configuration.ORIENTATION_LANDSCAPE) {
            // If the screen is now in landscape mode, we can show the
            // dialog in-line with the list so we don't need this activity.
            finish();
            return;
        }

        if (savedInstanceState == null) {
            // During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.content, details).commit();
        }
    } 
}
abhi
fonte
0

Na alteração da configuração, a estrutura criará uma nova instância do fragmento para você e a incluirá na atividade. Então, em vez disso:

FragmentOne fragment = new FragmentOne();

fragmentTransaction.add(R.id.fragment_container, fragment);

faça isso:

if (mFragmentManager.findFragmentByTag(FRAG1_TAG) == null) {
    FragmentOne fragment = new FragmentOne();

    fragmentTransaction.add(R.id.fragment_container, fragment, FRAG1_TAG);
}

Observe que a estrutura adiciona uma nova instância do FragmentOne na mudança de orientação, a menos que você chame setRetainInstance (true); nesse caso, ela adicionará a instância antiga do FragmentOne.

vlazzle
fonte
-1

Se você acabou de fazer um projeto, o gerente do projeto diz que é necessário obter a tela de função de comutação, mas não deseja que a comutação de tela carregue um layout diferente (pode criar um sistema de layout e porta de layout.

Você determinará automaticamente o estado da tela, carregará o layout correspondente), devido à necessidade de reinicializar a atividade ou fragmento, a experiência do usuário não é boa, não diretamente na opção de tela, refiro-me? Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtyt45ki19e0d1d0d0d0e0d0e0e1e1e0e05e03e1

A premissa é que seu layout use o peso da maneira como o layout do layout_weight, da seguinte maneira:

<LinearLayout
Android:id= "@+id/toplayout"
Android:layout_width= "match_parent"
Android:layout_height= "match_parent"
Android:layout_weight= "2"
Android:orientation= "horizontal" >

Portanto, minha abordagem é que, ao alternar a tela, não é necessário carregar um novo layout do arquivo de exibição, modificar o layout nos pesos dinâmicos onConfigurationChanged, as seguintes etapas: 1 primeiro conjunto: AndroidManifest.xml no atributo de atividade: android: configChanges = "keyboardHidden | orientação | screenSize" Para impedir a alternância de tela, evite recarregar, para poder monitorar a atividade de reescrita do onConfigurationChanged 2 ou o fragmento no método onConfigurationChanged.

@Override
Public void onConfigurationChanged (Configuration newConfig) {
    Super.onConfigurationChanged (newConfig);
    SetContentView (R.layout.activity_main);
    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById(R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Tradespace_layout.setLayoutParams (LP3);
    }
    else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
        //On the layout / / weight adjustment
        LinearLayout toplayout = (LinearLayout) findViewById (R.id.toplayout);
        LinearLayout.LayoutParams LP = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.8f);
        Toplayout.setLayoutParams (LP);
        LinearLayout tradespace_layout = (LinearLayout) findViewById (R.id.tradespace_layout);
        LinearLayout.LayoutParams LP3 = new LayoutParams (LinearLayout.LayoutParams.MATCH_PARENT, 0, 2.0f);
        Tradespace_layout.setLayoutParams (LP3);
    }
}
nihaoqiulinhe
fonte