-
Notifications
You must be signed in to change notification settings - Fork 533
Fix Android PAGView release race. #3561
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -555,20 +555,24 @@ public void onSurfaceTextureUpdated(SurfaceTexture surface) { | |
|
|
||
| @Override | ||
| protected void onAttachedToWindow() { | ||
| isAttachedToWindow = true; | ||
| synchronized (PAGView.this) { | ||
| isAttachedToWindow = true; | ||
| } | ||
| super.onAttachedToWindow(); | ||
| checkVisible(); | ||
| } | ||
|
|
||
| @Override | ||
| protected void onDetachedFromWindow() { | ||
| isAttachedToWindow = false; | ||
| synchronized (PAGView.this) { | ||
| isAttachedToWindow = false; | ||
| } | ||
| checkVisible(); | ||
| super.onDetachedFromWindow(); | ||
| if (pagSurface != null) { | ||
| pagSurface.release(); | ||
| pagSurface = null; | ||
| } | ||
| checkVisible(); | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -588,12 +592,18 @@ public void onVisibilityAggregated(boolean isVisible) { | |
| private boolean isVisible = false; | ||
|
|
||
| private void checkVisible() { | ||
| boolean visible = isAttachedToWindow && isShown(); | ||
| if (isVisible == visible) { | ||
| return; | ||
| boolean attached = false; | ||
| synchronized (PAGView.this) { | ||
| attached = isAttachedToWindow; | ||
| } | ||
| isVisible = visible; | ||
| if (isVisible) { | ||
| boolean visible = attached && isShown(); | ||
| synchronized (PAGView.this) { | ||
| if (isVisible == visible) { | ||
| return; | ||
| } | ||
| isVisible = visible; | ||
| } | ||
| if (visible) { | ||
| animator.setDuration(pagPlayer.duration()); | ||
| animator.update(); | ||
| } else { | ||
|
|
@@ -661,16 +671,17 @@ public void onAnimationRepeat(PAGAnimator animator) { | |
| } | ||
|
|
||
| public void onAnimationUpdate(PAGAnimator animator) { | ||
| pagPlayer.setProgress(animator.progress()); | ||
| boolean changed = false; | ||
| synchronized (PAGView.this) { | ||
| if (!isAttachedToWindow) { | ||
| return; | ||
| } | ||
| pagPlayer.setProgress(animator.progress()); | ||
| if (isVisible) { | ||
| animator.setDuration(pagPlayer.duration()); | ||
| } | ||
| changed = flush(); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. renderLock 把 surface 生命周期与异步渲染串行化,正确根治了 surface release 与 flush 并发的问题,锁交互本身安全(无重入死锁、无跨层锁顺序环)。但这里在 worker 线程持有 renderLock 的整个期间执行了重量级的 flush()(完整一帧渲染)。而 UI 线程的 onAttachedToWindow / onDetachedFromWindow / onSurfaceTextureAvailable/Destroyed 都要抢同一把 renderLock:当某帧 flush 较慢时,UI 线程的这些生命周期回调会被阻塞至该帧结束,极端情况下存在卡顿甚至 ANR 风险。建议评估是否可缩小锁粒度(例如渲染阶段与 surface 增删用更细的临界区),或确认 flush 时长在可接受范围内。非阻塞项,供参考。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这个锁难拆分。核心矛盾在于:flush() 渲染到 pagSurface 期间,必须保证 pagSurface 不被并发释放。 |
||
| } | ||
| if (isVisible) { | ||
| animator.setDuration(pagPlayer.duration()); | ||
| } | ||
| boolean changed = flush(); | ||
| if (changed) { | ||
| updateTextureView(); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,6 +26,7 @@ static jmethodID PAGAnimator_onAnimationEnd; | |
| static jmethodID PAGAnimator_onAnimationCancel; | ||
| static jmethodID PAGAnimator_onAnimationRepeat; | ||
| static jmethodID PAGAnimator_onAnimationUpdate; | ||
| static std::mutex PAGAnimator_contextLocker = {}; | ||
|
|
||
| class AnimatorListener : public pag::PAGAnimator::Listener { | ||
| public: | ||
|
|
@@ -131,7 +132,6 @@ class JPAGAnimator { | |
| tempAnimator = std::move(animator); | ||
| tempListener = std::move(listener); | ||
| } | ||
| // Call cancel outside the lock to avoid deadlock | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不建议删除这行 “Call cancel outside the lock to avoid deadlock” 注释。下方代码刻意采用“锁内 std::move 出局部变量、出锁后再 cancel()”的写法,正是为避免 cancel() 同步回调 listener 时重入 locker 造成死锁。删掉注释后,这段非直觉的写法失去了唯一的解释,后来者很可能误以为可简化为锁内直接 cancel 而重新引入死锁。项目规范也要求 workaround 需加注释说明原因,建议恢复。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| if (tempAnimator) { | ||
| tempAnimator->cancel(); | ||
| } | ||
|
|
@@ -147,6 +147,7 @@ class JPAGAnimator { | |
| using namespace pag; | ||
|
|
||
| std::shared_ptr<PAGAnimator> getPAGAnimator(JNIEnv* env, jobject thiz) { | ||
| std::lock_guard<std::mutex> autoLock(PAGAnimator_contextLocker); | ||
| auto animator = | ||
| reinterpret_cast<JPAGAnimator*>(env->GetLongField(thiz, PAGAnimator_nativeContext)); | ||
| if (animator == nullptr) { | ||
|
|
@@ -156,9 +157,23 @@ std::shared_ptr<PAGAnimator> getPAGAnimator(JNIEnv* env, jobject thiz) { | |
| } | ||
|
|
||
| void setPAGAnimator(JNIEnv* env, jobject thiz, JPAGAnimator* animator) { | ||
| auto old = reinterpret_cast<JPAGAnimator*>(env->GetLongField(thiz, PAGAnimator_nativeContext)); | ||
| JPAGAnimator* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGAnimator_contextLocker); | ||
| old = reinterpret_cast<JPAGAnimator*>(env->GetLongField(thiz, PAGAnimator_nativeContext)); | ||
| env->SetLongField(thiz, PAGAnimator_nativeContext, (jlong)animator); | ||
| } | ||
| delete old; | ||
| } | ||
|
|
||
| void clearPAGAnimator(JNIEnv* env, jobject thiz) { | ||
| JPAGAnimator* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGAnimator_contextLocker); | ||
| old = reinterpret_cast<JPAGAnimator*>(env->GetLongField(thiz, PAGAnimator_nativeContext)); | ||
| env->SetLongField(thiz, PAGAnimator_nativeContext, 0); | ||
| } | ||
| delete old; | ||
| env->SetLongField(thiz, PAGAnimator_nativeContext, (jlong)animator); | ||
| } | ||
|
|
||
| extern "C" { | ||
|
|
@@ -177,15 +192,11 @@ PAG_API void Java_org_libpag_PAGAnimator_nativeSetup(JNIEnv* env, jobject thiz) | |
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGAnimator_nativeRelease(JNIEnv* env, jobject thiz) { | ||
| auto jPlayer = | ||
| reinterpret_cast<JPAGAnimator*>(env->GetLongField(thiz, PAGAnimator_nativeContext)); | ||
| if (jPlayer != nullptr) { | ||
| jPlayer->clear(); | ||
| } | ||
| clearPAGAnimator(env, thiz); | ||
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGAnimator_nativeFinalize(JNIEnv* env, jobject thiz) { | ||
| setPAGAnimator(env, thiz, nullptr); | ||
| clearPAGAnimator(env, thiz); | ||
| } | ||
|
|
||
| PAG_API jboolean Java_org_libpag_PAGAnimator_isSync(JNIEnv* env, jobject thiz) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,24 +28,38 @@ | |
|
|
||
| namespace pag { | ||
| static jfieldID PAGPlayer_nativeContext; | ||
| } | ||
| static std::mutex PAGPlayer_contextLocker = {}; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里(以及 JPAGAnimator.cpp、JPAGSurface.cpp)新增使用了 std::mutex,但均未显式 #include ,目前依赖 pag.h 间接包含才能编译。按 IWYU 原则,建议在这三个 .cpp 中显式添加 #include ,避免未来上游头文件调整包含链时被动编译失败。非阻塞项。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } // namespace pag | ||
|
|
||
| using namespace pag; | ||
|
|
||
| std::shared_ptr<PAGPlayer> getPAGPlayer(JNIEnv* env, jobject thiz) { | ||
| std::lock_guard<std::mutex> autoLock(PAGPlayer_contextLocker); | ||
| auto jPlayer = reinterpret_cast<JPAGPlayer*>(env->GetLongField(thiz, PAGPlayer_nativeContext)); | ||
| if (jPlayer == nullptr) { | ||
| return nullptr; | ||
| } | ||
| return jPlayer->get(); | ||
| } | ||
|
|
||
| void clearPAGPlayer(JNIEnv* env, jobject thiz) { | ||
| JPAGPlayer* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGPlayer_contextLocker); | ||
| old = reinterpret_cast<JPAGPlayer*>(env->GetLongField(thiz, PAGPlayer_nativeContext)); | ||
| env->SetLongField(thiz, PAGPlayer_nativeContext, 0); | ||
| } | ||
| delete old; | ||
| } | ||
|
|
||
| void setPAGPlayer(JNIEnv* env, jobject thiz, JPAGPlayer* player) { | ||
| auto old = reinterpret_cast<JPAGPlayer*>(env->GetLongField(thiz, PAGPlayer_nativeContext)); | ||
| if (old != nullptr) { | ||
| delete old; | ||
| JPAGPlayer* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGPlayer_contextLocker); | ||
| old = reinterpret_cast<JPAGPlayer*>(env->GetLongField(thiz, PAGPlayer_nativeContext)); | ||
| env->SetLongField(thiz, PAGPlayer_nativeContext, (jlong)player); | ||
| } | ||
| env->SetLongField(thiz, PAGPlayer_nativeContext, (jlong)player); | ||
| delete old; | ||
| } | ||
|
|
||
| extern "C" { | ||
|
|
@@ -60,10 +74,7 @@ PAG_API void Java_org_libpag_PAGPlayer_nativeSetup(JNIEnv* env, jobject thiz) { | |
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGPlayer_nativeRelease(JNIEnv* env, jobject thiz) { | ||
| auto jPlayer = reinterpret_cast<JPAGPlayer*>(env->GetLongField(thiz, PAGPlayer_nativeContext)); | ||
| if (jPlayer != nullptr) { | ||
| jPlayer->clear(); | ||
| } | ||
| clearPAGPlayer(env, thiz); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nativeRelease 改用了 clearPAGPlayer,但下方 nativeFinalize 仍是 setPAGPlayer(env, thiz, nullptr)。而 Animator/Surface 的 finalize 都统一走了 clear 逻辑,三个文件处理方式不一致。虽然 setPAGPlayer(nullptr) 与 clearPAGPlayer 行为等价、无 bug,但建议把 nativeFinalize 也改为 clearPAGPlayer(env, thiz),让 setPAGPlayer 只保留 nativeSetup 的“设新值”语义,统一风格、便于维护。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGPlayer_nativeFinalize(JNIEnv* env, jobject thiz) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,11 +30,13 @@ | |
|
|
||
| namespace pag { | ||
| static jfieldID PAGSurface_nativeSurface; | ||
| static std::mutex PAGSurface_contextLocker = {}; | ||
| } // namespace pag | ||
|
|
||
| using namespace pag; | ||
|
|
||
| std::shared_ptr<PAGSurface> getPAGSurface(JNIEnv* env, jobject thiz) { | ||
| std::lock_guard<std::mutex> autoLock(PAGSurface_contextLocker); | ||
| auto pagSurface = | ||
| reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface)); | ||
| if (pagSurface == nullptr) { | ||
|
|
@@ -43,24 +45,34 @@ std::shared_ptr<PAGSurface> getPAGSurface(JNIEnv* env, jobject thiz) { | |
| return pagSurface->get(); | ||
| } | ||
|
|
||
| void clearPAGSurface(JNIEnv* env, jobject thiz) { | ||
| JPAGSurface* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGSurface_contextLocker); | ||
| old = reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface)); | ||
| env->SetLongField(thiz, PAGSurface_nativeSurface, 0); | ||
| } | ||
| delete old; | ||
| } | ||
|
|
||
| extern "C" { | ||
|
|
||
| PAG_API void Java_org_libpag_PAGSurface_nativeInit(JNIEnv* env, jclass clazz) { | ||
| PAGSurface_nativeSurface = env->GetFieldID(clazz, "nativeSurface", "J"); | ||
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGSurface_nativeRelease(JNIEnv* env, jobject thiz) { | ||
| auto jPAGSurface = | ||
| reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface)); | ||
| if (jPAGSurface != nullptr) { | ||
| jPAGSurface->clear(); | ||
| } | ||
| clearPAGSurface(env, thiz); | ||
| } | ||
|
|
||
| PAG_API void Java_org_libpag_PAGSurface_nativeFinalize(JNIEnv* env, jobject thiz) { | ||
| auto old = reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface)); | ||
| JPAGSurface* old = nullptr; | ||
| { | ||
| std::lock_guard<std::mutex> autoLock(PAGSurface_contextLocker); | ||
| old = reinterpret_cast<JPAGSurface*>(env->GetLongField(thiz, PAGSurface_nativeSurface)); | ||
| env->SetLongField(thiz, PAGSurface_nativeSurface, 0); | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nativeFinalize 这里把加锁+置0+delete 的逻辑内联了一遍,与上方刚新增的 clearPAGSurface 函数体逐行完全相同。建议直接调用 clearPAGSurface(env, thiz)(与 nativeRelease 一致),消除同文件内的重复逻辑,后续调整锁策略时也不易漏改一处。
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
| } | ||
| delete old; | ||
| env->SetLongField(thiz, PAGSurface_nativeSurface, (jlong)thiz); | ||
| } | ||
|
|
||
| PAG_API jint Java_org_libpag_PAGSurface_width(JNIEnv* env, jobject thiz) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里把 isShown() 特意放在前后两个 synchronized(PAGView.this) 块之间、而非合并为一个锁块,应该是刻意设计——isShown() 是 Android View 框架方法,持锁调用可能触发重入/回调导致死锁,所以必须在锁外调用。但代码无注释说明,读者容易把这两个锁误当作可合并的冗余,从而把 isShown() 挪进锁内引入死锁。建议加一行注释说明 isShown() 必须锁外调用的原因。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done