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



我一直在研究这个问题,并试图找出问题一天,但没有运气。我通常会提出问题作为最终措施,所以我现在有点绝望,因为其他问题中推荐的任何东西都不适合我......我一直在尝试数百种解决方案。可能是我只是在弄清楚正确的提问方式方面遇到了问题。

这是我在项目中的内容:

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

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的调用,然后出现以下异常:

日志猫:

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时,我看到了对onCreateView的调用。

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

根据消息,看起来Android将CacheFragmentArticleFragment混淆了...

任何帮助将不胜感激,因为我似乎不知道我可能做错了什么!

所以...经过几天的敲打并试图确保不是我的代码出错,我在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 片段,另一个是无头的。无头片段将添加到视图 ID == 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 的检查......

最新更新