最近項目進入了無休止的修bug階段,不少問題也着實讓我頭疼了一陣子,其中就包括對單Activity頁面中多Fragment的管理。多是我對Fragment瞭解太少了,遇到了不少問題,因此這篇文章着重於講述我遇到了怎樣的問題,以及個人解決方法。但願對有遇到相同問題的人提供一點幫助。java
5月23日修改,在我寫完這篇文章的5天后,修改了主頁的佈局,將大部份內容都放置到了ViewStub中進行一個延時加載的操做。結果發現下面的onSaveInstanceState中保存Fragment的方法失效了,每次銷燬後回來Fragment的數據還在,但頁面變成空了。通過我一天的不斷嘗試,最後終於發現··在ViewStub中,不會出現Fragment重疊的問題- -,屬實被本身給坑了。。android
我目前項目的首頁是一個MainActivity包含5個Fragment,經過hide&show來進行tab切換。在剛開始就遇到了一個很噁心的問題:當前Fragment頁,點擊能跳轉到其餘Fragment頁的內容。具體來講就是不該該被點擊的位置,出現了其它Fragment頁面對應位置的點擊事件。這個問題不是100%的復現的,並且有些機型不會出現,有些又很頻繁。最後終於看到了這個帖子解決了問題——關於Fragment疊加點擊穿透的解決方案](blog.csdn.net/xieluoxixi/…)。如下內容均借鑑於此貼:ide
這個問題其實是點擊事件分發的問題,當多個Fragment添加進Fragment棧時,棧底的Fragment的點擊事件在上層Fragment出現後仍然有效。具體的解決方法有三種,能夠點進帖子中查看。函數
在個人項目中因爲使用Fragment比較多,因此我使用了第二種方案,在BaseFragment中全局添加了view.setClickable(true);
問題再也沒復現過了。佈局
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(this.getLayoutId(), container, false);
rootView.setClickable(true); //把View的click屬性設爲true,截斷點擊時間段擴散
return super.onCreateView(inflater, container, savedInstanceState);
}
複製代碼
這個問題恰好跟上一個相反,上一個問題是界面看不出重疊,但點擊事件重疊了。而這個是隻有界面重疊,點擊卻沒有問題(也多是由於我已經把上一個問題解決了,不解決的話可能就都有問題了- - )優化
出現的狀況是當APP被異常銷燬重啓時,可能致使的又內存不足,或者旋轉屏幕方向以後沒有作處理等。能夠在開發者模式中勾選【不保留活動】,讓每次退回到桌面再切換回APP時都從新加載一遍,模擬內存不足的效果,能夠更方便地查看這個問題是否存在。this
這個問題的緣由也比較好找,解決方法也不難,網上能夠搜到不少帖子。其實前面講到旋轉屏幕後就會復現,那天然就能聯想到onSaveInstanceState()
,這個問題出現的緣由就在於,異常銷燬時,系統會默認使用銷燬前該Activity保存的狀態來進行恢復,也就是將以前的Fragment從新恢復了,但APP銷燬後從新啓動,Fragment又被Add了一遍,因此形成了Fragment重疊。spa
不過網上的帖子我看了一些,發現都沒有提到一個點,這個問題在Activity的xml根佈局中添加了android:fitsSystemWindows="true"
方法後,就不會出現了,至少對於我是這樣。由於個人項目以前一直沒有出現這個問題,在我某天將首頁的佈局改成沉浸式,去掉了這個方法後,就出現Fragment重疊的現象了。在後面優化了這個問題後,我在想爲何以前沒有出現過,是由於這行代碼嗎?因而我找了一個老一點的版本安裝到手機,打開開發者選項-不保存活動,發現這個問題真的沒有出現。我不肯定這是個別手機的問題,仍是設了android:fitsSystemWindows="true"
以後就真的不會出現重疊。但願有了解的朋友們告知一下。.net
回到如何解決這個問題。最簡單粗暴固然是直接禁止Activity銷燬時保存狀態,將onSaveInstanceState(Bundle outState)
方法中的內容註釋掉,重啓時天然就不會恢復而後重疊了。可是這樣太粗暴了,也沒有任何用戶體驗可言。那在項目中固然不能這麼一刀切,下面貼代碼講一下我是如何處理的:code
在首頁MainActivity中的onSaveInstanceState(Bundle outState)
方法裏,判斷當前全部Fragment,將已經加載的Fragment進行保存
@Override
protected void onSaveInstanceState(Bundle outState) {
/*fragment不爲空時 保存*/
for (int i = 0; i < TAB_SIZE; i++) {
//確保fragment是否已經加入到fragment manager中
if (mFragmentList[i].isAdded() && mFragmentList[i] != null) {
//保存已加載的Fragment
getSupportFragmentManager().putFragment(outState, mFragmentTags[i],
mFragmentList[i]);
}
}
//傳入當前選中的tab值,在銷燬重啓後再定向到該tab
outState.putInt(CURRENT_INDEX, mCurrentIndex);
super.onSaveInstanceState(outState);
}
複製代碼
這裏須要注意的是,經過getSupportFragmentManager().putFragment();
方法按Tag保存Fragment時,須要先確認該Fragment已經add到FragmentManager中了,不然會出現 IllegalStateException: Fragment is not currently in the FragmentManager 錯誤。
在onCreate(Bundle savedInstanceState)
中恢復保存的Fragment:
@Override
public void onCreate(Bundle savedInstanceState) {
if (savedInstanceState != null) {
/*獲取保存的fragment 沒有的話返回null*/
for (int i = 0; i < TAB_SIZE; i++) {
Fragment fragment = getSupportFragmentManager().getFragment(savedInstanceState, mFragmentTags[i]);
if (fragment != null) {
mFragmentList[i] = fragment;
}
}
mCurrentIndex = savedInstanceState.getInt(CURRENT_INDEX, INDEX_HOME);
}
initFragment();
initTab();
}
複製代碼
在進入onCreate
函數時,先判斷savedInstanceState
是否爲null,逐步判斷對應Tag的Fragment存不存在,存在則傳入到存儲Fragment的list中。
初始化Fragment
這一步原本是第一步,不過加了前面的操做以後,原本爲空的FragmentList如今就不必定爲空了,因此在初始化各個Fragment時,記得先判斷是否已經存在了,若是不存在才創新一個新的對象,不然就是已經添加了以前保存的Fragment:
private void initFragment() {
if (mFragmentList[0] == null) {
mFragmentList[0] = new xxFragment//須要建立的Fragment;
}
if (mFragmentList[1] == null) {
mFragmentList[1] = new xxFragment
}
if (mFragmentList[2] == null) {
mFragmentList[2] = new xxFragment
}
if (mFragmentList[3] == null) {
mFragmentList[3] = new xxFragment
}
if (mFragmentList[4] == null) {
mFragmentList[4] = new xxFragment
}
}
複製代碼
OK,到這裏Fragment該恢復的恢復,該建立的建立,接下來按正常流程執行就行了。重疊的問題就不會再出現啦。