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@001-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移动至mWaitingTransitionsmWaitingTransitions.add(mCollectingTransition);mCollectingTransition = null;} else if (mSyncEngine.hasActiveSync()) {// 遗留的transition正在进行中,所以我们必须等待。return;}// 移除mQueuedTransitions中第一个transition,赋值为mCollectingTransitionmQueuedTransitions.remove(0);if (queued.mTransition != null) {moveToCollecting(queued.mTransition);} else {// legacy syncmSyncEngine.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赋值给mCollectingTransitionmCollectingTransition = 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 obviouslyif (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 tracktrack = 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 {// 已准备好但仍在等待的transitionsfinal ArrayList<ActiveTransition> mReadyTransitions = new ArrayList<>();// 当前正在palying的transitionActiveTransition 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_MiuiAnimationEffectSlog.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 processReadyQueueonFinish(ready, null, null);return;}// 开始播放playTransition(ready);// 并尝试merge track中其它ready的transitionprocessReadyQueue(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 processReadyQueueonMerged(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 transitionprocessReadyQueue(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可以继续被mergeprocessReadyQueue(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 immediatelyProtoLog.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 syncwhile (!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;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java毕业设计 基于SpringBoot vue社区智慧养老监护管理平台

Java毕业设计 基于SpringBoot vue社区智慧养老监护管理平台 SpringBoot 社区智慧养老监护管理平台 功能介绍 登录注册 个人中心 修改密码 个人信息 房间信息管理 房间入住信息管理 反馈信息管理 留言管理 老人信息管理 公告管理 物资申请管理 管理员管理 护工管理 体检员管理…

C++多态(全)

多态 概念 调用函数的多种形态&#xff0c; 多态构成条件 1&#xff09;父子类完成虚函数的重写&#xff08;三同&#xff1a;函数名&#xff0c;参数&#xff0c;返回值相同&#xff09; 2&#xff09;父类的指针或者引用调用虚函数 虚函数 被virtual修饰的类成员函数 …

React:Router-1.BrowserRouter组件式

使用步骤 安装 react-router-dom 依赖 $ npm install react-router-dom6导入 BrowserRouter, Link, Routes, Route 对象 import {BrowserRouter, Link, Routes, Route} from react-router-dom;3.BrowserRouter&#xff1a;history模式路由&#xff1b; HashRouter&#xff1…

代码随想录算法训练营第二十二天|654.最大二叉树 、617.合并二叉树 、700.二叉搜索树中的搜索 、 98.验证二叉搜索树

654.最大二叉树 文档讲解&#xff1a;代码随想录 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 递归的三要素&#xff1a; 第一要素&#xff1a;明确这个函数想要干什么 传入一个数组&#xff0c;针对这个数组构造一个最大二叉树 第二要素&#xff1a;寻…

跟TED演讲学英文:What moral decisions should driverless cars make by Iyad Rahwan

What moral decisions should driverless cars make? Link: https://www.ted.com/talks/iyad_rahwan_what_moral_decisions_should_driverless_cars_make Speaker: Iyad Rahwan Date: September 2016 文章目录 What moral decisions should driverless cars make?Introduct…

kubeflow简单记录

kubeflow 13.7k star 1、Training Operator 包括PytorchJob和XGboostJob&#xff0c;支持部署pytorch的分布式训练 2、KFServing快捷的部署推理服务 3、Jupyter Notebook 基于Web的交互式工具 4、Katib做超参数优化 5、Pipeline 基于Argo Workflow提供机器学习流程的创建、编排…

漏洞挖掘之某厂商OAuth2.0认证缺陷

0x00 前言 文章中的项目地址统一修改为: a.test.com 保护厂商也保护自己 0x01 OAuth2.0 经常出现的地方 1&#xff1a;网站登录处 2&#xff1a;社交帐号绑定处 0x02 某厂商绑定微博请求包 0x02.1 请求包1&#xff1a; Request: GET https://www.a.test.com/users/auth/weibo?…

mysql从入门到起飞+面试基础题

mysql基础 MySQL基础 企业面试题1 代码 select m.id,m.num from ( select t.id as id,count(1) num from ( select ra.requester_id as id from RequestAccepted raunion all select ra.accepter_id as id from RequestAccepted ra ) t group by t.id ) m group by id ord…

生产制造中刀具管理系统,帮助工厂不再频繁换刀

一、刀具管理的定义与重要性 刀具管理是指对生产过程中使用的各种刀具进行计划、采购、存储、分配、使用、监控、维修和报废等全过程的管理。刀具作为制造过程中的直接工具&#xff0c;其性能、质量和使用效率直接影响产品的加工精度、表面质量和生产效率。因此&#xff0c;建…

ICode国际青少年编程竞赛- Python-1级训练场-基础训练2

ICode国际青少年编程竞赛- Python-1级训练场-基础训练2 1、 a 4 # 变量a存储的数字是4 Dev.step(a) # 因为变量a的值是4&#xff0c;所以Dev.step(a)就相当于Dev.step(4)2、 a 1 # 变量a的值为1 for i in range(4):Dev.step(a)Dev.turnLeft()a a 1 # 变量a的值变为…

ios苹果App上架到应用商店的操作流程

哈喽&#xff0c;大家好呀&#xff0c;淼淼又来和大家见面啦&#xff0c;发现最近有许多想要上架App的小伙伴&#xff0c;但是又不知道要怎么操作&#xff0c;对于开发者而言&#xff0c;将精心打造的iOS应用程序成功上架到苹果的 App Store 是向全球用户展示咱们的产品和服务的…

Windows+Linux的虚拟串口工具

文章目录 1.Windows虚拟串口工具1.1 安装教程1.2 使用方法 2.Linux系统虚拟串口工具2.1 socat安装2.2 开启虚拟串口2.3 测试2.3.1 命令测试2.3.2 Cutecom工具测试 2.4 关闭虚拟串口 3.参考资料 1.Windows虚拟串口工具 下载地址&#xff1a;https://www.downxia.com/downinfo/4…

9、String类型和基本数据类型转换(Java)

String类型和基本数据类型转换 1、基本数据类型转String类型2、String类型转基本数据类型⭐ 1、基本数据类型转String类型 Java中String类型是字符串类型&#xff0c;是用 “ ” 双引号括起来的内容&#xff0c;所以基本数据类型转String类型直接&#xff0b;“ ”即可。&…

三年软件测试经验遭遇求职困境?揭秘求职市场的隐藏陷阱

1.个人背景 小李&#xff0c;我的一位朋友&#xff0c;拥有三年多的软件测试工作经验。他本科毕业后便投身于测试行业&#xff0c;熟练掌握Python编程&#xff0c;能够编写自动化测试脚本&#xff0c;并且熟悉Selenium和性能测试。然而&#xff0c;尽管他具备这些技能和经验&am…

企业做网站,如何设计才有创意?

企业做网站&#xff0c;如何设计才有创意&#xff1f;我们都希望能打造一个有创意的网站建设&#xff0c;能在众多网站中脱颖而出&#xff0c;能够营销推广公司的产品&#xff0c;为公司带来更多的经济效益收益。广州网站建设的时候&#xff0c;记住直观的设计可以让用户体验更…

《尿不湿级》STM32 F103C8T6最小系统板搭建(五)BOOT

一、BOOT是什么&#xff1f; 大多数初学者第一次接触BOOT总是对这个词感到不解&#xff0c;从哪冒出一个奇奇怪怪的东西还要接跳线帽&#xff0c;为什么要配置它才能进行串口程序的下载&#xff1f;为什么不正确配置会导致单片机无法正常启动…… boot&#xff0c;及物动词&…

配置 Trunk,实现相同VLAN的跨交换机通信

1.实验环境 公司的员工人数已达到 100 人&#xff0c;其网络设备如图所示。现在的网络环境导致广播较多网速慢&#xff0c;并且也不安全。公司希望按照部门划分网络&#xff0c;并且能够保证一定的网络安全性。 其网络规划如下。 PC1和 PC3为财务部&#xff0c;属于VLAN 2&…

【NodeMCU实时天气时钟温湿度项目 5】获取关于城市天气实况和天气预报的JSON信息(心知天气版)

| 今天是第五专题内容&#xff0c;主要是介绍如何从心知天气官网&#xff0c;获取包含当前天气实况和未来 3 天天气预报的JSON数据信息。 在学习获取及显示天气信息前&#xff0c;我们务必要对JSON数据格式有个深入的了解。 如您需要了解其它专题的内容&#xf…

手撕多线程

用一个双线程轮流打印1-100 // 定义一个类&#xff0c;用于交替打印奇偶数 public class AlternatePrinting {// 当前待打印的数字&#xff0c;初始为1private int currentNumber 1;// 用作线程间同步的锁对象private final Object lock new Object();// 程序入口public sta…

【如此简单!数据库入门系列】之无序不代表混乱 -- 堆文件

文章目录 前言堆文件链表实现页目录实现总结系列文章 前言 还记得上次遗留的问题吗&#xff1f; 以什么组织方式将数据保存在磁盘中&#xff1f; 今天我们接着讨论这个问题。 首先想一个问题&#xff1a;有一天&#xff0c;你开着自己心爱的大型SUV去超市购物。在停车场入口看…