最近接手的项目友盟统计中有个RecyclerView的异常特别多,从日志中只能看出问题出现的业务范围,并且从代码的提交记录看,一年前有人调查过这个问题^^,但是这个产品和测试也没有复现这个问题,难道是不影响客户端的正常使用?
异常信息:java.lang.IllegalArgumentException:Tmp detached view should be removed from RecyclerView before it can be recycled:ViewHolderViewHolder,即在回收之前应该先从RecyclerView中移除。由于日志中没有明确到底是哪一行的问题,只能反推代码了。日志如下:
java.lang.IllegalArgumentException: Tmp detached view should be removed from RecyclerView
before it can be recycled:ViewHolder{4a13b78 position=1 id=-1, oldPos=-1, pLpos:-1
tmpDetached no parent} android.support.v7.widget.RecyclerView{570c467 VFED.
.... ........ 0,0-1080,869 #7f090274 app:id/messages}, adapter:com.
.fragment.adapter. Adapter@917c99d, layout:.LinearLayoutManager@afcde12, context:com.Activity@dd60286
//代码1
at android.support.v7.widget.RecyclerView$Recycler.b(RecyclerView.java:6059)
//代码2
at android.support.v7.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1375)
//代码3
at android.support.v7.widget.RecyclerView$ItemAnimatorRestoreListener.a(RecyclerView.java:12242)
//代码4
at android.support.v7.widget.RecyclerView$ItemAnimator.f(RecyclerView.java:12742)
//代码5
at android.support.v7.widget.SimpleItemAnimator.l(SimpleItemAnimator.java:303)
// 代码6
at android.support.v7.widget.DefaultItemAnimator$6.onAnimationEnd(DefaultItemAnimator.java:311)
at android.view.ViewPropertyAnimator$AnimatorEventListener.onAnimationEnd(ViewPropertyAnimator.java:1114)
at android.animation.ValueAnimator.endAnimation(ValueAnimator.java:1239)
at android.animation.ValueAnimator$AnimationHandler.doAnimationFrame(ValueAnimator.java:766)
at android.animation.ValueAnimator$AnimationHandler$1.run(ValueAnimator.java:801)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:862)
at android.view.Choreographer.doCallbacks(Choreographer.java:674)
at android.view.Choreographer.doFrame(Choreographer.java:607)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:848)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:179)
at android.app.ActivityThread.main(ActivityThread.java:5769)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:676)
首先看一下:异常信息时从哪里出现的
代码1:类RecyclerView
void recycleViewHolderInternal(ViewHolder holder) {
。。。。。。
if (holder.isTmpDetached()) {
throw new IllegalArgumentException("Tmp detached view should be removed "
+ "from RecyclerView before it can be recycled: " + holder
+ exceptionLabel());
}
。。。。。
代码2:类RecyclerView
boolean removeAnimatingView(View view) {
startInterceptRequestLayout();
final boolean removed = mChildHelper.removeViewIfHidden(view);
if (removed) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
mRecycler.unscrapView(viewHolder);
//代码1
mRecycler.recycleViewHolderInternal(viewHolder);
if (DEBUG) {
Log.d(TAG, "after removing animated view: " + view + ", " + this);
}
}
// only clear request eaten flag if we removed the view.
stopInterceptRequestLayout(!removed);
return removed;
}
代码3:类RecyclerView
private class ItemAnimatorRestoreListener implements ItemAnimator.ItemAnimatorListener {
ItemAnimatorRestoreListener() {
}
@Override
public void onAnimationFinished(ViewHolder item) {
item.setIsRecyclable(true);
if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vh
item.mShadowedHolder = null;
}
// always null this because an OldViewHolder can never become NewViewHolder w/o being
// recycled.
item.mShadowingHolder = null;
if (!item.shouldBeKeptAsChild()) {
//代码2
if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {
removeDetachedView(item.itemView, false);
}
}
}
}
代码4:在RecyclerView.ItemAnimator中
public final void dispatchAnimationFinished(ViewHolder viewHolder) {
onAnimationFinished(viewHolder);
if (mListener != null) {
//代码3
mListener.onAnimationFinished(viewHolder);
}
}
代码5:SimpleItemAnimator extends RecyclerView.ItemAnimator
public final void dispatchAddFinished(ViewHolder item) {
onMoveFinished(item);
//代码4
dispatchAnimationFinished(item);
}
代码6:类SimpleItemAnimator
void animateAddImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimator animation = view.animate();
mAddAnimations.add(holder);
animation.alpha(1).setDuration(getAddDuration())
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animator) {
dispatchAddStarting(holder);
}
@Override
public void onAnimationCancel(Animator animator) {
view.setAlpha(1);
}
@Override
public void onAnimationEnd(Animator animator) {
animation.setListener(null);
//代码5
dispatchAddFinished(holder);
mAddAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
代码7:类RecyclerView.ItemAnimator
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
。。。。。。。。
// Next, add stuff
if (additionsPending) {
final ArrayList<ViewHolder> additions = new ArrayList<>();
additions.addAll(mPendingAdditions);
mAdditionsList.add(additions);
mPendingAdditions.clear();
Runnable adder = new Runnable() {
@Override
public void run() {
for (ViewHolder holder : additions) {
//代码6
animateAddImpl(holder);
}
additions.clear();
mAdditionsList.remove(additions);
}
};
if (removalsPending || movesPending || changesPending) {
long removeDuration = removalsPending ? getRemoveDuration() : 0;
long moveDuration = movesPending ? getMoveDuration() : 0;
long changeDuration = changesPending ? getChangeDuration() : 0;
long totalDelay = removeDuration + Math.max(moveDuration, changeDuration);
View view = additions.get(0).itemView;
ViewCompat.postOnAnimationDelayed(view, adder, totalDelay);
} else {
adder.run();
}
}
}
代码8:类RecyclerView
private Runnable mItemAnimatorRunner = new Runnable() {
@Override
public void run() {
if (mItemAnimator != null) {
//代码7
mItemAnimator.runPendingAnimations();
}
mPostedAnimatorRunner = false;
}
};
代码9:类RecyclerView,。在下一帧执行Item的动画
/**
* Post a runnable to the next frame to run pending item animations. Only the first such
* request will be posted, governed by the mPostedAnimatorRunner flag.
*/
void postAnimationRunner() {
if (!mPostedAnimatorRunner && mIsAttached) {
//代码8
ViewCompat.postOnAnimation(this, mItemAnimatorRunner);
mPostedAnimatorRunner = true;
}
}
从异常日志信息来看:代码7,8,9好像没有体现,而日志中体现的问题就是在Item执行动画是产生的,
因此此异常的解决方案就是取消Item的动画执行,从代码8中只要设置mItemAnimator =null就可以了,RecyclerView也提供的Api:setItemAnimator()
/**
* Sets the {@link ItemAnimator} that will handle animations involving changes
* to the items in this RecyclerView. By default, RecyclerView instantiates and
* uses an instance of {@link DefaultItemAnimator}. Whether item animations are
* enabled for the RecyclerView depends on the ItemAnimator and whether
* the LayoutManager {@link LayoutManager#supportsPredictiveItemAnimations()
* supports item animations}.
*
* @param animator The ItemAnimator being set. If null, no animations will occur
* when changes occur to the items in this RecyclerView.
*/
public void setItemAnimator(ItemAnimator animator) {
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
mItemAnimator.setListener(null);
}
mItemAnimator = animator;
if (mItemAnimator != null) {
mItemAnimator.setListener(mItemAnimatorListener);
}
}
另外附上: ItemAnimator作触发于以下三种事件
I
notifyItemInserted(int position)方法
notifyItemRemoved(int position) 方法
notifyItemChanged(int position) 方法
注意:notifyDataSetChanged(),会触发列表的重绘,并不会出现任何动画效果