最近在開發(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的構(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"
會調(diào)用 FragmentController.restoreSaveState 方法彤悔,由注釋可知嘉抓,該方法是為了恢復(fù)所有被保存的fragment的狀態(tài)
接下來調(diào)用 FragmentManager.restoreSaveState,此方法內(nèi)activity onCreate傳入的序列化對象強轉(zhuǎn)為 FragmentManagerState
這里特別說一下晕窑,為什么可以直接強轉(zhuǎn)給FragmentManagerState對象抑片,原因在下面的方法,保存fragment實時狀態(tài)的:Parcelable p = mFragments.saveAllState();
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;
}