1

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

这是我的项目中的内容:

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

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. 以纵向模式启动应用程序 - 问题就在这里......当我切换方向时,我期望获得主细节布局。Activity 被重新创建并且应该使用横向布局,CacheFragment应该在那里,因为它是为了保留它的实例而创建的,这似乎是这种情况。我看到对第一个布局片段(TeaserListFragment )的onCreateView的调用,然后出现以下异常:

日志猫:

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时,似乎发生了一些奇怪的事情。

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

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

根据该消息,Android 似乎将CacheFragmentArticleFragment混淆了......

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

4

1 回答 1

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% 20总结%20星

从我的示例中可以看出,我有两种不同方向的布局。发生的情况是,当为肖像创建 Activity 时,只添加了一个 UI 片段,而另一个 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;

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

  1. TeaserListFragment 将通过 ID 找到,因为它已经创建。
  2. 由于没有创建 ArticleFragment,我们将到达上面显示的行和几行之后,根据逻辑,我们将进行以下调用:

第 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 的检查...

于 2013-09-25T23:16:24.323 回答