- 使用的replace,所以切换的时候页面会重新刷新
- 使用popBackStack()返回上一级的时候,上一级的页面会重新刷新,但是全局变量不会重新创建,可用于保存数据,但不需要保存的数据页会被保存,需要在生命周期里重新初始化
- 如果不使用popBackStack()返回上一级,而是使用navigate跳转,Android系统执行onBackPressed()的时候,会执行popBackStack(),所以需要拦截掉并抛出回调,并在回调中统一执行页面跳转逻辑
- popBackStack()返回时,RecyclerView缓存数据状态的时候,会记录之前的滚动位置,如下代码所示的样式,有当前页前面的数据无法缓存
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
}
}
- 谨慎使用lateinit
- 进程被杀后,会残留缓存,在下次开启时,可能导致状态错乱,需要清理缓存信息
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.clear()
}
- 只有popBackStack()会缓存数据,如果多tab页切换的情况下只能相互跳转,无法缓存数据,只能通过单例对象(静态类)存储数据或状态
- 多个页面同时跳转的时候,可能出现崩溃(连续两次调用navigate就可以触发),要么在页面跳转处做多页面见的防连点,要么在页面跳转的时候try-catch
- 处于后台的时候切换页面不执行
场景:初始化数据,判定跳转到首页的哪个tab,在调用数据初始化接口的时候,切到后台。等待初始化接口返回结果,本该navigator加载页面的log打印了,但是将应用切到前台的时候,发现页面并没有加载出来 - 众所周知,google出的Navigator各种异常,今天有幸遇到个新花样。展示DialogFragment的同时开启了二级页面,当二级页面返回的时候会崩溃。
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);
}
}
}
};
竟然是生命周期监听导致的崩溃,可二级页面关闭,重新回显当前页面,DialogFragment走生命周期很正常啊,为啥要验证view.getParent(),而且如果这么操作真的是问题,那google早被人冲了,怎么会等到现在被我发现。
求助大神同事帮忙排查,原因是返回的时候DialogFragment执行了onCreateView方法,而return的view竟然有parent了。
可DialogFragment已经展示了,为什么会再次执行onCreateView。而且只有当从二级页返回的时候会触发异常,但是home键回到桌面,再次打开应用的时候不会。
经过排查,原来是navigator在跳转二级页的时候,杀死了当前Fragment,当返回到当前页的时候,重新创建导致的。又由于navigator有缓存,所以新建的DialogFragment也会缓存对应的parent信息。
无奈,只能在onCreateView添加处理逻辑,虽然不够优雅,但至少好用吧。
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页面跳转(A -> B),DialogFragment轮询请求网络并依据请求结果进行状态变更,且A、B页面均弹出相同DialogFragment,请求信息一致
手顺:A页面双指按住B页面的跳转按钮和DialogFragment的弹出按钮,DialogFragmen按钮稍微早于页面跳转按钮抬起(感觉上是先抬起,视觉上基本看不出的时间差),让DialogFragmen弹出瞬间切换到B页面,然后在B页面唤起DialogFragmen,在DialogFragmen展示后,通过外部触发DialogFragmen请求结果的UI变更条件
现象:B页面UI无变化,关闭B页面的DialogFragmen,再关闭B页面,发现A页面的UI变更
分析:
A页面DialogFragmen的UI变更:网络请求轮询DialogFragmen持有页面A,导致navigator没有成功回收页面A,导致DialogFragmen依然能够接受到网络请求数据,执行页面UI切换。
B页面DialogFragmen无响应,着急修改更严重的问题,没进一步分析
问题二:
前提:navigator页面跳转(A -> B),DialogFragment延迟关闭。
手顺:A页面双指按住B页面的跳转按钮和DialogFragment的弹出按钮,DialogFragmen按钮稍微早于页面跳转按钮抬起(感觉上是先抬起,视觉上基本看不出的时间差),让DialogFragmen弹出瞬间切换到B页面,且DialogFragmen的延迟关闭逻辑是在切换到B页面后执行
现象:从B页面返回到A页面,本应该关闭的弹窗被再次展示出来,且不会自动关闭,点击关闭按钮有按下态效果,但弹窗依然不关闭
分析:
- 通过DialogFragment,onCreateView时候打印的hashcode可知,再次展示的DialogFragment与被异步关闭的DialogFragment是同一个
- 通过点击事件断点可知,点击事件确实有响应
-
代码分析如下:
不知道navigator在展示的时候是怎么创建的:
没有执行show
没有执行showNow
也没有执行onAttach
…………………………………………
不用千年等一回,bug又来嘞!!!
当前页面结构如图:
MainActivity
├── Fragment A
│ ├── Fragment A1
│ └── Fragment A2
└── Fragment B
且根据产品需求,在页面跳转时需要加入动画效果: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" />
原本只是完成了这个需求,结果测试的时候,多指操作,在Fragment A1中通过navigation的原生跳转方法调用Fragment A跳转到Fragment B中,并在动画执行过程中,再次点击Fragment A1的另一个button,执行Fragment A1跳转到Fragment A2的操作。
当执行完成后,可以看到应用当前展示的是Fragment B的页面,但当执行系统的虚拟返回操作时,Fragment B却没有监听到onBackPressed(),而是Fragment A2监听到了onBackPressed(),由于在Fragment A2的onBackPressed()执行了如下方法:
override fun onBackPressed() {
super.onBackPressed()
activity?.onBackPressed()
}
导致页面后台运行,当将应用唤到前台的时候,再次点击Fragment A1调用Fragment A跳转Fragment B的按钮的时候,发现Fragment A1无法找到parentFragment的方式找到Fragment A。暂未发现官方可用方案,暂时只能在执行跳转时拦截点击事件