带有 ViewPager 的 Android java.lang.IllegalStateException

Adr*_*ian 5 android illegalstateexception android-viewpager

我用 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)
Run Code Online (Sandbox Code Playgroud)

我应该怎么做才能弄清楚如何自己重现此错误,并找出如何修复它?我的 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)
Run Code Online (Sandbox Code Playgroud)

我应该发布我的 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://stackoverflow.com/questions/19118051/unable-to-cast-action-provider-to-share-action-provider
        mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
        if (mShareActionProvider == null) {
            // Following http://stackoverflow.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() { ... }

    }

}
Run Code Online (Sandbox Code Playgroud)

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

SE *_*lio 7

我在模拟器中安装了您的应用程序,并且能够使用猴子工具 ( 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());
Run Code Online (Sandbox Code Playgroud)

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

2. 重现错误

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

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

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

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

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

  1. 删除了对 R 类的所有引用(例如,用我自己的布局替换了您的布局,仅使用了一个 ViewPager,并用一个简单的 View 替换了您的 Fragment 布局。
  2. 从 返回零indexFirstUnsolvedPuzzle(),只为 Android Studio 不打扰
  3. 在 Fragment 类中,我添加了一个onClickListener启动对话框,只是因为您的应用程序有对话框。
  4. 使用PuzzleSelectionActivity模拟关卡选择,该活动SolvePuzzle在 onResume()启动时随机选择一个关卡
  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
Run Code Online (Sandbox Code Playgroud)

4、坠机原因

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

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

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

5. 解决方案

作为一般规则,只有在真正不可变的情况下才使用静态字段,例如常量,或者至少在您可以正确同步并发访问时使用。另一方面,由于内存泄漏,静态类几乎总是比内部类更可取。

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

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

    AppSectionsPagerAdapter mAppSectionsPagerAdapter; ViewPager mViewPager; 国际水平;字符串图像[]; 字符串级别描述[]; 字符串拼图[]; 字符串答案[]; 字符串提示[]; 字符串祝贺数组[];

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

  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;
            ...
        }
    
        /* ... */ 
    }
    
    Run Code Online (Sandbox Code Playgroud)
  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;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 在片段中,检索新参数而不是数组索引。

那应该这样做。