在最近做的項目中摹察,遇到了 Fragment 重疊的問題恩掷。在用Fragment做Tab頁面,發(fā)現(xiàn)有時候進入應用會同時顯示多個Tab內(nèi)容供嚎,UI發(fā)生重疊黄娘。直接back鍵退出應用再進入時,則沒有出現(xiàn)該問題克滴。
后面才知道逼争,當應用被強行關閉后(通過手機管家軟件手動強關,或系統(tǒng)為節(jié)省內(nèi)存自動關閉應用)劝赔,再次進入應用時誓焦,每次都有這現(xiàn)象。正常情況下顯示是對的着帽,現(xiàn)象發(fā)生在我切換到其他的app罩阵,操作一會之后,再回到當前的app启摄,有一定幾率會出現(xiàn) Fragment 重疊的現(xiàn)象稿壁。具體的情況是,app 需要在多個 Fragment 間切換歉备,并且保存每個 Fragment 的狀態(tài)傅是。官方的方法是使用 replace() 來替換 Fragment,但是 replace() 的調(diào)用會導致 Fragment 的 onCreateView() 被調(diào)用,所以切換界面時會無法保存當前的狀態(tài)喧笔。因此一般采用 add()帽驯、hide()與 show()配合,來達到保存 Fragment 的狀態(tài)书闸。
可以通過下面的方式進行手動重現(xiàn)這個BUG:
1.手機的 “設置” - “開發(fā)者選項” - 打開”不保留活動”(主要用于模擬Activity被及時回收)
2.把 app 切換到后臺尼变,再重新打開,通過點按不同的 tab 來切換 Fragment
起初我以為是我在使用add()浆劲、hide() 嫌术、show() 切換 Fragment 的時候有什么地方使用的不對,嘗試去解決重疊的 bug牌借,無果后度气,還是通過 google 找出了原因和解決方案。
原因
使用 Fragment 的狀態(tài)保存膨报,當系統(tǒng)內(nèi)存不足磷籍,F(xiàn)ragment 的宿主 Activity 回收的時候,F(xiàn)ragment 的實例并沒有隨之被回收现柠。Activity 被系統(tǒng)回收時院领,會主動調(diào)用 onSaveInstance() 方法來保存視圖層(View Hierarchy),所以當 Activity 通過導航再次被重建時够吩,之前被實例化過的 Fragment 依然會出現(xiàn)在 Activity 中栅盲,此時的 FragmentTransaction 中的相當于又再次 add 了 fragment 進去的,hide()和show()方法對之前保存的fragment已經(jīng)失效了废恋。綜上這些因素導致了多個Fragment重疊在一起。
通過分析發(fā)現(xiàn)扒寄,正常back鍵退出應用時鱼鼓,Activity及Fragment對象會被銷毀,因此再次進入時會在切換到Tab時創(chuàng)建對應的Fragment對象该编。
但是當強行關閉應用后迄本,Activity雖然被回收,但Fragment對象仍然保持课竣,再次進入應用時嘉赎,系統(tǒng)會分別調(diào)用Fragment的onAttach方法將其附加到Activity上,
這里對應的就是強行關閉應用前的fragment對象于樟,
后面會分別調(diào)用兩個fragment的onCreateView方法公条,因此這兩個Fragment對應的View層次結構都會加到Activity的View層次中。
雖然setSelection方法會把所有fragment先隱藏再顯示選中的對象迂曲,但由于此時Activity中Fragment對象的成員變量還未初始化靶橱,因此會再次實例化fragment對象,
之后add、show及hide的都是在第二次創(chuàng)建的對象上操作的关霸,而之前被保持的fragment對象的視圖層次已經(jīng)反映到Activity視圖中并且不會被hide传黄,因此發(fā)生了上述重疊現(xiàn)象。
解決方法:
方案一:
在Activity的onAttachFragment方法中队寇,有一個fragment參數(shù)膘掰,它就是onAttach方法對應的Fragment對象,
通過判斷這個fragment對象佳遣,如果屬于我們的FragmentTabX類并且該類還未被實例化過识埋,則將Activity的成員變量mFragmentTabX指向該fragment對象,這樣就可以在原來的fragment對象上操作add/show/hide苍日,因此不會有重疊現(xiàn)象
方案二:
Activity 中的 onSaveInstanceState() 里面有一句super.onSaveInstanceState(outState);惭聂,Google 對于這句話的解釋是 “Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執(zhí)行這句代碼來調(diào)用父類去保存視圖層的狀態(tài)”相恃。通過注釋掉這句話辜纲,這樣主 Activity 因為種種原因被回收的時候就不會保存之前的 fragment state,也可以成功解決重疊的問題拦耐。
//解決重疊耕腾,方法1
@Override
protectedvoidonSaveInstanceState(Bundle outState) {
//如果用以下這種做法則不保存狀態(tài),再次進來的話會顯示默認tab
//super.onSaveInstanceState(outState);
}
//解決重疊杀糯,方法2
@Override
publicvoidonAttachFragment(Fragment fragment){
//當前的界面的保存狀態(tài)扫俺,只是重新讓新的Fragment指向了原本未被銷毀的fragment,它就是onAttach方法對應的Fragment對象
if(FragmentA ==null&& fragmentinstanceofFragmentTabA)
{
FragmentA = (FragmentTabA)fragment;
}else if(FragmentB ==null&& fragmentinstanceofFragmentTabB){
FragmentB = (FragmentTabB)fragment;
}else if(FragmentC ==null&& fragmentinstanceofFragmentTabC){
FragmentC = (FragmentTabC)fragment;
}else if(FragmentD ==null&& fragmentinstanceofFragmentTabD){
FragmentD = (FragmentTabD)fragment;
}
}
通過以上也可知固翰,某些情況當系統(tǒng)需要將Activity回收以便節(jié)省內(nèi)存時狼纬,Activity內(nèi)部保持的fragment不會被銷毀,可用于保存/恢復數(shù)據(jù)骂际。
下面是全部的代碼:
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) {
//如果用以下這種做法則不保存狀態(tài),再次進來的話會顯示默認的tab
// super.onSaveInstanceState(outState);
}
//解決重疊歉铝,方法2
/* @Override
public void onAttachFragment(Fragment fragment){
//當前的界面的保存狀態(tài)盈简,只是從新讓新的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;
}
}
*/
}