在方向之间切换并具有动态非UI片段和单独的布局文件时,会出现异常

时间:2013-09-08 22:57:35

标签: android android-layout

我一直在关注这个并试图解决问题一天,但没有运气。我通常会把问题作为最后的衡量标准,所以我现在有点绝望,因为在其他问题中建议的任何事情都不适合我......而且我一直在尝试数百种解决方案。可能是我在找出问题的正确方法时遇到了问题。

以下是我项目中的内容:

我的项目中所有活动都扩展的基础片段活动类:

public abstract class BaseFragmentActivity extends SherlockFragmentActivity implements IActivity {

    protected CacheFragment cacheFragment;

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.actionbarsherlock.app.SherlockActivity#onOptionsItemSelected(com.
     * actionbarsherlock.view.MenuItem)
     */
    @Override
    public boolean onOptionsItemSelected(final MenuItem item) {
        NavigationHandler.switchToActivity(this, item.getItemId());
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.app.Activity#onCreate(android.os.Bundle)
     */
    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.getSupportActionBar().setHomeButtonEnabled(this.isHomeButtonEnabled());
        this.getSupportActionBar().setDisplayHomeAsUpEnabled(this.isHomeDisplayedAsUp());
    }

    /**
     * Returns whether the home button is enabled.
     * 
     * @return <code>TRUE</code> if the home button is enabled.
     */
    public abstract boolean isHomeButtonEnabled();

    /**
     * Returns whether the home button should be displayed as an up button with
     * a arrow on the left.
     * 
     * @return <code>TRUE</code> if the home button should be shown with an
     *         arrow.
     */
    public abstract boolean isHomeDisplayedAsUp();

    /**
     * Gets the cache manager of the given fragment activity.
     */
    public CacheManager getCacheManager() {
        if (this.cacheFragment == null) {
            this.cacheFragment = CacheFragment.getOrCreateCacheFragment(this.getSupportFragmentManager(), this);
        }
        return this.cacheFragment.getCacheManager();
    }

    /*
     * (non-Javadoc)
     * 
     * @see com.actionbarsherlock.app.SherlockFragmentActivity#onPause()
     */
    @Override
    protected void onPause() {
        super.onPause();
        if (this.cacheFragment != null) {
            this.cacheFragment.getCacheManager().flushDiskCache();
        }
    }
}

具有两种不同布局(纵向布局)和横向布局的家庭活动,它扩展了基本活动类:

人像:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".HomeActivity" >

    <fragment
        android:id="@+id/teaserFragment"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        class="com.nikolov.ivan.android.metalafisha.fragments.TeaserListFragment" >
    </fragment>

</RelativeLayout>

风景:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:baselineAligned="true"
    android:orientation="horizontal"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".HomeActivity" >

    <fragment
        android:id="@+id/teaserFragment"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="2"
        class="com.nikolov.ivan.android.metalafisha.fragments.TeaserListFragment" >
    </fragment>

    <fragment
        android:id="@+id/articleFragment"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="@dimen/article_detail_landscape_margin_left"
        android:layout_weight="3"
        class="com.nikolov.ivan.android.metalafisha.fragments.ArticleFragment" >
    </fragment>

</LinearLayout>

预告片段:

布局:

<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/lvItems"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:choiceMode="singleChoice" >

</ListView>

文章片段

布局:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scrollArticle"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <LinearLayout
        android:id="@+id/layoutLinear"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >
    </LinearLayout>

</ScrollView>

缓存片段:

public class CacheFragment extends SherlockFragment {

    private static final String TAG = "CacheFragment";

    private CacheManager cacheManager;

    public CacheFragment() {
    }

    /**
     * Gets the application cache fragment or creates a new one.
     */
    public static CacheFragment getOrCreateCacheFragment(final FragmentManager fm, final Context context) {
        CacheFragment result = (CacheFragment) fm.findFragmentByTag(CacheFragment.TAG);
        if (result == null) {
            result = new CacheFragment();
            result.cacheManager = new CacheManager(context);

            FragmentTransaction ft = fm.beginTransaction();
            ft.add(result, CacheFragment.TAG);
            ft.commit();
        }
        return result;
    }

    public CacheManager getCacheManager() {
        return this.cacheManager;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * android.support.v4.app.Fragment#onCreateView(android.view.LayoutInflater,
     * android.view.ViewGroup, android.os.Bundle)
     */
    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see android.support.v4.app.Fragment#onCreate(android.os.Bundle)
     */
    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setRetainInstance(true);
    }
}

缓存片段的想法是没有UI,并且在配置更改期间使用和保留。所有磁盘访问都在不同于主要线程的线程上完成。

现在有两种情况:

  1. 以横向模式启动应用程序 - 一切都很好,事情可以正常显示,当我改变方向或对应用做任何事情时都没有问题。
  2. 以纵向模式启动应用程序 - 问题出现在这里......当我切换方向时,我希望得到主 - 细节布局。活动将被重新创建,并且应该使用横向布局, CacheFragment 应该在那里,因为它是为了保留其实例而创建的,这似乎就是这种情况。我看到第一个布局片段( TeaserListFragment )的 onCreateView 的调用,然后我得到以下异常:
  3. logcat的:

    09-08 15:41:37.104: E/AndroidRuntime(1731): FATAL EXCEPTION: main
    09-08 15:41:37.104: E/AndroidRuntime(1731): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.nikolov.ivan.android.metalafisha/com.nikolov.ivan.android.metalafisha.HomeActivity}: android.view.InflateException: Binary XML file line #21: Error inflating class fragment
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2663)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2679)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3815)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.access$2400(ActivityThread.java:125)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2037)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.os.Handler.dispatchMessage(Handler.java:99)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.os.Looper.loop(Looper.java:123)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.main(ActivityThread.java:4627)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at java.lang.reflect.Method.invokeNative(Native Method)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at java.lang.reflect.Method.invoke(Method.java:521)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at dalvik.system.NativeStart.main(Native Method)
    09-08 15:41:37.104: E/AndroidRuntime(1731): Caused by: android.view.InflateException: Binary XML file line #21: Error inflating class fragment
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:582)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.rInflate(LayoutInflater.java:618)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.inflate(LayoutInflater.java:407)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.inflate(LayoutInflater.java:320)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.inflate(LayoutInflater.java:276)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at com.actionbarsherlock.internal.ActionBarSherlockCompat.setContentView(ActionBarSherlockCompat.java:840)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at com.actionbarsherlock.app.SherlockFragmentActivity.setContentView(SherlockFragmentActivity.java:262)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at com.nikolov.ivan.android.metalafisha.HomeActivity.onCreate(HomeActivity.java:27)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2627)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     ... 12 more
    09-08 15:41:37.104: E/AndroidRuntime(1731): Caused by: java.lang.IllegalStateException: Fragment com.nikolov.ivan.android.metalafisha.fragments.ArticleFragment did not create a view.
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:293)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:558)
    09-08 15:41:37.104: E/AndroidRuntime(1731):     ... 21 more
    

    当我在切换到横向模式之前创建了 CacheFragment 时,似乎发生了一些奇怪的事情。

    我注意到的另一件事是,当Android文档声明不应该为没有UI片段调用时,我会看到对 onCreateView 的调用。

    我确保我在布局文件中有ID,我甚至检查它们是否对应用程序是全局唯一的以及许多其他似乎合理的东西,但我仍然看到相同的结果。唯一不会发生这种情况的方法是,如果我不在事务中添加 CacheFragment ,那么实际上没有任何意义......

    根据消息,Android似乎将 CacheFragment ArticleFragment 混淆......

    任何帮助将不胜感激,因为我似乎没有想法我可能做错了什么!

1 个答案:

答案 0 :(得分:1)

所以...经过几天敲打我的脑袋并试图确保这不是我的代码是错误的,我在Android代码中寻找解决方案,我在几分钟内找到了它。从第一步开始,我应该首先查看Android问题跟踪器......

我在这里找到了答案的说明:https://code.google.com/p/android/issues/detail?id=22564&q=fragment%20did%20not%20create%20a%20view&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars

从我的示例中可以看出,我有两种不同方向的布局。发生的事情是,当为肖像创建活动时,只添加一个UI片段而另一个片段是无头的。无头片段被添加到 viewId == 0 。这对理解正在发生的事情非常重要。

现在看一下support-v4 / r7代码,更具体地说是FragmentActivity类的第225行到第303行

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    if (!"fragment".equals(name)) {
        return super.onCreateView(name, context, attrs);
    }

    String fname = attrs.getAttributeValue(null, "class");
    TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
    if (fname == null) {
        fname = a.getString(FragmentTag.Fragment_name);
    }
    int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
    String tag = a.getString(FragmentTag.Fragment_tag);
    a.recycle();

    View parent = null; // NOTE: no way to get parent pre-Honeycomb.
    int containerId = parent != null ? parent.getId() : 0;
    if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
        throw new IllegalArgumentException(attrs.getPositionDescription()
                + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
    }

    // If we restored from a previous state, we may already have
    // instantiated this fragment from the state and should use
    // that instance instead of making a new one.
    Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
    if (fragment == null && tag != null) {
        fragment = mFragments.findFragmentByTag(tag);
    }
    if (fragment == null && containerId != View.NO_ID) {
        fragment = mFragments.findFragmentById(containerId);
    }

    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
            + Integer.toHexString(id) + " fname=" + fname
            + " existing=" + fragment);
    if (fragment == null) {
        fragment = Fragment.instantiate(this, fname);
        fragment.mFromLayout = true;
        fragment.mFragmentId = id != 0 ? id : containerId;
        fragment.mContainerId = containerId;
        fragment.mTag = tag;
        fragment.mInLayout = true;
        fragment.mFragmentManager = mFragments;
        fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
        mFragments.addFragment(fragment, true);

    } else if (fragment.mInLayout) {
        // A fragment already exists and it is not one we restored from
        // previous state.
        throw new IllegalArgumentException(attrs.getPositionDescription()
                + ": Duplicate id 0x" + Integer.toHexString(id)
                + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
                + " with another fragment for " + fname);
    } else {
        // This fragment was retained from a previous instance; get it
        // going now.
        fragment.mInLayout = true;
        // If this fragment is newly instantiated (either right now, or
        // from last saved state), then give it the attributes to
        // initialize itself.
        if (!fragment.mRetaining) {
            fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
        }
        mFragments.moveToState(fragment);
    }

    if (fragment.mView == null) {
        throw new IllegalStateException("Fragment " + fname
                + " did not create a view.");
    }
    if (id != 0) {
        fragment.mView.setId(id);
    }
    if (fragment.mView.getTag() == null) {
        fragment.mView.setTag(tag);
    }
    return fragment.mView;
}

这是 onCreateView 方法。现在深入了解以下一行:

int containerId = parent != null ? parent.getId() : 0;

让我们假设我们以纵向模式启动了应用程序。这意味着我们的活动中有两个片段:TeaserListFragment及其ID和无头片段。如果我们现在切换方向,我们将不得不添加另一个片段 - ArticleFragment,它是横向布局文件中的第二个片段。以下是将要发生的事情:

  1. 将通过ID找到TeaserListFragment,因为它已经创建。
  2. 由于没有创建ArticleFragment,我们将在上面的几行显示,基于逻辑,我们将进行以下调用:
  3. 第255行:

    fragment = mFragments.findFragmentById(containerId);
    

    这将返回无头片段(因为上面提到的viewId)而不是 null ,这就是错误发生的原因。我设法调试它并将containerId的值更改为-1,它工作正常。通常,解决方案是注释掉以下代码:

    if (fragment == null && containerId != View.NO_ID) {
        fragment = mFragments.findFragmentById(containerId);
    }
    

    这些是第254到256行。同时将containerId的默认值设为-1应该没问题,但我相信删除多余的 if 更好,以便保留未设置的检查的ID ...