在最近做的項(xiàng)目中,遇到了 Fragment 重疊的問題。在用Fragment做Tab頁(yè)面,發(fā)現(xiàn)有時(shí)候進(jìn)入應(yīng)用會(huì)同時(shí)顯示多個(gè)Tab內(nèi)容,UI發(fā)生重疊。直接back鍵退出應(yīng)用再進(jìn)入時(shí),則沒有出現(xiàn)該問題。
后面才知道,當(dāng)應(yīng)用被強(qiáng)行關(guān)閉后(通過手機(jī)管家軟件手動(dòng)強(qiáng)關(guān),或系統(tǒng)為節(jié)省內(nèi)存自動(dòng)關(guān)閉應(yīng)用),再次進(jìn)入應(yīng)用時(shí),每次都有這現(xiàn)象。正常情況下顯示是對(duì)的,現(xiàn)象發(fā)生在我切換到其他的app,操作一會(huì)之后,再回到當(dāng)前的app,有一定幾率會(huì)出現(xiàn) Fragment 重疊的現(xiàn)象。具體的情況是,app 需要在多個(gè) Fragment 間切換,并且保存每個(gè) Fragment 的狀態(tài)。官方的方法是使用 replace() 來(lái)替換 Fragment,但是 replace() 的調(diào)用會(huì)導(dǎo)致 Fragment 的 onCreateView() 被調(diào)用,所以切換界面時(shí)會(huì)無(wú)法保存當(dāng)前的狀態(tài)。因此一般采用 add()、hide()與 show()配合,來(lái)達(dá)到保存 Fragment 的狀態(tài)。
可以通過下面的方式進(jìn)行手動(dòng)重現(xiàn)這個(gè)BUG:
1.手機(jī)的 “設(shè)置” - “開發(fā)者選項(xiàng)” - 打開”不保留活動(dòng)”(主要用于模擬Activity被及時(shí)回收)
2.把 app 切換到后臺(tái),再重新打開,通過點(diǎn)按不同的 tab 來(lái)切換 Fragment
起初我以為是我在使用add()、hide() 、show() 切換 Fragment 的時(shí)候有什么地方使用的不對(duì),嘗試去解決重疊的 bug,無(wú)果后,還是通過 google 找出了原因和解決方案。
原因
使用 Fragment 的狀態(tài)保存,當(dāng)系統(tǒng)內(nèi)存不足,F(xiàn)ragment 的宿主 Activity 回收的時(shí)候,F(xiàn)ragment 的實(shí)例并沒有隨之被回收。Activity 被系統(tǒng)回收時(shí),會(huì)主動(dòng)調(diào)用 onSaveInstance() 方法來(lái)保存視圖層(View Hierarchy),所以當(dāng) Activity 通過導(dǎo)航再次被重建時(shí),之前被實(shí)例化過的 Fragment 依然會(huì)出現(xiàn)在 Activity 中,此時(shí)的 FragmentTransaction 中的相當(dāng)于又再次 add 了 fragment 進(jìn)去的,hide()和show()方法對(duì)之前保存的fragment已經(jīng)失效了。綜上這些因素導(dǎo)致了多個(gè)Fragment重疊在一起。
通過分析發(fā)現(xiàn),正常back鍵退出應(yīng)用時(shí),Activity及Fragment對(duì)象會(huì)被銷毀,因此再次進(jìn)入時(shí)會(huì)在切換到Tab時(shí)創(chuàng)建對(duì)應(yīng)的Fragment對(duì)象。
但是當(dāng)強(qiáng)行關(guān)閉應(yīng)用后,Activity雖然被回收,但Fragment對(duì)象仍然保持,再次進(jìn)入應(yīng)用時(shí),系統(tǒng)會(huì)分別調(diào)用Fragment的onAttach方法將其附加到Activity上,
這里對(duì)應(yīng)的就是強(qiáng)行關(guān)閉應(yīng)用前的fragment對(duì)象,
后面會(huì)分別調(diào)用兩個(gè)fragment的onCreateView方法,因此這兩個(gè)Fragment對(duì)應(yīng)的View層次結(jié)構(gòu)都會(huì)加到Activity的View層次中。
雖然setSelection方法會(huì)把所有fragment先隱藏再顯示選中的對(duì)象,但由于此時(shí)Activity中Fragment對(duì)象的成員變量還未初始化,因此會(huì)再次實(shí)例化fragment對(duì)象,
之后add、show及hide的都是在第二次創(chuàng)建的對(duì)象上操作的,而之前被保持的fragment對(duì)象的視圖層次已經(jīng)反映到Activity視圖中并且不會(huì)被hide,因此發(fā)生了上述重疊現(xiàn)象。
解決方法:
方案一:
在Activity的onAttachFragment方法中,有一個(gè)fragment參數(shù),它就是onAttach方法對(duì)應(yīng)的Fragment對(duì)象,
通過判斷這個(gè)fragment對(duì)象,如果屬于我們的FragmentTabX類并且該類還未被實(shí)例化過,則將Activity的成員變量mFragmentTabX指向該fragment對(duì)象,這樣就可以在原來(lái)的fragment對(duì)象上操作add/show/hide,因此不會(huì)有重疊現(xiàn)象
方案二:
Activity 中的 onSaveInstanceState() 里面有一句super.onSaveInstanceState(outState);,Google 對(duì)于這句話的解釋是 “Always call the superclass so it can save the view hierarchy state”,大概意思是“總是執(zhí)行這句代碼來(lái)調(diào)用父類去保存視圖層的狀態(tài)”。通過注釋掉這句話,這樣主 Activity 因?yàn)榉N種原因被回收的時(shí)候就不會(huì)保存之前的 fragment state,也可以成功解決重疊的問題。
//解決重疊,方法1
@Override
protectedvoidonSaveInstanceState(Bundle outState) {
//如果用以下這種做法則不保存狀態(tài),再次進(jìn)來(lái)的話會(huì)顯示默認(rèn)tab
//super.onSaveInstanceState(outState);
}
//解決重疊,方法2
@Override
publicvoidonAttachFragment(Fragment fragment){
//當(dāng)前的界面的保存狀態(tài),只是重新讓新的Fragment指向了原本未被銷毀的fragment,它就是onAttach方法對(duì)應(yīng)的Fragment對(duì)象
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;
}
}
通過以上也可知,某些情況當(dāng)系統(tǒng)需要將Activity回收以便節(jié)省內(nèi)存時(shí),Activity內(nèi)部保持的fragment不會(huì)被銷毀,可用于保存/恢復(fù)數(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),再次進(jìn)來(lái)的話會(huì)顯示默認(rèn)的tab
// super.onSaveInstanceState(outState);
}
//解決重疊,方法2
/* @Override
public void onAttachFragment(Fragment fragment){
//當(dāng)前的界面的保存狀態(tài),只是從新讓新的Fragment指向了原本未被銷毀的fragment,它就是onAttach方法對(duì)應(yīng)的Fragment對(duì)象
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;
}
}
*/
}