WM Shell多动画场景处理

Shell导致的内存泄漏

基本上都是某个动画未正常结束,执行时间太久导致后续动画堆积或被merge到异常动画导致相关Surface得不到释放导致的。

某个Transition执行时间太久导致后续动画堆积

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

某个Transtion没有正常finish导致后续动画被merge

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. Core 特指WM Core,运行在system server进程;Shell运行在systemui进程;
  2. Transition Root这个layer是在Core创建的,且会针对每一个动画创建一个;
  3. 桌面点击图标打开动画涉及到三个进程
    在这里插入图片描述

多个动画如何处理?

Collecting阶段

目前,一次只有1个transition能成为CollectingTransition,但是collecting实际上可以分为两个阶段:

  1. 实际进行WM change 并收集参与的container;mCollectingTransition
  2. 等待参与container 准备ready(例如重绘内容)mCollectingTransition & mWaitingTransitions
    因为2花费了大部分时间并且不会改变WM,所以我们实际上可以在阶段2同时进行多个transition(mWaitingTransitions)。
    在这里插入图片描述
创建动画并收集-startCollectOrQueue

在这里插入图片描述
在这里插入图片描述

/** 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;
}
空闲收集- tryStartCollectFromQueue

有两种场景会尝试从现有的等待队列mQueuedTransitions中重新收集transition:

  1. 动画收集 change完毕: applyReady -> onTransitionPopulated -> tryStartCollectFromQueue
    当mCollectingTransition结束第1阶段(wm change收集)并等待时,就会发生并行collecting。将当前mCollectingTransition移动至mWaitingTransitions,新的transition将成为mCollectingTransition。
    当所有涉及的wc收集完毕且ready时,会回调onTransitionPopulated,从而调用tryStartCollectFromQueue方法。
// 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();
}
  1. 动画涉及的窗口绘制完成处于ready状态:SyncGroup#finishNow -> tryStartCollectFromQueue
    当BlastSyncEnginee没有active sync时也会去尝试从现有queue中获取待collect的transition。
// 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 */);
        }
    });
}
moveToPlaying

如果当前的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);
    .....
}

在这里插入图片描述

playing阶段

Track引入

现有策略:
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。

  • 例如transition是Recent动画,mPlayingTransitions中都是activity级别的动画,则分配到的track为1
// 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都有一个生命周期。

  1. 当直接启动或请求transition时,将其添加到“pending”状态。
  2. 一旦 WMCore 应用transition并发出通知,transition就会转至“ready”状态。
  3. 当transition开始动画时,它会转至“active”状态。

基本上:–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();
    }
}
dispatchReady
  • Transition ready派发到shell时,会先根据track id创建或获取对应的track,并将当前transition添加到对应track的mReadyTransitions。
  • 如果mReadyTransitions中已经有transition了,则会直接返回,继续等待。
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;
}
mergeAnimation

获取mReadyTransitions的第一个transition,标记为ready。

  1. 如果没有transition正处于active状态,则将ready标记为active并开始播放;随后再次遍历track中的transition并尝试merge。
  2. 当有transition正处于active状态时,ready的transition将首先发送到active transition的hanlder中,以使其有机会“merge” 传入的transition。
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种预期响应:

  1. 取消当前播放的transition并立即开始传入的transition。这可以在mergeAnimation让当前play的transition自行取消(即立即完成来实现)。然后shell逻辑将立即开始下一个transition(processReadyQueue)。
// 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);
}
  1. 在当前active transition结束播放后播放。只要active transition的handler拒绝/忽略merge请求(是默认处理)。
  2. merge传入的transition。当active transition的handler需要对传入的transition执行一些特殊逻辑时,可merge实现。然后,它为传入的transition调用finish回调(在finish自己的transtion前)以指示它已被merge。
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);
}
Sync

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;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/603675.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

卡牌——蓝桥杯十三届2022国赛大学B组真题

样例输入 4 5 1 2 3 4 5 5 5 5样例输出 3样例说明 这 5 张空白牌中,拿2张写1,拿1张写2,这样每种牌的牌数就变为了3,3,3,4, 可以凑出 3套牌,剩下2张空白牌不能再帮助小明凑出一套。 评测用例规模与约定 对于30%的数据&#xff0c;保证n ⩽ \leqslant ⩽ 2000; 对于100%的数据…

笔记85:如何计算递归算法的“时间复杂度”和空间复杂度?

先上公式&#xff1a; 递归算法的时间复杂度 递归次数 x 每次递归消耗的时间颗粒数递归算法的空间复杂度 递归深度 x 每次递归消耗的内存空间大小 注意&#xff1a; 时间复杂度指的是在执行这一段程序的时候&#xff0c;所花费的全部的时间&#xff0c;即时间的总和而空间复…

ORA-28575: unable to open RPC connection to external procedure agent

环境&#xff1a; Oracle 11.2.0.4x64 RAC AIX6.1版本SDE for aix oracle11g版本10.0 x64 sde配置情况如下&#xff1a; 检查oracle和grid用户下的$ORACLE_HOME/hs/admin/extproc.ora文件均包含有如下&#xff1a; SET EXTPROC_DLLSANY 两个节点sde下的user_libraries都正常…

【STM32+HAL+Proteus】系列学习教程---中断(NVIC、EXTI、按键)

实现目标 1、掌握STM32的中断知识 2、学会STM32CubeMX软件关于中断的配置 3、具体目标&#xff1a;1、外部中断检测按键&#xff0c;每按一次计一次数&#xff0c;满5次LED1状态取反。 一、中断概述 1.1、中断定义 CPU执行程序时&#xff0c;由于发生了某种随机的事件(包括…

巡检机器人有哪些功能和作用?

在科技如此发达的时代&#xff0c;巡检机器人犹如一位不知疲倦的守护者&#xff0c;悄然走进了我们的生活。它们具备着令人惊叹的功能和作用&#xff0c;成为了保障安全、提高效率的重要力量。那么&#xff0c;巡检机器人功能和作用&#xff1f;下面我们来说说旗晟机器人的几款…

faad2交叉编译——aac解码为pcm,解决faad单通道转双通道问题

FAAD是比较成熟高效的开源AAC解码库&#xff0c;这里用于解码AAC生成PCM数据&#xff0c;用于音频播放。这里因为faad库&#xff0c;会将单通道转化为双通道踩了些坑&#xff0c;所以记录一下。 我使用的是2.11.0版本&#xff0c;貌似往前的版本没有使用CMake&#xff0c;需要c…

自动化测试:Selenium入门指南!

Selenium是一个强大的自动化测试工具&#xff0c;特别适用于Web应用测试。本指南将介绍Selenium的安装、常用功能以及一些常见方法&#xff0c;帮助入门并能够更灵活地进行自动化测试。Selenium是一个用于自动化浏览器操作的工具&#xff0c;它广泛应用于Web应用程序的测试和网…

【前缀和】560. 和为 K 的子数组 974. 和可被 K 整除的子数组

题目链接 974. 和可被 K 整除的子数组 560. 和为 K 的子数组 今天刷题的时候&#xff0c;刷了这两题&#xff0c;感觉挺有意思的。代码写起来挺简单的&#xff0c;但是思路和其中的细节以及涉及到的知识点确实让我挺意外的。这里写个博客解析一波&#xff0c;也是巩固一下。 力…

分享《2024年中国企业级SaaS行业研究报告》

&#xff08;文章作者与来源&#xff1a;艾瑞咨询&#xff09; 大浪淘沙&#xff0c;SaaS行业进入关键转折点&#xff0c;企业级SaaS的总体市场规模达到888亿元&#xff0c;同比增长13.0%。内外部因素叠加之下&#xff0c;预计三年未来企业级SaaS市场规模的增速将稳定在15%-20…

请大数据把我推荐给正在申请小程序地理位置接口的人

小程序地理位置接口有什么功能&#xff1f; 若提审后被驳回&#xff0c;理由是“当前提审小程序代码包中地理位置相关接口( chooseAddress、getLocation )暂未开通&#xff0c;建议完成接口开通后或移除接口相关内容后再进行后续版本提审”&#xff0c;那么遇到这种情况&#x…

2024速通python之python基础

文章目录 一、你好&#xff0c;世界二、基本数据类型&#xff08;1&#xff09;数字型&#xff08;2&#xff09;字符串&#xff08;3&#xff09;列表&#xff08;4&#xff09;元组&#xff08;5&#xff09;集合&#xff08;6&#xff09;字典 二、注释&#xff08;1&#x…

【面试干货】http请求报文的组成与作用?

【面试干货】http请求报文的组成与作用&#xff1f; 一、http 的请求报文组成二、请求行&#xff08;Request Line&#xff09;三、请求头部&#xff08;Request Headers&#xff09;四、请求体&#xff08;Request Body&#xff09;五、响应头部 &#xff08;Response Headers…

LeetCode70:爬楼梯

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 解题思想 1.确定dp数组以及下标的含义 dp[i]&#xff1a; 爬到第i层楼梯&#xff0c;有dp[i]种方法 2.确定递推公式 从dp[i]的定义可以…

Ansible任务剧本Playbook之变量、模板、角色介绍

前言 上篇介绍了 Ansible 单模块&#xff08;AD-Hoc&#xff09;的相关内容Ansible自动化运维工具单模块介绍-CSDN博客&#xff0c;Ad-Hoc 命令是一次性的、即时执行的命令&#xff0c;用于在远程主机上执行特定任务&#xff0c;这些命令通常用于快速执行简单的任务。当需要在…

【AI绘画】Midjourney 工笔画 水蓝色衣服的少女

using Midjourney 提示词&#xff1a; highly detailed,细节刻画细腻,超高清晰度,32k,HD,大师作品,高质量,动漫少女,水墨人像,20岁年轻身材很好的中国少女,惊人的美貌,五官精致,精致的妆容,华丽的水蓝色衣服,古风服饰,华丽的珠宝,飞扬的黑色长发,大风吹起头发,宝石发光,黄金装饰…

如何给正弦信号添加12V直流偏置

一个有趣问题的探究&#xff1a; 运放在单电源的情况下只能输出正电压&#xff08;单方向的&#xff09;&#xff0c;这就使得有正负值的信号电压只能输出一半&#xff1a; 【单电源供电的运放如何增加直流偏置】&#xff08;电阻分压法&#xff09;&#xff1a; 单电源供电的…

某云eHR PtFjk.mob 任意文件上传漏洞复现

0x01 产品简介 某云eHR是大中型企业广泛采用人力资源管理系统。某云是国内顶尖的HR软件供应商,是新一代eHR系统的领导者。 0x02 漏洞概述 某云EHR系统PtFjk.mob接口处存在未授权文件上传漏洞,攻击者可上传webshell来命令执行,获取服务器权限。 0x03 复现环境 FOFA:bod…

算法-并查集

目录 什么是并查集 并查集基础 &#xff08;1&#xff09;原理 &#xff08;2&#xff09;初始化 &#xff08;3&#xff09;查询 &#xff08;4&#xff09;合并 &#xff08;5&#xff09;判断是否同一集合 并查集优化 路径压缩 启发式合并 并查集模板 模板 例题…

线下订单平台操作步揍

收款管理 1微信收款查询 1. 获取微信数据 获取微信数据。通过时间范围 查找微信数据调用第三方接口如下&#xff1a; Map map HttpPost.doPost("https://qyapi.weixin.qq.com/cgi-bin/externalpay/get_bill_list?access_token"ApiUtils.getWxtoken(),args); 其中…