Fragment必須提供無參構(gòu)造函數(shù)

最近在開發(fā)中遇到一個crash,仔細(xì)研究了一下捞蚂,記錄一下:
先說結(jié)論:使用Fragment時妇押,要聲明一個無參的構(gòu)造函數(shù),否則在狀態(tài)恢復(fù)時會出現(xiàn)crash
因為當(dāng)Fragment因為某種原因重新創(chuàng)建時姓迅,會調(diào)用到onCreate方法傳入之前保存的狀態(tài)敲霍,在instantiate方法中通過反射無參構(gòu)造函數(shù)創(chuàng)建一個Fragment,并且為Arguments初始化為原來保存的值丁存,而此時如果沒有無參構(gòu)造函數(shù)就會拋出異常色冀,造成程序崩潰。

java.lang.RuntimeException: Unable to start activity ComponentInfo{com.demo/com.demo.activity.DemoActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.demo.fragment.DemoFragment: could not find Fragment constructor
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2944)
    at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3079)
    at android.app.ActivityThread.handleRelaunchActivityInner(ActivityThread.java:4815)
    at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:4724)
    at android.app.servertransaction.ActivityRelaunchItem.execute(ActivityRelaunchItem.java:69)
    at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1836)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:193)
    at android.app.ActivityThread.main(ActivityThread.java:6702)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:911)
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment com.demo.fragment.DemoFragment: could not find Fragment constructor
    at androidx.fragment.app.Fragment.instantiate(Fragment.java:563)
    at androidx.fragment.app.FragmentContainer.instantiate(FragmentContainer.java:57)
    at androidx.fragment.app.FragmentManager$3.instantiate(FragmentManager.java:390)
    at androidx.fragment.app.FragmentStateManager.<init>(FragmentStateManager.java:74)
    at androidx.fragment.app.FragmentManager.restoreSaveState(FragmentManager.java:2454)
    at androidx.fragment.app.FragmentController.restoreSaveState(FragmentController.java:196)
    at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:287)
    at androidx.appcompat.app.AppCompatActivity.onCreate(AppCompatActivity.java:115)
    at com.demo.activity.BaseActivity.onCreate(BaseActivity.java:110)
    at com.demo.activity.DemoActivity.onCreate(DemoActivity.java:81)
    at android.app.Activity.performCreate(Activity.java:7136)
    at android.app.Activity.performCreate(Activity.java:7127)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2924)
    ... 13 more
Caused by: java.lang.NoSuchMethodException: <init> []
    at java.lang.Class.getConstructor0(Class.java:2327)
    at java.lang.Class.getConstructor(Class.java:1725)
    at androidx.fragment.app.Fragment.instantiate(Fragment.java:548)
    ... 26 more

根據(jù)堆棧提示柱嫌,找到了出問題的地方Fragmet類:

fragment.png

可以看到,問題原因是沒有找到fragment的構(gòu)造函數(shù)屯换,具體是在Fragment f = clazz.getConstructor().newInstance(); 調(diào)用無參構(gòu)造函數(shù)時發(fā)出了錯誤编丘。
什么時候會調(diào)用 instantiate 方法呢
在activity創(chuàng)建時,由FragmentActivity onCeate 透傳序列化的方法state:FRAGMENTS_TAG = "android:support:fragments"
onCreate.png

會調(diào)用 FragmentController.restoreSaveState 方法彤悔,由注釋可知嘉抓,該方法是為了恢復(fù)所有被保存的fragment的狀態(tài)
接下來調(diào)用 FragmentManager.restoreSaveState,此方法內(nèi)activity onCreate傳入的序列化對象強轉(zhuǎn)為 FragmentManagerState
FragmentManager.restoreSaveState

這里特別說一下晕窑,為什么可以直接強轉(zhuǎn)給FragmentManagerState對象抑片,原因在下面的方法,保存fragment實時狀態(tài)的:Parcelable p = mFragments.saveAllState();
FragmentActivity.png

Parcelable saveAllState() {
    // Make sure all pending operations have now been executed to get
    // our state update-to-date.
    forcePostponedTransactions();
    endAnimatingAwayFragments();
    execPendingActions();
    // 省略部分代碼......
 
    // First collect all active fragments.
    int size = mActive.size();
    // 省略部分代碼......
 
    // Build list of currently added fragments.
    size = mAdded.size();
    // 省略部分代碼......
 
    // Now save back stack.
    // 省略部分代碼......
 
    FragmentManagerState fms = new FragmentManagerState();
    fms.mActive = active;
    fms.mAdded = added;
    fms.mBackStack = backStack;
    if (mPrimaryNav != null) {
        fms.mPrimaryNavActiveWho = mPrimaryNav.mWho;
    }
    fms.mNextFragmentIndex = mNextFragmentIndex;
    return fms;
}

可以看到杨赤,saveAllState() 返回的對象其實就是FragmentManagerState敞斋,這里返回Parcelable而不是FragmentManagerState對象主要是方便數(shù)據(jù)的持久化處理。
因此在恢復(fù)狀態(tài)時 FragmentManager.restoreSaveState方法可以直接將Parcelable對象強轉(zhuǎn)為FragmentManagerState對象疾牲。
FragmentManager中構(gòu)建了默認(rèn)的FragmentFractory植捎,F(xiàn)actory中重寫了instantiate方法,調(diào)用了FragmentHostCallback的instantiate方法阳柔。該方法最終調(diào)用了Fragment類的中靜態(tài)方法焰枢。
即文章開始提到的 Fragment.instantiate 方法,在instantiate中嘗試用無參構(gòu)造函數(shù)創(chuàng)建fragment實例時由于找不到無參的構(gòu)造函數(shù)而報錯
此外需要注意舌剂,創(chuàng)建fragment涉及到相關(guān)參數(shù)保存的操作济锄,官方建調(diào)用fragment.setArguments(args)方法,系統(tǒng)會再恢復(fù)狀態(tài)時同步恢復(fù)這些參數(shù)霍转,從而避免業(yè)務(wù)數(shù)據(jù)的丟失荐绝。

/**
 * Create a new instance of a Fragment with the given class name.  This is
 * the same as calling its empty constructor, setting the {@link ClassLoader} on the
 * supplied arguments, then calling {@link #setArguments(Bundle)}.
 *
 * ....省略部分
 *
 */
/**
 * Supply the construction arguments for this fragment.
 * The arguments supplied here will be retained across fragment destroy and
 * creation.
 * <p>This method cannot be called if the fragment is added to a FragmentManager and
 * if {@link #isStateSaved()} would return true.</p>
 */
public void setArguments(@Nullable Bundle args) {
    if (mFragmentManager != null && isStateSaved()) {
        throw new IllegalStateException("Fragment already added and state has been saved");
    }
    mArguments = args;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谴忧,隨后出現(xiàn)的幾起案子很泊,更是在濱河造成了極大的恐慌角虫,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件委造,死亡現(xiàn)場離奇詭異戳鹅,居然都是意外死亡,警方通過查閱死者的電腦和手機昏兆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門枫虏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人爬虱,你說我怎么就攤上這事隶债。” “怎么了跑筝?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵死讹,是天一觀的道長。 經(jīng)常有香客問我曲梗,道長赞警,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任虏两,我火速辦了婚禮愧旦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘定罢。我一直安慰自己笤虫,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布祖凫。 她就那樣靜靜地躺著琼蚯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惠况。 梳的紋絲不亂的頭發(fā)上凌停,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音售滤,去河邊找鬼罚拟。 笑死,一個胖子當(dāng)著我的面吹牛完箩,可吹牛的內(nèi)容都是我干的赐俗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼弊知,長吁一口氣:“原來是場噩夢啊……” “哼阻逮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起秩彤,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤叔扼,失蹤者是張志新(化名)和其女友劉穎事哭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瓜富,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡鳍咱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了与柑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谤辜。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖价捧,靈堂內(nèi)的尸體忽然破棺而出丑念,到底是詐尸還是另有隱情,我是刑警寧澤结蟋,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布脯倚,位于F島的核電站,受9級特大地震影響嵌屎,放射性物質(zhì)發(fā)生泄漏挠将。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一编整、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧乳丰,春花似錦掌测、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至什燕,卻和暖如春粘勒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屎即。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工庙睡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人技俐。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓乘陪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雕擂。 傳聞我的和親對象是個殘疾皇子啡邑,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

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