デバイスの向きを変更すると、タブとフラグメントを含むアクティビティを再読み込みする際に問題が発生します。
状況は次のとおりです。
アクション バーに 3 つのタブがあるアクティビティがあります。FrameLayout
各タブは、メイン ビューに異なるフラグメントをロードします。デバイスの向きを変えなければ、すべて正常に動作します。しかし、そうすると、Android は現在選択されているフラグメントを 2 回初期化しようとし、次のエラーが発生します。
E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment
エラーが発生する一連の手順は次のとおりです。
- アクティビティをロードし、タブ番号 2 を選択して、デバイスの向きを変更します。
- Android は、アクティビティと、タブ番号 2 によって読み込まれたフラグメントのインスタンス (以降、「フラグメント 2」) を破棄します。次に、アクティビティとフラグメントの新しいインスタンスの作成に進みます。
- 内部
Activity.onCreate()
では、最初のタブをアクション バーに追加します。そうすると、このタブが自動的に選択されます。将来的には問題になるかもしれませんが、今は気にしません。onTabSelected
が呼び出され、最初のフラグメントの新しいインスタンスが作成されてロードされます (以下のコードを参照)。 - イベントがトリガーされることなく、他のすべてのタブを追加します。これで問題ありません。
ActionBar.selectTab(myTab)
Tab nr 2 を選択するために呼び出します。onTabUnselected()
最初のタブで呼び出され、次にonTabSelected()
2 番目のタブで呼び出されます。このシーケンスは、フラグメント 2 のインスタンスの現在のフラグメントを置き換えます (以下のコードを参照)。- 次に、
Fragment.onCreateView()
Fragment 2 インスタンスで呼び出され、フラグメント レイアウトが膨張します。 - これが問題です。Android を呼び出してから、もう一度フラグメント インスタンスを呼び出すと、レイアウトを (2 回目) 膨張させようとすると例外が発生します
onCreate()
。onCreateView()
明らかに問題は、Android がフラグメントを 2 回初期化していることですが、その理由はわかりません。
アクティビティを再読み込みするときに 2 番目のタブを選択しないようにしましたが、2 番目のフラグメントはとにかく初期化され、表示されません (タブを選択しなかったため)。
私はこの質問を見つけました: Android Fragments recreated on orientation change
ユーザーは基本的に私と同じように尋ねますが、私は選択した答えが好きではありません (これは単なる回避策です)。android:configChanges
トリックなしでこれを機能させる方法がいくつかあるはずです。
不明な場合は、フラグメントの再作成を防ぐ方法と、フラグメントの二重初期化を回避する方法を知りたいです。なぜこれが起こっているのかを知っておくとよいでしょう。:P
関連するコードは次のとおりです。
public class MyActivity extends Activity implements ActionBar.TabListener {
private static final String TAG_FRAGMENT_1 = "frag1";
private static final String TAG_FRAGMENT_2 = "frag2";
private static final String TAG_FRAGMENT_3 = "frag3";
Fragment frag1;
Fragment frag2;
Fragment frag3;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// my_layout contains a FragmentLayout inside
setContentView(R.layout.my_layout);
// Get a reference to the fragments created automatically by Android
// when reloading the activity
FragmentManager fm = getFragmentManager();
this.frag1 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_1);
this.frag2 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_2);
this.frag3 = fm.findFragmentByTag(MyActivity.TAG_FRAGMENT_3)
ActionBar actionBar = getActionBar();
// snip...
// This triggers onTabSelected for the first tab
actionBar.addTab(actionBar.newTab()
.setText("Tab1").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_1));
actionBar.addTab(actionBar.newTab()
.setText("Tab2").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_2));
actionBar.addTab(actionBar.newTab()
.setText("Tab3").setTabListener(this)
.setTag(MyActivity.TAG_FRAGMENT_3));
Tab t = null;
// here I get a reference to the tab that must be selected
// snip...
// This triggers onTabUnselected/onTabSelected
ab.selectTab(t);
}
@Override
protected void onDestroy() {
// Not sure if this is necessary
this.frag1 = null;
this.frag2 = null;
this.frag3 = null;
super.onDestroy();
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
curFrag = createFragmentInstanceForTag(tab.getTag().toString());
if(curFrag == null) {
// snip...
return;
}
}
ft.replace(R.id.fragment_container, curFrag, tab.getTag().toString());
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft)
{
Fragment curFrag = getFragmentInstanceForTag(tab.getTag().toString());
if (curFrag == null) {
// snip...
return;
}
ft.remove(curFrag);
}
private Fragment getFragmentInstanceForTag(String tag)
{
// Returns this.frag1, this.frag2 or this.frag3
// depending on which tag was passed as parameter
}
private Fragment createFragmentInstanceForTag(String tag)
{
// Returns a new instance of the fragment requested by tag
// and assigns it to this.frag1, this.frag2 or this.frag3
}
}
Fragment のコードは関係ありませんonCreateView()
。メソッドのオーバーライドで膨張したビューを返すだけです。