最近接手的项目友盟统计中有个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 RecyclerViewbefore 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//代码1at android.support.v7.widget.RecyclerView$Recycler.b(RecyclerView.java:6059)//代码2at android.support.v7.widget.RecyclerView.removeAnimatingView(RecyclerView.java:1375)//代码3at android.support.v7.widget.RecyclerView$ItemAnimatorRestoreListener.a(RecyclerView.java:12242)//代码4at android.support.v7.widget.RecyclerView$ItemAnimator.f(RecyclerView.java:12742)//代码5at android.support.v7.widget.SimpleItemAnimator.l(SimpleItemAnimator.java:303)// 代码6at 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);//代码1mRecycler.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() {}@Overridepublic void onAnimationFinished(ViewHolder item) {item.setIsRecyclable(true);if (item.mShadowedHolder != null && item.mShadowingHolder == null) { // old vhitem.mShadowedHolder = null;}// always null this because an OldViewHolder can never become NewViewHolder w/o being// recycled.item.mShadowingHolder = null;if (!item.shouldBeKeptAsChild()) {//代码2if (!removeAnimatingView(item.itemView) && item.isTmpDetached()) {removeDetachedView(item.itemView, false);}}}}
代码4:在RecyclerView.ItemAnimator中
public final void dispatchAnimationFinished(ViewHolder viewHolder) {onAnimationFinished(viewHolder);if (mListener != null) {//代码3mListener.onAnimationFinished(viewHolder);}}
代码5:SimpleItemAnimator extends RecyclerView.ItemAnimator
public final void dispatchAddFinished(ViewHolder item) {onMoveFinished(item);//代码4dispatchAnimationFinished(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() {@Overridepublic void onAnimationStart(Animator animator) {dispatchAddStarting(holder);}@Overridepublic void onAnimationCancel(Animator animator) {view.setAlpha(1);}@Overridepublic void onAnimationEnd(Animator animator) {animation.setListener(null);//代码5dispatchAddFinished(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 animatereturn;}。。。。。。。。// Next, add stuffif (additionsPending) {final ArrayList<ViewHolder> additions = new ArrayList<>();additions.addAll(mPendingAdditions);mAdditionsList.add(additions);mPendingAdditions.clear();Runnable adder = new Runnable() {@Overridepublic void run() {for (ViewHolder holder : additions) {//代码6animateAddImpl(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() {@Overridepublic void run() {if (mItemAnimator != null) {//代码7mItemAnimator.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) {//代码8ViewCompat.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(),会触发列表的重绘,并不会出现任何动画效果