【问题标题】:Rare crash when using fragments with unique id's使用具有唯一 ID 的片段时发生罕见的崩溃
【发布时间】:2014-02-03 23:49:51
【问题描述】:

我有一个非常不寻常的崩溃,可能每月左右发生一次。似乎没有任何押韵或理由。这是一个大型项目的一部分,由于我不确定问题到底出在哪里,所以我不确定要在此处粘贴哪些代码部分。因此,我将只描述我在做什么,我认为可能是导致崩溃的原因。

我有一个包含许多嵌套片段的活动,我使用 FragmentManager 管理这些片段。因为嵌套的片段可以是相同的类型,所以我必须给每个片段的根视图一个唯一的 ID。该应用程序在调用 super.onResume 时崩溃,该调用是我重载的 onResume 函数的第一行(因此我的代码永远不会有机会搞砸任何事情)。我认为它由于唯一片段 ID 而崩溃的原因是因为我在移动到唯一 ID 后开始遇到这个问题,并且崩溃总是引用低编号的资源 ID。

这个错误似乎是在框架本身中抛出的,所以我不确定我能做些什么。有没有其他人遇到过这个问题,并找到了解决方案?或者,有谁知道你是否可以用 try/catch 块包围 super.onResume ?我不确定应用程序是否能够在那时恢复,而且似乎没有任何方法可以始终如一地重现此错误以对其进行测试。

01-24 20:55:09.602 E/AndroidRuntime( 8156): FATAL EXCEPTION: main
01-24 20:55:09.602 E/AndroidRuntime( 8156): java.lang.RuntimeException: Unable to resume activity {github.daneren2005.dsub/github.daneren2005.dsub.activity.SubsonicFragmentActivity}: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x2
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3014)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3055)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2392)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.access$600(ActivityThread.java:151)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1331)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.os.Handler.dispatchMessage(Handler.java:99)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.os.Looper.loop(Looper.java:155)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.main(ActivityThread.java:5454)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at java.lang.reflect.Method.invokeNative(Native Method)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at java.lang.reflect.Method.invoke(Method.java:511)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at dalvik.system.NativeStart.main(Native Method)
01-24 20:55:09.602 E/AndroidRuntime( 8156): Caused by: android.content.res.Resources$NotFoundException: Unable to find resource ID #0x2
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.content.res.Resources.getResourceName(Resources.java:1666)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:919)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1467)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.support.v4.app.FragmentActivity.onResume(FragmentActivity.java:445)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at github.daneren2005.dsub.activity.SubsonicActivity.onResume(SubsonicActivity.java:130)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at github.daneren2005.dsub.activity.SubsonicFragmentActivity.onResume(SubsonicFragmentActivity.java:250)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.Instrumentation.callActivityOnResume(Instrumentation.java:1266)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.Activity.performResume(Activity.java:5148)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     at android.app.ActivityThread.performResumeActivity(ActivityThread.java:2997)
01-24 20:55:09.602 E/AndroidRuntime( 8156):     ... 12 more
01-24 20:55:09.602 W/ActivityManager(  596):   Force finishing activity github.daneren2005.dsub/.activity.SubsonicFragmentActivity

编辑:添加了唯一 ID 生成。在 onCreateView 我使用 rootView.setId(rootId):

    public SelectDirectoryFragment() {
        super();
        rootId = getNewId();
    }

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        if(bundle != null) {
            int tmp = bundle.getInt(Constants.FRAGMENT_ID, -1);
            if(tmp > 0) {
                rootId = tmp;
            }
            entries = (List<MusicDirectory.Entry>) bundle.getSerializable(Constants.FRAGMENT_LIST);
            restoredInstance = true;
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(Constants.FRAGMENT_ID, rootId);
        outState.putSerializable(Constants.FRAGMENT_LIST, (Serializable) entries);
    }

    protected int getNewId() {
        for (;;) {
            final int result = nextGeneratedId.get();
            // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
            int newValue = result + 1;
            if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
            if (nextGeneratedId.compareAndSet(result, newValue)) {
                return result;
            }
        }
    }

【问题讨论】:

    标签: android android-fragments


    【解决方案1】:

    检查您的 Fragment 或 Activity 中您可能正在进行setText() 调用的任何位置。您可能正在尝试将文本设置为整数的 String.valueOf() 值,但 setText() 也有一个重载,它采用整数 resource 值(例如 R.string.my_string)。当我错误地执行以下操作时,我已经看到了这个崩溃:

    int myCount = doSomeCalculation();
    textView.setText(myCount);
    

    这将在内部尝试将myCount 解析为资源(例如getString(myCount)),而不是将文本设置为myCount 变量的字符串值。

    编辑:等等,你是如何在 Fragment 的根上设置这个唯一 ID 的?

    【讨论】:

    • 设置文本时不会导致崩溃,而不是稍后恢复?无论哪种方式,我都会寻找这种情况。我用你要求的代码更新了。
    • 据我所知,我想问题是您正在更改视图创建后的 ID,并且在它尝试恢复该状态后(重新创建视图后)它里面有原来的ID,不能恢复状态。如果您只需要将多个相同类型的片段添加到布局中,我建议(除非我在您的实现中遗漏了某些内容)您只需使用标签而不是修改视图 ID。当您添加片段 (FragmentTransaction#add(int containerId, Fragment toAdd, String tag)) 时,为片段设置一个唯一的 标记 以便稍后检索它。
    • 我遇到的问题是,在添加时,我需要一个唯一的 ID 来在 transaction.add 中引用。如果所有片段都具有相同的 ID,它会将片段添加到顶级片段,您最终会同时看到它们。我曾经通过添加大量hacky代码来隐藏不可见的片段来解决这个问题,但这导致了其他问题。这个解决方案看起来更优雅,所以我希望有一种方法可以修复这个解决方案,而不是返回。
    • 听起来你应该只使用replace() 而不是add()。
    • 现在可能确实如此。在我开发所有这些解决方法的时候,我使用了几个选项卡,每个选项卡都有嵌套的片段,并且使用替换会导致各种似乎没有任何解决方法的问题。我将尝试只使用替换,看看现在是否可以使用拉出式抽屉进行更简单的设置。
    猜你喜欢
    • 2016-12-23
    • 1970-01-01
    • 2016-06-13
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 2020-08-29
    • 1970-01-01
    • 2020-02-24
    相关资源
    最近更新 更多