Vòng đời của Android Fragment qua các thay đổi về hướng


120

Sử dụng gói tương thích để nhắm mục tiêu 2.2 bằng Fragment.

Sau khi mã hóa một hoạt động để sử dụng các phân đoạn trong một ứng dụng, tôi không thể làm cho các thay đổi định hướng / quản lý trạng thái hoạt động, vì vậy tôi đã tạo một ứng dụng thử nghiệm nhỏ với một FragmentActivity và một Fragment duy nhất.

Nhật ký từ các thay đổi hướng là kỳ lạ, với nhiều lệnh gọi đến các phân đoạn OnCreateView.

Rõ ràng là tôi đang thiếu thứ gì đó - chẳng hạn như tách mảnh và gắn lại nó thay vì tạo một phiên bản mới, nhưng tôi không thể thấy bất kỳ tài liệu nào cho biết tôi đang làm sai ở đâu.

Bất cứ ai có thể làm sáng tỏ những gì tôi đang làm sai ở đây xin vui lòng. Cảm ơn

Nhật ký như sau sau khi thay đổi hướng.

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

Hoạt động chính (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();
}

Và mảnh vỡ

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

Rõ ràng

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

Tôi không biết đó có phải là câu trả lời thích hợp hay không, nhưng hãy thử sử dụng thẻ khi thêm phân đoạn, hãy thêm (R.id.fragment_container, segment, "MYTAG"), nếu không, hãy thay thế (R.id.fragment_container, segment, "MYTAG ")
Jason

2
Thực hiện một số cuộc điều tra. Khi Hoạt động chính (FragmentTestActivity) khởi động lại khi thay đổi hướng và tôi nhận được một phiên bản mới của FragmentManager, sau đó thực hiện một FindFragmentByTag để xác định phân đoạn mà nó vẫn tồn tại, vì vậy, phân đoạn đó được giữ lại trong quá trình tạo lại hoạt động chính. Nếu tôi tìm thấy phân mảnh và không làm gì thì nó vẫn được hiển thị lại với MainActivity.
MartinS 14/12/11

Câu trả lời:


189

Bạn đang xếp các Phân đoạn của mình lên đầu mảnh kia.

Khi thay đổi cấu hình xảy ra, Fragment cũ sẽ tự thêm vào Activity mới khi nó được tạo lại. Đây là một nỗi đau lớn ở phía sau hầu hết thời gian.

Bạn có thể ngăn lỗi xảy ra bằng cách sử dụng cùng một Fragment thay vì tạo lại một cái mới. Chỉ cần thêm mã này:

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
}

Tuy nhiên, hãy cảnh báo: sự cố sẽ xảy ra nếu bạn cố gắng và truy cập Chế độ xem hoạt động từ bên trong Phân mảnh vì các vòng đời sẽ thay đổi một cách tinh vi. (Lấy Lượt xem từ Hoạt động chính từ Fragment không dễ dàng).


54
"Đây là một nỗi đau lớn ở phía sau hầu hết thời gian" (không thích)
rushe

1
Làm thế nào có thể xử lý cùng một kịch bản trong trường hợp sử dụng ViewPage với FragmentStatePagerAdapter ... bất kỳ đề xuất nào?
CoDe

5
Có khẳng định tương tự trong tài liệu chính thức không? Đây không phải là một mâu thuẫn với những gì được nêu trong hướng dẫn "when the activity is destroyed, so are all fragments":? Kể từ khi "When the screen orientation changes, the system destroys and recreates the activity [...]".
cYrus

4
Cyrus - Không, Activity thực sự đã bị phá hủy, các Fragment mà nó chứa được tham chiếu trong FragmentManager, không chỉ từ Activity, vì vậy nó vẫn còn và được đọc lại.
Graeme

4
ghi nhật ký các đoạn trên các phương thức onCreate và onDestroy cũng như mã băm của nó sau khi tìm thấy trong FragmentManager cho thấy rõ ràng rằng đoạn IS đã phá hủy. nó chỉ được tạo lại và gắn lại tự động. chỉ khi bạn đặt setRetainInstance (true) trong các mảnh vỡ phương pháp onCreate nó thực sự won't bị phá hủy
Lemao1981

87

Trích dẫn cuốn sách này , "để đảm bảo trải nghiệm người dùng nhất quán, Android duy trì bố cục Fragment và ngăn xếp dự phòng được liên kết khi một Hoạt động được khởi động lại do thay đổi cấu hình." (tr. 124)

Và cách để tiếp cận điều đó là trước tiên hãy kiểm tra xem ngăn xếp ngược Fragment đã được điền chưa và chỉ tạo phiên bản phân mảnh mới nếu nó chưa:

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

2
Bạn có thể đã tiết kiệm cho tôi rất nhiều thời gian với cái này ... cảm ơn rất nhiều. Bạn có thể kết hợp câu trả lời này với câu trả lời từ Graeme để có được giải pháp hoàn hảo để xử lý các thay đổi và phân đoạn cấu hình.
azpublic

10
Đây thực sự là câu trả lời đúng, không phải là câu được đánh dấu. Cảm ơn rât nhiều!
Uriel Frankel

làm thế nào có thể xử lý cùng một tình huống trong trường hợp triển khai Phân đoạn ViewPager.
CoDe

Viên ngọc nhỏ này đã giúp giải quyết một vấn đề mà tôi đã xem xét trong vài ngày. Cảm ơn bạn! Đây chắc chắn là giải pháp.
Whome

1
@SharpEdge Nếu bạn có nhiều phân đoạn, bạn nên cấp cho chúng các thẻ khi thêm vào vùng chứa, sau đó sử dụng mFragmentManager.findFragmentByTag (thay vì findFragmentById) để nhận tham chiếu đến chúng - bằng cách này, bạn sẽ biết lớp của từng phân đoạn và có thể truyền chính xác
k29.

10

Phương thức onCreate () của hoạt động của bạn được gọi sau khi thay đổi hướng như bạn đã thấy. Vì vậy, không thực thi FragmentTransaction bổ sung Fragment sau khi thay đổi hướng trong hoạt động của bạn.

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

Các Fragment nên và phải không thay đổi.


Chúng ta có biết phiên bản sẽ được lưu sau khi phân đoạn được tạo và thêm vào không? Ý tôi là mũ nếu người dùng xoay ngay trước khi Fragment được thêm vào? Chúng tôi vẫn sẽ có không null savedInstanceState không chứa đoạn nhà nước
Farid

4

Bạn có thể @Overridesử dụng FragmentActivity onSaveInstanceState(). Hãy đảm bảo không gọi super.onSaveInstanceState()phương thức trong.


2
Điều này rất có thể sẽ phá vỡ vòng đời hoạt động, dẫn đến nhiều vấn đề tiềm ẩn hơn trong quá trình vốn đã khá lộn xộn này. Nhìn vào mã nguồn của FragmentActivity: nó đang lưu trạng thái của tất cả các đoạn ở đó.
Brian

Tôi đã gặp sự cố rằng tôi có số bộ điều hợp khác nhau cho hướng khác nhau. Vì vậy, tôi luôn gặp một tình huống kỳ lạ sau khi xoay thiết bị và vuốt một số trang, tôi đã nhận được một trang cũ và sai. Với việc xoay SaveInstance, nó hoạt động tốt nhất mà không bị rò rỉ bộ nhớ (tôi đã sử dụng setSavedEnabled (false) befor và cuối cùng bị rò rỉ bộ nhớ lớn mỗi lần thay đổi hướng)
Informatic0re

0

Chúng ta nên luôn cố gắng ngăn chặn ngoại lệ nullpointer, vì vậy trước tiên chúng ta phải kiểm tra trong phương thức saveinstance để biết thông tin gói. để giải thích ngắn gọn để kiểm tra liên kết blog này

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

0

Nếu bạn chỉ làm một dự án, thì người quản lý dự án nói rằng bạn cần đạt được màn hình chức năng chuyển đổi, nhưng bạn không muốn màn hình chuyển đổi tải các bố cục khác nhau (có thể tạo bố cục và hệ thống cổng bố trí).

Bạn sẽ tự động xác định trạng thái màn hình, tải bố cục tương ứng), do cần phải khởi tạo lại hoạt động hoặc phân mảnh, trải nghiệm người dùng không tốt, không trực tiếp trên màn hình chuyển đổi, tôi tham khảo? Url = YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fj0045525191082

Tiền đề là bố cục của bạn sử dụng trọng số của cách bố cục của layout_weight, như sau:

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

Vì vậy, cách tiếp cận của tôi là, khi chuyển đổi màn hình, không cần tải bố cục mới của tệp chế độ xem, hãy sửa đổi bố cục trong trọng số động onConfigurationChanged, theo các bước sau: 1 bộ đầu tiên: AndroidManifest.xml trong thuộc tính hoạt động: android: configChanges = "keyboardHidden | direction | screenSize" Để ngăn chuyển đổi màn hình, hãy tránh tải lại, để có thể giám sát hoạt động hoặc phân đoạn ghi lại onConfigurationChanged 2 trong phương thức 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);
    }
}

0

Khi thay đổi cấu hình, khung công tác sẽ tạo một phiên bản mới của phân đoạn cho bạn và thêm nó vào hoạt động. Vì vậy, thay vì điều này:

FragmentOne fragment = new FragmentOne();

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

làm cái này:

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

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

Xin lưu ý rằng khung công tác thêm một phiên bản mới của FragmentOne khi thay đổi hướng trừ khi bạn gọi setRetainInstance (true), trong trường hợp này, nó sẽ thêm phiên bản cũ của FragmentOne.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.