当前位置: 首页 > news >正文

网站开发种类如何建立一个网站

网站开发种类,如何建立一个网站,做好网站建设通知,重庆建设信息工程信息网前言 在使用Fragment的时候,在内存重启的后,很容易出现一些难以预期的bug,下面将继续一边分析源码,一边看看这个bug是怎么产生的。 这个报错的原因,可能和你的情况并不尽然相同。但是你可以通过对源码的理解&#xf…

前言

在使用Fragment的时候,在内存重启的后,很容易出现一些难以预期的bug,下面将继续一边分析源码,一边看看这个bug是怎么产生的。

这个报错的原因,可能和你的情况并不尽然相同。但是你可以通过对源码的理解,来加深对fragment的认识,从而能更优雅的解决问题。

推荐一下我维护的fragment库,轻松实现单activity+多fragment 的框架,https://github.com/JantHsueh/Fragmentation

报错

通俗的讲,就是在要使用Fragment 的 mHost 变量的时候,这个 变量为空。导致下面的报错

2019-12-30 09:39:55.755 28136-28136/com.icisoo.xw.staging E/MessageQueue-JNI: Exception in MessageQueue callback: handleReceiveCallback
2019-12-30 09:39:55.761 28136-28136/com.icisoo.xw.staging E/MessageQueue-JNI: java.lang.IllegalStateException: Fragment has not been attached yet.at androidx.fragment.app.Fragment.instantiateChildFragmentManager(Fragment.java:2383)at androidx.fragment.app.Fragment.getChildFragmentManager(Fragment.java:845)at me.yokeyword.fragmentation.debug.DebugStackDelegate.getChildFragmentRecords(DebugStackDelegate.java:183)at me.yokeyword.fragmentation.debug.DebugStackDelegate.addDebugFragmentRecord(DebugStackDelegate.java:211)at me.yokeyword.fragmentation.debug.DebugStackDelegate.getFragmentRecords(DebugStackDelegate.java:153)at me.yokeyword.fragmentation.debug.DebugStackDelegate.showFragmentStackHierarchyView(DebugStackDelegate.java:101)at me.yokeyword.fragmentation.debug.DebugStackDelegate$1.onClick(DebugStackDelegate.java:67)at android.view.View.performClick(View.java:6597)at me.yokeyword.fragmentation.debug.DebugStackDelegate$StackViewTouchListener.onTouch(DebugStackDelegate.java:257)at android.view.View.dispatchTouchEvent(View.java:12509)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:3030)at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2719)at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:440)at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1830)at android.app.Activity.dispatchTouchEvent(Activity.java:3400)at me.yokeyword.fragmentation.SupportActivity.dispatchTouchEvent(SupportActivity.java:59)at androidx.appcompat.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:398)at android.view.View.dispatchPointerEvent(View.java:12752)at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5106)at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4909)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4585)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4642)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4479)at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4445)at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4453)at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4426)at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7092)at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:7061)at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:7022)at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7195)at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)at android.os.MessageQueue.nativePollOnce(Native Method)at android.os.MessageQueue.next(MessageQueue.java:326)at android.os.Looper.loop(Looper.java:160)at android.app.ActivityThread.main(ActivityThread.java:6718)at java.lang.reflect.Method.invoke(Native Method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)at 

重现步骤

为了方便描述,下面SplashFragment 实例 简称S,LoginFragment 实例简称 L。第一次打开APP,通过replace(S)启动S后,会add(L)hide(S)

如何模拟内存重启,查看这篇博客

1、第一次打开app,有 S1,L1
2、点击home键(模拟内存重启,系统会保存S1,L1),
3、打开app,有S2,L2,S1,L1 (S1,L1 是系统自动恢复的)
4、点击home键(模拟内存重启,系统会保存S2,L2,S1,L1)
5、打开app,有S3,L3,S2,L2,S1,L1(S2,L2,S1,L1 是系统自动恢复的)

这时候S1和L1的mHost 为空,导致上面这个错误

当然这样的启动过程,是有问题的,同学们也很容易想出解决办法。我们通过源码来深入分析一下,为什么会出现这个问题。

源码分析

通过追溯源码,发现在第二次内存重启后,出现S1 L1 的mHost 为空的问题。
在这里插入图片描述

到当前断点看下更深的函数调用
在这里插入图片描述

这里就是重现步骤中的第五步,其中 1和 2 的实例的mHost 为空,如下图(图中只展示1的变量情况):

在这里插入图片描述

通过不断调试,发现在重现步骤的第五步,系统恢复S2,L2,S1,L1,

在这里插入图片描述

这时候S2,L2,S1,L1 中的mHost 值都为null,图中只展示S2 的mHost数据

在这里插入图片描述

那么S2,L2 的mHost 是在哪里赋值的呢?为什么S1,L1的mHost 没有被赋值

在onCreate()函数执行的时候,调用了 dispatchStateChange(Fragment.CREATED); 最终调用下图中的moveToState() 来把mAdded 中的fragment 移动到指定的生命周期状态
在这里插入图片描述
moveFragmentToExpectedState中判断要置为哪种初始状态,在下图中的moveToState 中的函数设置了mHost的值。也就是说在初始化中,只有在mAdded中的Fragment 才会设置mHost。在恢复数据时,S1,L1 没有添加到mAdded,也就是说上次保存数据的时候,S1,L1没有被保存在mAdded中
在这里插入图片描述

来看看上一次保存fragment 实例时(重现步骤第3步)是什么样的

mActive 中有S2,L2,S1,L1 ,mAdded 中有S2,L2
在这里插入图片描述
下面是上图中的 saveAllState()的源码

    Parcelable saveAllState() {// Make sure all pending operations have now been executed to get// our state update-to-date.//在保存状态前,保证所以的Transactions都已经执行,动画都已经结束(因为动画结束也会影响fragment的状态,例如animateRemoveFragment(),)。//在onSaveInstance后,不能提交事务.共同保证了,最终保存fragment状态的将不再改变forcePostponedTransactions();endAnimatingAwayFragments();execPendingActions();mStateSaved = true;mSavedNonConfig = null;if (mActive == null || mActive.size() <= 0) {return null;}// First collect all active fragments.int N = mActive.size();//FragmentState一个序列化的用来保存fragment的类FragmentState[] active = new FragmentState[N];boolean haveFragments = false;//遍历mActive中的每一个 fragmentfor (int i=0; i<N; i++) {Fragment f = mActive.valueAt(i);if (f != null) {if (f.mIndex < 0) {throwException(new IllegalStateException("Failure saving state: active " + f+ " has cleared index: " + f.mIndex));}haveFragments = true;//把fragment 转为FragmentState FragmentState fs = new FragmentState(f);//存储在数组active中,这个数组最终被保存active[i] = fs;if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) {fs.mSavedFragmentState = saveFragmentBasicState(f);if (f.mTarget != null) {if (f.mTarget.mIndex < 0) {throwException(new IllegalStateException("Failure saving state: " + f+ " has target not in fragment manager: " + f.mTarget));}if (fs.mSavedFragmentState == null) {fs.mSavedFragmentState = new Bundle();}putFragment(fs.mSavedFragmentState,FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget);if (f.mTargetRequestCode != 0) {fs.mSavedFragmentState.putInt(FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG,f.mTargetRequestCode);}}} else {fs.mSavedFragmentState = f.mSavedFragmentState;}}}int[] added = null;BackStackState[] backStack = null;// Build list of currently added fragments.//因为mActive 是mAdded的超级,所以这里保存mAdded,就只保存了索引值N = mAdded.size();if (N > 0) {added = new int[N];for (int i = 0; i < N; i++) {added[i] = mAdded.get(i).mIndex;if (added[i] < 0) {throwException(new IllegalStateException("Failure saving state: active " + mAdded.get(i)+ " has cleared index: " + added[i]));}if (DEBUG) {Log.v(TAG, "saveAllState: adding fragment #" + i+ ": " + mAdded.get(i));}}}// Now save back stack.//把mBackStack信息保存在BackStackState中if (mBackStack != null) {N = mBackStack.size();if (N > 0) {backStack = new BackStackState[N];for (int i=0; i<N; i++) {backStack[i] = new BackStackState(mBackStack.get(i));if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i+ ": " + mBackStack.get(i));}}}//保存active、added、backStack的序列化类FragmentManagerState fms = new FragmentManagerState();fms.mActive = active;fms.mAdded = added;fms.mBackStack = backStack;if (mPrimaryNav != null) {fms.mPrimaryNavActiveIndex = mPrimaryNav.mIndex;}fms.mNextFragmentIndex = mNextFragmentIndex;saveNonConfig();return fms;}

保存数据搞明白了,恢复数据就很简单,它的逆操作。这里不再详细展开。

那么问题来了,为什么保存的时候,mAdded中只有两个本次添加的fragment呢,系统恢复的fragment为什么没在mAdded中呢?

此时我有两种思路:

1、到上上次保存数据看看。可能上上次保存数据时候,mAdded中没有S1,L1,导致上次恢复数据的时候,没有S1,L1。这种可能应该不大,毕竟是系统的源码,不可能出现这个错误吧。

2、在上次恢复数据后(对应重新步骤3),有恢复S1,L1到mAdded中,但是由于某种操作,给删除了。

经过调试发现,是第二种问题,那么下面来详细描述一下,是什么导致mAdded中的S1,L1的被删除

在上次恢复数据后(对应重新步骤3),S1,L1的mHost 也是被赋值过的(与上面分析的S2,L2是一样的过程),只是后来mAdded中的S1,L1被删除了

在这里插入图片描述

经过不断调试发现,事务中执行了S1,L1的remove()操作,原来是S2使用replace() ,系统优化Ops 导致 S1,L1被remove,下面通过源码分析一下,这个remove行为是如何产生的

打开APP时,业务代码会先使用replace()来启动S(这里指S2),fragment在处理这个事务的时候,在函数expandPos()对操作进行优化,包括replace

在这里插入图片描述

在这里插入图片描述

下图可以看到replace S2,命令 最终边转变为,remove L1 ,S1,add S2,所以

这里顺便插个题外话,调试代码中op是mOps中的第 0 索引的值,但是在代码运行过程中,mOps 第0索引的值,发生变化,op也发生了变化。于是出现下图中,debugger中的局部变量op和代码中对应的op的变量内容不一样

在这里插入图片描述
下面是上图的代码,下面进行详细分析

   // 对mOps操作 进行预处理,也算是一种对op操作优化的操作Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {for (int opNum = 0; opNum < mOps.size(); opNum++) {// 遍历mOps 中所有的操作。例如:add,show,hide,replace 等这些事务中的操作,都会保存在mOps final Op op = mOps.get(opNum);switch (op.cmd) {case OP_ADD:case OP_ATTACH:added.add(op.fragment);break;case OP_REMOVE:case OP_DETACH: {added.remove(op.fragment);if (op.fragment == oldPrimaryNav) {mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));opNum++;oldPrimaryNav = null;}}break;case OP_REPLACE: {//获取该操作对应的fragment,例如:add(f1),这里的op.fragment 就是f1final Fragment f = op.fragment;//获取f的容器idfinal int containerId = f.mContainerId;//是否已经加入到added,这个list中保存所有存活的fragment(没有remove和detached ),mActive包含mAddedboolean alreadyAdded = false;//遍历added中的fragmentfor (int i = added.size() - 1; i >= 0; i--) {final Fragment old = added.get(i);//如果是针对同一容器进行replaceif (old.mContainerId == containerId) {if (old == f) {//例如:replace(f1),f1已经在madded中,也就是说f1之前被添加过alreadyAdded = true;} else {// This is duplicated from above since we only make// a single pass for expanding ops. Unset any outgoing primary nav.if (old == oldPrimaryNav) {mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));opNum++;oldPrimaryNav = null;}//删除相同容器id的fragmentfinal Op removeOp = new Op(OP_REMOVE, old);removeOp.enterAnim = op.enterAnim;removeOp.popEnterAnim = op.popEnterAnim;removeOp.exitAnim = op.exitAnim;removeOp.popExitAnim = op.popExitAnim;//增加一个新的删除操作mOps.add(opNum, removeOp);//从added list中删除这个具有相同容器id的fragmentadded.remove(old);//操作的数量++opNum++;}}}if (alreadyAdded) {//如果已经添加过,删除replace操作//这里删除的是replace的操作,因为mOps.add 都是添加在opNum位置,把replace往后挤了一位,//所以opNum++ 后,opNum 就是replace的索引位置mOps.remove(opNum);opNum--;} else {//如果没有添加过,把replace改为addop.cmd = OP_ADD;//添加到added中added.add(f);}}break;case OP_SET_PRIMARY_NAV: {// It's ok if this is null, that means we will restore to no active// primary navigation fragment on a pop.mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));opNum++;// Will be set by the OP_SET_PRIMARY_NAV we inserted before when runoldPrimaryNav = op.fragment;}break;}}return oldPrimaryNav;}

这里可以看到,最后removeFragment函数中,调用了mAdder.remove() 把L1 和S1删除了,(下图只展示了L1被删除的断点)
在这里插入图片描述

所以在此时保存数据时,是没有L1和S1,也就是说在恢复数据时,mAdder中也没有L1和S1,也就不会给mHost赋值,导致上面这个错误。

解决方法:

第一种:

初次启动App,加载fragment使用add,而不是用replace,避免退出 已经恢复的其他fragment

第二种:

启动App时,判断是否已经有目标fragment的实例,如果已经有该实例,则不创建新的

http://www.lbrq.cn/news/2707435.html

相关文章:

  • 国内网站设计如何进行营销推广
  • 徐州网站推广优化排名优化公司
  • 中国建站网培训网站建设
  • 如何介绍自己做的网站微信腾讯会议
  • 网站建设 图片他达拉非功效与作用主要会有哪些
  • 学设计的网站推荐项目网站
  • 山东淄博网站建设公司人民网 疫情
  • 百度上如何做企业网站地推放单平台
  • 手机网站开发总结百度付费推广有几种方式
  • 东莞正规网站建设seo推广网络
  • 房地产集团网站欣赏google网站推广
  • 德阳网站建设新闻热点事件
  • 高端网网站建设关键词采集软件
  • 一个网站怎么做流量统计软件外包平台
  • 淄博网站制作企业高端海外销售平台有哪些
  • 网站页面设计怎么分析百度推广按效果付费是多少钱
  • 杭州的设计网站建设怎么让百度收录网址
  • 站开发技术培训百度竞价推广计划
  • 网站怎么做百度排名病毒式营销方法
  • dll网站服务超级seo助手
  • 美橙互联网站模板淘宝推广工具
  • 网站店招用什么软件做的广告联盟全自动赚钱系统
  • 建站网站建设郑州网站制作公司
  • 深圳拼团手机网站开发百度站长平台快速收录
  • 山东网站推广营销设计做网络推广一个月的收入
  • 项目计划书范文关键词优化心得
  • ashx做网站网络营销优化培训
  • 怎样给网站做推广沈阳全网推广公司哪家好
  • 彩票网站建设方案企业如何网络推广
  • 东营网站建设电话全国疫情最新情况最新消息今天
  • 一致性哈希Consistent Hashing
  • Windows bypassUAC 提权技法详解(一)
  • 面试题之项目中git如何进行管理
  • 解决EKS中KEDA访问AWS SQS权限问题:完整的IRSA配置指南
  • 【Docker项目实战】使用Docker部署todo任务管理器
  • 图论理论部分