应用程序被杀死并恢复后重叠的隐藏片段

时间:2013-04-24 10:02:36

标签: android android-fragments

我在隐藏最后一个片段和添加新片段之间切换片段(请参阅下面的代码) - 将其添加到 back-stack < / strong>以及。这样,用户可以在不重新加载片段数据的情况下快速切换片段。

这很有效,直到该应用被杀(场景:用户使用其他几个应用,我的应用会被持续存在并被杀死)。

当用户打开应用时,它正在恢复,所有片段都会显示 - 重叠

问题:恢复的片段如何以隐藏状态恢复?也许我错过了一些旗帜?某处?也许有更好的解决方案可以快速切换片段(无需重新加载数据)?

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

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

7 个答案:

答案 0 :(得分:16)

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

通常,我使用生成的代码来查找取消隐藏片段并隐藏它们。

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

下载示例代码:

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);
        }
    }
}

答案 1 :(得分:9)

我也有这个问题,这里有一个可能的解决方案:让每个片段保存自己关于它是否被隐藏的状态,然后将其自身隐藏在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();
        }
    }
}

答案 2 :(得分:3)

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

我遇到了完全相同的问题。不幸的是,唯一的好办法是切换到使用fragmentTransaction.replace代替fragmentTransaction.hide并添加。

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

  1. 如果活动没有被销毁,那么唯一需要重新创建的是视图(通过onCreateView)其他所有内容仍然在内存中,所以这只是一个将视图中的适配器挂钩的问题。 #39;重新完成。

  2. 如果活动被破坏,我需要重新创建视图和适配器。为了最小化加载时间,我在savedInstanceState

  3. 中存储了重新创建适配器所需的数据

    然而,你的抱怨是有效的,我不知道为什么Android不会支持使用添加和隐藏的片段从已销毁的活动中恢复正确的隐藏状态。

答案 5 :(得分:1)

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

我遇到了与你提到的问题相同的问题,即片段彼此重叠。我查看了遍历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中,当保存的实例状态不为null时,您可以使用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 上没有松散的结尾。

答案 6 :(得分:0)

我遇到了同样的问题,我认为这是Android框架的bug.Here是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);
}