Siklus hidup Fragmen Android atas perubahan orientasi

120

Menggunakan paket kompatibilitas untuk menargetkan 2.2 menggunakan Fragmen.

Setelah mengodekan ulang aktivitas untuk menggunakan fragmen dalam aplikasi, saya tidak bisa mendapatkan perubahan orientasi / manajemen status yang berfungsi, jadi saya telah membuat aplikasi uji kecil dengan satu FragmentActivity dan satu Fragmen.

Log dari perubahan orientasi itu aneh, dengan beberapa panggilan ke fragmen OnCreateView.

Saya jelas kehilangan sesuatu - seperti melepaskan fragmen dan memasangnya kembali daripada membuat contoh baru, tetapi saya tidak dapat melihat dokumentasi yang akan menunjukkan di mana kesalahan saya.

Adakah yang bisa menjelaskan kesalahan saya di sini. Terima kasih

Lognya adalah sebagai berikut setelah orientasi berubah.

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

Aktivitas Utama (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();
}

Dan fragmennya

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());
}

Nyata

<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
sumber
Saya tidak tahu apakah itu jawaban yang tepat, tetapi coba gunakan tag saat menambahkan fragmen, tambahkan (R.id.fragment_container, fragment, "MYTAG"), atau jika gagal, ganti (R.id.fragment_container, fragment, "MYTAG ")
Jason
2
Melakukan beberapa investigasi. Saat Aktivitas Utama (FragmentTestActivity) dimulai ulang saat orientasi berubah dan saya mendapatkan instance baru dari FragmentManager, lalu menjalankan FindFragmentByTag untuk menemukan fragmen yang masih ada, sehingga fragmen tersebut dipertahankan selama pembuatan ulang aktivitas utama. Jika saya menemukan fragmen dan tidak melakukan apa pun, maka fragmen itu akan ditampilkan kembali dengan MainActivity.
MartinS

Jawaban:

189

Anda melapisi Fragmen Anda satu di atas yang lain.

Saat terjadi perubahan konfigurasi, Fragmen lama menambahkan dirinya sendiri ke Aktivitas baru saat dibuat ulang. Ini adalah rasa sakit yang luar biasa di bagian belakang hampir sepanjang waktu.

Anda dapat menghentikan kesalahan yang terjadi dengan menggunakan Fragmen yang sama daripada membuat ulang yang baru. Cukup tambahkan kode ini:

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
}

Berhati-hatilah: masalah akan muncul jika Anda mencoba dan mengakses Tampilan Aktivitas dari dalam Fragmen karena siklus proses akan berubah secara halus. (Mendapatkan Views dari Aktivitas induk dari Fragmen tidaklah mudah).

Graeme
sumber
54
"Ini adalah rasa sakit yang luar biasa di sebagian besar waktu" (jempol ke atas)
bergegas
1
Bagaimana cara menangani skenario yang sama jika ViewPage digunakan dengan FragmentStatePagerAdapter ... ada saran?
CoDe
5
Apakah ada pernyataan serupa di dokumentasi resmi? Bukankah ini kontradiksi dengan apa yang dinyatakan dalam panduan "when the activity is destroyed, so are all fragments":? Sejak "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus
4
Cyrus - Tidak, Aktivitas memang dimusnahkan, Fragmen yang ada di dalamnya direferensikan di FragmentManager, tidak hanya dari Aktivitas, jadi aktivitas tetap ada dan dibaca.
Graeme
4
logging fragmen metode onCreate dan onDestroy serta kode hashnya setelah ditemukan di FragmentManager dengan jelas menunjukkan bahwa fragmen IS dimusnahkan. itu hanya dibuat ulang dan dipasang kembali secara otomatis. hanya jika Anda meletakkan setRetainInstance (true) di fragmen metode onCreate itu benar-benar tidak akan dihancurkan
Lemao1981
87

Mengutip buku ini , "untuk memastikan pengalaman pengguna yang konsisten, Android mempertahankan tata letak Fragmen dan back-stack terkait saat Aktivitas dimulai ulang karena perubahan konfigurasi." (hlm. 124)

Dan cara untuk mendekatinya adalah dengan terlebih dahulu memeriksa apakah tumpukan belakang Fragmen telah diisi, dan membuat instance fragmen baru hanya jika belum:

@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
sumber
2
Anda mungkin menghemat banyak waktu dengan yang satu ini ... terima kasih banyak. Anda dapat menggabungkan jawaban ini dengan jawaban dari Graeme untuk mendapatkan solusi sempurna untuk menangani perubahan konfigurasi dan fragmen.
azpublic
10
Ini sebenarnya jawaban yang benar, bukan yang ditandai. Terima kasih banyak!
Uriel Frankel
bagaimana menangani skenario yang sama dalam kasus implementasi Fragmen ViewPager.
CoDe
Permata kecil ini membantu mengatasi masalah yang telah saya perhatikan selama beberapa hari. Terima kasih! Ini pasti solusinya.
Siapa
1
@SharpEdge Jika Anda memiliki beberapa fragmen, Anda harus memberinya tag saat menambahkannya ke penampung, lalu menggunakan mFragmentManager.findFragmentByTag (bukan findFragmentById) untuk mendapatkan referensi ke sana - dengan cara ini Anda akan mengetahui kelas dari setiap fragmen dan dapat cast dengan benar
k29
10

Metode onCreate () aktivitas Anda dipanggil setelah orientasi berubah seperti yang Anda lihat. Jadi, jangan jalankan FragmentTransaction yang menambahkan Fragment setelah orientasi berubah dalam aktivitas Anda.

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

Fragmen harus dan tidak boleh diubah.

Αλέκος
sumber
Apakah kita tahu bahwa instance akan disimpan setelah fragmen dibuat dan ditambahkan? Maksud saya, apakah pengguna memutar tepat sebelum Fragmen ditambahkan? Kami masih akan memiliki saveInstanceState non-null yang tidak berisi status fragmen
Farid
4

Anda dapat @Overridemenggunakan FragmentActivity onSaveInstanceState(). Harap pastikan untuk tidak memanggil super.onSaveInstanceState()dalam metode ini.

Victor. Chan
sumber
2
Ini kemungkinan besar akan memutus siklus hidup aktivitas yang memperkenalkan lebih banyak potensi masalah dalam proses yang sudah cukup berantakan ini. Lihat kode sumber FragmentActivity: ini menyimpan status semua fragmen di sana.
Brian
Saya memiliki masalah bahwa saya memiliki jumlah adaptor yang berbeda untuk orientasi yang berbeda. Jadi saya selalu mengalami situasi yang aneh setelah menghidupkan perangkat dan menggesek beberapa halaman, saya mendapatkan yang lama dan salah. dengan memutar SavedInstance bekerja paling baik tanpa kebocoran memori (saya menggunakan setSavedEnabled (false) sebelum dan berakhir dengan kebocoran memori besar pada setiap perubahan orientasi)
Informatic0re
0

Kami harus selalu mencoba untuk mencegah pengecualian nullpointer, jadi kami harus memeriksa terlebih dahulu di metode saveinstance untuk informasi bundel. untuk penjelasan singkatnya cek link blog ini

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
sumber
0

Jika Anda hanya melakukan sebuah proyek, maka manajer proyek mengatakan Anda perlu mencapai layar fungsi switching, tetapi Anda tidak ingin layar switching memuat tata letak yang berbeda (dapat membuat tata letak dan sistem tata letak-port.

Anda secara otomatis akan menentukan status layar, memuat tata letak yang sesuai), karena kebutuhan untuk menginisialisasi ulang aktivitas atau fragmen, pengalaman pengguna tidak baik, tidak langsung pada sakelar layar, saya lihat? Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_f0000aqtytJ199 & wd

Premisnya adalah bahwa layout Anda menggunakan bobot cara layout layout_weight, sebagai berikut:

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

Jadi pendekatan saya adalah, saat pengalihan layar, tidak perlu memuat tata letak baru dari file tampilan, ubah tata letak dalam bobot dinamis onConfigurationChanged, langkah-langkah berikut: 1 set pertama: AndroidManifest.xml di atribut aktivitas: android: configChanges = "keyboardHidden | orientasi | screenSize" Untuk mencegah peralihan layar, hindari memuat ulang, agar dapat memantau aktivitas atau fragmen penulisan ulang onConfigurationChanged 2 dalam metode 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
sumber
0

Saat perubahan konfigurasi, framework akan membuat instance baru dari fragmen untuk Anda dan menambahkannya ke aktivitas. Jadi, alih-alih ini:

FragmentOne fragment = new FragmentOne();

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

melakukan hal ini:

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

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

Harap diperhatikan bahwa framework menambahkan instance baru FragmentOne saat orientasi berubah kecuali Anda memanggil setRetainInstance (true), yang dalam hal ini akan menambahkan instance lama FragmentOne.

vlazzle
sumber