19

デバイスの向きを変更すると、タブとフラグメントを含むアクティビティを再読み込みする際に問題が発生します。

状況は次のとおりです。

アクション バーに 3 つのタブがあるアクティビティがあります。FrameLayout各タブは、メイン ビューに異なるフラグメントをロードします。デバイスの向きを変えなければ、すべて正常に動作します。しかし、そうすると、Android は現在選択されているフラグメントを 2 回初期化しようとし、次のエラーが発生します。

E/AndroidRuntime(2022): Caused by: android.view.InflateException: Binary XML file line #39: Error inflating class fragment

エラーが発生する一連の手順は次のとおりです。

  1. アクティビティをロードし、タブ番号 2 を選択して、デバイスの向きを変更します。
  2. Android は、アクティビティと、タブ番号 2 によって読み込まれたフラグメントのインスタンス (以降、「フラグメント 2」) を破棄します。次に、アクティビティとフラグメントの新しいインスタンスの作成に進みます。
  3. 内部Activity.onCreate()では、最初のタブをアクション バーに追加します。そうすると、このタブが自動的に選択されます。将来的には問題になるかもしれませんが、今は気にしません。onTabSelectedが呼び出され、最初のフラグメントの新しいインスタンスが作成されてロードされます (以下のコードを参照)。
  4. イベントがトリガーされることなく、他のすべてのタブを追加します。これで問題ありません。
  5. ActionBar.selectTab(myTab)Tab nr 2 を選択するために呼び出します。
  6. onTabUnselected()最初のタブで呼び出され、次にonTabSelected()2 番目のタブで呼び出されます。このシーケンスは、フラグメント 2 のインスタンスの現在のフラグメントを置き換えます (以下のコードを参照)。
  7. 次に、Fragment.onCreateView()Fragment 2 インスタンスで呼び出され、フラグメント レイアウトが膨張します。
  8. これが問題です。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()。メソッドのオーバーライドで膨張したビューを返すだけです。

4

9 に答える 9

2

それに対する簡単な答えを得ました:

setRetainInstance(true);Fragment のonAttach(Activity activity)orに追加するだけonActivityCreated(Bundle savedInstanceState)です。これら 2 つは Fragment クラスのコールバックです。

つまり、基本的にsetRetainInstance(true)は次のとおりです。フラグメントが通過するときに、フラグメントの状態をそのまま維持します。

  • onPause();
  • onStop();

アクティビティが何を通過しても、フラグメントのインスタンスを維持します。問題は、フラグメントが多すぎると、システムに負担がかかる可能性があることです。

それが役に立てば幸い。

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);

setRetainInstance(true);

}

いつものように修正のために開きます。よろしく、エドワード・キホーテ。

于 2014-09-14T03:21:12.673 に答える
1

画面を回転させてアプリを再起動すると、フラグメントのクラスのデフォルトのコンストラクターを呼び出して各フラグメントを再作成しているようです。

于 2013-01-10T03:15:42.190 に答える
0

あなたは私が直面したことに直面していると思います。で始まるjson用のスレッドダウンローダーがありましonCreate()た。方向を変更するたびに、スレッドが呼び出され、ダウンロードが開始されます。リストが空でないかどうかを確認することと組み合わせて、json 応答をリストに渡すためにonSaveInstance()およびを使用してこれを修正したため、追加のダウンロードは必要ありません。onRestoreInstance()

これがヒントになることを願っています。

于 2014-08-04T11:06:00.363 に答える
0

android:configChanges="orientation|screenSize" マニフェスト ファイルに追加する

于 2014-11-20T12:01:21.290 に答える
0

同じ問題が発生し、次の回避策を使用しました。

フラグメントの onCreateView の先頭:

if (mView != null) {
    // Log.w(TAG, "Fragment initialized again");
    ((ViewGroup) mView.getParent()).removeView(mView);
    return mView;
}
// normal onCreateView
mView = inflater.inflate(R.layout...)
于 2014-03-09T11:23:50.070 に答える
0

configChangesアクティビティの再作成を保護するには、次のように (マニフェストに) アクティビティ タグを追加してみてください。

android:configChanges="keyboardHidden|orientation|screenSize"
于 2014-12-02T09:44:53.413 に答える