- 使用的replace,所以切換的時候頁面會重新刷新
- 使用popBackStack()返回上一級的時候,上一級的頁面會重新刷新,但是全局變量不會重新創(chuàng)建,可用于保存數(shù)據(jù),但不需要保存的數(shù)據(jù)頁會被保存,需要在生命周期里重新初始化
- 如果不使用popBackStack()返回上一級,而是使用navigate跳轉(zhuǎn),Android系統(tǒng)執(zhí)行onBackPressed()的時候,會執(zhí)行popBackStack(),所以需要攔截掉并拋出回調(diào),并在回調(diào)中統(tǒng)一執(zhí)行頁面跳轉(zhuǎn)邏輯
- popBackStack()返回時,RecyclerView緩存數(shù)據(jù)狀態(tài)的時候,會記錄之前的滾動位置,如下代碼所示的樣式,有當(dāng)前頁前面的數(shù)據(jù)無法緩存
class DemoAdapter: RecyclerView.Adapter<RecyclerView.ViewHolder>{
private val data = mutableListOf<Info>()
private val positionMap = mutableMapOf<String, Int>()
fun initData(list:MutableList<Info>){
positionMap.clear()
data.addAll(list)
notifyDataSetChanged()
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
positionMap[data[holder.bindingAdapterPosition].name] = holder.bindingAdapterPosition
}
}
- 謹(jǐn)慎使用lateinit
- 進程被殺后,會殘留緩存,在下次開啟時,可能導(dǎo)致狀態(tài)錯亂,需要清理緩存信息
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.clear()
}
- 只有popBackStack()會緩存數(shù)據(jù),如果多tab頁切換的情況下只能相互跳轉(zhuǎn),無法緩存數(shù)據(jù),只能通過單例對象(靜態(tài)類)存儲數(shù)據(jù)或狀態(tài)
- 多個頁面同時跳轉(zhuǎn)的時候,可能出現(xiàn)崩潰(連續(xù)兩次調(diào)用navigate就可以觸發(fā)),要么在頁面跳轉(zhuǎn)處做多頁面見的防連點,要么在頁面跳轉(zhuǎn)的時候try-catch
- 處于后臺的時候切換頁面不執(zhí)行
場景:初始化數(shù)據(jù),判定跳轉(zhuǎn)到首頁的哪個tab,在調(diào)用數(shù)據(jù)初始化接口的時候,切到后臺。等待初始化接口返回結(jié)果,本該navigator加載頁面的log打印了,但是將應(yīng)用切到前臺的時候,發(fā)現(xiàn)頁面并沒有加載出來 - 眾所周知,google出的Navigator各種異常,今天有幸遇到個新花樣。展示DialogFragment的同時開啟了二級頁面,當(dāng)二級頁面返回的時候會崩潰。
java.lang.IllegalStateException: DialogFragment can not be attached to a container view
at androidx.fragment.app.DialogFragment$4.onChanged(DialogFragment.java:151)
at androidx.fragment.app.DialogFragment$4.onChanged(DialogFragment.java:144)
at androidx.lifecycle.LiveData.considerNotify(LiveData.java:133)
at androidx.lifecycle.LiveData.dispatchingValue(LiveData.java:151)
at androidx.lifecycle.LiveData.setValue(LiveData.java:309)
at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3115)
at androidx.fragment.app.DialogFragment.performCreateView(DialogFragment.java:510)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1433)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2977)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2888)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1433)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2977)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2888)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1433)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2977)
at androidx.fragment.app.FragmentManager.dispatchViewCreated(FragmentManager.java:2888)
at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3129)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:552)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1823)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1760)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:547)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7664)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:607)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:995)
粗略一看,DialogFragment崩了,而且崩潰的日志是純原生的,這是什么鬼。。。
private Observer<LifecycleOwner> mObserver = new Observer<LifecycleOwner>() {
@SuppressLint("SyntheticAccessor")
@Override
public void onChanged(LifecycleOwner lifecycleOwner) {
if (lifecycleOwner != null && mShowsDialog) {
View view = requireView();
if (view.getParent() != null) {
throw new IllegalStateException(
"DialogFragment can not be attached to a container view");
}
if (mDialog != null) {
if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
Log.d(TAG, "DialogFragment " + this + " setting the content view on "
+ mDialog);
}
mDialog.setContentView(view);
}
}
}
};
竟然是生命周期監(jiān)聽導(dǎo)致的崩潰,可二級頁面關(guān)閉,重新回顯當(dāng)前頁面,DialogFragment走生命周期很正常啊,為啥要驗證view.getParent(),而且如果這么操作真的是問題,那google早被人沖了,怎么會等到現(xiàn)在被我發(fā)現(xiàn)。
求助大神同事幫忙排查,原因是返回的時候DialogFragment執(zhí)行了onCreateView方法,而return的view竟然有parent了。
可DialogFragment已經(jīng)展示了,為什么會再次執(zhí)行onCreateView。而且只有當(dāng)從二級頁返回的時候會觸發(fā)異常,但是home鍵回到桌面,再次打開應(yīng)用的時候不會。
經(jīng)過排查,原來是navigator在跳轉(zhuǎn)二級頁的時候,殺死了當(dāng)前Fragment,當(dāng)返回到當(dāng)前頁的時候,重新創(chuàng)建導(dǎo)致的。又由于navigator有緩存,所以新建的DialogFragment也會緩存對應(yīng)的parent信息。
無奈,只能在onCreateView添加處理邏輯,雖然不夠優(yōu)雅,但至少好用吧。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
if (mBinding.root.parent != null) {
(mBinding.root.parent as? ViewGroup)?.removeView(mBinding.root)
}
return mBinding.root
}
…………………………………………
Android的坑千千萬,navigator占一半,繼上次dialog崩潰后,再次遇到navigator + DialogFragment的組合套餐
問題一:
前提:navigator頁面跳轉(zhuǎn)(A -> B),DialogFragment輪詢請求網(wǎng)絡(luò)并依據(jù)請求結(jié)果進行狀態(tài)變更,且A、B頁面均彈出相同DialogFragment,請求信息一致
手順:A頁面雙指按住B頁面的跳轉(zhuǎn)按鈕和DialogFragment的彈出按鈕,DialogFragmen按鈕稍微早于頁面跳轉(zhuǎn)按鈕抬起(感覺上是先抬起,視覺上基本看不出的時間差),讓DialogFragmen彈出瞬間切換到B頁面,然后在B頁面喚起DialogFragmen,在DialogFragmen展示后,通過外部觸發(fā)DialogFragmen請求結(jié)果的UI變更條件
現(xiàn)象:B頁面UI無變化,關(guān)閉B頁面的DialogFragmen,再關(guān)閉B頁面,發(fā)現(xiàn)A頁面的UI變更
分析:
A頁面DialogFragmen的UI變更:網(wǎng)絡(luò)請求輪詢DialogFragmen持有頁面A,導(dǎo)致navigator沒有成功回收頁面A,導(dǎo)致DialogFragmen依然能夠接受到網(wǎng)絡(luò)請求數(shù)據(jù),執(zhí)行頁面UI切換。
B頁面DialogFragmen無響應(yīng),著急修改更嚴(yán)重的問題,沒進一步分析
問題二:
前提:navigator頁面跳轉(zhuǎn)(A -> B),DialogFragment延遲關(guān)閉。
手順:A頁面雙指按住B頁面的跳轉(zhuǎn)按鈕和DialogFragment的彈出按鈕,DialogFragmen按鈕稍微早于頁面跳轉(zhuǎn)按鈕抬起(感覺上是先抬起,視覺上基本看不出的時間差),讓DialogFragmen彈出瞬間切換到B頁面,且DialogFragmen的延遲關(guān)閉邏輯是在切換到B頁面后執(zhí)行
現(xiàn)象:從B頁面返回到A頁面,本應(yīng)該關(guān)閉的彈窗被再次展示出來,且不會自動關(guān)閉,點擊關(guān)閉按鈕有按下態(tài)效果,但彈窗依然不關(guān)閉
分析:
- 通過DialogFragment,onCreateView時候打印的hashcode可知,再次展示的DialogFragment與被異步關(guān)閉的DialogFragment是同一個
- 通過點擊事件斷點可知,點擊事件確實有響應(yīng)
-
代碼分析如下:
調(diào)用dialog的dismiss
mDismissed為true
不知道navigator在展示的時候是怎么創(chuàng)建的:

沒有執(zhí)行show

沒有執(zhí)行showNow

也沒有執(zhí)行onAttach

…………………………………………
不用千年等一回,bug又來嘞?。。?br>
當(dāng)前頁面結(jié)構(gòu)如圖:
MainActivity
├── Fragment A
│ ├── Fragment A1
│ └── Fragment A2
└── Fragment B
且根據(jù)產(chǎn)品需求,在頁面跳轉(zhuǎn)時需要加入動畫效果:navigator.xml
<fragment
android:id="@+id/fragmentA"
android:name="com.demo.ui.fragment.AFragment"
android:label="fragmentA"
tools:layout="@layout/fragmentA">
<action
android:id="@+id/fragment_a_to_fragment_b"
app:destination="@id/fragmentB"
app:enterAnim="@anim/anim_page_in"
app:exitAnim="@anim/anim_page_out"
app:popEnterAnim="@anim/anim_page_in"
app:popExitAnim="@anim/anim_page_out" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="com.demo.ui.fragment.BFragment"
android:label="BFragment">
</fragment>
anim_page_in.xml
<?xml version="1.0" encoding="utf-8"?>
<alpha android:fromAlpha="0"
android:toAlpha="1"
android:duration="500"
xmlns:android="http://schemas.android.com/apk/res/android" />
anim_page_out.xml
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:fromAlpha="1"
android:toAlpha="0" />
原本只是完成了這個需求,結(jié)果測試的時候,多指操作,在Fragment A1中通過navigation的原生跳轉(zhuǎn)方法調(diào)用Fragment A跳轉(zhuǎn)到Fragment B中,并在動畫執(zhí)行過程中,再次點擊Fragment A1的另一個button,執(zhí)行Fragment A1跳轉(zhuǎn)到Fragment A2的操作。
當(dāng)執(zhí)行完成后,可以看到應(yīng)用當(dāng)前展示的是Fragment B的頁面,但當(dāng)執(zhí)行系統(tǒng)的虛擬返回操作時,F(xiàn)ragment B卻沒有監(jiān)聽到onBackPressed(),而是Fragment A2監(jiān)聽到了onBackPressed(),由于在Fragment A2的onBackPressed()執(zhí)行了如下方法:
override fun onBackPressed() {
super.onBackPressed()
activity?.onBackPressed()
}
導(dǎo)致頁面后臺運行,當(dāng)將應(yīng)用喚到前臺的時候,再次點擊Fragment A1調(diào)用Fragment A跳轉(zhuǎn)Fragment B的按鈕的時候,發(fā)現(xiàn)Fragment A1無法找到parentFragment的方式找到Fragment A。暫未發(fā)現(xiàn)官方可用方案,暫時只能在執(zhí)行跳轉(zhuǎn)時攔截點擊事件

