Android11 窗口动画

news2024/9/24 21:27:03

窗口进入动画
应用端窗口绘制完成之后,调用finshDraw告知WMS,WMS这边最后就会调用WindowSurfacePlacer的performSurfacePlacement方法,最终调用到 WindowStateAnimator的commitFinishDrawingLocked方法

//frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
	boolean commitFinishDrawingLocked() {
       
       //省略
        mDrawState = READY_TO_SHOW;//READY_TO_SHOW状态
        boolean result = false;
        final ActivityRecord activity = mWin.mActivityRecord;
        if (activity == null || activity.canShowWindows()
                || mWin.mAttrs.type == TYPE_APPLICATION_STARTING) {
            result = mWin.performShowLocked();
        }
        return result;
    }

对于系统窗口,直接调用WindowState的performShowLocked 方法

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
boolean performShowLocked() {
	//省略
	mWinAnimator.applyEnterAnimationLocked();//动画
	mWinAnimator.mDrawState = HAS_DRAWN;//HAS_DRAWN状态
	//省略
}

applyEnterAnimationLocked

//frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
void applyEnterAnimationLocked() {
      	//省略
        final int transit;
        if (mEnterAnimationPending) {
            mEnterAnimationPending = false;
            transit = WindowManagerPolicy.TRANSIT_ENTER;//走这个分支
        } else {
            transit = WindowManagerPolicy.TRANSIT_SHOW;
        }

        // We don't apply animation for application main window here since this window type
        // should be controlled by AppWindowToken in general.
        if (mAttrType != TYPE_BASE_APPLICATION) {
            applyAnimationLocked(transit, true);//动画
        }
	//省略
    }

最后调用applyAnimationLocked去播放动画。

窗口退出动画
应用端remove,导致WMS的removeWindow被调用

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
 void removeWindow(Session session, IWindow client) {
        synchronized (mGlobalLock) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win != null) {
                win.removeIfPossible();
                return;
            }

            // Remove embedded window map if the token belongs to an embedded window
            mEmbeddedWindowController.remove(client);
        }
    }

removeIfPossible

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
    void removeIfPossible() {
        super.removeIfPossible();
        removeIfPossible(false /*keepVisibleDeadWindow*/);
        immediatelyNotifyBlastSync();
    }

private void removeIfPossible(boolean keepVisibleDeadWindow) {
	//省略
if (wasVisible) {
	final int transit = (!startingWindow) ? TRANSIT_EXIT : TRANSIT_PREVIEW_DONE;//transit 为TRANSIT_EXIT 

	// Try starting an animation.
	if (mWinAnimator.applyAnimationLocked(transit, false)) {
		mAnimatingExit = true;
		//省略
	}
}
//省略

和窗口进入动画一样,也是调用WindowStateAnimator的applyAnimationLocked去处理动画相关的工作,只是传入的参数不一致。窗口进入时,transit为TRANSIT_ENTER,退出时,transit为TRANSIT_EXIT

applyAnimationLocked

//frameworks/base/services/core/java/com/android/server/wm/WindowStateAnimator.java
boolean applyAnimationLocked(int transit, boolean isEntrance) {
	//省略
	if (mWin.mToken.okToAnimate()) {
		int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimation(mWin, transit);//对于系统窗口,返回0
		Animation a = null;
		if (anim != DisplayPolicy.ANIMATION_STYLEABLE) {
           //省略    
    	} else {//进这个分支
			switch (transit) {
                    case WindowManagerPolicy.TRANSIT_ENTER:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowEnterAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_EXIT:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowExitAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_SHOW:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowShowAnimation;
                        break;
                    case WindowManagerPolicy.TRANSIT_HIDE:
                        attr = com.android.internal.R.styleable.WindowAnimation_windowHideAnimation;
                        break;
                }
                if (attr >= 0) {
                    a = mWin.getDisplayContent().mAppTransition.loadAnimationAttr(
                            mWin.mAttrs, attr, TRANSIT_NONE);//加载动画
                }
		}
		if (a != null) {
                if (DEBUG_ANIM) logWithStack(TAG, "Loaded animation " + a + " for " + this);
                Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#startAnimation");
                mWin.startAnimation(a);//开始动画
                Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                mAnimationIsEntrance = isEntrance;
		}
		//省略
}

首先根据不同的transit去加载不同的XML文件并创建对应的Animation 对象,然后调用WindowState的startAnimation 开始动画

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
void startAnimation(Animation anim) {
		//省略

        final DisplayInfo displayInfo = getDisplayInfo();
        anim.initialize(mWindowFrames.mFrame.width(), mWindowFrames.mFrame.height(),
                displayInfo.appWidth, displayInfo.appHeight);
        anim.restrictDuration(MAX_ANIMATION_DURATION);
        anim.scaleCurrentDuration(mWmService.getWindowAnimationScaleLocked());
        final AnimationAdapter adapter = new LocalAnimationAdapter(
                new WindowAnimationSpec(anim, mSurfacePosition, false /* canSkipFirstFrame */,
                        0 /* windowCornerRadius */),
                mWmService.mSurfaceAnimationRunner);//创建LocalAnimationAdapter
        startAnimation(getPendingTransaction(), adapter);
        commitPendingTransaction();
    }

首先创建LocalAnimationAdapter对象,LocalAnimationAdapter对象中的mSpec成员是WindowAnimationSpec对象,WindowAnimationSpec对象中的mAnimation成员是上一步创建的Animation 对象。LocalAnimationAdapter对象中的mAnimator指向的是WMS中的mSurfaceAnimationRunner。创建好LocalAnimationAdapter对象后,继续调用startAnimation处理

//frameworks/base/services/core/java/com/android/server/wm/WindowState.java
 private void startAnimation(Transaction t, AnimationAdapter adapter) {
        startAnimation(t, adapter, mWinAnimator.mLastHidden, ANIMATION_TYPE_WINDOW_ANIMATION);
 }

调用父类WindowContainer的startAnimation方法

//frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback) {
            
        // TODO: This should use isVisible() but because isVisible has a really weird meaning at
        // the moment this doesn't work for all animatable window containers.
        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);
    }

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type) {
        startAnimation(t, anim, hidden, type, null /* animationFinishedCallback */);
    }

SurfaceAnimator.startAnimation

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;//mAnimation 就为之前创建的LocalAnimationAdapter对象
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        final SurfaceControl surface = mAnimatable.getSurfaceControl();//mAnimatable为窗口的WindowState
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);//创建leash图层
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
      
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);//继续处理
    }

首先创建leash图层,然后调用LocalAnimationAdapter的startAnimation继续处理,注意传入的mInnerAnimationFinishedCallback参数,后续动画完成后会回调里面的方法。高版本的动画都是基于leash图层来做的

createAnimationLeash

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
static SurfaceControl createAnimationLeash(Animatable animatable, SurfaceControl surface,
            Transaction t, @AnimationType int type, int width, int height, int x, int y,
            boolean hidden, Supplier<Transaction> transactionFactory) {
        if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
        final SurfaceControl.Builder builder = animatable.makeAnimationLeash()
                .setParent(animatable.getAnimationLeashParent())//将leash图层挂载到WindowState父亲的图层下
                .setName(surface + " - animation-leash")
                // TODO(b/151665759) Defer reparent calls
                // We want the leash to be visible immediately because the transaction which shows
                // the leash may be deferred but the reparent will not. This will cause the leashed
                // surface to be invisible until the deferred transaction is applied. If this
                // doesn't work, you will can see the 2/3 button nav bar flicker during seamless
                // rotation.
                .setHidden(hidden)
                .setEffectLayer()
                .setCallsite("SurfaceAnimator.createAnimationLeash");
        final SurfaceControl leash = builder.build();
        t.setWindowCrop(leash, width, height);
        t.setPosition(leash, x, y);
        t.show(leash);
        t.setAlpha(leash, hidden ? 0 : 1);

        t.reparent(surface, leash);//将windowState的图层挂载在leash下
        return leash;
    }

注意这这只是对surface图层的关系进行操作,WMS这边的层级树是没有改变的。此时,图层的关系如下:
在这里插入图片描述
LocalAnimationAdapter.startAnimation

//frameworks/base/services/core/java/com/android/server/wm/LocalAnimationAdapter.java
	@Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));//前面说过,mAnimator为SurfaceAnimationRunner对象,mSpec为WindowAnimationSpec对象
    }

SurfaceAnimationRunner.startAnimation

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
void startAnimation(AnimationSpec a, SurfaceControl animationLeash, Transaction t,
            Runnable finishCallback) {
        synchronized (mLock) {
            final RunningAnimation runningAnim = new RunningAnimation(a, animationLeash,
                    finishCallback);//创建RunningAnimation对象
            mPendingAnimations.put(animationLeash, runningAnim);//放入map中,注意map中也放入了leash图层
            if (!mAnimationStartDeferred) {
                mChoreographer.postFrameCallback(this::startAnimations);//请求vsync
            }
			//省略
        }
    }

又创建了一个RunningAnimation对象,RunningAnimation中的mAnimSpec为WindowAnimationSpec对象,这个WindowAnimationSpec对象中是有之前创建的Animation 对象的。然后将RunningAnimation对象保存到mPendingAnimations Map中。然后请求vsync信号。当vsync信号来了的时候,调用startAnimations方法

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
private void startAnimations(long frameTimeNanos) {
        synchronized (mLock) {
            startPendingAnimationsLocked();
        }
        mPowerManagerInternal.powerHint(PowerHint.INTERACTION, 0);
}

@GuardedBy("mLock")
    private void startPendingAnimationsLocked() {
        for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {//遍历mPendingAnimations
            startAnimationLocked(mPendingAnimations.valueAt(i));
        }
        mPendingAnimations.clear();
    }

startAnimationLocked就是去执行动画的播放了。

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
@GuardedBy("mLock")
    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();//创建ValueAnimator 

        // Animation length is already expected to be scaled.
        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);//播放动画
                }
            }

            // Transaction will be applied in the commit phase.
            scheduleApplyTransaction();//请求下一个vsync信号
        });
	
	//省略

applyTransformation

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java
private void applyTransformation(RunningAnimation a, Transaction t, long currentPlayTime) {
        a.mAnimSpec.apply(t, a.mLeash, currentPlayTime);
    }

WindowAnimationSpec.apply

//frameworks/base/services/core/java/com/android/server/wm/WindowAnimationSpec.java
@Override
    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
        final TmpValues tmp = mThreadLocalTmps.get();
        tmp.transformation.clear();
        mAnimation.getTransformation(currentPlayTime, tmp.transformation);
        tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
        t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
        t.setAlpha(leash, tmp.transformation.getAlpha());

        boolean cropSet = false;
        if (mStackClipMode == STACK_CLIP_NONE) {
            if (tmp.transformation.hasClipRect()) {
                t.setWindowCrop(leash, tmp.transformation.getClipRect());
                cropSet = true;
            }
        } else {
            mTmpRect.set(mStackBounds);
            if (tmp.transformation.hasClipRect()) {
                mTmpRect.intersect(tmp.transformation.getClipRect());
            }
            t.setWindowCrop(leash, mTmpRect);
            cropSet = true;
        }

        // We can only apply rounded corner if a crop is set, as otherwise the value is meaningless,
        // since it doesn't have anything it's relative to.
        if (cropSet && mAnimation.hasRoundedCorners() && mWindowCornerRadius > 0) {
            t.setCornerRadius(leash, mWindowCornerRadius);
        }
    }

可以看出播放动画就是对leash图层进行操作。

以一张图来总结下窗口动画的播放流程
在这里插入图片描述
leash图层的移除
在播放动画时,创建了leash图层,动画结束后需要移除该图层。
在上面SurfaceAnimationRunner的startAnimationLocked方法中,除了有动画更新的回调,当动画结束时也是有回调的

private void startAnimationLocked(RunningAnimation a) {
	//省略
	anim.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationStart(Animator animation) {
                synchronized (mCancelLock) {
                    if (!a.mCancelled) {
                        // TODO: change this back to use show instead of alpha when b/138459974 is
                        // fixed.
                        mFrameTransaction.setAlpha(a.mLeash, 1);
                    }
                }
            }

            @Override
            public void onAnimationEnd(Animator animation) {//动画结束
                synchronized (mLock) {
                    mRunningAnimations.remove(a.mLeash);
                    synchronized (mCancelLock) {
                        if (!a.mCancelled) {

                            // Post on other thread that we can push final state without jank.
                            mAnimationThreadHandler.post(a.mFinishCallback);
                        }
                    }
                }
            }
        });
	//省略
}

动画结束时,post了一个Runnable,了解了前面动画的播放流程的话,就知道这个Runnable是 SurfaceAnimator的 mInnerAnimationFinishedCallback,mInnerAnimationFinishedCallback的定义如下

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
SurfaceAnimator(Animatable animatable,
            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback,
            WindowManagerService service) {
       //省略
        mInnerAnimationFinishedCallback = getFinishedCallback(staticAnimationFinishedCallback);
}

private OnAnimationFinishedCallback getFinishedCallback(
            @Nullable OnAnimationFinishedCallback staticAnimationFinishedCallback) {
        return (type, anim) -> {
            synchronized (mService.mGlobalLock) {
                final SurfaceAnimator target = mService.mAnimationTransferMap.remove(anim);
                if (target != null) {
                    target.mInnerAnimationFinishedCallback.onAnimationFinished(type, anim);
                    return;
                }

                if (anim != mAnimation) {
                    return;
                }
                final Runnable resetAndInvokeFinish = () -> {
                    // We need to check again if the animation has been replaced with a new
                    // animation because the animatable may defer to finish.
                    if (anim != mAnimation) {
                        return;
                    }
                    final OnAnimationFinishedCallback animationFinishCallback =
                            mAnimationFinishedCallback;
                    reset(mAnimatable.getPendingTransaction(), true /* destroyLeash */);//移除leash
                   //省略
                };
                // If both the Animatable and AnimationAdapter requests to be deferred, only the
                // first one will be called.
                if (!(mAnimatable.shouldDeferAnimationFinish(resetAndInvokeFinish)
                        || anim.shouldDeferAnimationFinish(resetAndInvokeFinish))) {
                    resetAndInvokeFinish.run();//执行上面的Runnable 
                }
            }
        };
    }

可以看出,动画结束时,调用reset去移除leash图层

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
private void reset(Transaction t, boolean destroyLeash) {
        mService.mAnimationTransferMap.remove(mAnimation);
        mAnimation = null;
        mAnimationFinishedCallback = null;
        mAnimationType = ANIMATION_TYPE_NONE;
        if (mLeash == null) {
            return;
        }
        SurfaceControl leash = mLeash;
        mLeash = null;
        final boolean scheduleAnim = removeLeash(t, mAnimatable, leash, destroyLeash);
        if (scheduleAnim) {
            mService.scheduleAnimationLocked();
        }
    }

removeLeash

//frameworks/base/services/core/java/com/android/server/wm/SurfaceAnimator.java
static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
            boolean destroy) {
        boolean scheduleAnim = false;
        final SurfaceControl surface = animatable.getSurfaceControl();//得到windowstate的图层
        final SurfaceControl parent = animatable.getParentSurfaceControl();//windowstate对应父节点的图层

        // If the surface was destroyed or the leash is invalid, we don't care to reparent it back.
        // Note that we also set this variable to true even if the parent isn't valid anymore, in
        // order to ensure onAnimationLeashLost still gets called in this case.
        final boolean reparent = surface != null;
        if (reparent) {
            if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to original parent: " + parent);
            // We shouldn't really need these isValid checks but we do
            // b/130364451
            if (surface.isValid() && parent != null && parent.isValid()) {
                t.reparent(surface, parent);//将windowstate的图层重新挂载到父节点的图层下
                scheduleAnim = true;
            }
        }
        if (destroy) {
            t.remove(leash);//移除leash图层
            scheduleAnim = true;
        }

        if (reparent) {
            // Make sure to inform the animatable after the surface was reparented (or reparent
            // wasn't possible, but we still need to invoke the callback)
            animatable.onAnimationLeashLost(t);
            scheduleAnim = true;
        }
        return scheduleAnim;
    }

所以,在动画播放和结束的流程中,图层有以下变化
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于Transformer的端到端的目标检测 | 读论文

本文正在参加 人工智能创作者扶持计划 提及到计算机视觉的目标检测&#xff0c;我们一般会最先想到卷积神经网络&#xff08;CNN&#xff09;&#xff0c;因为这算是目标检测领域的开山之作了&#xff0c;在很长的一段时间里人们都折服于卷积神经网络在图像处理领域的优势&…

Redis 主从复制,集群与高可用

虽然Redis可以实现单机的数据持久化&#xff0c;但无论是RDB也好或者AOF也好&#xff0c;都解决不了单点宕机问题&#xff0c;即一旦单台 redis服务器本身出现系统故障、硬件故障等问题后&#xff0c;就会直接造成数据的丢失 此外,单机的性能也是有极限的,因此需要使用另外的技…

数字安全护航技术能力全景图 | 亚信安全实力占据75领域

近日&#xff0c;2024全球数字经济大会——数字安全生态建设专题论坛在北京成功举办。会上&#xff0c;中国信息通信研究院&#xff08;简称“中国信通院”&#xff09;正式发布了《数字安全护航技术能力全景图》&#xff0c;亚信安全凭借全面的产品技术能力&#xff0c;成功入…

蓝卓创始人褚健:工业软件是数字化转型的灵魂和核心驱动力

如果把“工业3.0”简单理解为就是“自动化”&#xff0c;“工业4.0”理解为是“智能化”&#xff0c;那么“智能化”的实现一定要有软件。如同今天的移动互联网&#xff0c;是因为有大量的APP&#xff0c;所以让人们进入了智能时代。映射到工业、制造业领域&#xff0c;就是要依…

[GICv3] 4. 中断分发和路由(Distribution and Routing)

&#x1f4a1;介绍如何将中断分发和路由到目标PE&#xff0c;以及中断号的分配。 分发和重分发&#xff08;The disributor an Redistributors&#xff09; 分配器为SPI提供路由配置&#xff0c;并持有所有相关的路由和优先级信息。重新分配器提供PPI和SGI的配置设置。 重新分…

京东速运|通过python查询快递单号API

本次讲解如何使用快递聚合供应商来实现查询京东速运快递物流轨迹&#xff0c;首先&#xff0c;我们需要准备的资源。 平台的密钥key&#xff1a;登录后在个人中心查看 测试接口的链接&#xff1a;在下方文档处查看 其中&#xff0c;KEY为用户后台我的api页面展示的API密钥, 代…

《米小圈漫画历史》:历史启蒙,看漫画书就可以啦!

在当今信息爆炸的时代&#xff0c;如何让孩子在娱乐中学习&#xff0c;一直是许多家长关心的问题。《米小圈漫画历史》系列作为一部集合了趣味性和教育性的漫画书&#xff0c;以其独特的视角和精彩的故事情节&#xff0c;成为了许多家庭历史启蒙的首选。本文将通过探索漫画书的…

MT3046 愤怒的象棚

思路&#xff1a; a[]存愤怒值&#xff1b;b[i]存以i结尾的&#xff0c;窗口里的最大值&#xff1b;c[i]存以i结尾的&#xff0c;窗口里面包含✳的最大值。 &#xff08;✳为新大象的位置&#xff09; 例&#xff1a;1 2 3 4 ✳ 5 6 7 8 9 则ans的计算公式b3b4c4c5c6b7b8b9…

探索AI大模型(LLM)减少幻觉的三种策略

大型语言模型&#xff08;LLM&#xff09;在生成文本方面具有令人瞩目的能力&#xff0c;但在面对陌生概念和查询时&#xff0c;它们有时会输出看似合理却实际错误的信息&#xff0c;这种现象被称为“幻觉”。近期的研究发现&#xff0c;通过策略性微调和情境学习、检索增强等方…

Linux基础指令解析+项目部署环境

文章目录 前言基础指令部署项目环境总结 前言 Linux的魅力在于其强大的可定制性和灵活性&#xff0c;这使得它成为了众多开发者和运维人员的首选工具。然而&#xff0c;Linux的指令系统庞大而复杂&#xff0c;初学者往往容易迷失其中。因此&#xff0c;本文将带领大家走进Linu…

一键换衣,这个AI可以让你实现穿衣自由

基于图像的虚拟穿衣是一种流行且前景广阔的图像合成技术&#xff0c;能够显著改善消费者的购物体验&#xff0c;并降低服装商家的广告成本。顾名思义&#xff0c;虚拟穿衣任务旨在生成目标人穿着给定服装的图像。 OOTDiffusion简述 图1 虚拟换衣 基于图像的虚拟穿衣目前面临两…

解决linux服务器下微信公众号授权和业务接口授权失败的问题

我们的公众号web站点代码在Windows服务器IIS下运行没有问题&#xff0c;迁移到linux 服务器的nginx下之后&#xff0c;出现了微信授权和接口授权无法通过引起的问题。如下图所示&#xff1a; 经过排查&#xff0c;发现是因为nginx配置默认对 http 配置节下的 underscores_in_he…

MySQL黑马教学对应视屏笔记分享之聚合函数,以及排序语句的讲解笔记

聚合函数 注意&#xff1a;null值不参与聚合函数的计算。 分组查询 2.where与having的区别 执行时机不同&#xff1a;where是在分组之前进行过滤&#xff0c;不满足where条件&#xff0c;不参与分组&#xff1b;而having是分组之后对结果进行过滤。判断条件不同&#xff1a;w…

3,区块链加密(react+区块链实战)

3&#xff0c;区块链加密&#xff08;react区块链实战&#xff09; 3.1 哈希3.2 pow-pos-dpos3.3非对称加密&#xff08;1&#xff09;对称加密AES&#xff08;2&#xff09;非对称加密RSA 3.4 拜占庭将军3.5 P2P网络3.6 区块链 3.1 哈希 密码学&#xff0c;区块链的技术名词 …

【SQL】MySQL中的字符串处理函数:concat 函数拼接字符串,COALESCE函数处理NULL字符串

MySQL中的字符串处理函数&#xff1a;concat 函数 一、concat &#xff08;&#xff09;函数1.1、基本语法1.2、示例1.3、特殊用途 二、COALESCE&#xff08;&#xff09;函数2.1、基本语法2.2、示例2.3、用途 三、进阶练习3.1 条件和 SQL 语句3.2、解释 一、concat &#xff0…

java中stirng真的不可改变么?

目录 1. 字符数组的私有性和不可变性 2. 没有提供修改内容的方法 3. 共享字符串常量池 4.不可变性的优点 5.结论 &#x1f388;边走、边悟&#x1f388;迟早会好 Java 中的 String 对象是不可变的。不可变性意味着一旦创建了一个 String 对象&#xff0c;它的值就不能再被…

怎么提高音频声音大小?提高音频声音大小的四种方法

怎么提高音频声音大小&#xff1f;在音频处理和编辑中&#xff0c;增加声音的音量是一个常见的需求&#xff0c;尤其是在确保音频清晰度和听觉效果的同时。调整音频的音量不仅仅是简单地提高音频的响度&#xff0c;它也涉及到如何保持音质的高标准&#xff0c;确保没有失真或削…

STM32智能机器人手臂控制系统教程

目录 引言环境准备智能机器人手臂控制系统基础代码实现&#xff1a;实现智能机器人手臂控制系统 4.1 数据采集模块 4.2 数据处理与控制算法 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景&#xff1a;机器人手臂管理与优化问题解决方案与优化收尾与总结 1. 引言 …

计算机组成原理:408考研|王道|学习笔记II

系列目录 计算机组成原理 学习笔记I 计算机组成原理 学习笔记II 目录 系列目录第四章 指令系统4.1 指令系统4.1.1 指令格式4.1.2 扩展操作码指令格式 4.2 指令的寻址方式4.2_1 指令寻址4.2_2 数据寻址 4.3 程序的机器级代码表示4.3.1 高级语言与机器级代码之间的对应4.3.2 常用…

leetcode--从前序与中序遍历序列构造二叉树

leetcode地址&#xff1a;从前序与中序遍历序列构造二叉树 给定两个整数数组 preorder 和 inorder &#xff0c;其中 preorder 是二叉树的先序遍历&#xff0c; inorder 是同一棵树的中序遍历&#xff0c;请构造二叉树并返回其根节点。 示例 1: 输入: preorder [3,9,20,15,…