Fragment no longer exists for key f0: unique id e81e86a9-84bc-4577-b32a-0f989479ce38

先貼一下bugly上抓到的bug信息

image.png

一般是使用了Fragment+ViewPager+FragmentStatePagerAdapter+Fragment這種結(jié)構(gòu)才會出現(xiàn)上述的bug倾剿。
個人推測應該是Activity異常退出,然后重建Activity時,里面的Adapter中的Fragment會從FragmentManager中進行恢復职抡,在恢復的過程中出錯了件炉。
現(xiàn)在網(wǎng)上的做法核心就是在異常退出的時候不保存狀態(tài),所以就不存在恢復,就走不到下面的代碼琐馆,也就不存在報上面的異常了夭拌。

下面分析下異常魔熏。根據(jù)日志可以看出是在FragmentManagerImpl的getFragment()方法中報出的異常。

/**
 * Container for fragments associated with an activity.
 */
final class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
...
@Override
    @Nullable
    public Fragment getFragment(Bundle bundle, String key) {
        String who = bundle.getString(key);
        if (who == null) {
            return null;
        }
        Fragment f = mActive.get(who);
        if (f == null) {
            throwException(new IllegalStateException("Fragment no longer exists for key "
                    + key + ": unique id " + who));
        }
        return f;
    }
...
}

這個FragmentManagerImpl其實就是我們常用的FragmentManager的具體實現(xiàn)類鸽扁。在往上看哪里調(diào)用了這個getFragment()方法蒜绽。FragmentStatePagerAdapter中restoreState()調(diào)用的


public abstract class FragmentStatePagerAdapter extends PagerAdapter {
...
   private final FragmentManager mFragmentManager;
 
   private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
    private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
...
 @Override
    public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key: keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        while (mFragments.size() <= index) {
                            mFragments.add(null);
                        }
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
        }
    }
...
}

這一步看方法名就能理解,是恢復狀態(tài)的作用桶现。從bundle中獲取存儲的狀態(tài)躲雅,再通過FragmentManager獲取到對應的Fragment然后存儲到mFragments中。其實就是對Adapter中的Fragment進行恢復。向上追溯可以看到該方法會被ViewPager調(diào)用。

    @Override
    public class ViewPager extends ViewGroup {
    ...
    public void onRestoreInstanceState(Parcelable state) {
        if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

        SavedState ss = (SavedState) state;
        super.onRestoreInstanceState(ss.getSuperState());

        if (mAdapter != null) {
            mAdapter.restoreState(ss.adapterState, ss.loader);
            setCurrentItemInternal(ss.position, false, true);
        } else {
            mRestoredCurItem = ss.position;
            mRestoredAdapterState = ss.adapterState;
            mRestoredClassLoader = ss.loader;
        }
    }
...
}

在ViewPager的onRestoreInstanceState()方法中涂圆,如果mAdapter 不為空的話十饥,會走到上面我們看過的mAdapter.restoreState(ss.adapterState, ss.loader);方法。而如果我們不想讓他報異常的話殖妇,不讓他走這個方法就行。就是讓下面代碼恒成立即可:

 if (!(state instanceof SavedState)) {
            super.onRestoreInstanceState(state);
            return;
        }

onRestoreInstanceState對應的是onSaveInstanceState存儲的state。

  @Override
    public Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.position = mCurItem;
        if (mAdapter != null) {
            ss.adapterState = mAdapter.saveState();
        }
        return ss;
    }

現(xiàn)在網(wǎng)上流行的方法绵脯,就是重寫mAdapter方法讓其返回null,這樣onRestoreInstanceState中判斷條件成立,然后就不會走getFragment()就不會報錯休里。

解決方式有兩個:

  • 第一種 FragmentStatePagerAdapter換成FragmentPagerAdapter
  • 第二種 實現(xiàn)FragmentStatePagerAdapter時重寫saveState()方法返回 null蛆挫。

其實FragmentPagerAdapter也是將saveState()方法返回 null

public abstract class FragmentPagerAdapter extends PagerAdapter {
...
    @Override
    @Nullable
    public Parcelable saveState() {
        return null;
    }
...
}

而FragmentStatePagerAdapter的saveState()方法默認實現(xiàn)如下

public abstract class FragmentStatePagerAdapter extends PagerAdapter {
...
  @Override
    @Nullable
    public Parcelable saveState() {
        Bundle state = null;
        if (mSavedState.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
            mSavedState.toArray(fss);
            state.putParcelableArray("states", fss);
        }
        for (int i=0; i<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null && f.isAdded()) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }
...
}

總結(jié):個人感覺這種方式只是一種投機的解決方式,會影響一定的性能(Fragment沒能復用妙黍,當然這點性能可以忽略不計)璃吧。看看能不能找到更好的解決方式废境。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畜挨,一起剝皮案震驚了整個濱河市筒繁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巴元,老刑警劉巖毡咏,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異逮刨,居然都是意外死亡呕缭,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門修己,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恢总,“玉大人,你說我怎么就攤上這事睬愤∑拢” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵尤辱,是天一觀的道長砂豌。 經(jīng)常有香客問我,道長光督,這世上最難降的妖魔是什么阳距? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮结借,結(jié)果婚禮上筐摘,老公的妹妹穿的比我還像新娘。我一直安慰自己船老,他們只是感情好蓄拣,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著努隙,像睡著了一般球恤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荸镊,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天咽斧,我揣著相機與錄音,去河邊找鬼躬存。 笑死张惹,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的岭洲。 我是一名探鬼主播宛逗,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盾剩!你這毒婦竟也來了雷激?” 一聲冷哼從身側(cè)響起替蔬,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屎暇,沒想到半個月后承桥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡根悼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年凶异,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挤巡。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡剩彬,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出矿卑,到底是詐尸還是另有隱情喉恋,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布粪摘,位于F島的核電站,受9級特大地震影響绍坝,放射性物質(zhì)發(fā)生泄漏徘意。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一轩褐、第九天 我趴在偏房一處隱蔽的房頂上張望椎咧。 院中可真熱鬧,春花似錦把介、人聲如沸勤讽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脚牍。三九已至,卻和暖如春巢墅,著一層夾襖步出監(jiān)牢的瞬間诸狭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工君纫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留驯遇,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓蓄髓,卻偏偏與公主長得像叉庐,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子会喝,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內(nèi)容