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 执行远程动画的流程。后续有机会,再补充吧。

相关推荐
haoqi好奇8 分钟前
uniapp+node+mysql接入deepseek实现流式输出
android·mysql·uni-app
alexhilton1 小时前
高效地在Jetpack Compose中设计UI组件
android·kotlin·android jetpack
恋猫de小郭1 小时前
Flutter 小技巧之通过 MediaQuery 优化 App 性能
android·前端·flutter
Android采码蜂1 小时前
SurfaceFlinger10-Transaction在sf进程中的提交过程
android
CYRUS_STUDIO1 小时前
安卓逆向魔改版 Base64 算法还原
android·算法·逆向
CYRUS_STUDIO2 小时前
安卓实现魔改版 Base64 算法
android·算法·逆向
盖盖衍上2 小时前
2-002:MySQL 索引的最左前缀匹配原则是什么?
android·数据库·mysql
然后就去远行吧4 小时前
小程序 wxml 语法 —— 37 setData() - 修改对象类型数据
android·前端·小程序
熙曦Sakura5 小时前
【MySQL】数据类型
android·mysql·adb
故事与他6455 小时前
CTFHub-上传文件
android·ide·windows·web安全·网络安全·android studio·xss