navigator这个坑货

  1. 使用的replace,所以切换的时候页面会重新刷新
  2. 使用popBackStack()返回上一级的时候,上一级的页面会重新刷新,但是全局变量不会重新创建,可用于保存数据,但不需要保存的数据页会被保存,需要在生命周期里重新初始化
  3. 如果不使用popBackStack()返回上一级,而是使用navigate跳转,Android系统执行onBackPressed()的时候,会执行popBackStack(),所以需要拦截掉并抛出回调,并在回调中统一执行页面跳转逻辑
  4. 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
  }
}
  1. 谨慎使用lateinit
  2. 进程被杀后,会残留缓存,在下次开启时,可能导致状态错乱,需要清理缓存信息
override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)
    outState.clear()
}
  1. 只有popBackStack()会缓存数据,如果多tab页切换的情况下只能相互跳转,无法缓存数据,只能通过单例对象(静态类)存储数据或状态
  2. 多个页面同时跳转的时候,可能出现崩溃(连续两次调用navigate就可以触发),要么在页面跳转处做多页面见的防连点,要么在页面跳转的时候try-catch
  3. 处于后台的时候切换页面不执行
    场景:初始化数据,判定跳转到首页的哪个tab,在调用数据初始化接口的时候,切到后台。等待初始化接口返回结果,本该navigator加载页面的log打印了,但是将应用切到前台的时候,发现页面并没有加载出来
  4. 众所周知,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页面,本应该关闭的弹窗被再次展示出来,且不会自动关闭,点击关闭按钮有按下态效果,但弹窗依然不关闭
分析:

  1. 通过DialogFragment,onCreateView时候打印的hashcode可知,再次展示的DialogFragment与被异步关闭的DialogFragment是同一个
  2. 通过点击事件断点可知,点击事件确实有响应
  3. 代码分析如下:


    调用dialog的dismiss

    mDismissed为true

不知道navigator在展示的时候是怎么创建的:


FragmentStateManager

没有执行show


show

没有执行showNow
showNow

也没有执行onAttach


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。暂未发现官方可用方案,暂时只能在执行跳转时拦截点击事件

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容