【问题标题】:Android add fragment multiple times but just one instance foundAndroid多次添加片段但只找到一个实例
【发布时间】:2016-01-21 17:53:22
【问题描述】:

我的活动包含一个带有项目列表的片段。

活动类:

    public class CategoryActivity extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_subcategory);

        setupActionBar();

        CategoryFragment fragment = (CategoryFragment) getSupportFragmentManager().findFragmentById(R.id.category_fragment);
        fragment.setBrand(mBrand);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        UIUtils.unbindDrawables(findViewById(R.id.RootView));
        CategoryFragment fragment = (CategoryFragment) getSupportFragmentManager().findFragmentById(R.id.category_fragment);
        if(fragment != null)
            getSupportFragmentManager().beginTransaction().remove(fragment).commit();

        System.gc();
    }

    public static class CategoryFragment extends Fragment {

        private ListView mListView;
        private CategoryAdapter mAdapter;
        private Category mRoot;
        private List<Category> mCategories;
        private Brand brand;

        public CategoryFragment(){}

        public void setBrand(Brand brand) {
            this.brand = brand;
        }

        private boolean mIsAnimating;

        @Override
        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
            View view = inflater.inflate(R.layout.fragment_category, container, false);
            mListView = (ListView) view.findViewById(R.id.list_view);

            try {
                if (mCategories == null) {
                    loadCategories(AppController.getInstance().getCategory());
                }

                if (mCategories != null) {
                    mAdapter = new CategoryAdapter(getActivity(), mRoot);
                    mAdapter.setListener(new CategoryAdapter.Listener() {
                        @Override
                        public void onCategoryClick(String category) {
                            if (brand == null) return;

                            Utils.openQueryView(getActivity(), category, brand);
                        }

                        @Override
                        public void openSubcategory(Category category) {
                            openSubcategoriesView(category);
                        }

                        @Override
                        public void onBrowseAllClick(Category category) {
                            if (brand == null) return;

                            if (category.name.equals(Category.ROOT)) {
                                Utils.openQueryView(getActivity(), category.name, brand);
                            } else {
                                Utils.openStoreActivity(getActivity(), category, brand);
                            }
                        }
                    });
                    mListView.setAdapter(mAdapter);
                }
            }catch (Exception e){
                LOGD(TAG, e.getMessage());
                return view;
            }
            return view;
        }

        private void loadCategories(Category root) {
            // stuff here... it work well
        }

        private void openSubcategoriesView(Category category) {

            AnalyticsManager.sendScreenView(SCREEN_NAME);
            final CategoryFragment fragment = new CategoryFragment();
            fragment.setCategories(category);
            fragment.setBrand(brand);
            if (mIsAnimating) {
                return;
            }
            mIsAnimating = true;

            FragmentTransaction transaction = getFragmentManager().beginTransaction();
            transaction.setCustomAnimations(R.anim.slide_in_right, 0, 0,
                    R.anim.slide_out_right);
            transaction.add(R.id.category_fragment, fragment);
            transaction.addToBackStack(null);
            transaction.commit();

            mIsAnimating = false;

            List<Fragment> fs = getFragmentManager().getFragments();

            for(int i = 0; i< fs.size(); i++){
                LOGD(TAG, "fragment[" + i + "]: " + fs.get(i).getActivity());
            }
        }

        public void setCategories(Category category) {
            loadCategories(category);
        }
    }
}

活动布局xml:

    <fragment
        android:id="@+id/the_main_fragment"
        android:tag="firstFragment"
              android:name="com.greelane.gapp.ui.CategoryActivity$CategoryFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:background="@drawable/header_shadow"
        android:layout_width="match_parent"
        android:layout_height="12dp"></LinearLayout>
</RelativeLayout>

每次我点击一个项目时,一个新的 CategoryFragment 将被添加到这个活动的 FragmentManager 中,并且在 xml 布局中具有相同的 Fragment 类(category_fragment id)作为一个孩子如果一个孩子有他的孩子,这个概念会重复。

我尝试登录查看我有多少片段,每次我点击一个项目:

函数openSubcategoriesView

List<Fragment> fs = getFragmentManager().getFragments();
// fs size > 1, but just one instance of CategoryFragment
           for(int i = 0; i< fs.size(); i++){
               LOGD(TAG, "fragment[" + i + "]: " + fs.get(i).getActivity());
           }

导航到活动后我第一次点击一个项目,它只创建一个片段,所以 fs 大小为 1;

然后我点击返回 > 点击另一个项目,我看到 fs 大小是 2,但是我找到了一个 CategoryFragment 的实例。

我不知道它如何处理活动中的片段,但有时我的应用程序在 onCreateActivity setContentView(R.layout.activity_subcategory); 的这一行崩溃;

错误日志:

原因:android.view.InflateException: Binary XML file line #14: Error inflating class fragment

我的 xml 布局中的第 14 行从这里开始

片段 android:id="@+id/category_fragment" ...

所以问题是:

  1. 为什么我在多次添加后只有一个 CategoryFragment 实例以及如何解决此问题?

  2. 为什么有时我会收到上述充气异常广告以及如何解决此问题?

#Updated1

第二个问题的错误日志,找不到任何异常堆栈跟踪:

01-21 11:16:54.276 16783-16783/? W/System.err: java.lang.RuntimeException: Unable to start activity ComponentInfo{vn.app.alezaa/com.greelane.gapp.ui.CategoryActivity}: android.view.InflateException: Binary XML file line #14: Error inflating class fragment
01-21 11:16:54.279 16783-16783/? W/System.err: Caused by: android.view.InflateException: Binary XML file line #14: Error inflating class fragment
01-21 11:16:54.282 16783-16783/? W/System.err: Caused by: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.greelane.gapp.ui.CategoryActivity$CategoryFragment: make sure class name exists, is public, and has an empty constructor that is public
01-21 11:16:54.286 16783-16783/? W/System.err: Caused by: java.lang.InstantiationException: can't instantiate class com.greelane.gapp.ui.CategoryActivity$CategoryFragment; no empty constructor

#Updated2

  1. 我不能使用android:name=...CategoryActivity.CategoryFragment 代替android:name=...CategoryActivity$CategoryFragment 但仍然有同样的错误
  2. 我第一次发布这个问题时,空的构造函数已经有了。

我已编辑我的代码以添加新的 CategoryFragment,例如:

final CategoryFragment fragment = new CategoryFragment();
        fragment.setCategories(category);
        fragment.setBrand(brand);
        if (mIsAnimating) {
            return;
        }
        mIsAnimating = true;

        FragmentTransaction transaction = getFragmentManager().beginTransaction();
        transaction
                .setCustomAnimations(R.anim.slide_in_right, 0, 0, R.anim.slide_out_right)
                .replace(R.id.container, fragment, category.title)// set tag is title of ctg
                .addToBackStack(category.title)
                .commit();

        mIsAnimating = false;

        List<Fragment> fs = getFragmentManager().getFragments();
// `error raised here after two times add CategoryFragment, the **fs** size > 1, but just one instance of it.`
        for(int i = 0; i< fs.size(); i++){
            LOGD(TAG, "fragment[" + i + "]: " + fs.get(i).getActivity());
        }

xml 布局已编辑:

    <fragment
        android:id="@+id/the_main_fragment"
        android:tag="firstFragment"
        android:name="com.greelane.gapp.ui.CategoryActivity$CategoryFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <LinearLayout
        android:background="@drawable/header_shadow"
        android:layout_width="match_parent"
        android:layout_height="12dp"></LinearLayout>
</RelativeLayout>

或者我是否需要将 CategoryFragment 分离到其他类并转向非静态,因为现在我无法删除静态。有什么想法吗?

【问题讨论】:

  • 2.您应该附加完整的异常堆栈跟踪。那里会提供更多信息。

标签: android android-fragments


【解决方案1】:

要回答您多次加载相同片段的问题,您可以查看here,它在“注释”部分中说明

每个片段都需要一个唯一标识符,系统可以使用该标识符 如果活动重新启动,则恢复片段(并且您可以 用于捕获片段以执行事务,例如删除 它)。可以通过三种方式为片段提供 ID:

  • 为 android:id 属性提供唯一 ID。
  • 为 android:tag 属性提供一个唯一的字符串。
  • 如果您不提供前两个,系统将使用容器视图的 ID。

所以你可以多次使用同一个片段,但如果通过 layout.xml 添加,则需要唯一的 id 和标签,如果通过代码添加,则需要唯一的标签。

在您的情况下,您可以考虑在加载片段时使用 Category 对象中的一些唯一字段作为 TAG,因此您加载的每个 CategoryFragment 实例都将具有唯一标签,并且在片段管理器堆栈中将被视为唯一。

关于你的第二个问题,有两个建议

  1. 将 xml 中片段标记中的类属性更改为 android:name 并添加完全限定的片段类名称,即 android:name = "mypackage.CategoryFragment"

  2. 如果您检查,也根据您的崩溃,提到 java.lang.InstantiationException: can't instantiate class com.greelane.gapp.ui.CategoryActivity$CategoryFragment;没有空的构造函数。所以创建一个空的或默认的构造函数,比如

    公共 CategoryFragment(){

    }

【讨论】:

  • 好的。让我试试这个,然后尽快反馈给你
  • 嗨,我已经设置了标签,但仍然遇到与上面第一个问题中描述的相同的错误。
  • 需要有空的构造函数来启动片段。我建议将片段分离为另一个类,然后尝试。
  • 好的 Rajen,我正在测试你的建议。会尽快反馈给您。谢谢。
【解决方案2】:

关于这个错误我终于想通了

  List<Fragment> fs = getFragmentManager().getFragments();
// `error raised here after two times add CategoryFragment, the **fs** size > 1, but just one instance of it.`
        for(int i = 0; i< fs.size(); i++){
            LOGD(TAG, "fragment[" + i + "]: " + fs.get(i).getActivity());
        }

根据文档,

调用 commit() 不会立即执行事务。相反,一旦线程能够这样做,它就会安排它在活动的 UI 线程(“主”线程)上运行。但是,如有必要,您可以从 UI 线程调用 executePendingTransactions() 以立即执行由 commit() 提交的事务。除非事务是其他线程中作业的依赖项,否则通常不需要这样做。

所以我们需要等待一点时间来提交。现在没问题。

但仍在等待关于我上面提到的第二个错误的测试。如果有好的结果我会尽快更新。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多