PAGView在RecyclerView中复用Item导致PAGView无法播放的问题

先说解决方法:将版本号升级到大于等于 com.tencent.tav:libpag:4.0.5.17 的版本可以解决问题。官方已经修复了这个问题。Tencent/libpagv4.0.5.17_release_20220704

有问题的版本:com.tencent.tav:libpag:4.0.5.11。低于这个版本都有问题。我们项目中的版本是 com.tencent.tav:libpag:4.0.5.10

Github测试demo地址

问题复现操作步骤

第一个问题

  1. RecyclerView 中先加载一个数据。 然后通过点击按钮播放PAGView动画。
  2. 然后通过点击 展开所有的item,调用 notifyDataSetChanged 展开全部两条数据。 第一条数据的ViewHolder会被复用。
  3. 发现第一个数据中的 PAGView 动画无法停止了。
  4. 即使再次通过点击按钮播放第一个数据中的PAGView动画。依然无法执行。

在这里插入图片描述

第二个问题

  1. RecyclerView 中先加载一个数据。
  2. 然后通过点击 展开所有的item,调用 notifyDataSetChanged 展开全部两条数据。 第一条数据的ViewHolder会被复用。
  3. 第一个数据中通过点击按钮播放PAGView动画,发现动画无法执行。

在这里插入图片描述

没有问题的版本:版本号大于等于 com.tencent.tav:libpag:4.0.5.17 的版本。

原因分析

根本原因

对比了两个版本 PAGView

  • com.tencent.tav:libpag:4.0.5.11` 版本有问题的 PAGView 部分代码。
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    
    
    pagPlayer.setSurface(null);
    if(mListener != null) {
    
    
        mListener.onSurfaceTextureDestroyed(surface);
    }
    if(pagSurface != null) {
    
    
        pagSurface.freeCache();
    }
    //注释1处
    animator.removeUpdateListener(mAnimatorUpdateListener);
    boolean surfaceDestroy = true;
    if(g_PAGViewHandler != null && surface != null) {
    
    
        SendMessage(MSG_SURFACE_DESTROY, surface);
        surfaceDestroy = false;
    }
    if(Build.VERSION.SDK_INT >= ANDROID_SDK_VERSION_O) {
    
    
        synchronized(g_HandlerLock) {
    
    
            DestroyHandlerThread();
        }
    }
    return surfaceDestroy;
}

注释1处,移除了 mAnimatorUpdateListener。 这个一个监听属性动画进度更新的回调。而PAGView正是通过这个监听回调来监听属性动画的进度更新并执行PAGView的动画的。所以移除了就会有问题。

private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() {
    
    
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    
    
        PAGView.this.currentPlayTime = animation.getCurrentPlayTime();
        NeedsUpdateView(PAGView.this);
    }
};
  • com.tencent.tav:libpag:4.0.5.17版本没问题的 PAGView 部分代码
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    
    
   this.pagPlayer.setSurface((PAGSurface)null);
   if (this.mListener != null) {
    
    
       this.mListener.onSurfaceTextureDestroyed(surface);
   }

   if (this.pagSurface != null) {
    
    
       this.pagSurface.freeCache();
   }

   boolean surfaceDestroy = true;
   if (g_PAGViewHandler != null && surface != null) {
    
    
       SendMessage(1, surface);
       surfaceDestroy = false;
   }

   if (VERSION.SDK_INT >= 26) {
    
    
       synchronized(g_HandlerLock) {
    
    
           DestroyHandlerThread();
       }
   }

   return surfaceDestroy;
}
protected void finalize() throws Throwable {
    
    
    super.finalize();
    this.animator.removeUpdateListener(this.mAnimatorUpdateListener);
}

没有问题的 PAGView 是在 View 真正被回收的时候才移除了 mAnimatorUpdateListener。

第一个问题出现的具体原因

  1. RecyclerView 中先加载一个数据。 然后通过点击按钮播放PAGView动画。
  2. 然后通过点击 展开所有的item,调用 notifyDataSetChanged 展开全部两条数据。 第一条数据的ViewHolder会被复用。
  3. 发现第一个数据中的 PAGView 动画无法停止了。
  4. 即使再次通过点击按钮播放第一个数据中的PAGView动画。依然无法执行。

当我们调用 notifyDataSetChanged 展开所有数据的时候,第一条数据的 View 会先 detachedFromWindow,会调用 PAGView 的 onDetachedFromWindow 方法。

protected void onDetachedFromWindow() {
    
    
    this.isAttachedToWindow = false;
    super.onDetachedFromWindow();
    if(this.pagSurface != null) {
    
    
        this.pagSurface.release();
        this.pagSurface = null;
    }
    //注释1处
    this.pauseAnimator();
    if(VERSION.SDK_INT < 26) {
    
    
        synchronized(g_HandlerLock) {
    
    
            DestroyHandlerThread();
        }
    }
    //...
}

注释1处,把属性动画暂停了。并且 onDetachedFromWindow 会导致 PAGView 的 Surface 被销毁。 回调 onSurfaceTextureDestroyed 方法。

@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
    
    
    pagPlayer.setSurface(null);
    if(mListener != null) {
    
    
        mListener.onSurfaceTextureDestroyed(surface);
    }
    if(pagSurface != null) {
    
    
        pagSurface.freeCache();
    }
    //注释1处
    animator.removeUpdateListener(mAnimatorUpdateListener);
    boolean surfaceDestroy = true;
    if(g_PAGViewHandler != null && surface != null) {
    
    
        SendMessage(MSG_SURFACE_DESTROY, surface);
        surfaceDestroy = false;
    }
    if(Build.VERSION.SDK_INT >= ANDROID_SDK_VERSION_O) {
    
    
        synchronized(g_HandlerLock) {
    
    
            DestroyHandlerThread();
        }
    }
    return surfaceDestroy;
}

当被复用的View重新 onAttachedToWindow 的时候

protected void onAttachedToWindow() {
    
    
    this.isAttachedToWindow = true;
    super.onAttachedToWindow();
    this.animator.addListener(this.mAnimatorListenerAdapter);
    synchronized(g_HandlerLock) {
    
    
        StartHandlerThread();
    }
    //注释1处
    this.resumeAnimator();
}

注释1处,会调用 resumeAnimator 方法。

private void resumeAnimator() {
    
    
    if(this._isPlaying && !this.animator.isRunning() && (this._isAnimatorPreRunning == null || this._isAnimatorPreRunning)) {
    
    
        this._isAnimatorPreRunning = null;
        //注释1处
        this.doPlay();
    } else {
    
    
        this._isAnimatorPreRunning = null;
        this.updateTextureView();
    }
}

会走到注释1处。

public void play() {
    
    
    this._isPlaying = true;
    this._isAnimatorPreRunning = null;
    if((double) this.animator.getAnimatedFraction() == 1.0 D) {
    
    
        this.setProgress(0.0 D);
    }
    //调用 doPlay 方法。
    this.doPlay();
}
private void doPlay() {
    
    
    this.pagPlayer.prepare();
    if(!this.isAttachedToWindow) {
    
    
        Log.e("PAGView", "doPlay: PAGView is not attached to window");
    } else {
    
    
        Log.i("PAGView", "doPlay");
        this.animator.setCurrentPlayTime(this.currentPlayTime);
        //注释1处,启动属性动画
        this.startAnimator();
    }
}

注释1处,启动属性动画

//属性动画
private ValueAnimator animator;

private void startAnimator() {
    
    
    if(this.isMainThread()) {
    
    
        this.animator.start();
    } else {
    
    
        this.removeCallbacks(this.mAnimatorCancelRunnable);
        this.post(this.mAnimatorStartRunnable);
    }
}

在复用的View重新 onAttachedToWindow 的时候,会再次启动 属性动画。但是由于在 onSurfaceTextureDestroyed 中移除了 mAnimatorUpdateListener 。
并且后续再启动动画等操作都没有重新添加 mAnimatorUpdateListener 监听属性动画。所以PAGView 动画就执行不了了。

第二个问题出现的具体原因

  1. RecyclerView 中先加载一个数据。
  2. 然后通过点击 展开所有的item,调用 notifyDataSetChanged 展开全部两条数据。 第一条数据的ViewHolder会被复用。
  3. 第一个数据中通过点击按钮播放PAGView动画,发现动画无法执行。

也是类似的。当在View复用以后通过点击按钮播放PAGView动画,但是由于在 onSurfaceTextureDestroyed 中移除了 mAnimatorUpdateListener 。并且后续再启动动画等操作都没有重新添加 mAnimatorUpdateListener 监听属性动画。所以PAGView 动画就执行不了了。

猜你喜欢

转载自blog.csdn.net/leilifengxingmw/article/details/128931934