【问题标题】:Android Fragment lifecycle over orientation changesAndroid Fragment 生命周期超过方向变化
【发布时间】:2011-12-12 12:10:46
【问题描述】:

使用 Fragments 使用兼容性包来定位 2.2。

在重新编码活动以在应用中使用片段后,我无法使方向更改/状态管理正常工作,因此我创建了一个带有单个 FragmentActivity 和单个 Fragment 的小型测试应用。

方向变化的日志很奇怪,多次调用片段 OnCreateView。

我显然遗漏了一些东西 - 比如分离片段并重新附加它而不是创建一个新实例,但我看不到任何说明我哪里出错的文档。

任何人都可以阐明我在这里做错了什么。 谢谢

方向改变后的日志如下。

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

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

还有片段

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

清单

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

【问题讨论】:

  • 我不知道这是否是一个正确的答案,但尝试在添加片段时使用标签,添加(R.id.fragment_container,片段,“MYTAG”),或者失败,替换(R。 id.fragment_container , 片段 , "MYTAG")
  • 做一些调查。当主活动(FragmentTestActivity)在方向改变时重新启动并且我获得了一个新的 FragmentManager 实例,然后执行 FindFragmentByTag 来定位它仍然存在的片段,因此它在主活动的重新创建过程中被保留。如果我找到该片段并且什么都不做,那么无论如何它都会与 MainActivity 一起重新显示。

标签: android android-activity android-fragments orientation


【解决方案1】:

您正在将您的 Fragment 一层一层地分层。

当发生配置更改时,旧 Fragment 在重新创建时会将其自身添加到新 Activity。大多数情况下,这是一种巨大的背部疼痛。

您可以通过使用相同的片段而不是重新创建一个新片段来阻止错误发生。只需添加以下代码:

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
}

但请注意:如果您尝试从 Fragment 内部访问 Activity 视图,则会出现问题,因为生命周期会发生微妙的变化。 (从 Fragment 获取父 Activity 的视图并不容易)。

【讨论】:

  • “这在大多数情况下是背部的巨大疼痛”(竖起大拇指)
  • 如果 ViewPage 与 FragmentStatePagerAdapter 一起使用,如何处理相同的场景...有什么建议吗?
  • 官方文档中是否有类似的断言?这是否与指南中所述的内容相矛盾:"when the activity is destroyed, so are all fragments"?自"When the screen orientation changes, the system destroys and recreates the activity [...]".
  • Cyrus - 不,Activity 确实被销毁了,它包含的 Fragment 在 FragmentManager 中被引用,而不仅仅是来自 Activity,所以它仍然存在并被读取。
  • 在 FragmentManager 中找到后记录片段的 onCreate 和 onDestroy 方法以及其哈希码清楚地表明该片段已被破坏。它只是自动重新创建和重新附加。仅当您将 setRetainInstance(true) 放入 Fragments onCreate 方法时,它才真正不会被销毁
【解决方案2】:

引用this book,“以确保 一致的用户体验,Android 在 由于配置更改,活动重新启动。”(第 124 页)

解决方法是首先检查 Fragment 回栈是否已经被填充,只有在没有填充的情况下才创建新的 Fragment 实例:

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

【讨论】:

  • 你可能为我节省了很多时间...非常感谢。您可以将此答案与 Graeme 的答案结合起来,以获得处理配置更改和片段的完美解决方案。
  • 这实际上是正确答案,而不是标记的答案。非常感谢!
  • 在 ViewPager Fragment 实现的情况下如何处理相同的场景。
  • 这个小宝石帮助解决了我几天来一直在研究的问题。谢谢!这绝对是解决方案。
  • @SharpEdge 如果你有多个片段,你应该在添加到容器时给它们标签,然后使用 mFragmentManager.findFragmentByTag(而不是 findFragmentById)来获取对它们的引用 - 这样你就会知道每个片段的类并能够正确投射
【解决方案3】:

如您所见,您的活动的 onCreate() 方法在方向更改后被调用。所以,不要在你的activity中执行方向改变后添加Fragment的FragmentTransaction。

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

片段应该并且必须保持不变。

【讨论】:

  • 我们知道创建和添加片段后会保存实例吗?我的意思是如果用户在添加 Fragment 之前旋转?我们仍然会有不包含片段状态的非空 savedInstanceState
【解决方案4】:

您可以使用onSaveInstanceState() @Override FragmentActivity。请务必不要在方法中调用super.onSaveInstanceState()

【讨论】:

  • 这很可能会破坏活动生命周期,在这个已经相当混乱的过程中引入更多潜在问题。查看 FragmentActivity 的源代码:它保存了所有 Fragment 的状态。
  • 我遇到的问题是针对不同方向的适配器数量不同。所以我在转动设备并刷一些页面后总是有一个奇怪的情况,我得到了旧的和错误的。通过打开已保存实例,它在没有内存泄漏的情况下工作得最好(我使用 setSavedEnabled(false) 之前并最终在每次方向更改时都会出现大量内存泄漏)
【解决方案5】:

我们应该总是尽量防止空指针异常,所以我们必须首先在 saveinstance 方法中检查捆绑信息。有关简要说明,请查看此博客link

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

【讨论】:

    【解决方案6】:

    如果你只是做一个项目,那么项目经理说你需要实现屏幕切换功能,但你不想屏幕切换加载不同的布局(可以创建布局和布局端口系统。

    你会自动判断屏幕状态,加载相应的布局),因为需要重新初始化activity或者fragment,用户体验不好,不能直接在屏幕上切换,我参考 ? Url=YgNfP-vHy-Nuldi7YHTfNet3AtLdN-w__O3z1wLOnzr3wDjYo7X7PYdNyhw8R24ZE22xiKnydni7R0r35s2fOLcHOiLGYT9Qh_fjqtytJki&wd=&eqid=f258719e0001f24000000004582

    前提是你的布局使用了layout_weight的权重方式,如下:

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

    所以我的做法是,屏幕切换时,不需要加载新布局的视图文件,在onConfigurationChanged动态权重中修改布局,步骤如下: 1 第一组:AndroidManifest.xml中activity属性:android:configChanges="keyboardHidden|orientation|screenSize" 防止屏幕切换,避免重新加载,以便能够在onConfigurationChanged中监控 2 重写onConfigurationChanged方法中的activity或fragment。

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

    【讨论】:

      【解决方案7】:

      在配置更改时,框架将为您创建片段的新实例并将其添加到活动中。所以不要这样:

      FragmentOne fragment = new FragmentOne();
      
      fragmentTransaction.add(R.id.fragment_container, fragment);
      

      这样做:

      if (mFragmentManager.findFragmentByTag(FRAG1_TAG) == null) {
          FragmentOne fragment = new FragmentOne();
      
          fragmentTransaction.add(R.id.fragment_container, fragment, FRAG1_TAG);
      }
      

      请注意,除非您调用 setRetainInstance(true),否则框架会在方向更改时添加 FragmentOne 的新实例,在这种情况下,它将添加 FragmentOne 的旧实例。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-02-02
        • 2019-02-12
        • 1970-01-01
        • 1970-01-01
        • 2014-06-08
        • 1970-01-01
        相关资源
        最近更新 更多