最近开发的项目出现界面莫名其秒的卡住,直到发生ANR
异常退出的问题。
问题排查许久,才发现是由于使用的ViewPager
导致的。
在ViewPager
中使用setCurrentItem
长距离设置当前显示的位置的时候,比如0->600
,600->1000
,1000->500
这样的长距离跳转。会导致函数卡住在
1 |
void populate(int newCurrentItem) |
函数中很长时间, 甚至一直不能跳出循环。具体卡住的的循环代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
for(int pos = this.mCurItem + 1; pos < N; ++pos) { if (extraWidthRight >= rightWidthNeeded && pos > endPos) { if (ii == null) { break; } if (pos == ii.position && !ii.scrolling) { this.mItems.remove(itemIndex); this.mAdapter.destroyItem(this, pos, ii.object); ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null; } } else if (ii != null && pos == ii.position) { extraWidthRight += ii.widthFactor; ++itemIndex; ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null; } else { ii = this.addNewItem(pos, itemIndex); ++itemIndex; extraWidthRight += ii.widthFactor; ii = itemIndex < this.mItems.size() ? (ViewPager.ItemInfo)this.mItems.get(itemIndex) : null; } } |
上述代码中循环体会一直循环到N
结束为止。而这个N
是通过
1 |
int N = this.mAdapter.getCount(); |
赋值的。恰好,我们的代码中返回的是Integer.MAX_VALUE
。咨询了同事,当时设置这个数值的目的是为了解决循环滚动展示才设置的。代码是直接抄的 Android无限广告轮播 自定义BannerView / 如何优雅的实现一个可复用的 PagerAdapter。
通过Google搜索关键词 BannerAdapter extends PagerAdapter
可以找到类似的代码。
具体的复现BUG
的例子代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
package com.mobibrw.viewpager; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; import android.support.v7.app.AppCompatActivity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import java.util.Random; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ViewPager viewPager = findViewById(R.id.viewPager); viewPager.setAdapter(new PagerAdapter() { @Override public int getCount() { return Integer.MAX_VALUE; } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return view == object; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public Object instantiateItem(ViewGroup container, final int position) { final View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.pager_view, null); final TextView tv = view.findViewById(R.id.text); tv.setText("" + position); container.addView(view); return view; } }); final Button btnCmd = findViewById(R.id.btnCmd); btnCmd.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Random rdm = new Random(System.currentTimeMillis()); int rd = Math.abs(rdm.nextInt())%700 + 1; viewPager.setCurrentItem(rd); } }); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" android:clipToPadding="false"/> <Button android:id="@+id/btnCmd" android:text="@string/change_size" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> </android.support.constraint.ConstraintLayout> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/hello_world" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.53" /> </android.support.constraint.ConstraintLayout> |
完整的项目代码可以点击此处下载 ViewPager / ViewPagerAppCompatV7。项目编译完成之后,多点击几次 Change Size
按钮就可以复现界面长时间卡住的情况。第二个项目是支持appcompat-v7:18.0.0
的版本,目的是观察各个版本的代码是否存在不同,结论是,都一样。
这个问题目前的解决方法就是保证ViewPager
中的数据不要太多,比如底层分页显示,每页不要太多。另外就是不要大距离跳转,否则会有很多问题。
如果能把android.support
升级到androidx
的话,可以试试最新的ViewPager2
,最新的ViewPager2
是用RecyclerView
实现的,应该不会有以前的问题了。
这个问题,我已经向Google提交了BUG,估计最后的答复也是要升级到androidx
。