最近接手的項目友盟統計中有個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(),會觸發列表的重繪,並不會出現任何動畫效果