赞
踩
基本上都是某个动画未正常结束,执行时间太久导致后续动画堆积或被merge到异常动画导致相关Surface得不到释放导致的。
Visible layers 中有1558 个Transition Root相关layer
Visible layers (count = 3519)
* Layer 0xb4000074162f8000 (Transition Root: Task=1#919112)
......
* Layer 0xb400007415384000 (Transition Root: ActivityRecord{42e4d75 u0 com.miui.cloudservice/.ui.MiCloudEntranceActivity t14504}#921129)
shell端18min内新的transition一直在等待被执行,track中某个动画执行时间太久,导致后续动画一直未被执行,Transition Root一直未被释放。
01-17 11:58:36.950 1000 2319 2460 D WindowManager: Calling onTransitionReady info={id=86180 t=OPEN f=0x0 trk=0 r=[0@Point(0, 0)] c=[{WCT{RemoteToken{4cb58a Task
{bac9f7 #1 type=home}}} m=CHANGE f=SHOW_WALLPAPER|MOVE_TO_TOP leash=Surface(name=Task=1#44)/@0x8321f7e sb=Rect(0, 0 - 1080, 2400) eb=Rect(0, 0 - 1080, 2400) d=0},{WCT{RemoteToken{8e472f5 Task{a7c9f7b #14657 type=standard A=10295:com.baidu.searchbox}}} m=TO_BACK f=TRANSLUCENT leash=Surface(name=Task=14657#928886)/@0x81ba3df sb=Rect(0, 0 - 1080, 2400) eb=Rect(0, 0 - 1080, 2400) d=0}] noAni=[false]}, mToken=Token{17a8773 TransitionRecord{4ceac18 id=86180 type=OPEN flags=0x0}}
01-17 11:58:37.032 1000 5443 5603 I ShellTransitions: track.mReadyTransitions.size() > 1, return, active = (#86180)android.os.BinderProxy@3ad1c76@0
01-17 12:16:57.300 1000 2319 2460 D WindowManager: Calling onTransitionReady info={id=86420 t=OPEN f=0x0 trk=0 r=[0@Point(0, 0)] c=[{WCT{RemoteToken{a57f26f Task{c60213c #14711 type=standard A=10282:com.xiaomi.shop}}} m=OPEN f=NONE leash=Surface(name=Task=14711#930794)/@0xbacb449 sb=Rect(0, 0 - 1080, 2400) eb=Rect(0, 0 - 1080, 2400) d=0},{WCT{RemoteToken{4cb58a Task{bac9f7 #1 type=home}
}} m=TO_BACK f=SHOW_WALLPAPER leash=Surface(name=Task=1#44)/@0x8321f7e sb=Rect(0, 0 - 1080, 2400) eb=Rect(0, 0 - 1080, 2400) d=0}] noAni=[false]}, mToken=Token{42a3b7c TransitionRecord{9afb74b id=86420 type=OPEN flags=0x0}}
01-17 12:16:57.303 1000 5443 5603 I ShellTransitions: track.mReadyTransitions.size() > 1, return, active = (#86420)android.os.BinderProxy@b5ed8d5@0
20min 后XXXCompat$RemoteTransitionCompat 都没有正常finish,导致后续的transition都merge到了该ransition,这些动画一直未被结束,从而导致内存泄漏。
10-10 08:47:21.611 1000 6633 6720 V WindowManagerShell: Transition (#24482)android.os.BinderProxy@e66050f@1 ready while (#23229)android.os.BinderProxy@28c5935@1 is still animating. Notify the animating transition in case they can be merged
10-10 08:47:21.611 1000 6633 6720 V WindowManagerShell: Merge into remote: RemoteTransition { remoteTransition = com.android.wm.shell.xxx.xxx.transition.XXXTransitionCompat$RemoteTransitionCompat@4a32658, appThread = null, debugName = null }
10-10 08:47:21.776 1000 6633 6720 D XXXTransitionCompat: mergeAnimation start
10-10 08:47:21.793 1000 6633 6720 V WindowManagerShell: Transition was merged: (#24482)android.os.BinderProxy@e66050f@1 into (#23229)android.os.BinderProxy@28c5935@1
……
10-10 09:07:21.613 1000 6633 6720 V WindowManagerShell: Transition (#24528)android.os.BinderProxy@c578364@1 ready while (#23229)android.os.BinderProxy@28c5935@1 is still animating. Notify the animating transition in case they can be merged
10-10 09:07:21.618 1000 6633 6720 V WindowManagerShell: Merge into remote: RemoteTransition { remoteTransition = com.android.wm.shell.xx.xx.transition.XXXTransitionCompat$RemoteTransitionCompat@4a32658, appThread = null, debugName = null }
10-10 09:07:21.701 1000 6633 6720 V WindowManagerShell: Transition was merged: (#24528)android.os.BinderProxy@c578364@1 into (#23229)android.os.BinderProxy@28c5935@1
// 手机重启
10-10 09:08:06.977 root 0 0 E : Out of memory: Kill process 2039 (system_server) score 0 or sacrifice child
目前,一次只有1个transition能成为CollectingTransition,但是collecting实际上可以分为两个阶段:
/** Returns {@code true} if it started collecting, {@code false} if it was queued. */ boolean startCollectOrQueue(Transition transit, OnStartCollect onStartCollect) { if (!mQueuedTransitions.isEmpty()) { // Just add to queue since we already have a queue. queueTransition(transit, onStartCollect); return false; } if (mSyncEngine.hasActiveSync()) { if (isCollecting()) { // Check if we can run in parallel here. if (canStartCollectingNow(transit)) { // start running in parallel. ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from" + " collecting to waiting.", mCollectingTransition.getSyncId()); mWaitingTransitions.add(mCollectingTransition); mCollectingTransition = null; moveToCollecting(transit); onStartCollect.onCollectStarted(false /* deferred */); return true; } } else { // 旧的动画 Slog.w(TAG, "Ongoing Sync outside of transition."); } queueTransition(transit, onStartCollect); return false; } moveToCollecting(transit); onStartCollect.onCollectStarted(false /* deferred */); return true; }
有两种场景会尝试从现有的等待队列mQueuedTransitions中重新收集transition:
// Transition.java
private void applyReady() {
......
final boolean ready = mReadyTracker.allReady();
boolean changed = mSyncEngine.setReady(mSyncId, ready);
// 结束wm change收集,即首次ready为true时
if (changed && ready) {
......
mController.onTransitionPopulated(this);
}
}
// TransitionController.java
void onTransitionPopulated(Transition transition) {
tryStartCollectFromQueue();
}
// SyncGroup#finishNow
// Notify idle listeners
for (int i = mOnIdleListeners.size() - 1; i >= 0; --i) {
// If an idle listener adds a sync, though, then stop notifying.
if (mActiveSyncs.size() > 0) break;
mOnIdleListeners.get(i).run();
}
// TransitionController.java void tryStartCollectFromQueue() { if (mQueuedTransitions.isEmpty()) return; final QueuedTransition queued = mQueuedTransitions.get(0); if (mCollectingTransition != null) { // 如果它是之前的sync,则需要等到没有收集transition为止。 if (queued.mTransition == null) return; if (!canStartCollectingNow(queued.mTransition)) return; ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS_MIN, "Moving #%d from collecting" + " to waiting.", mCollectingTransition.getSyncId()); // 将当前mCollectingTransition移动至mWaitingTransitions mWaitingTransitions.add(mCollectingTransition); mCollectingTransition = null; } else if (mSyncEngine.hasActiveSync()) { // 遗留的transition正在进行中,所以我们必须等待。 return; } // 移除mQueuedTransitions中第一个transition,赋值为mCollectingTransition mQueuedTransitions.remove(0); if (queued.mTransition != null) { moveToCollecting(queued.mTransition); } else { // legacy sync mSyncEngine.startSyncSet(queued.mLegacySync); } // 发布此内容以便当前播放的transition逻辑不会被中断。 mAtm.mH.post(() -> { synchronized (mAtm.mGlobalLock) { queued.mOnStartCollect.onCollectStarted(true /* deferred */); } }); }
如果当前的mCollectingTransition在mWaitingTransitions中任何一个等待transition之前开始playing,则将mWaitingTransitions中的第一个transition赋值给mCollectingTransition。这维持了 WM 其余部分当前所期望的“全局”抽象。
// TransitionController.java void moveToPlaying(Transition transition) { if (transition == mCollectingTransition) { mCollectingTransition = null; if (!mWaitingTransitions.isEmpty()) { // 将mWaitingTransitions中的第一个transition赋值给mCollectingTransition mCollectingTransition = mWaitingTransitions.remove(0); } ...... } else { if (!mWaitingTransitions.remove(transition)) { throw new IllegalStateException("Trying to move non-collecting transition to" + "playing " + transition.getSyncId()); } } mPlayingTransitions.add(transition); ..... }
现有策略:
Shell transition 仅支持一次play一个动画,它通过merge对并发动画提供有限支持,但这适用于特定情况。由于通过merge的支持很复杂,因此实现并不多,相反,默认情况下,动画只会跳到末尾以允许下一个动画立即开始(以最大限度减少感知延迟)。
引入track:
将现有的queue/merge机制移动至“track”中,然后添加对多个track play的支持。这样,对于不独立的transitions,机制不会改变;然而,对于真正独立的transitions,他们的动画可以独立的运行。
Track id:
期望WM Core可以为transitions分配track ID。然后Shell可以使用此信息并行play他们,或者在将来进行某种类型的merge。
默认情况下,具有相同track ID的所有transitions都将在同一track中按顺序play,但track之间是独立的。
然而,在某些情况下,transition可能与多个track冲突。在此情况下,我们引入“SYNC”,并在开始前结束所有正在运行的transitions/track。
Core
Track是一组连续且按顺序执行的transitions,如果一个transition与所有其他的transition并行(在动画就绪时),那么他将被分配一个新的track。否则,如果它与现有track的transition重叠,它将分配给该track。
当一个transition被移动到playing时,我们会根据其他playing transition进行检查。如果它不与他们重叠,它可以并行动画,在这种情况下,它将被分配一个新的track。
// TransitionContr mTrackCount = Math.max(mTrackCount, track + 1);oller.java void assignTrack(Transition transition, TransitionInfo info) { int track = -1; boolean sync = false; for (int i = 0; i < mPlayingTransitions.size(); ++i) { // ignore ourself obviously if (mPlayingTransitions.get(i) == transition) continue; // 目前只有一对相互独立的对:所有activity-level transition和transient-launch(Recent),其中没有任何activities是瞬态启动task的一部分 if (getIsIndependent(mPlayingTransitions.get(i), transition)) continue; if (track >= 0) { // 此时,transition与多个track重叠,因此只需等待即可 sync = true; break; } track = mPlayingTransitions.get(i).mAnimationTrack; } if (sync) { track = 0; } if (track < 0) { // Didn't overlap with anything, so give it its own track track = mTrackCount; if (track > 0) { ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Playing #%d in parallel on " + "track #%d", transition.getSyncId(), track); } } if (sync) { info.setFlags(info.getFlags() | TransitionInfo.FLAG_SYNC); ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Marking #%d animation as SYNC.", transition.getSyncId()); } transition.mAnimationTrack = track; info.setTrack(track); mTrackCount = Math.max(mTrackCount, track + 1); }
Shell
在Shell中,每个transition都有一个生命周期。
基本上:–start–> PENDING --onTransitionReady–> READY --play–> ACTIVE --finish–> | --merge–>merge–
READY 及之后的生命周期按“track”进行管理,在一个track中,所有动画都按描述的方式进行排序,一个track内,一次只能有一个transition处于active状态;但是,多个track可以同时play。
// Transitions.java
private static class Track {
// 已准备好但仍在等待的transitions
final ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();
// 当前正在palying的transition
ActiveTransition mActiveTransition = null;
boolean isIdle() {
return mActiveTransition == null && mReadyTransitions.isEmpty();
}
}
boolean dispatchReady(ActiveTransition active) { final TransitionInfo info = active.mInfo; ..... final Track track = getOrCreateTrack(info.getTrack()); track.mReadyTransitions.add(active); ...... // 如果track中某个transition执行时间太久,可能会导致mReadyTransitions过大,从而导致内存泄漏 if (track.mReadyTransitions.size() > 1) { // MIUI ADD: WMS_MiuiAnimationEffect Slog.i(TAG, "track.mReadyTransitions.size() > 1, return, active = " + active); if (track.mActiveTransition != null && track.mActiveTransition.mInfo != null){ Slog.i(TAG, "The current active is " + track.mActiveTransition.mInfo.toString() +" \n handler is "+track.mActiveTransition.mHandler); } // END WMS_MiuiAnimationEffect // There are already transitions waiting in the queue, so just return. return true; } processReadyQueue(track); return true; }
获取mReadyTransitions的第一个transition,标记为ready。
void processReadyQueue(Track track) { ...... final ActiveTransition ready = track.mReadyTransitions.get(0); if (track.mActiveTransition == null) { // 1. track中没有正在播放的transition,则直接进入active状态 track.mReadyTransitions.remove(0); track.mActiveTransition = ready; if (ready.mAborted) { if (ready.mStartT != null) { ready.mStartT.apply(); } // finish now since there's nothing to animate. Calls back into processReadyQueue onFinish(ready, null, null); return; } // 开始播放 playTransition(ready); // 并尝试merge track中其它ready的transition processReadyQueue(track); return; } // An existing animation is playing, so see if we can merge. final ActiveTransition playing = track.mActiveTransition; if (ready.mAborted) { // record as merged since it is no-op. Calls back into processReadyQueue onMerged(playing, ready); return; } ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition %s ready while" + " %s is still animating. Notify the animating transition" + " in case they can be merged", ready, playing); mTracer.logMergeRequested(ready.mInfo.getDebugId(), playing.mInfo.getDebugId()); // 2. handler针对merge的处理,有三种处理方式 playing.mHandler.mergeAnimation(ready.mToken, ready.mInfo, ready.mStartT, playing.mToken, (wct, cb) -> onMerged(playing, ready)); }
对于重叠的transitions(mergeAnimation)有3种预期响应:
// DefaultTransitionHandler.java @Override public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget, @NonNull Transitions.TransitionFinishCallback finishCallback) { ArrayList<Animator> anims = mAnimations.get(mergeTarget); if (anims == null) return; for (int i = anims.size() - 1; i >= 0; --i) { final Animator anim = anims.get(i); mAnimExecutor.execute(anim::end); } } private void onFinish(ActiveTransition active, @Nullable WindowContainerTransaction wct, @Nullable WindowContainerTransactionCallback wctCB) { final Track track = mTracks.get(active.getTrack()); ...... track.mActiveTransition = null; ...... // Now that this is done, check the ready queue for more work. // finish 后继续分发当前track中的ready transition processReadyQueue(track); }
finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
private void onMerged(@NonNull ActiveTransition playing, @NonNull ActiveTransition merged) { ...... final Track track = mTracks.get(playing.getTrack()); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Transition was merged: %s into %s", merged, playing); int readyIdx = 0; ....... // merge后从ready中移除当前merged的transition,以阻止后续会被继续分发 track.mReadyTransitions.remove(readyIdx); if (playing.mMerged == null) { playing.mMerged = new ArrayList<>(); } playing.mMerged.add(merged); // if it was aborted, then onConsumed has already been reported. if (merged.mHandler != null && !merged.mAborted) { merged.mHandler.onTransitionConsumed(merged.mToken, false /* abort */, merged.mFinishT); } for (int i = 0; i < mObservers.size(); ++i) { mObservers.get(i).onTransitionMerged(merged.mToken, playing.mToken); } mTracer.logMerged(merged.mInfo.getDebugId(), playing.mInfo.getDebugId()); // See if we should merge another transition. // 继续遍历当前track是否有ready transition可以继续被merge processReadyQueue(track); }
WM core中确定transition与 > 1个track重叠,则会将其标为SYNC;SYNC transition播放前,必须flushed(结束)当前所有active的track。
boolean dispatchReady(ActiveTransition active) { final TransitionInfo info = active.mInfo; if (info.getType() == TRANSIT_SLEEP || active.isSync()) { // Adding to *front*! If we are here, it means that it was pulled off the front // so we are just putting it back; or, it is the first one so it doesn't matter. mReadyDuringSync.add(0, active); boolean hadPreceding = false; // Now flush all the tracks. for (int i = 0; i < mTracks.size(); ++i) { final Track tr = mTracks.get(i); if (tr.isIdle()) continue; hadPreceding = true; // Sleep starts a process of forcing all prior transitions to finish immediately ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Start finish-for-sync track %d", i); finishForSync(active, i, null /* forceFinish */); } if (hadPreceding) { return false; } // Actually able to process the sleep now, so re-remove it from the queue and continue // the normal flow. mReadyDuringSync.remove(active); }
void processReadyQueue(Track track) { if (track.mReadyTransitions.isEmpty()) { if (track.mActiveTransition == null) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Track %d became idle", mTracks.indexOf(track)); if (areTracksIdle()) { if (!mReadyDuringSync.isEmpty()) { // Dispatch everything unless we hit another sync while (!mReadyDuringSync.isEmpty()) { ActiveTransition next = mReadyDuringSync.remove(0); boolean success = dispatchReady(next); // Hit a sync or sleep, so stop dispatching. if (!success) break; } } else if (mPendingTransitions.isEmpty()) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "All active transition " + "animations finished"); // Run all runnables from the run-when-idle queue. for (int i = 0; i < mRunWhenIdleQueue.size(); i++) { mRunWhenIdleQueue.get(i).run(); } mRunWhenIdleQueue.clear(); } } } return; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。