草庐IT

android - fragment 回栈和 isRemoving()

coder 2023-06-09 原文

当 Activity 刚刚将 fragment 添加到返回堆栈时,我遇到了来自 Fragment.isRemoving() 的不一致返回值。 第一次 fragment 因配置更改而被临时销毁,isRemoving()返回true。如果 fragment 被第二次临时销毁,isRemoving() 返回 false!

我的代码:

public class MainActivityFragment extends Fragment {
    private static final String TAG = "MainActivityFragment";
    private static final String LEVEL = "MainActivityFragment.LEVEL";

    public MainActivityFragment() {
    }

    public static MainActivityFragment newInstance(int n) {
        MainActivityFragment f = new MainActivityFragment();
        f.setArguments(new Bundle());
        f.getArguments().putInt(LEVEL, n);
        return f;
    }

    private int getLevel() {
        return (getArguments() == null) ? 0 : getArguments().getInt(LEVEL);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_main, container, false);

        Button button = (Button) rootView.findViewById(R.id.button);

        button.setText(String.valueOf(getLevel()));

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getActivity().getSupportFragmentManager()
                        .beginTransaction()
                        .replace(R.id.fragment, MainActivityFragment.newInstance(getLevel() + 1))
                        .addToBackStack(null)
                        .commit();
            }
        });

        return rootView;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.i(TAG, String.valueOf(getLevel()) + ": onCreate");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.i(TAG, String.valueOf(getLevel()) + ": onDestroy");
        Log.i(TAG, String.valueOf(getLevel()) + ": isChangingConfigurations() == " + getActivity().isChangingConfigurations());
        Log.i(TAG, String.valueOf(getLevel()) + ": isRemoving() == " + isRemoving());
    }

日志(以#开头的行是我的评论):

# Start Activity
I/MainActivityFragment: 0: onCreate
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true # ???????
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Rotate the device a second time
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Ok, correct
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true # WHY????
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate

这是 Android 中的错误还是我理解错了?

更新:我在 onDestroy 中添加了对 Fragment.dump() 的调用,得到以下结果:

fragment 放入回栈之前:

mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=2 mIndex=0 mWho=android:fragment:0 mBackStackNesting=0
mAdded=true mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{336d670b in HostCallbacks{387c69e8}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@387c69e8
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
  Child FragmentManager{2b6916a6 in null}}:
    FragmentManager misc state:
    mHost=null
    mContainer=null
    mCurState=0 mStateSaved=true mDestroyed=true

fragment放入回栈并第一次销毁后:

mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=true mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{34638ae1 in HostCallbacks{2db8e006}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@2db8e006
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}
Child FragmentManager{169d66c7 in null}}:
  FragmentManager misc state:
    mHost=null
    mContainer=null
    mCurState=0 mStateSaved=true mDestroyed=true

第二次销毁:

mFragmentId=#7f0c006b mContainerId=#7f0c006b mTag=null
mState=1 mIndex=0 mWho=android:fragment:0 mBackStackNesting=1
mAdded=false mRemoving=false mResumed=false mFromLayout=false mInLayout=false
mHidden=false mDetached=false mMenuVisible=true mHasMenu=false
mRetainInstance=false mRetaining=false mUserVisibleHint=true
mFragmentManager=FragmentManager{23beb2bc in HostCallbacks{c0f9245}}
mHost=android.support.v4.app.FragmentActivity$HostCallbacks@c0f9245
mSavedFragmentState=Bundle[{android:view_state={2131492979=android.view.AbsSavedState$1@6adf801}}]
mSavedViewState={2131492979=android.view.AbsSavedState$1@6adf801}

第一个(尚未入栈)和第二个(入栈)之间的区别是:

  1. mState=2 (ACTIVITY_CREATED) 与 mState=1 (CREATED)
  2. mBackStackNesting=0 与 mBackStackNesting=1
  3. mAdded=true 与 mAdded=false
  4. mRemoving=false 与 mRemoving=true(显然)

第二次(第一次销毁)和第三次(第二次+次销毁)的区别是:

  1. mRemoving=true 与 mRemoving=false
  2. mSavedFragmentState=null 与 mSavedFragmentState=Bundle[...]
  3. 有子 FragmentManager 与没有子 FragmentManager

但是,我不知道如何解释这些结果。

我开始认为 isRemoving 不是我需要的(我真正需要的是相当于 Activity.isFinishing 的东西,但对于 fragment 。我需要知道“这个 fragment 将永远再次被重用”,所以我可以取消后台任务。现在我正在使用 isRemoving() && !getActivity().isChangingConfigurations() 但是我不确定这是正确的解决方案)。

最佳答案

原来不是很正确的答案

我不确定这是错误还是设计使然,但 fragment 仅在 FragmentManager.removeFragment 中设置为删除支持v4库v23.1.1的方法。

这很可能会有所不同,具体取决于您是否使用支持库以及版本,但对于您在 GitHub 存储库中的代码,这就是原因。

只有在删除已放置在后堆栈上的 fragment 时才会调用此方法。

这里是完整的方法供引用:

public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
    if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
    final boolean inactive = !fragment.isInBackStack();
    if (!fragment.mDetached || inactive) {
        if (mAdded != null) {
            mAdded.remove(fragment);
        }
        if (fragment.mHasMenu && fragment.mMenuVisible) {
            mNeedMenuInvalidate = true;
        }
        fragment.mAdded = false;
        fragment.mRemoving = true;
        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
                transition, transitionStyle, false);
    }
}

“如何知道这个 fragment 永远不会被再次使用”问题的可能答案

要回答您关于如何知道可以在 fragment 中取消后台任务的问题,通常这些 fragment 使用 setRetainInstance(true)

这样,当设备的方向改变时,相同的 Fragment 将被重复使用,并且可以保留任何正在进行的后台操作。

当保留实例为真时, fragment 的 onDestroy()在方向更改期间也不会调用方法,因此您可以将取消逻辑放在那里以了解 fragment 是否会永久消失。


isRemoving 工作原理的更好答案基于查看源代码

看到这个答案已被接受,我觉得我应该从我的原始答案中修正一些不准确的地方。我说“只有在删除已放置在后堆栈上的 fragment 时才调用此方法”,这并不完全正确。替换一个 fragment 也调用了该方法并正确地将 isRemoving 设置为 true 作为一个示例。

现在通过分析您的日志来回答您关于为什么 isRemoving 在轮换中出现不一致的问题。我的附加评论以##开头

# Start Activity
# Click button in fragment 0 to add it to back stack and replace it with fragment 1
## FragmentManager.removeFragment is called on fragment 0 setting mRemoving to true
I/MainActivityFragment: 1: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == true ## To emphasize, this is true because as soon as you replaced fragment 0 it was set to true in the FragmentManager.removeFragment method.
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false ## fragment 1 is never actually removed so mRemoving is false.
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate

# Rotate the device a second time
## after rotating the device the first time your same fragments are not reused but new instances are created. This resets all the internal state of the fragments so mRemoving is false for all fragments.
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false # Correct result
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
# Click button in fragment 1 to add it to back stack and replace it with fragment 2
## fragment 1 now has mRemoving set to true in FragmentManager.removeFragment
I/MainActivityFragment: 2: onCreate
# Rotate the device
I/MainActivityFragment: 0: onDestroy
I/MainActivityFragment: 0: isChangingConfigurations() == true
I/MainActivityFragment: 0: isRemoving() == false ## still false from prior rotation
I/MainActivityFragment: 1: onDestroy
I/MainActivityFragment: 1: isChangingConfigurations() == true
I/MainActivityFragment: 1: isRemoving() == true ## true because mRemoving was set to true in FragmentManager.removeFragment.
I/MainActivityFragment: 2: onDestroy
I/MainActivityFragment: 2: isChangingConfigurations() == true
I/MainActivityFragment: 2: isRemoving() == false
I/MainActivityFragment: 0: onCreate
I/MainActivityFragment: 1: onCreate
I/MainActivityFragment: 2: onCreate

如果您再次旋转设备,所有 fragment 都会从 isRemoving() 返回 false。

有趣的是,即使使用相同的 fragment 实例,您仍然可能得到相同的输出。 Fragment 中有一个方法类名为 initState有以下评论:

Called by the fragment manager once this fragment has been removed, so that we don't have any left-over state if the application decides to re-use the instance. This only clears state that the framework internally manages, not things the application sets.

在旋转期间为每个 fragment 调用一次此方法,它所做的一件事就是将 mRemoving 重置为 false。

关于android - fragment 回栈和 isRemoving(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34649126/

有关android - fragment 回栈和 isRemoving()的更多相关文章

  1. 安卓apk修改(Android反编译apk) - 2

    最近因为项目需要,需要将Android手机系统自带的某个系统软件反编译并更改里面某个资源,并重新打包,签名生成新的自定义的apk,下面我来介绍一下我的实现过程。APK修改,分为以下几步:反编译解包,修改,重打包,修改签名等步骤。安卓apk修改准备工作1.系统配置好JavaJDK环境变量2.需要root权限的手机(针对系统自带apk,其他软件免root)3.Auto-Sign签名工具4.apktool工具安卓apk修改开始反编译本文拿Android系统里面的Settings.apk做demo,具体如何将apk获取出来在此就不过多介绍了,直接进入主题:按键win+R输入cmd,打开命令窗口,并将路

  2. Android Studio开发之使用内容组件Content获取通讯信息讲解及实战(附源码 包括添加手机联系人和发短信) - 2

    运行有问题或需要源码请点赞关注收藏后评论区留言一、利用ContentResolver读写联系人在实际开发中,普通App很少会开放数据接口给其他应用访问。内容组件能够派上用场的情况往往是App想要访问系统应用的通讯数据,比如查看联系人,短信,通话记录等等,以及对这些通讯数据及逆行增删改查。首先要给AndroidMaifest.xml中添加响应的权限配置 下面是往手机通讯录添加联系人信息的例子效果如下分成三个步骤先查出联系人的基本信息,然后查询联系人号码,再查询联系人邮箱代码 ContactAddActivity类packagecom.example.chapter07;importandroid

  3. Android 10.0 设置默认launcher后安装另外launcher后默认Launcher失效的功能修复 - 2

    1.前言 在10.0的系统rom定制化开发中,在系统中有多个launcher的时候,会在开机进入launcher的时候弹窗launcher列表,让用户选择进入哪个launcher,这样显得特别的不方便所以产品开发中,要求用RoleManager的相关api来设置默认Launcher,但是在设置完默认Launcher以后,在安装一款Launcher的时候,默认Launcher就会失效,在系统设置的默认应用中Launcher选项就为空,点击home键的时候会弹出默认Launcher列表,让选择进入哪个默认Launcher.所以需要从安装Launcher的流程来分析相关的设置。来解决问题设置默认La

  4. AiBote 2022 新研发的自动化框架,支持 Android 和 Windows 系统。速度非常快 - 2

    Ai-Bot基于流行的Node.js和JavaScript语言的一款新自动化框架,支持Windows和Android自动化。1、Windowsxpath元素定位算法支持支持Windows应用、.NET、WPF、Qt、Java和Electron客户端程序和ie、edgechrome浏览器2、Android支持原生APP和H5界面,元素定位速度是appium十倍,无线远程自动化操作多台安卓设备3、基于opencv图色算法,支持找图和多点找色,1080*2340全分辨率找图50MS以内4、内置免费OCR人工智能技术,无限制获取图片文字和找字功能。5、框架协议开源,除官方node.jsSDK外,用户可

  5. Android Gradle 7.1+新版本依赖变化 - 2

    前一段时间由于工作需要把可爱的小雪狐舍弃了,找到了小蜜蜂。但是新版本的小蜜蜂出现了很多和旧版本不一样的位置。1.功能位置迁移,原来在工程build.gradle的buildscript和allprojects移动至setting.gradle并改名为pluginManagement和dependencyResolutionManagement。里面的东西依旧可以按照原来的copy过来。pluginManagement{repositories{gradlePluginPortal()google()mavenCentral()}}dependencyResolutionManagement{r

  6. ruby - Ruboto 的最佳教程(适用于 Android 的 ruby​​)? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion我几乎用完了Ruby,但现在想试试Ruboto,android上的ruby​​。谷歌未能给我足够的(几乎没有结果)。所以任何人都可以分享一些关于Ruboto的教程。

  7. Android Studio 解决Could not resolve com.android.tools.build:gradle:7.4.2问题 - 2

    Aproblemoccurredconfiguringrootproject'MyApplication2'.>Couldnotresolveallfilesforconfiguration':classpath'.  >Couldnotresolvecom.android.tools.build:gradle:7.4.2.   Requiredby:     project:>com.android.application:com.android.application.gradle.plugin:7.4.2     project:>com.android.library:com.andr

  8. Android对话框的详细介绍(提示对话框,自定义对话框) - 2

    简介:我们都知道在Android开发中,当我们的程序在与用户交互时,用户会得到一定的反馈,其中以对话框的形式的反馈还是比较常见的,接下来我们来介绍几种常见的对话框的基本使用。前置准备:(文章最后附有所有代码)我们首先先写一个简单的页面用于测试这几种Dialog(对话框)代码如下,比较简单,就不做解释了一、提示对话框(即最普通的对话框)首先我们给普通对话框的按钮设置一个点击事件,然后通过AlertDialog.Builder来构造一个对象,为什么不直接Dialog一个对象,是因为Dialog是一个基类,我们尽量要使用它的子类来进行实例化对象,在实例化对象的时候,需要将当前的上下文传过去,因为我这

  9. android 多屏幕显示activity,副屏,无线投屏 - 2

    目录1.首先,需要一个副屏1.1可以通过代码的形式自己创建VirtualDispaly,创建副屏。1.2或者,在手机的开发者模式中直接开启模拟副屏,也是可以的。2.0怎么利用这个副屏幕?2.1 用作presentation演示ppt:2.2克隆主屏幕的内容,就是主屏幕显示什么,副屏显示同样的内容,镜像模式。2.3 将一个activity从第二个屏幕上启动,作为一个独立的屏幕首先说明一下这个多屏幕的概念,这里不是指分屏显示。分屏显示:是一个屏幕分出多个窗口,分别显示不同app.多屏支持:是一个设备有多个屏幕,怎么让不同的屏幕显示不同的app,或者是一个app同时用两个屏幕来显示不同的页面内容。多

  10. 【Android】获取TextView宽度或高度 - 2

    需要提前知道的一些东西Android中获取View的宽度或者高度,可以通过View自带的方法getWidth()、getHeight(),但这仅限于layout_width和layout_height的值是具体的dp或者match_parent,如果值是wrap_content,那么直接调用getWidth()、getHeight()方法,可能返回的会是0。直接调用getWidth()、getHeight()可能返回0的原因是,View可能还没有被添加到界面上(这里添加到界面上是指View执行了onMeasure方法),View添加到界面上之后,才计算完宽度和高度,所以如果宽度或高度如果设置w

随机推荐