【问题标题】:Android java.lang.IllegalStateException with ViewPager带有 ViewPager 的 Android java.lang.IllegalStateException
【发布时间】:2025-12-10 01:05:02
【问题描述】:

我用 ViewPager 制作了一个非常简单的 Android 拼图应用程序,它可以让用户在一系列拼图中滑动。我在生产中看到一个我不知道如何重现或调试的错误:

java.lang.IllegalStateException: 
  at android.support.v4.view.ViewPager.a (ViewPager.java:204)
  at android.support.v4.view.ViewPager.c (ViewPager.java:2)
  at android.support.v4.view.ViewPager.onMeasure (ViewPager.java:207)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6580)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6580)
  at android.support.v7.internal.widget.ActionBarOverlayLayout.onMeasure (ActionBarOverlayLayout.java:257)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6580)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6580)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1514)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:806)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:685)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6580)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at com.android.internal.policy.DecorView.onMeasure (DecorView.java:721)
  at android.view.View.measure (View.java:22002)
  at android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:2414)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:2159)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:1390)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:6762)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:966)
  at android.view.Choreographer.doCallbacks (Choreographer.java:778)
  at android.view.Choreographer.doFrame (Choreographer.java:713)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:952)
  at android.os.Handler.handleCallback (Handler.java:789)
  at android.os.Handler.dispatchMessage (Handler.java:98)
  at android.os.Looper.loop (Looper.java:169)
  at android.app.ActivityThread.main (ActivityThread.java:6595)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:240)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:767)

我应该怎么做才能弄清楚如何自己重现此错误并找出解决方法?我的 android 编程技能相当有限,上面的堆栈跟踪对我来说非常神秘。

如果有帮助,这里是第二个似乎相关的堆栈跟踪:

java.lang.IllegalStateException: 
  at android.support.v4.view.ViewPager.access$000 (ViewPager.java)
  or                     .access$200 (ViewPager.java)
  or                     .addNewItem (ViewPager.java)
  or                     .calculatePageOffsets (ViewPager.java)
  or                     .canScroll (ViewPager.java)
  or                     .completeScroll (ViewPager.java)
  or                     .determineTargetPage (ViewPager.java)
  or                     .distanceInfluenceForSnapDuration (ViewPager.java)
  or                     .executeKeyEvent (ViewPager.java)
  or                     .getChildRectInPagerCoordinates (ViewPager.java)
  or                     .infoForChild (ViewPager.java)
  or                     .initViewPager (ViewPager.java)
  or                     .isGutterDrag (ViewPager.java)
  or                     .onPageScrolled (ViewPager.java)
  or                     .onSecondaryPointerUp (ViewPager.java)
  or                     .populate (ViewPager.java)
  or                     .recomputeScrollPosition (ViewPager.java)
  or                     .scrollToItem (ViewPager.java)
  or                     .setCurrentItem (ViewPager.java)
  or                     .setCurrentItemInternal (ViewPager.java)
  or                     .smoothScrollTo (ViewPager.java)
  at android.support.v4.view.ViewPager.arrowScroll (ViewPager.java)
  or                     .populate (ViewPager.java)
  or                     .requestParentDisallowInterceptTouchEvent (ViewPager.java)
  at android.support.v4.view.ViewPager.onMeasure (ViewPager.java)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6124)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6124)
  at android.support.v7.internal.widget.ActionBarOverlayLayout.onMeasure (ActionBarOverlayLayout.java)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6124)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6124)
  at android.widget.LinearLayout.measureChildBeforeLayout (LinearLayout.java:1464)
  at android.widget.LinearLayout.measureVertical (LinearLayout.java:758)
  at android.widget.LinearLayout.onMeasure (LinearLayout.java:640)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewGroup.measureChildWithMargins (ViewGroup.java:6124)
  at android.widget.FrameLayout.onMeasure (FrameLayout.java:185)
  at com.android.internal.policy.DecorView.onMeasure (DecorView.java:687)
  at android.view.View.measure (View.java:19759)
  at android.view.ViewRootImpl.performMeasure (ViewRootImpl.java:2283)
  at android.view.ViewRootImpl.performTraversals (ViewRootImpl.java:2036)
  at android.view.ViewRootImpl.doTraversal (ViewRootImpl.java:1258)
  at android.view.ViewRootImpl$TraversalRunnable.run (ViewRootImpl.java:6348)
  at android.view.Choreographer$CallbackRecord.run (Choreographer.java:871)
  at android.view.Choreographer.doCallbacks (Choreographer.java:683)
  at android.view.Choreographer.doFrame (Choreographer.java:619)
  at android.view.Choreographer$FrameDisplayEventReceiver.run (Choreographer.java:857)
  at android.os.Handler.handleCallback (Handler.java:751)
  at android.os.Handler.dispatchMessage (Handler.java:95)
  at android.os.Looper.loop (Looper.java:154)
  at android.app.ActivityThread.main (ActivityThread.java:6123)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:867)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:757)

我应该发布我的 ViewPager 代码的缩短/简化版本吗?


编辑:这是完成大部分工作的类:

public class SolvePuzzle extends ActionBarActivity {

    // The code is longer than is shown here, but hopefully this is enough to be helpful
    static AppSectionsPagerAdapter mAppSectionsPagerAdapter;
    static ViewPager mViewPager;
    static int level;
    static String images[];
    static String levelDescriptions[];
    static String puzzles[];
    static String answers[];
    static String hints[];
    static String congratulationsArray[];

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_solve_puzzle);

        // Some code related to getSupportActionBar(); which I've cut out for brevity

        Intent intent = getIntent();
        if (intent != null) {
            level = intent.getIntExtra(PuzzleSelection.LEVEL, 0);  // Possible bug here with default level set to 0?
        }

        // Here there's code that populates images, levelDescriptions, puzzles, answers, and other arrays using the level integer
        // The arrays will have different lengths depending on the level
        // i.e. there is a different number of puzzles in each level
        // I do something along the lines of puzzles = getPuzzlesGivenLevel(level); and similarly for answers, hints, etc.

        mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());
        mViewPager = (ViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mAppSectionsPagerAdapter);
        mViewPager.setOnPageChangeListener(new OnPageChangeListener() {
            @Override public void onPageScrollStateChanged(int arg0) {
            }
            @Override public void onPageScrolled(int arg0, float arg1, int arg2) {
            }
            @Override public void onPageSelected(int puzzleIndex) {
        callSetShareIntent(puzzles[puzzleIndex]);  // Make share button share the correct puzzle
            }
        });
        mViewPager.setCurrentItem(indexFirstUnsolvedPuzzle());
    }

    private int indexFirstUnsolvedPuzzle() {
    // Gets index of first unsolved puzzle in this level
    }

    private void callSetShareIntent(String puzzleStatement) {
    // Creates an Intent called shareIntent; and then calls setShareIntent(shareIntent);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                NavUtils.navigateUpFromSameTask(this);
                return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.solve_puzzle, menu);
        MenuItem item = menu.findItem(R.id.menu_item_share);

        // Following http://*.com/questions/19118051/unable-to-cast-action-provider-to-share-action-provider
        mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
        if (mShareActionProvider == null) {
            // Following http://*.com/questions/19358510/why-menuitemcompat-getactionprovider-returns-null
            mShareActionProvider = new ShareActionProvider(this);
            MenuItemCompat.setActionProvider(item, mShareActionProvider);
        }
        callSetShareIntent(puzzles[mViewPager.getCurrentItem()]);
        return true;  // Return true to display menu
    }

    private void setShareIntent(Intent shareIntent) {
        if (mShareActionProvider != null) {
            mShareActionProvider.setShareIntent(shareIntent);  // Should be called whenever new fragment is displayed
        }
    }

    public class AppSectionsPagerAdapter extends FragmentPagerAdapter {

        public AppSectionsPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getItem(int puzzleIndex) {
            Fragment fragment = new SolvePuzzleFragment();
            Bundle args = new Bundle();
            args.putInt(PUZZLE_INDEX, puzzleIndex);
            fragment.setArguments(args);
            return fragment;
        }

        @Override
        public int getCount() {
            return puzzles.length;
        }
    }

    public static class SolvePuzzleFragment extends Fragment implements OnClickListener {

        public double correctAnswer;
        public int puzzleIndex;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_solve_puzzle, container, false);
            Bundle args = getArguments();
            puzzleIndex = args.getInt(PUZZLE_INDEX);

        // Sets a bunch of TextViews using the puzzleIndex
        // For example, get string in puzzles[puzzleIndex] and put it in a TextView, et cetera
        // Set a bunch of onClickListenters

    }

    // A bunch of functions for checking the user's answer, opening congratulations, etc
    // E.g. public void openCongratulationsAlert(View view) { ... }, public void openIncorrectAnswerToast() { ... }

    }

}

如果需要更多解释,请告诉我。

【问题讨论】:

  • 您是否在运行时向ViewPager 添加/删除项目。如果是,请提供您的代码。'
  • @ADM 我将发布我的代码的编辑版本,如果需要更多解释,请告诉我。谢谢!
  • 链接到应用程序,以防有助于澄清发生了什么:play.google.com/store/search?q=probability%20puzzles&c=apps(概率谜题,应该是第一个结果)
  • 你的谜题是从哪里开始的?以及你的 indexFirstUnsolvedPuzzle 中有什么
  • 你会在其他地方调用 mViewPager.setCurrentItem 吗?

标签: android android-viewpager illegalstateexception


【解决方案1】:

我在模拟器中安装了您的应用,并且能够使用猴子工具 (https://developer.android.com/studio/test/monkey.html) 重现此崩溃。该工具以高速模拟用户动作,例如点击、触摸、旋转等。但是我无法手动复制它。

您在问题中写道,您想知道如何找出问题所在,因此我将详细解释我的过程。对于实际解决方案,请跳至第 5 节。

1.如何知道IllegalStateException的消息

第一个堆栈跟踪表明异常是在 ViewPager.java 中引发的,因此我在该文件中搜索了字符串“throw new IllegalStateException”。有不同的版本,但我检查的少数版本仅在 6 个地方 抛出此异常,

  • 两个是关于程序化 ViewPager 拖动,但您的应用不这样做,所以我丢弃了这些
  • 两个将不会在继承自 ViewPager 的类中调用超级方法,但由于在您的代码中您只是使用 ViewPager,所以我也丢弃了这些方法
  • 一个addView()中,它没有出现在你的堆栈跟踪中,所以我也丢弃了它。

剩下的唯一一个被扔到populate(int),它必须是这个。但为了确定,我检查了第二个堆栈跟踪:

  • 第四行“at”表示调用了ViewPager.onMeasure()。这里没有“或”行,所以这是肯定的。

  • 第三个“at”行提供了三个选项。查看源代码,只有populate()onMeasure() 调用过,所以它必须是那个。

  • 第二个“at”行提供了 21 个选项,但再次查看源代码,populate() 确实只是调用了 populate(int)... 与之前的方法相同,所以一切都合适。

这是populate(int)中的抛出代码:

throw new IllegalStateException("The application's PagerAdapter changed the adapter's"
    + " contents without calling PagerAdapter#notifyDataSetChanged!"
    + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N
    + " Pager id: " + resName
    + " Pager class: " + getClass()
    + " Problematic adapter: " + mAdapter.getClass());

这就是我第一次回答这个问题的原因。

2。重现错误

在模拟器中下载您的应用程序后,我从命令行运行:

adb shell monkey -p atorch.statspuzzles -v --pct-rotation 20 100000

这会以非常高的速度向模拟器发送 100.000 个半随机触摸事件、旋转、音量增大/减小等,以在压力下测试应用程序。如果您在应用程序的调试版本中运行此命令,您应该能够看到我在第 1 步中获得的堆栈跟踪。

3.获取有关错误的信息(主要是推测性的)

从这里,您可以在代码中添加很多 Log.d() 行,运行 monkey,这应该让您了解用户做了什么导致您的应用程序崩溃。当然我做不到,所以我用你提供的代码写了一个小应用程序,然后用monkey 运行它,看看我能不能得到别的东西。我所做的是:

  1. 删除了对您的 R 类的所有引用(例如,将您的布局替换为我自己的布局,仅使用 ViewPager,并将您的片段布局替换为简单的视图。
  2. indexFirstUnsolvedPuzzle() 返回零,只是为了让 Android Studio 不打扰
  3. 在 Fragment 类中,我添加了一个 onClickListener,它启动了一个对话框,只是因为您的应用有对话框。
  4. 使用PuzzleSelection 随机选择关卡的活动在 onResume() 处启动 SolvePuzzle 模拟关卡选择
  5. 在两个活动中添加了onBackPressed(),只是为了能够记录后按。
  6. 我在每个方法中都添加了Log.d(),以便能够在 logcat 中跟踪该过程。

我用monkey 运行了这个应用程序,就在崩溃之前,我在日志中得到了这个:

(section 1)
SolvePuzzle@84e25c: back pressed
ViewPager{9256292}: scrolled, offset: 9.2589855E-4
AppSectionsPagerAdapter@4b49965: getCount() invoked, we have 3 elements
(section 2)
Creating Activity PuzzleSelection@580af24. Orientation: portrait
Starting puzzle for level 1
ViewPager{9256292}: scrolled, offset: 0.0
AppSectionsPagerAdapter@4b49965: getCount() invoked, we have 3 elements
AppSectionsPagerAdapter@4b49965: getCount() invoked, we have 3 elements
(section 3)
Creating Activity SolvePuzzle@16d7aaffor level 3. Orientation: portrait
Creating adapter AppSectionsPagerAdapter@958bebc with 2 elements.
AppSectionsPagerAdapter@958bebc: getCount() invoked, we have 2 elements
AppSectionsPagerAdapter@958bebc: getCount() invoked, we have 2 elements
ViewPager{c002954}: scrollStateChanged to 0
AppSectionsPagerAdapter@4b49965: getCount() invoked, we have 2 elements

4.崩溃原因

请注意,当SolvePuzzle 不再显示在屏幕上时,第 1 节 (@4b49965) 中的适配器在第 2 节中不断被调用。它甚至在第 3 节中被调用,在创建了一个新的适配器之后。该适配器的第 1 节和第 3 节中 getCount() 的结果不同,这意味着 适配器已更改其内容,因此引发了异常。

这个适配器很可能在它的SolveActivity 完成后继续使用,因为它的ViewPager 在变得不可见后正在做一些家务。问题是旧 Activity 读取了对新 Activity 刚刚写入的 String 数组的引用,而这只是因为数组是 SolvePuzzle 的静态成员

(附带说明一下,这些静态数组确实会在您的应用程序中引起另一个错误:如果您选择 X 级,请在此处解决问题并通过祝贺对话框返回菜单,然后选择另一个 Y 级,然后按返回两次,你最终会进入 Y 级,而不是 X 级。)

5.解决方案

作为一般规则,仅当静态字段真正不可变时才使用它们,例如常量,或者至少在您可以正确同步并发访问时使用。另一方面,由于内存泄漏,静态类几乎总是优于内部类。

在您的特定情况下,您应该:

  1. 从所有数组(实际上,从所有字段)中删除 static 关键字。

    AppSectionsPagerAdapter mAppSectionsPagerAdapter; ViewPager mViewPager; 整数级; 字符串图像[]; 字符串级别描述[]; 字符串拼图[]; 字符串答案[]; 字符串提示[]; 字符串 congratulationsArray[];

您也可以将这些字段设为私有,但这不是绝对必要的。

  1. 也使您的适配器类成为静态的。 Android Studio 会报错找不到puzzles,所以可以通过构造函数传递,也可以传递之前的其他静态数组。

    public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {
    
        private String images[];
        private String puzzles[];
        ...
    
        public AppSectionsPagerAdapter(FragmentManager fm, 
            String[] puzzles, 
            String[] images, 
            ...
        ) {
            super(fm);
            this.puzzles = puzzles;
            this.images = images;
            ...
        }
    
        /* ... */ 
    }
    
  2. In getItem(),而不是传递 Fragments 数组索引,而是传递他们需要的特定值:

    @Override
    public Fragment getItem(int puzzleIndex) {
        Fragment fragment = new SolvePuzzleFragment();
        Bundle args = new Bundle();
        args.putString(PUZZLE_CONTENT, puzzles[puzzleIndex]);
        args.putString(PUZZLE_IMAGE, images[puzzleIndex]);
        ...
        fragment.setArguments(args);
        return fragment;
    }
    
  3. 在片段中,检索新参数而不是数组索引。

应该可以的。

【讨论】:

  • 谢谢,我会试试看是否有帮助。该应用程序有几个级别,每个级别都有自己的puzzles 数组。主屏幕每个级别都有一个按钮,将用户发送到SolvePuzzle,意图中携带的级别。
  • 我又在看这个,我很困惑:我设置puzzles 数组(以及answers 和其他数组)的唯一位置是onCreatepublic class SolvePuzzle .这意味着除非调用onCreate,否则puzzles 不能更改。由于onCreate 也设置了mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());,我应该在哪里调用mAppSectionsPagerAdapter.notifyDataSetChanged()
  • 由于您不打算在创建适配器后修改您的数组,因此您真的不需要notifyOnDateSetChanged()。但是,您的数组确实会在您不知情的情况下被修改。请检查我更新的答案
  • 谢谢,伙计!我在一个被要求修复错误的项目中遇到了这个问题。最奇怪的是,该错误仅在小米设备和三星上重现,并且发生在第二次快速活动打开(活动关闭并再次打开)时。已尽一切可能消除该错误,但没有任何帮助。除了一个小的变化 - 删除传递给寻呼机适配器的片段列表的静态修饰符。看起来垃圾收集器无法清理该字段,并且在我的情况下已经填充了 2 个而不是 0 个项目列表。
【解决方案2】:

问题出在那一行 static AppSectionsPagerAdapter mAppSectionsPagerAdapter; static ViewPager mViewPager;

你为什么把它设为静态

【讨论】:

  • 可能是因为我是一个非常业余的 Android 开发者。这会导致什么样的问题?该应用程序“有效”是因为用户能够在关卡和谜题之间导航并输入答案。 static 会导致什么错误,我该如何重现它?
  • 因为静态所有 SolvePuzzle 实例共享相同的 AppSectionsPagerAdapter 和 ViewPager,为了重现该错误,请尝试打开 SolvePuzzle 几次
  • 该应用程序有一个主菜单,每个级别都有不同的按钮。如果你点击一个,你会被发送到 SolvePuzzle,并带有一个带有关卡的意图。如果您随后返回并打开不同的关卡,应用程序不会崩溃。这是为什么?如何让它崩溃?如果您愿意安装并测试它,请参阅play.google.com/store/apps/details?id=atorch.statspuzzles
  • 好的,我有一个答案(关于我为什么要制作东西static):在public static class SolvePuzzleFragment 里面我有一个按钮可以进入下一个谜题(用户成功后会看到这个按钮回答一个谜题)。积极按钮操作的行包括mViewPager.setCurrentItem(next_puzzleIndex);。如果我将 mViewPager 设为非静态,我会在 android studio 中收到“无法从静态上下文引用非静态字段”错误
  • 为了简洁起见,我没有在我的问题中的代码 sn-p 中显示与按钮相关的代码。如果您希望我添加它,请告诉我。