大家好,我是稳稳,一个曾经励志用技术改变世界,现在为失业做准备的中年奶爸程序员,与你分享生活和学习的点滴。
好了,废话不多说了,咱们继续来学习
#面试#android
案例:某社交App在个人主页切换时使用属性动画实现卡片翻转效果,频繁操作后触发OOM崩溃。
内存快照显示:泄漏的Activity被Animator$AnimatorListener持有。
诡异现象:
// Animator.java
publicvoidstart() {
// 动画被加入全局调度器
AnimationHandler.getInstance().addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
}
// AnimationHandler通过Choreographer关联主线程Looper
privatefinal Choreographer.FrameCallbackmFrameCallback=newChoreographer.FrameCallback() {
@Override
publicvoiddoFrame(long frameTimeNanos) {
// 持有Animator引用,形成强引用链
doAnimationFrame(getProvider().getFrameTime());
}
};// Animator.java
private ArrayList<AnimatorListener> mListeners = new ArrayList<>();
public void addListener(AnimatorListener listener) {
mListeners.add(listener); // 监听器被Animator强引用
}致命逻辑:
// 泄漏代码:匿名内部类隐式持有Activity
ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
// 此处持有Activity的this引用
updateUI()
}
})
}public classMainActivityextendsActivity {
privateclassAnimationControllerimplementsAnimatorListener {
// 非静态内部类隐式持有外部Activity
voidstartAnimation() {
ObjectAnimatoranim= ObjectAnimator.ofFloat(...);
anim.addListener(this);
}
}
}view.postDelayed({
// Runnable持有View,View持有Activity
val anim = ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
anim.start()
}, 1000)public class AnimManager {
private static AnimManager instance;
private List<Animator> runningAnimators = new ArrayList<>();
public void playAnim(Animator anim) {
runningAnimators.add(anim); // 单例长期持有Animator
anim.start();
}
}
// Activity中调用:AnimManager.getInstance().playAnim(myAnim);val anim = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply {
repeatCount = ValueAnimator.INFINITE
start()
}
// onDestroy中未调用anim.cancel(),导致动画持续持有Viewclass SafeAnimListener(activity: Activity) : AnimatorListenerAdapter() {
private val weakActivity = WeakReference(activity)
override fun onAnimationEnd(animation: Animator) {
weakActivity.get()?.updateUI() ?: animation.cancel() // 自动回收
}
}
// 使用:
anim.addListener(SafeAnimListener(this))@Override
protected void onDestroy() {
super.onDestroy();
if (animator != null) {
animator.removeAllListeners(); // 关键!
animator.cancel();
animator = null;
}
}view.animate()
.alpha(1f)
.setListener(null) // 默认无监听器
.withEndAction { /* 使用Runnable,内部自动弱引用处理 */ }// 基类Activity中统一管理
private val animators = mutableListOf<Animator>()
fun registerAnimator(animator: Animator) {
animators.add(animator)
}
override fun onDestroy() {
animators.forEach { it.cancel() }
super.onDestroy()
}// 使用弱引用容器管理
public class AnimManager {
private WeakHashMap<Animator, Boolean> weakAnims = new WeakHashMap<>();
public void playAnim(Animator anim) {
weakAnims.put(anim, true);
anim.start();
}
}class App : Application() {
overridefunonCreate() {
super.onCreate()
LeakCanary.config = LeakCanary.config.copy(
onHeapAnalyzedListener = { heapAnalysis ->
if (heapAnalysis.leakTraces.any { trace ->
trace.className.contains("Animator")
}) {
// 触发动画泄漏报警
}
}
)
}
}答案:
正确代码:
val anim = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply {
repeatCount = ValueAnimator.INFINITE
addListener(object : AnimatorListenerAdapter() {
privateval weakView = WeakReference(view)
overridefunonAnimationCancel(animation: Animator) {
weakView.get()?.animation = null// 清除View的动画引用
}
})
}
// Activity的onDestroy中:
overridefunonDestroy() {
anim.cancel()
view.animation = null// 关键!
super.onDestroy()
}陷阱解析:
// 静态内部类 + 弱引用
class SafeListener(view: View) : AnimatorListenerAdapter() {
private val weakView = WeakReference(view)
override fun onAnimationEnd(animation: Animator) {
weakView.get()?.visibility = View.GONE
}
}
// 使用:
anim.addListener(SafeListener(view))动画内存泄漏的本质是生命周期不对称的强引用链。
掌握源码中Choreographer、Animator、Listener的交互逻辑,结合弱引用与生命周期管控,可让动画既流畅又安全。
实战工具推荐: