Fragmentオーバーラップ問題
17552 ワード
最近のプロジェクトでは、Fragmentが重なる問題に遭遇しました.FragmentでTabページを作成すると,アプリケーションに入ると同時に複数のTabコンテンツが表示され,UIが重なることがある.直接backキーがアプリケーションを終了してからアクセスすると、この問題は発生しません.アプリケーションが強制的にシャットダウンされた後(携帯電話の執事ソフトで手動でスイッチを入れたり、メモリを節約するために自動的にアプリケーションをシャットダウンしたり)、再びアプリケーションに入ったとき、毎回この現象が発生していることが後で分かった.通常の表示は正しいですが、現象は私が他のappに切り替えて、しばらく操作した後、現在のappに戻って、Fragmentが重なる可能性があります.具体的には、appは複数のFragment間を切り替え、各Fragmentの状態を保存する必要がある.公式の方法はreplace()を使用してFragmentを置き換えることですが、replace()の呼び出しはFragmentのonCreateView()が呼び出されるため、インタフェースを切り替えると現在の状態を保存できません.そのため、一般的にadd()、hide()とshow()を組み合わせて、Fragmentを保存する状態にします.
このBUG:1は以下のように手動で再現することができる.携帯電話の「設定」-「開発者オプション」-「開く」は活動を保留しない(主にActivityがタイムリーに回収されるシミュレーションに用いる).アプリをバックグラウンドに切り替えて再度開き、ポイントを押してFragmentを切り替えます
最初はadd()、hide()、show()を使ってFragmentを切り替えたときに何か間違っていたのかと思って、重なったバグを解決しようとしたが、結果が出なかった後、googleを通じて原因と解決策を見つけた.
の原因となる
Fragmentの状態で保存すると,システムメモリが不足し,FragmentのホストActivityが回収されると,Fragmentのインスタンスは回収されない.Activityがシステムによって回収されると、onSaveInstance()メソッドがアクティブに呼び出されてビューレイヤ(View Hierarchy)が保存されるので、Activityがナビゲーションによって再構築されると、以前にインスタンス化されたFragmentは依然としてActivityに表示され、このときのFragmentTransactionでは再びfragmentをaddしたものに相当し、hide()とshow()メソッドは以前に保存したfragmentに対して失効している.これらの要因により複数のFragmentが重なった.
解析により,通常のbackキーがアプリケーションを終了するとActivityおよびFragmentオブジェクトが破棄されるため,再びアクセスするとTabに切り替わったときに対応するFragmentオブジェクトが作成されることが分かった.しかし、強制的にアプリケーションを閉じるとActivityは回収されるが、Fragmentオブジェクトは保持され、再びアプリケーションに入ると、システムはFragmentのonAttachメソッドをそれぞれ呼び出してActivityに付加し、
ここでは、適用前のfragmentオブジェクトを強制的に閉じ、2つのfragmentのonCreateViewメソッドが呼び出されるので、この2つのFragmentに対応するView階層がActivityのView階層に追加されます.setSelectionメソッドでは、すべてのfragmentを非表示にしてから選択したオブジェクトが表示されますが、ActivityではFragmentオブジェクトのメンバー変数が初期化されていないため、fragmentオブジェクトが再びインスタンス化され、add、show、hideは2回目に作成されたオブジェクトで操作され、以前保持されていたfragmentオブジェクトのビュー階層はActivityビューに反映され、hideは表示されません.このため,上記のオーバーラップ現象が発生した.
解決方法:
シナリオ1:ActivityのonAttachFragmentメソッドには、onAttachメソッドに対応するFragmentオブジェクトであるfragmentパラメータがあり、このfragmentオブジェクトを判断することによって、我々のFragmentTabXクラスに属し、クラスがまだインスタンス化されていない場合、Activityのメンバー変数mFragmentTabXをそのfragmentオブジェクトに指向することで、元のfragmentオブジェクト上でadd/show/hideを操作することができるので、重複現象はない
シナリオ2:ActivityのonSaveInstanceState()にsuperという文があります.onSaveInstanceState(outState);,Googleはこの言葉について「Always call the superclass so it can save the view hierarchy state」と説明し、「親を呼び出してビューレイヤの状態を保存する」という意味だろう.この言葉を注釈することで,主Activityが様々な理由で回収された場合には以前のfragment stateを保存することなく,重複する問題の解決にも成功する.
このBUG:1は以下のように手動で再現することができる.携帯電話の「設定」-「開発者オプション」-「開く」は活動を保留しない(主にActivityがタイムリーに回収されるシミュレーションに用いる).アプリをバックグラウンドに切り替えて再度開き、ポイントを押してFragmentを切り替えます
最初はadd()、hide()、show()を使ってFragmentを切り替えたときに何か間違っていたのかと思って、重なったバグを解決しようとしたが、結果が出なかった後、googleを通じて原因と解決策を見つけた.
の原因となる
Fragmentの状態で保存すると,システムメモリが不足し,FragmentのホストActivityが回収されると,Fragmentのインスタンスは回収されない.Activityがシステムによって回収されると、onSaveInstance()メソッドがアクティブに呼び出されてビューレイヤ(View Hierarchy)が保存されるので、Activityがナビゲーションによって再構築されると、以前にインスタンス化されたFragmentは依然としてActivityに表示され、このときのFragmentTransactionでは再びfragmentをaddしたものに相当し、hide()とshow()メソッドは以前に保存したfragmentに対して失効している.これらの要因により複数のFragmentが重なった.
解析により,通常のbackキーがアプリケーションを終了するとActivityおよびFragmentオブジェクトが破棄されるため,再びアクセスするとTabに切り替わったときに対応するFragmentオブジェクトが作成されることが分かった.しかし、強制的にアプリケーションを閉じるとActivityは回収されるが、Fragmentオブジェクトは保持され、再びアプリケーションに入ると、システムはFragmentのonAttachメソッドをそれぞれ呼び出してActivityに付加し、
ここでは、適用前のfragmentオブジェクトを強制的に閉じ、2つのfragmentのonCreateViewメソッドが呼び出されるので、この2つのFragmentに対応するView階層がActivityのView階層に追加されます.setSelectionメソッドでは、すべてのfragmentを非表示にしてから選択したオブジェクトが表示されますが、ActivityではFragmentオブジェクトのメンバー変数が初期化されていないため、fragmentオブジェクトが再びインスタンス化され、add、show、hideは2回目に作成されたオブジェクトで操作され、以前保持されていたfragmentオブジェクトのビュー階層はActivityビューに反映され、hideは表示されません.このため,上記のオーバーラップ現象が発生した.
解決方法:
シナリオ1:ActivityのonAttachFragmentメソッドには、onAttachメソッドに対応するFragmentオブジェクトであるfragmentパラメータがあり、このfragmentオブジェクトを判断することによって、我々のFragmentTabXクラスに属し、クラスがまだインスタンス化されていない場合、Activityのメンバー変数mFragmentTabXをそのfragmentオブジェクトに指向することで、元のfragmentオブジェクト上でadd/show/hideを操作することができるので、重複現象はない
シナリオ2:ActivityのonSaveInstanceState()にsuperという文があります.onSaveInstanceState(outState);,Googleはこの言葉について「Always call the superclass so it can save the view hierarchy state」と説明し、「親を呼び出してビューレイヤの状態を保存する」という意味だろう.この言葉を注釈することで,主Activityが様々な理由で回収された場合には以前のfragment stateを保存することなく,重複する問題の解決にも成功する.
// , 1
@Override
protected void onSaveInstanceState(Bundle outState) {
// , tab
//super.onSaveInstanceState(outState);
}
// , 2
@Override
public void onAttachFragment(Fragment fragment){
// , Fragment fragment, onAttach Fragment
if(FragmentA == null && fragment instanceof FragmentTabA){
FragmentA = (FragmentTabA)fragment;
}else if(FragmentB == null && fragment instanceof FragmentTabB){
FragmentB = (FragmentTabB)fragment;
}else if(FragmentC == null && fragment instanceof FragmentTabC){
FragmentC = (FragmentTabC)fragment;
}else if(FragmentD == null && fragment instanceof FragmentTabD){
FragmentD = (FragmentTabD)fragment;
}
}
以上からも分かるように、メモリを節約するためにActivityを回収する必要がある場合、Activity内部に保持されているfragmentは破棄されず、データの保存/復元に使用できる場合があります.次はすべてのコードです.package com.mobile.margaret.margaretapplication.activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import com.mobile.margaret.margaretapplication.R;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabA;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabB;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabC;
import com.mobile.margaret.margaretapplication.fragment.FragmentTabD;
/**
* @author Liz_Miller
* @Title: TabActivity_Fragment_RadioGroup
* @Package com.mobile.margaret.margaretapplication.activity
* @Description: ${todo}( )
* @date 2016/5/30 15:07
*/
public class TabActivity_Fragment_RadioGroup extends FragmentActivity{
private static final String TAG = "Liz_Miller";
private FrameLayout mHomeContent;
private RadioGroup mHomeRadioGroup;
private RadioButton mHomeA;
private RadioButton mHomeB;
private RadioButton mHomeC;
private RadioButton mHomeD;
private Fragment FragmentA;
private Fragment FragmentB;
private Fragment FragmentC;
private Fragment FragmentD;
private int tabIds[] = new int[]{
R.id.id_tab_weixin,
R.id.id_tab_frd,
R.id.id_tab_address,
R.id.id_tab_setting,
};
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.d(TAG,"onCreate");
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.layout_fragment_radiogroup_maintab);
initViews();
initEvents();
}
//
public void initViews(){
mHomeContent = (FrameLayout)findViewById(R.id.fragment_content);
mHomeRadioGroup = (RadioGroup)findViewById(R.id.mHomeRadioGroup);
mHomeA = (RadioButton)findViewById(R.id.id_tab_weixin);
mHomeB = (RadioButton)findViewById(R.id.id_tab_frd);
mHomeC = (RadioButton)findViewById(R.id.id_tab_address);
mHomeD = (RadioButton)findViewById(R.id.id_tab_setting);
}
@Override
public void onAttachFragment(Fragment fragment) {
// TODO Auto-generated method stub
super.onAttachFragment(fragment);
Log.d(TAG,"onAttachFragment");
}
@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
Log.d(TAG,"onDestroy");
}
@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
Log.d(TAG,"onPause");
}
@Override
protected void onResume() {
// TODO Auto-generated method stub
super.onResume();
Log.d(TAG,"onResume");
}
@Override
protected void onStart() {
// TODO Auto-generated method stub
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
super.onStop();
Log.d(TAG, "onStop");
}
public void initEvents(){
mHomeRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
for (int i = 0; i < tabIds.length; i++) {
if (tabIds[i] == checkedId) {
setSelection(i);
break;
}
}
}
});
setSelection(0);
}
private void setSelection(int position){
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction mfragmentTransaction = fm.beginTransaction();
hideAllFragments(mfragmentTransaction);
switch (position){
case 0:
mHomeA.setSelected(true);
if (FragmentA == null) {
FragmentA = new FragmentTabA();
mfragmentTransaction.add(R.id.fragment_content, FragmentA);
}else {
mfragmentTransaction.show(FragmentA);
}
break;
case 1:
mHomeB.setSelected(true);
if (FragmentB == null) {
FragmentB = new FragmentTabB();
mfragmentTransaction.add(R.id.fragment_content, FragmentB);
}else {
mfragmentTransaction.show(FragmentB);
}
break;
case 2:
mHomeC.setSelected(true);
if (FragmentC == null) {
FragmentC = new FragmentTabC();
mfragmentTransaction.add(R.id.fragment_content, FragmentC);
}else {
mfragmentTransaction.show(FragmentC);
}
break;
case 3:
mHomeD.setSelected(true);
if (FragmentD == null) {
FragmentD = new FragmentTabD();
mfragmentTransaction.add(R.id.fragment_content, FragmentD);
}else {
mfragmentTransaction.show(FragmentD);
}
break;
default:
break;
}
mfragmentTransaction.commit();
}
private void hideAllFragments(FragmentTransaction ft){
if (FragmentA != null) {
ft.hide(FragmentA);
mHomeA.setSelected(false);
}
if (FragmentB != null) {
ft.hide(FragmentB);
mHomeB.setSelected(false);
}
if (FragmentC != null) {
ft.hide(FragmentC);
mHomeC.setSelected(false);
}
if (FragmentD != null) {
ft.hide(FragmentD);
mHomeD.setSelected(false);
}
}
// , 1
@Override
protected void onSaveInstanceState(Bundle outState) {
// , tab
// super.onSaveInstanceState(outState);
}
// , 2
/* @Override
public void onAttachFragment(Fragment fragment){
// , Fragment fragment, onAttach Fragment
if(FragmentA == null && fragment instanceof FragmentTabA){
FragmentA = (FragmentTabA)fragment;
}else if(FragmentB == null && fragment instanceof FragmentTabB){
FragmentB = (FragmentTabB)fragment;
}else if(FragmentC == null && fragment instanceof FragmentTabC){
FragmentC = (FragmentTabC)fragment;
}else if(FragmentD == null && fragment instanceof FragmentTabD){
FragmentD = (FragmentTabD)fragment;
}
}*/
}