Android app 冷启动(7) 执行动画

前面一篇文章分析了 Transition 的数据化,结果如下

txt 复制代码
{
    id=13 
    t=OPEN 
    f=0x0 
    trk=0 
    r=[0@Point(0, 0)] 
    
    c=[ 
        {
            WCT{RemoteToken{21e666b Task{d7726f #39 type=standard A=10206:com.awesome.helloworld}}} 
            m=OPEN 
            f=NONE 
            leash=Surface(name=Task=39)/@0x173bb7c 
            sb=Rect(0, 0 - 1280, 1840) 
            eb=Rect(0, 0 - 1280, 1840) 
            d=0 
            endFixedRotation=1
        }, 

        {
            WCT{RemoteToken{b77580e Task{c7b2d2 #1 type=home}}} 
            m=TO_BACK 
            f=SHOW_WALLPAPER 
            leash=Surface(name=Task=1)/@0xb50d72d 
            sb=Rect(0, 0 - 1280, 1840) 
            eb=Rect(0, 0 - 1280, 1840) 
            d=0
        }
      ]
}

然后把数据发送给 WMShell ,让其执行动画,如下

java 复制代码
// Transitions.java

void onTransitionReady(@NonNull IBinder transitionToken, @NonNull TransitionInfo info,
        @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
    // ...
    
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "onTransitionReady (#%d) %s: %s",
            info.getDebugId(), transitionToken, info);
            
    
    int activeIdx = findByToken(mPendingTransitions, transitionToken);
   
    // ...
    
    // Move from pending to ready
    final ActiveTransition active = mPendingTransitions.remove(activeIdx);
    // ActiveTransition 保存了 WMCore 传入的执行动画的数据
    active.mInfo = info;
    active.mStartT = t;
    active.mFinishT = finishT;

    if (!mReadyDuringSync.isEmpty()) {
        
    } else {
        dispatchReady(active);
    }
}

根据 transition token ,取出待执行的 ActiveTransition,然后开始对其进行处理

java 复制代码
// Transitions.java

/**
 * Returns true if dispatching succeeded, otherwise false. Dispatching can fail if it is
 * blocked by a sync or sleep.
 */
boolean dispatchReady(ActiveTransition active) {
    final TransitionInfo info = active.mInfo;

    // ...

    // 每一个 Transition 都属于一个 Track
    final Track track = getOrCreateTrack(info.getTrack());
    
    // Transition 在执行前,需要先保存到 Track 的 ready queue 中
    track.mReadyTransitions.add(active);

    // ...

    // 初始化 Transition 启动状态,例如,visibility/alpha/transforms
    setupStartState(active.mInfo, active.mStartT, active.mFinishT);

    // ...
    
    // 处理 Track 的 ready queue 中所有 Transition
    processReadyQueue(track);
    return true;
}

每一个 Transition 都必须属于一个 Track,对于本文分析的案例,此时需要创建一个 Track。

有了 Track 之后,Transition 必须加入到 Track 的 ready queue 中,并对 Transition 进行状态初始化,如下

java 复制代码
// Transitions.java

/**
 * Sets up visibility/alpha/transforms to resemble the starting state of an animation.
 */
private static void setupStartState(@NonNull TransitionInfo info,
        @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
    // transition type 是 OPEN
    // true
    boolean isOpening = isOpeningType(info.getType());
    
    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
        final TransitionInfo.Change change = info.getChanges().get(i);
        
        if (change.hasFlags(FLAGS_IS_NON_APP_WINDOW & ~FLAG_IS_WALLPAPER)) {

        }
        
        if (change.hasFlags(FLAG_IS_WALLPAPER) && !ensureWallpaperInTransitions()) {

        }
        
        final SurfaceControl leash = change.getLeash();
        final int mode = info.getChanges().get(i).getMode();

        if (mode == TRANSIT_TO_FRONT) {

        }

        // 动画目标只有两个 Task,它们都没有做动画的 parent,所以可以独立做动画
        // Don't move anything that isn't independent within its parents
        if (!TransitionInfo.isIndependent(change, info)) {
            
        }

        if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
            t.show(leash);
            t.setMatrix(leash, 1, 0, 0, 1);
            if (isOpening
                    // If this is a transferred starting window, we want it immediately visible.
                    && (change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) == 0) {
                
            }
            
            finishT.show(leash);
        } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
            finishT.hide(leash);
        } else if (isOpening && mode == TRANSIT_CHANGE) {
           
        }
    }
}

根据 Transition 数据,Transition 状态初始化时

  1. 对于 app Task,mode 为 OPEN,在 start transactin 和 finish transaction 中都会 show 其 leash surface。
  2. 对于 launcher Task,mode 为 TO_BACK,在 finish transaction 中 show 其 leash surface。

Transition 状态初始化完成后,就开始处理 Track ready queue 中的所有 Transition,如下

java 复制代码
// Transtions.java

void processReadyQueue(Track track) {
    if (track.mReadyTransitions.isEmpty()) {
        // ..
    }
    
    final ActiveTransition ready = track.mReadyTransitions.get(0);
    if (track.mActiveTransition == null) {
        // The normal case, just play it.
        track.mReadyTransitions.remove(0);
        // ready -> active
        track.mActiveTransition = ready;
        if (ready.mAborted) {
            
        }
        
        // 执行动画
        playTransition(ready);
        
        // 本文不涉及 merge transition
        // Attempt to merge any more queued-up transitions.
        processReadyQueue(track);
        return;
    }
    
    // ...
}
java 复制代码
// Transitions.java

private void playTransition(@NonNull ActiveTransition active) {
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Playing animation for %s", active);
    final var token = active.mToken;

    for (int i = 0; i < mObservers.size(); ++i) {
        mObservers.get(i).onTransitionStarting(token);
    }

    // 初始化动画层级
    setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);

    // 首先由 active.mHandler 优先执行动画
    // If a handler already chose to run this animation, try delegating to it first.
    if (active.mHandler != null) {
        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
                active.mHandler);
        boolean consumed = active.mHandler.startAnimation(token, active.mInfo,
                active.mStartT, active.mFinishT, (wct) -> onFinish(token, wct));
        if (consumed) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
            mTransitionTracer.logDispatched(active.mInfo.getDebugId(), active.mHandler);
            return;
        }
    }
    
    /// 如果 active.mHandler 不能执行动画,那么分发给其他 TransitionHandler 去尝试执行
    // Otherwise give every other handler a chance
    active.mHandler = dispatchTransition(token, active.mInfo, active.mStartT,
            active.mFinishT, (wct) -> onFinish(token, wct), active.mHandler);
}

在把 Transition 交给 TransitionHandler 执行之前,还会对动画层级进行初始化,如下

java 复制代码
// T
/**
 * Reparents all participants into a shared parent and orders them based on: the global transit
 * type, their transit mode, and their destination z-order.
 */
private static void setupAnimHierarchy(@NonNull TransitionInfo info,
        @NonNull SurfaceControl.Transaction t, @NonNull SurfaceControl.Transaction finishT) {
    final int type = info.getType();
    
    // start transaction 中 show transition root surface
    for (int i = 0; i < info.getRootCount(); ++i) {
        t.show(info.getRoot(i).getLeash());
    }
    
    final int numChanges = info.getChanges().size();
    // changes should be ordered top-to-bottom in z
    for (int i = numChanges - 1; i >= 0; --i) {
        final TransitionInfo.Change change = info.getChanges().get(i);
        final SurfaceControl leash = change.getLeash();

        // 两个动画目标 Task,都没有执行动画的 parent,因此可以独立做动画
        // Don't reparent anything that isn't independent within its parents
        if (!TransitionInfo.isIndependent(change, info)) {
            continue;
        }

        // false
        boolean hasParent = change.getParent() != null;

        final TransitionInfo.Root root = TransitionUtil.getRootFor(change, info);
        if (!hasParent) {
            // start transaction 中,两个 task leash 都 reparent 到 transition root 上
            t.reparent(leash, root.getLeash());
            // start transaction 中,设置相对于 parent(此时为 transition root) 的偏移坐标
            // 此时都是 0
            t.setPosition(leash,
                    change.getStartAbsBounds().left - root.getOffset().x,
                    change.getStartAbsBounds().top - root.getOffset().y);
        }
        
        // 计算 layer,并在 start transaction 中设置 layer
        final int layer = calculateAnimLayer(change, i, numChanges, type);
        t.setLayer(leash, layer);
    }
}
java 复制代码
// Transitions.java

static int calculateAnimLayer(@NonNull TransitionInfo.Change change, int i,
        int numChanges, @WindowManager.TransitionType int transitType) {
        
    // Put animating stuff above this line and put static stuff below it.
    final int zSplitLine = numChanges + 1;
    
    // transitionType 此时是 OPEN
    // true
    final boolean isOpening = isOpeningType(transitType);
    // false
    final boolean isClosing = isClosingType(transitType);
    
    // Launcher Task 的 mode 为 TRANSIT_TO_BACK
    // app Task 的 mode 为 TRANSIT_OPEN
    final int mode = change.getMode();
    
    // Ensure wallpapers stay in the back
    if (change.hasFlags(FLAG_IS_WALLPAPER) && Flags.ensureWallpaperInTransitions()) {

    }
    
    // Put all the OPEN/SHOW on top
    if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
        if (isOpening) {
            // put on top
            return zSplitLine + numChanges - i;
        } else if (isClosing) {
           
        } else {
            
        }
    } else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
        if (isOpening) {
            // put on bottom and leave visible
            return zSplitLine - i;
        } else {
            
        }
    } else { // CHANGE or other
        
    }
}

根据 Transition 信息,动画层级初始化,如下

  1. 对于 app Task,它的 mode 为 OPEN,没有 parent。其 leash surface 被 reparent 到 transition root surface,并且在 start transaction 中,对其 layer 更新为 5。
  2. 对于 launcher Task,mode 为 TO_BACK,没有 parent。其 leash surface 被 reparent 到 transition root surface,并且在 start transaction 中,对其 layer 更新为 2。

那么,现在可以预见,在 start transaction apply 之时,surface 层级结构,是这样的

stateDiagram-v2 TaskDisplayArea_surface --> transition_root_surface transition_root_surface --> app_task_surface(layer为2) transition_root_surface --> launcher_task_surface(layer为5)

动画层级初始化完成后,就把 Transition 动画交给 TransitionHandler 执行。对于远程动画,最终是交给 RemoteTransitionHandler 执行,如下

java 复制代码
// RemoteTransitionHandler.java

public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
        @NonNull SurfaceControl.Transaction startTransaction,
        @NonNull SurfaceControl.Transaction finishTransaction,
        @NonNull Transitions.TransitionFinishCallback finishCallback) {
    // ...
    
    // RemoteTransitionHandler 处理 transition request 时,保存过 remote
    RemoteTransition pendingRemote = mRequestedRemotes.get(transition);
    
    // ...
    
    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Delegate animation for (#%d) to %s",
            info.getDebugId(), pendingRemote);

    if (pendingRemote == null) return false;

    final RemoteTransition remote = pendingRemote;
    
    // 动画执行完成的回调
    IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() {
        @Override
        public void onTransitionFinished(WindowContainerTransaction wct,
                SurfaceControl.Transaction sct) {
            unhandleDeath(remote.asBinder(), finishCallback);
            if (sct != null) {
                finishTransaction.merge(sct);
            }
            mMainExecutor.execute(() -> {
                mRequestedRemotes.remove(transition);
                finishCallback.onTransitionFinished(wct);
            });
        }
    };
    
    // remote 是远程的,不用复制 start transaction
    // If the remote is actually in the same process, then make a copy of parameters since
    // remote impls assume that they have to clean-up native references.
    final SurfaceControl.Transaction remoteStartT =
            copyIfLocal(startTransaction, remote.getRemoteTransition());
    final TransitionInfo remoteInfo =
            remoteStartT == startTransaction ? info : info.localRemoteCopy();
    try {
        handleDeath(remote.asBinder(), finishCallback);
        
        // 通知 launcher 执行动画
        // 发送的数据有 transition token
        remote.getRemoteTransition().startAnimation(transition, remoteInfo, remoteStartT, cb);
        
        // start transaction 已经通知 binder 调用复制给 launcher 端,因此这里直接 clear
        // assume that remote will apply the start transaction.
        startTransaction.clear();
        
        Transitions.setRunningRemoteTransitionDelegate(remote.getAppThread());
    } catch (RemoteException e) {
        // ...
    }
    return true;
}

Launcher 启动 app 时,传入了一个 IRemoteTransition Binder 参数,现在就利用这个 Binder 来通知 Launcher 执行远程动画。

由于一直没得到一个机会去研究 Launcher,因此本文也无法展示 Launcher 执行远程动画的流程。后续有机会,再补充吧。

相关推荐
MiyamuraMiyako1 小时前
从 0 到发布:Gradle 插件双平台(MavenCentral + Plugin Portal)发布记录与避坑
android
NRatel1 小时前
Unity 游戏提升 Android TargetVersion 相关记录
android·游戏·unity·提升版本
叽哥4 小时前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走4 小时前
创建自定义语音录制View
android·前端
用户2018792831674 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831674 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
Kapaseker6 小时前
你一定会喜欢的 Compose 形变动画
android
QuZhengRong6 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil7 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌14 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端