Android java.lang.IllegalStateException 使用 ViewPager
Android java.lang.IllegalStateException with ViewPager
我制作了一个非常简单的 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[=13=]0 (ViewPager.java)
or .access0 (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)
我应该 post 我的 ViewPager 代码的 shortened/simplified 版本吗?
编辑:这是完成大部分工作的 class:
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
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
if (mShareActionProvider == null) {
// Following
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() { ... }
}
}
如果这需要更多解释,请告诉我。
我在模拟器中安装了您的应用程序,并且能够使用 monkey 工具重现此崩溃 (https://developer.android.com/studio/test/monkey.html)。该工具可高速模拟用户操作,例如点击、触摸、旋转等。虽然我无法手动复制它。
你在问题中写道,你想知道如何找出问题所在,所以我会详细解释我的过程。对于实际解决方案,请跳至第 5 节。
1.如何知道 IllegalStateException
的消息
第一个堆栈跟踪表明异常是在 ViewPager.java 处抛出的,因此我在该文件中搜索了字符串 "throw new IllegalStateException"。有不同的版本,但我检查的几个版本只在6个地方、
抛出这个异常
- 两个 是关于程序化的 ViewPager 拖动,但你的应用程序不这样做,所以我放弃了这些
- two 将不会在继承自 ViewPager 的 class 中调用超级方法,但由于在您的代码中您只是使用 ViewPager,所以我丢弃了那些太
- 一个在
addView()
中,它没有出现在你的堆栈跟踪中,所以我也放弃了它。
剩下的唯一一个扔在了populate(int)
,必须是这个。但为了确定,我检查了第二个堆栈跟踪:
第四行 "at" 表示调用了 ViewPager.onMeasure()
。这里没有"or"行,所以这是肯定的。
第三行"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 个半 运行dom 触摸事件、旋转、音量 up/down 等,以在压力下测试应用程序。如果你 运行 使用你的应用程序的调试版本执行此命令,你应该能够看到我在第 1 步中获得的堆栈跟踪。
3。获取有关错误的信息(主要是推测性的)
从这里开始,您可以在代码中添加很多 Log.d()
行,运行 monkey
,这应该让您了解用户做了什么导致您的崩溃应用程序。我当然不能那样做,所以我用你提供的代码写了一个小应用程序,然后 运行 它和 monkey
看看我能不能得到别的东西。我所做的是:
- 删除了对您的 R class 的所有引用(例如,将您的布局替换为我自己的布局,只有一个 ViewPager,并将您的 Fragment 布局替换为一个简单的视图。
- 从
indexFirstUnsolvedPuzzle()
返回零,只是为了 Android Studio 不打扰
- 在片段 class 中,我添加了一个
onClickListener
来启动一个对话框,只是因为您的应用程序有对话框。
- 具有
PuzzleSelection
Activity 的模拟关卡选择 运行domly 选择关卡 SolvePuzzle
在 onResume() 启动
- 在两个活动中添加了
onBackPressed()
,只是为了能够记录后退。
- 我在每个方法上添加了
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 节中被调用,在一个新的 Adapter 被创建之后。此适配器的第 1 部分和第 3 部分中的 getCount() 结果不同,这意味着 适配器已更改其内容 ,因此抛出异常。
很可能这个适配器在其 SolveActivity
完成后继续使用,因为它的 ViewPager
在变得不可见后正在做一些内务处理。问题是旧 Activity 读取了对新 Activity 刚刚写入的字符串数组的引用,并且 这只是因为数组是静态成员 SolvePuzzle
.
(附带说明一下,这些静态数组确实会在您的应用程序中导致另一个错误:如果您选择 X 级,请在那里解决问题并通过祝贺对话框返回菜单,然后选择另一个 Y 级,然后然后按两次后退,你最终会到达 Y 层,而不是 X 层。)
5.解决方案
作为一般规则,只有当静态字段真正不可变时才使用它们,例如常量,或者至少当您可以正确同步并发访问时。另一方面,由于内存泄漏,静态 classes 几乎总是优于内部 classes。
在您的特定情况下,您应该:
从您的所有数组(实际上,从您的所有字段)中删除 static
关键字。
AppSectionsPagerAdapter mAppSectionsPagerAdapter;
ViewPager mViewPager;
国际水平;
字符串图像[];
字符串级别描述[];
字符串拼图[];
字符串答案[];
字符串提示[];
字符串 congratulationsArray[];
您也可以将这些字段设为私有,但这并不是绝对必要的。
也使您的适配器 class 静态化。 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;
...
}
/* ... */
}
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;
}
在片段中,检索新参数而不是数组索引。
应该可以了。
问题出在那些行
static AppSectionsPagerAdapter mAppSectionsPagerAdapter;
static ViewPager mViewPager;
你为什么把它设为静态?
我制作了一个非常简单的 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[=13=]0 (ViewPager.java)
or .access0 (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)
我应该 post 我的 ViewPager 代码的 shortened/simplified 版本吗?
编辑:这是完成大部分工作的 class:
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
mShareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item);
if (mShareActionProvider == null) {
// Following
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() { ... }
}
}
如果这需要更多解释,请告诉我。
我在模拟器中安装了您的应用程序,并且能够使用 monkey 工具重现此崩溃 (https://developer.android.com/studio/test/monkey.html)。该工具可高速模拟用户操作,例如点击、触摸、旋转等。虽然我无法手动复制它。
你在问题中写道,你想知道如何找出问题所在,所以我会详细解释我的过程。对于实际解决方案,请跳至第 5 节。
1.如何知道 IllegalStateException
的消息第一个堆栈跟踪表明异常是在 ViewPager.java 处抛出的,因此我在该文件中搜索了字符串 "throw new IllegalStateException"。有不同的版本,但我检查的几个版本只在6个地方、
抛出这个异常- 两个 是关于程序化的 ViewPager 拖动,但你的应用程序不这样做,所以我放弃了这些
- two 将不会在继承自 ViewPager 的 class 中调用超级方法,但由于在您的代码中您只是使用 ViewPager,所以我丢弃了那些太
- 一个在
addView()
中,它没有出现在你的堆栈跟踪中,所以我也放弃了它。
剩下的唯一一个扔在了populate(int)
,必须是这个。但为了确定,我检查了第二个堆栈跟踪:
第四行 "at" 表示调用了
ViewPager.onMeasure()
。这里没有"or"行,所以这是肯定的。第三行"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 个半 运行dom 触摸事件、旋转、音量 up/down 等,以在压力下测试应用程序。如果你 运行 使用你的应用程序的调试版本执行此命令,你应该能够看到我在第 1 步中获得的堆栈跟踪。
3。获取有关错误的信息(主要是推测性的)
从这里开始,您可以在代码中添加很多 Log.d()
行,运行 monkey
,这应该让您了解用户做了什么导致您的崩溃应用程序。我当然不能那样做,所以我用你提供的代码写了一个小应用程序,然后 运行 它和 monkey
看看我能不能得到别的东西。我所做的是:
- 删除了对您的 R class 的所有引用(例如,将您的布局替换为我自己的布局,只有一个 ViewPager,并将您的 Fragment 布局替换为一个简单的视图。
- 从
indexFirstUnsolvedPuzzle()
返回零,只是为了 Android Studio 不打扰 - 在片段 class 中,我添加了一个
onClickListener
来启动一个对话框,只是因为您的应用程序有对话框。 - 具有
PuzzleSelection
Activity 的模拟关卡选择 运行domly 选择关卡SolvePuzzle
在 onResume() 启动
- 在两个活动中添加了
onBackPressed()
,只是为了能够记录后退。 - 我在每个方法上添加了
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 节中被调用,在一个新的 Adapter 被创建之后。此适配器的第 1 部分和第 3 部分中的 getCount() 结果不同,这意味着 适配器已更改其内容 ,因此抛出异常。
很可能这个适配器在其 SolveActivity
完成后继续使用,因为它的 ViewPager
在变得不可见后正在做一些内务处理。问题是旧 Activity 读取了对新 Activity 刚刚写入的字符串数组的引用,并且 这只是因为数组是静态成员 SolvePuzzle
.
(附带说明一下,这些静态数组确实会在您的应用程序中导致另一个错误:如果您选择 X 级,请在那里解决问题并通过祝贺对话框返回菜单,然后选择另一个 Y 级,然后然后按两次后退,你最终会到达 Y 层,而不是 X 层。)
5.解决方案
作为一般规则,只有当静态字段真正不可变时才使用它们,例如常量,或者至少当您可以正确同步并发访问时。另一方面,由于内存泄漏,静态 classes 几乎总是优于内部 classes。
在您的特定情况下,您应该:
从您的所有数组(实际上,从您的所有字段)中删除
static
关键字。AppSectionsPagerAdapter mAppSectionsPagerAdapter; ViewPager mViewPager; 国际水平; 字符串图像[]; 字符串级别描述[]; 字符串拼图[]; 字符串答案[]; 字符串提示[]; 字符串 congratulationsArray[];
您也可以将这些字段设为私有,但这并不是绝对必要的。
也使您的适配器 class 静态化。 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; ... } /* ... */ }
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; }
在片段中,检索新参数而不是数组索引。
应该可以了。
问题出在那些行
static AppSectionsPagerAdapter mAppSectionsPagerAdapter;
static ViewPager mViewPager;
你为什么把它设为静态?