IllegalArgumentException:快速切换 ActionBar 选项卡时找不到片段 id 的视图

发布于 2024-12-07 02:28:22 字数 5490 浏览 1 评论 0原文

我正在为平板电脑开发 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 保留对一个片段的引用,而是拥有一组片段。另外,我没有使用附加/分离,因为这些是刚刚在 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)

谢谢!!

I am developing an Android app for tablets and not using the compatibility library.

There is just one Activity and it uses the ActionBar with 3 tabs.
In the TabListener I use setContentView to load the layout specific to that tab, and then add the relevant fragments to their frames.
This almost works exactly like I want, except when you switch between the tabs fast enough the app will crash.

I am using a Samsung Galaxy Tab as my debugging device and switching tabs is really fast. At a normal pace I can tap back and forth between them and the pages are loaded instantly. The problem is when I hyper switch between the tabs.

At first I got an

IllegalStateException: Fragment not added

as seen here:
http://code.google.com/p/android/issues/detail?id=17029
Following the suggestion to use try/catch blocks in onTabUnselected, I made the app a little more robust, but that lead to the issue at hand:

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

I have not found any other case on the web of anyone else having the same issue, so I am concerned that I may be doing something that's not supported. Again, what I am trying to do is use 3 different layouts in one Activity - when you click on a tab, the listener will call setContentView to change the layout, and then add the fragments. It works beautifully unless you start aggressively switching between tabs.

I got the idea for this from: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.html
and instead of the TabListener keeping a reference to one fragment, I have an array of them. Also, I am not using attach/detach since those were just added in API 13.

My theory is that either setContentView hasn't finished creating the views, and that's why FragmentTransaction can't add them, OR the fragments are being added for one tab when another tab is selected and setContentView is called, destroying the other set of views.

I tried to hack something in to slow down tab switching but didn't get anywhere.

Here is the code for my 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");
            }
        }
    }

}

And finally, the stack trace:

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)

THANK YOU!!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

许一世地老天荒 2024-12-14 02:28:22

好吧,找到了解决这个问题的方法:

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

异常处理已经解决了!

Okay, found a way around this:

Put the references to the fragments in the layout files, and surrounded the setContentView call in onTabSelected in a try/catch block.

The exception handling took care of it!

陈独秀 2024-12-14 02:28:22

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

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

但在此特定崩溃的上下文中,执行显示/隐藏而不是添加/替换可以避免对片段上的 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()]);
}

I know it's a slightly old question but I was having the same and find a different work around.

The method itself is described here Avoid recreating same view when perform tab switching

but on the context of this specific crash, doing Show/Hide instead of add/replace avoids multiple fast calls to onCreateView on the fragment.

My final code ended up something like this:

@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()]);
}
李不 2024-12-14 02:28:22

在 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:片段未添加”而爆炸

未添加?如果findFragmentById能够找到它,它怎么会没有添加呢?

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

被删除的片段(fragDisplay)是之前添加的,我确信它的 FragmentTransaction 已提交。

Seeing a similar case in a Google Play crash report.

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)

Relevant code:

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);

^^ This is where it crashes

The fragment is definitely there (it was returned by findFragmentById), but calling hide() blew up with the "IllegalStateException: Fragment not added"

Not added? How's it not added if findFragmentById was able to find it?

All FragmentManager calls are being made from the UI thread.

The fragment being removed (fragDisplay) was added earlier and I'm sure that its FragmentTransaction was commited.

迷离° 2024-12-14 02:28:22

通过循环遍历我的片段,删除已添加的任何片段,然后添加新片段来修复此问题。还要首先检查您是否没有尝试将其替换为 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);
}

Fixed it by looping through my fragments, removing any of them which have been added and then adding the new one. Also check first that you're not trying to replace it with null or an existing fragment.

Edit: looks like it was just

getChildFragmentManager().executePendingTransactions();

that stopped it from crashing

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);
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文