【问题标题】:Overlapping hidden fragments after application gets killed and restored应用程序被杀死和恢复后重叠的隐藏片段
【发布时间】:2013-04-17 19:59:17
【问题描述】:

我通过隐藏最后一个片段并添加一个新片段(参见下面的代码)在片段之间切换 - 将其添加到 back-stack 也是如此。这样,用户可以在片段之间快速切换,而无需重新加载片段数据。

这在应用被杀死之前运行良好(场景:用户使用其他几个应用,而我的应用被持久化并被杀死)。

当用户打开应用程序时,该应用程序正在恢复并且所有片段都显示出来 - 重叠彼此。

问题:如何恢复已恢复的片段以隐藏状态?也许我缺少一些标志?某处?也许有更好的解决方案可以在片段之间快速切换(无需重新加载数据)?

添加片段的示例代码 - 单击某处时使用不同的片段多次调用:

FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.hide(lastFragment);
fragmentTransaction.add(newFragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
lastFragment = newFragment;

【问题讨论】:

  • 当使用replace(而不是hide)时,当然没有重叠。但是当切换回前一个片段时,它会重新加载——这是我想要阻止的。
  • [旁注] hidereplace 的另一个非常重要的优势是使用后退按钮时(无论应用程序是否被杀死)。当隐藏而不是替换并按下后退按钮时,前一个片段会弹出而不重新加载其数据,因为它已经存在。
  • 在我看来,这几乎是任何应用程序都需要的东西,我希望这很容易并且有据可查:(
  • 我认为这是 android 的错误,不知道为什么它仍然在 android 5 中发生。

标签: android android-fragments


【解决方案1】:

希望有人找到更好的解决方案。在我接受我的解决方案之前,我会等待一个:

一般来说,我使用生成的标签来查找unhidden片段并隐藏它们。

详细地说,我为每个片段(StackEntry)生成一个唯一的标签,并在片段本身堆叠时堆叠标签。我将堆栈保留在捆绑包中,并在应用程序恢复时加载它以继续使用它。然后我使用标签列表找到所有 unhidden 片段并将它们隐藏 - 除了最后一个。

这里是示例代码:

public class FragmentActivity extends Activity {

    private static final String FRAGMENT_STACK_KEY = "FRAGMENT_STACK_KEY";

    private Stack<StackEntry> fragmentsStack = new Stack<StackEntry>();

    public FragmentActivity() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.content_frame);

        if (savedInstanceState == null) {
            // Init for the first time - not restore
            // ...
        } else {
            Serializable serializable = savedInstanceState.getSerializable(FRAGMENT_STACK_KEY);
            if (serializable != null) {
                // Workaround Android bug.
                // See: http://stackoverflow.com/questions/13982192/when-using-an-android-bundle-why-does-a-serialised-stack-deserialise-as-an-arra
                // And: https://code.google.com/p/android/issues/detail?id=3847
                @SuppressWarnings("unchecked")
                List<StackEntry> arrayList = (List<StackEntry>) serializable;
                fragmentsStack = new Stack<StackEntry>();
                fragmentsStack.addAll(arrayList);
            }

            // Hide all the restored fragments instead of the last one
            if (fragmentsStack.size() > 1) {
                FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
                for (int i = 0; i < fragmentsStack.size()-1; i++) {
                    String fragTag = fragmentsStack.get(i).getFragTag();
                    Fragment fragment = getFragmentManager().findFragmentByTag(fragTag);
                    fragmentTransaction.hide(fragment);
                }
                fragmentTransaction.commit();
            }
        }
        getFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
            @Override
            public void onBackStackChanged() {
                Fragment lastFragment = getLastFragment();
                if (lastFragment.isHidden()) {
                    FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
                    fragmentTransaction.show(lastFragment);
                    fragmentTransaction.commit();
                }
            }
        });
    }

    private Fragment getLastFragment() {
        if (fragmentsStack.isEmpty()) return null;
        String fragTag = fragmentsStack.peek().getFragTag();
        Fragment fragment = getFragmentManager().findFragmentByTag(fragTag);
        return fragment;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putSerializable(FRAGMENT_STACK_KEY, fragmentsStack);
    }

    @Override
    public void onBackPressed() {
        if (!fragmentsStack.isEmpty()) {
            fragmentsStack.pop();
        }
    }

    public void switchContent(Fragment fragment) {
        FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
        Fragment lastFragment = getLastFragment();
        if (lastFragment != null) {
            fragmentTransaction.hide(lastFragment);
        }
        String fragTag;
        if (fragment.isAdded()) {
            fragmentTransaction.show(fragment);
            fragTag = fragment.getTag();
        } else {
            fragTag = Long.toString(System.currentTimeMillis());
            fragmentTransaction.add(R.id.content_frame, fragment, fragTag);
        }
        if (!isFirstFragment()) {
            // Add to backstack only the first content fragment and not the state before (that has nothing)
            fragmentTransaction.addToBackStack(null);
        }
        fragmentTransaction.commit();

        fragmentsStack.push(new StackEntry(fragTag));
    }

    public boolean isFirstFragment() {
        return fragmentsStack.size() == 0;
    }

    private static class StackEntry implements Serializable {
        private static final long serialVersionUID = -6162805540320628024L;

        private String fragTag = null;
        public StackEntry(String fragTag) {
            super();
            this.fragTag = fragTag;
        }
        public String getFragTag() {
            return fragTag;
        }
    }


    public static class Intent extends android.content.Intent {
        public Intent(Context packageContext) {
            super(packageContext, FragmentActivity.class);
        }
    }
}

【讨论】:

  • 干得好,感谢分享。显然远非理想——对我来说,操作系统应该跟踪这种状态——但在没有其他任何东西的情况下提供了一些可行的东西。
  • 小心这种方法,你可能会遇到内存不足的错误。 stackoverflow.com/a/17981491/1112882 BTW 我真的不明白为什么有人需要这种类型的功能。那里有很多不错的、复杂的和大型的应用程序,它们运行良好,没有延迟,也没有重新加载。你能告诉我一个你必须使用这个功能的场景吗?
  • 据我了解,如果在您的应用程序中正确实现了缓存,那么按下后退按钮将需要时间来重新创建 GUI,仅此而已。并且 GUI 创建时间非常短,以至于用户不会注意到。
  • @M-WaJeEh - 我想隐藏旧片段,这样当回到它们时,它们不会被刷新,我也不需要保存任何状态。问题在于,当应用程序被操作系统杀死时,片段会在没有隐藏状态的情况下复活 - 彼此重叠。旁注:我不介意从头开始重新加载片段。
  • @AlikElzin-kilaka 很有趣,我遇到了同样的问题,下面将布局更改为LinearLayout 的答案似乎对我有用。虽然我不知道它是如何解决的。
【解决方案2】:

我遇到了同样的问题,通过在每个片段的onCreate() 方法中设置setRetainInstance(true); 解决了它。

【讨论】:

  • 引用:这只能用于不在后台堆栈中的片段。这意味着一个诺诺。见来源:developer.android.com/reference/android/app/…
  • 嗯,确实如此。但是,它似乎有效。可能是因为它没有“被摧毁和重新创造”
【解决方案3】:

我也遇到了这个问题,这里有一个可能的解决方案:让每个片段保存自己的状态关于它是否被隐藏,然后将自己隐藏在它的 onCreate 中。

@Override
public void onSaveInstanceState(Bundle bundle) {
    super.onSaveInstanceState(bundle);
    if (this.isHidden()) {
        bundle.putBoolean("hidden",  true);
    }
}

@Override
public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    if (bundle != null) {
        if (bundle.getBoolean("hidden",  false)) {
            getFragmentManager()
                .beginTransaction()
                .hide(this)
                .commit();
        }
    }
}

【讨论】:

  • 不错。我在这里看到的唯一缺点是每个片段都需要在容器内处理它自己的状态。我希望容器能够处理它的兄弟姐妹的状态 - 对片段透明。
【解决方案4】:

我遇到了完全相同的问题。不幸的是,唯一好的解决方案是切换到使用 fragmentTransaction.replace 而不是 fragmentTransaction.hide 并添加。

当时它很糟糕,但我真的很高兴我做到了。它迫使我思考savedInstanceState 并妥善处理它。您提到在导航回来时会重新加载片段。我遇到了完全相同的问题,迫使我正确处理savedInstanceState。有2个案例。

    1234563完成。
  1. 如果活动被破坏,我需要重新创建视图和适配器。为了尽量减少加载时间,我将重新创建适配器所需的数据存储在 savedInstanceState

不过,您的抱怨是有道理的,我不知道为什么 Android 不支持从使用 add 和 hide 的片段的已破坏 Activity 以正确的隐藏状态返回。

【讨论】:

    【解决方案5】:

    既然您提到您不介意从头开始重新加载片段,为什么不使用意图并重新启动主要片段活动?

    我遇到了与您提到的相同的问题,其中片段彼此重叠。我查看了整个stackoverflow,发现只有这个讨论这个特定问题的线程。我尝试了 Walt 提供的解决方案,但没有按预期工作。

    下面的解决方法至少对我有用,所以分享它以防有人最终遇到这种情况

    在父片段中的 onSaveInstanceState 处,我设置了一个标记以确保捆绑包中保存了一些内容。

    public void onSaveInstanceState(Bundle outState) 
    {
        // TODO Auto-generated method stub
        super.onSaveInstanceState(outState);
    
        outState.putString(TAG, "Get ready to be terminated");
    };
    

    在 onCreate 中,当保存的实例状态不为空时,您可以使用 Intent 指定要加载的类,

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {       
        super.onCreate(savedInstanceState);
        this.setContentView(R.layout.layout);
    
        if(savedInstanceState != null)
        {   
            Intent myIntent = new Intent(this, your.class);
    
            // Closing the parent fragment
            finish();
    
            this.startActivity(myIntent);
        }
        else
        {
            // your main code
            ...........
            ...........
        };
    };
    

    这将确保您的片段是从头开始重新创建的。

    在我的情况下,当用户打开我的应用程序时,我有一个登录屏幕,如果他们已经“登录”,那么他们将被重定向到片段屏幕。

    在终止过程中,一旦应用程序进入前台,我会将用户重定向到登录页面,然后我已经存在的代码负责将用户再次重定向回新创建的片段屏幕。

    注意:您需要确保您的子片段在 onCreateView 处没有松散的结尾。

    【讨论】:

    • 我还是想保留fragments的backstack。
    • 如果我没记错的话,您想显示在​​重新创建期间再次显示杀死的特定片段屏幕吗?如果这是要求,您可以将片段位置保存在包中并通过意图传递它并使用它再次显示选定的片段屏幕。这对我有用,因为我必须向用户显示之前被杀死的确切片段屏幕。
    • 对。这就是我在其中一个答案中所做的。我在您的答案代码中没有看到它。
    【解决方案6】:

    最后我找到了解决这个问题的最简单方法: 将 content_frame 从 FramLayout 更改为 LinearLayout。

    【讨论】:

    • 当我将其更改为 LinearLayout 时,这似乎解决了我的问题。我不明白为什么这会解决它。
    • 这解决了我的重叠问题,但片段仍然无法点击,应用程序 ui 卡在该片段上,只是它没有随片段更新。
    • 我也有同样的问题,你能找到解决这个@NishantPardamwar 的方法吗?
    【解决方案7】:

    我遇到了同样的问题,我认为这是Android框架的错误。这里是issue

    不管我的方法对你有用,我们应该重写onSaveInstanceState(Bundle outState)方法,将我们的自定义数据保存到outState,但永远不要调用super.onSaveInstanceState(outState);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ......
    
        if (savedInstanceState != null) {
            mCustomVariable = savedInstanceState.getInt("variable", 0);
        }
    }
    
    @Override
    protected void onSaveInstanceState(Bundle outState) {
        //super.onSaveInstanceState(outState);
        outState.putInt("variable", mCustomVariable);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-30
      • 1970-01-01
      • 1970-01-01
      • 2013-01-02
      • 2020-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多