非法参数异常:在快速切换操作栏选项卡时,无法找到片段的ID视图。

9
我正在为平板电脑开发一个Android应用程序,并且使用兼容库。
只有一个Activity,它使用带有3个选项卡的ActionBar。在TabListener中,我使用setContentView加载特定于该选项卡的布局,然后将相关片段添加到它们的框架中。这 几乎 与我想要的完全相同,除了当您快速切换选项卡时,应用程序会崩溃。
我正在使用三星Galaxy Tab作为我的调试设备,切换选项卡非常快。以正常的速度,我可以在它们之间来回轻敲,页面会立即加载。问题是当我在选项卡之间超级快速切换时。
起初我收到了一个

IllegalStateException: Fragment not added

如下所示: http://code.google.com/p/android/issues/detail?id=17029 按照在onTabUnselected中使用try/catch块的建议,我使应用程序更加健壮,但这导致了手头的问题:

IllegalArgumentException: No view found for id 0x... for fragment ...

我在网上没有找到任何其他人有相同的问题,所以我担心我可能在做一些不被支持的事情。 再次强调,我试图在一个Activity中使用3个不同的布局 - 当你点击选项卡时,监听器会调用setContentView来更改布局,然后添加片段。它工作得很好,除非你开始频繁地切换选项卡。

我从这里得到了灵感:http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.html 而我不是像TabListener那样保留对一个片段的引用,而是有一个数组。此外,我没有使用attach/detach,因为它们只在API 13中添加。

我的理论是,要么setContentView还没有完成创建视图,这就是为什么FragmentTransaction无法添加它们的原因,要么当选择另一个选项卡并调用setContentView时,片段正在为一个选项卡添加,破坏了另一组视图。

我尝试了一些方法来减缓选项卡切换,但没有进展。

这是我的TabListener代码:

private class BTabListener<T extends Fragment> implements ActionBar.TabListener{

    private int mLayout;
    private Fragment[] mFrags;
    private TabData mTabData;
    private Activity mAct;
    private boolean mNoNewFrags;


    public BTabListener(Activity act, int layout, TabData td, boolean frags){
        mLayout = layout;
        mTabData = td;
        mAct = act;
        mNoNewFrags = frags;

        mFrags = new Fragment[mTabData.fragTags.length];
        for(int i=0; i<mFrags.length; i++){
            //on an orientation change, this will find the fragments that were recreated by the system
            mFrags[i] = mAct.getFragmentManager().findFragmentByTag(mTabData.fragTags[i]);
        }

    }

    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {

    }

    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        //this gets called _after_ unselected
        //note: unselected wont have been called after an orientation change!
        //we also need to watch out because tab 0 always gets selected when adding the tabs

        //set the view for this tab
        mAct.setContentView(mLayout);

        for(int i=0; i<mFrags.length; i++){
            //this will be null when the tab is first selected
            if(mFrags[i]==null ){
                mFrags[i] = Fragment.instantiate(GUITablet.this, mTabData.classes[i].getName());                    
            }

            //if there was an orientation change when we were on this page, the fragment is already added
            if(!mNoNewFrags || mDefaultTab!=tab.getPosition()){
                ft.add(mTabData.containterIDs[i], mFrags[i], mTabData.fragTags[i]);
            }
        }
        mNoNewFrags = false;


    }

    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
        // this gets called when another tab is selected, before it's onSelected method 

        for(Fragment f : mFrags){
            try{ //extra safety measure
                ft.remove(f);
            }catch(Exception e){
                e.printStackTrace();
                System.out.println("unselect couldnt remove");
            }
        }
    }

}

最后,堆栈跟踪:

09-29 01:53:08.200: ERROR/AndroidRuntime(4611): java.lang.IllegalArgumentException: No view found for id 0x7f0b0078 for fragment Fraggle{40ab2230 #2 id=0x7f0b0078 dummy2}
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:729)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:926)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.BackStackRecord.run(BackStackRecord.java:578)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1226)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.FragmentManagerImpl$1.run(FragmentManager.java:374)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Handler.handleCallback(Handler.java:587)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Handler.dispatchMessage(Handler.java:92)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.os.Looper.loop(Looper.java:132)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at android.app.ActivityThread.main(ActivityThread.java:4028)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at java.lang.reflect.Method.invokeNative(Native Method)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at java.lang.reflect.Method.invoke(Method.java:491)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:844)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:602)
09-29 01:53:08.200: ERROR/AndroidRuntime(4611):     at dalvik.system.NativeStart.main(Native Method)

谢谢你!!


有人能帮我解决这个错误吗?链接 - Sam Kazmi
@SamKazmi,你试过建议的答案了吗? - hooby3dfx
4个回答

5

好的,我找到了一个解决方法:

在布局文件中引用片段,并在onTabSelected中将setContentView调用放在try/catch块中。

异常处理解决了这个问题!


你不是最后只会得到一个空白屏幕吗? - CACuzcatlan
为什么我最终会得到一个空白屏幕? 我认为错误只会在快速切换未选择或已选择的情况下发生,而在另一个选择期间,最终选择总是有效的(没有例外)。 - hooby3dfx

1

我知道这是一个稍微有点老的问题,但我也遇到了同样的问题,并找到了不同的解决方法。

该方法本身在这里描述:在执行选项卡切换时避免重新创建相同的视图

但在这个特定崩溃的上下文中,使用Show/Hide而不是add/replace可以避免在片段上多次快速调用onCreateView。

我的最终代码类似于:

@Override
public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
if (fragments[tab.getPosition()] == null) {
     switch(tag.getPosition()){
        case 0: fragments[tab.getPosition()] = new // fragment for this position
        break;
        // repeat for all the tabs, for each `case`
     }
     fragmentTransaction.add(R.id.container, fragments[tab.getPosition()]);
}else{
     fragmentTransaction.show(fragments[tab.getPosition()]);
}
 }

@Override
public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
if (fragments[tab.getPosition()] != null)
    fragmentTransaction.hide(fragments[tab.getPosition()]);
}

0
在 Google Play 崩溃报告中看到了一个类似的案例。
java.lang.IllegalStateException:
    Fragment not added: MessageDisplayFragment{406e28f0 #2 id=0x7f0b0020}
at android.app.BackStackRecord.hide(BackStackRecord.java:397)
at org.kman.AquaMail.ui.AccountListActivity$UIMediator_API11_TwoPane.
    closeMessageDisplay(AccountListActivity.java:2585)

相关代码:

AbsMessageFragment fragDisplay = (AbsMessageFragment)
    mFM.findFragmentById(R.id.fragment_id_message_display);

if (fragDisplay != null) {
    FragmentTransaction ft = mFM.beginTransaction();
    ft.setTransition(FragmentTransaction.TRANSIT_NONE);
    ft.show(some other fragment);
    ft.hide(fragDisplay);

^^ 这是它崩溃的地方

这个片段肯定在那里(通过findFragmentById返回),但调用hide()时会出现“IllegalStateException:Fragment not added”的错误。

没有添加?如果findFragmentById能够找到它,为什么它没有被添加呢?

所有FragmentManager调用都是从UI线程进行的。

要删除的片段(fragDisplay)之前已经添加过了,我确信它的FragmentTransaction已经提交了。


0
通过循环遍历我的片段,删除已添加的任何片段,然后添加新片段来解决了这个问题。还要先检查一下,确保你不是试图用null或现有片段替换它。 编辑:看起来只是这样
getChildFragmentManager().executePendingTransactions();

阻止了它的崩溃

private void replaceCurrentTabFragment(TabFragment tabFragment){

    if (tabFragment == null){ return; }

    if (tabFragment == getActiveTabFragment()){ return; }

    FragmentTransaction ft = getChildFragmentManager().beginTransaction();

    for (TabFragment fragment : mTabButtons.values()){
        if (((Fragment)fragment).isAdded()){
            ft.remove((Fragment)fragment);
        }
    }

    ft.add(R.id.fragment_frameLayout, (Fragment) tabFragment);
    ft.commit();
    getChildFragmentManager().executePendingTransactions();

}

private TabFragment getActiveTabFragment(){
    return (TabFragment) getChildFragmentManager().findFragmentById(R.id.fragment_frameLayout);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接