代码之家  ›  专栏  ›  技术社区  ›  Adrian

Android java。lang.IllegalStateException with ViewPager

  •  5
  • Adrian  · 技术社区  · 8 年前

    我制作了一个非常简单的Android拼图应用程序,它带有一个ViewPager,用户可以通过它来浏览一系列拼图。我在生产中看到一个错误,我不知道如何复制或调试:

    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://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() { ... }
    
        }
    
    }
    

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

    2 回复  |  直到 7 年前
        1
  •  7
  •   Leo supports Monica Cellio    7 年前

    我在模拟器中安装了你的应用程序,并能够使用monkey工具再现此崩溃( https://developer.android.com/studio/test/monkey.html )。此工具以高速模拟用户操作,如单击、触摸、旋转等。但我无法手动复制它。

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

    1、如何了解IllegalStateException的信息

    第一个堆栈跟踪显示异常是在ViewPager上引发的。java,所以我在该文件中搜索字符串“抛出新的IllegalStateException”。有不同的版本,但我检查的少数版本只会引发此异常 6个位置 ,则,

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

    剩下的唯一一个 被抛出 populate(int) ,它 必须是 这个。但为了确定,我检查了第二个stacktrace:

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

    • 第三行“at”给出了三个选项。仅查看来源 populate() 曾经从 onMeasure() ,所以它必须是那个。

    • 第二行“at”给出了21个选项,但再次查看源代码, 填充() 只是打电话 填充(int) 。。。和以前一样的方法,所以都适用。

    下面是抛出的代码 填充(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

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

    3、获取有关bug的信息(大部分是推测性的)

    从这里,你可以 Log.d() 代码中的行,运行 monkey ,这应该让您了解用户是如何使应用程序崩溃的。当然我做不到,所以我用你提供的代码编写了一个小应用程序,并用它运行 猴子 看看我能不能买点别的。我所做的是:

    1. 删除了对R类的所有引用(例如,用我自己的一个视图页面替换了布局,用一个简单的视图替换了片段布局)。
    2. 从返回零 indexFirstUnsolvedPuzzle() ,仅供Android Studio使用
    3. 在Fragment类中,我添加了一个 onClickListener 它启动了一个对话框,只是因为你的应用程序有对话框。
    4. 模拟电平选择 PuzzleSelection 随机选择关卡的活动启动 SolvePuzzle 在onResume()上
    5. 已添加 onBackPressed() 在这两个活动中,只需能够记录背压。
    6. 我补充道 日志d() 在每一种方法上都能够遵循logcat中的流程。

    我使用运行此应用程序 猴子 就在撞车之前,我在日志中记下了:

    (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、碰撞原因

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

    很可能此适配器在其 SolveActivity 已完成,因为 ViewPager 在隐身后做家务。问题是,旧活动读取了对新活动刚刚编写的字符串数组的引用,并且 这只是因为数组是静态成员 属于 解决难题

    (顺便说一句,这些静态数组确实会在你的应用程序中造成另一个bug:如果你选择level X,解决那里的一个问题,然后通过祝贺对话框返回菜单,然后选择另一个level Y,然后按两次back,你将进入level Y,而不是level X。)

    5、解决方案

    一般来说,只有当静态字段是真正不可变的(例如常量)时,或者至少当您可以正确同步并发访问时,才使用静态字段。另一方面,由于内存泄漏,静态类几乎总是优于内部类。

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

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

      AppSectionsPagerAdapter映射SectionsPagerAdapter; ViewPager mViewPager; int级别; 字符串图像[]; 字符串levelDescriptions[]; 字符串拼图[]; 字符串答案[]; 字符串提示[]; 字符串祝贺数组[];

    您还可以将这些字段设置为私有,尽管这不是严格必要的。

    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() ,而不是将片段传递给数组索引,而是传递它们所需的特定值:

      @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. 在片段中,检索新参数,而不是数组索引。

    这应该可以做到。

        2
  •  2
  •   Linh Nguyen    7 年前

    问题就在这些方面 static AppSectionsPagerAdapter mAppSectionsPagerAdapter; static ViewPager mViewPager;

    你为什么要这么做 静止的 ?