【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
– 服装学院的IT男
本篇已收录于Activity短暂的一生系列
欢迎一起学习讨论Android应用开发或者WMS
V:WJB6995
Q:707409815
正文
生命周期系列:
-
Activity生命周期之onPause
-
onCreate,onStart,onResume-1
-
onCreate,onStart,onResume-2
-
Activity生命周期之onStop-1
-
Activity生命周期之onStop-2
-
Activity生命周期之onDestory
在整个启动流程中,上一篇介绍了第一阶段:SourceActivity 的 onPause
现在分析第二阶段:TargetActivity 启动后会依次执行 onCreate,onStart,onResume。
另外还会完整的介绍一下 Activity 生命周期的事务是如何触发的。
1. 启动流程介绍
我们知道 onCreate 这个生命周期表示 Activity 的创建,对应 LaunchActivityItem 这个事务,源码中构建这个事务唯一的地方就在 ActivityTaskSupervisor::realStartActivityLocked 方法。
而 ActivityTaskSupervisor::realStartActivityLocked 方法的执行逻辑,在[Activity启动流程系列]说过要启动 TargetActivity 有2个必要条件:
-
- TargetActivity 所在的应用进程已经启动
-
- SourceActivity 需要执行 onPause
不考虑异常情况,能执行 onCreate 的场景就是冷热启动了,冷热启动区别也就在于多了个需要创建应用进程,不过最终都会执行到 ActivityTaskSupervisor::realStartActivityLocked 方法。
无论是启动进程后执行过来的,还是上篇提到的 SourceActivity 执行 activityPaused 流程触发过来的结果都是一样的,不必太在意
开始撸代码
# ActivityTaskSupervisor boolean realStartActivityLocked(ActivityRecord r, WindowProcessController proc,boolean andResume, boolean checkConfig) throws RemoteException {// 1. 判断是否执行完了pause 。 也就是2个条件之一,必须要执行完pause才可以进入后面if (!mRootWindowContainer.allPausedActivitiesComplete()) {// While there are activities pausing we skipping starting any new activities until// pauses are complete. NOTE: that we also do this for activities that are starting in// the paused state because they will first be resumed then paused on the client side.// 不满足添加就打logProtoLog.v(WM_DEBUG_STATES,"realStartActivityLocked: Skipping start of r=%s some activities pausing...",r);return false;}......// 2. wm_restart_activity EventLogTags.writeWmRestartActivity(r.mUserId, System.identityHashCode(r),task.mTaskId, r.shortComponentName);// 3.重点* 创建Activity启动事务.将构建的 LaunchActivityItem 添加到 clientTransaction 中final ClientTransaction clientTransaction = ClientTransaction.obtain(proc.getThread(), r.token);......clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),System.identityHashCode(r), r.info,// TODO: Have this take the merged configuration instead of separate global// and override configs.mergedConfiguration.getGlobalConfiguration(),mergedConfiguration.getOverrideConfiguration(), r.compat,r.getFilteredReferrer(r.launchedFromPackage), task.voiceInteractor,proc.getReportedProcState(), r.getSavedState(), r.getPersistentSavedState(),results, newIntents, r.takeOptions(), isTransitionForward,proc.createProfilerInfoIfNeeded(), r.assistToken, activityClientController,r.shareableActivityToken, r.getLaunchedFromBubble(), fragmentToken));final ActivityLifecycleItem lifecycleItem;if (andResume) {// 4.重点*. 设置预期的最终状态Resume逻辑,启动走的这。 表示需要执行到onCreatelifecycleItem = ResumeActivityItem.obtain(isTransitionForward);} else {// Pause 逻辑lifecycleItem = PauseActivityItem.obtain();}clientTransaction.setLifecycleStateRequest(lifecycleItem);// 5.重点* 调度事务,将clientTransaction添加到生命周期管理器中mService.getLifecycleManager().scheduleTransaction(clientTransaction);}
-
- 如果有Activity没有执行完 pause ,则不能执行新的Activity的启动。 如果不满足条件也会相应的ProtoLog
-
- 打印Events日志,格式为:wm_restart_activity: [0,253598020,21,com.google.android.dialer/.extensions.GoogleDialtactsActivity]
-
- 构建 LaunchActivityItem 事务,将会触发创建Activity,并执行到 onCreate
-
- 构建 ResumeActivityItem 事务,将会触发执行到Activity的 onResume
-
- 执行事务
后面的流程其实可以直接去看应用端这2个事务的执行,不过我对这中间的调用比较好奇,所以把生命周期跨进程调用事务的逻辑也梳理了一遍。对这块比较了解的可以跳过,直接看第三章生命周期相关的内容
2. Activity 生命周期事务跨进程处理方式详解
2.1 了解ClientTransaction
在 ActivityTaskSupervisor::realStartActivityLocked 方法中对跨进程执行生命周期事务的代码可以归纳为以下几步:
-
- 构建出 ClientTransaction
-
- 构建 onCreate 和 onResume对应的2个事务,并保存到 ClientTransaction 中
-
- 通过 ClientLifecycleManager::scheduleTransaction 方法来触发事务的调度执行,唯一参数就是 ClientTransaction
这一小节先来了解一下这个 ClientTransaction 到底是什么
# ClientTransaction// 实现Parcelable接口,说明可序列化public class ClientTransaction implements Parcelable, ObjectPoolItem {// 几个重要的成员变量private List<ClientTransactionItem> mActivityCallbacks;private ActivityLifecycleItem mLifecycleStateRequest;// 目标进程private IApplicationThread mClient;// 目标Activityprivate IBinder mActivityToken;// client :对应进程// activityToken :创建ActivityRecord时的匿名tokenpublic static ClientTransaction obtain(IApplicationThread client, IBinder activityToken) {ClientTransaction instance = ObjectPool.obtain(ClientTransaction.class);if (instance == null) {instance = new ClientTransaction();}instance.mClient = client;instance.mActivityToken = activityToken;return instance;}// 拿到对应public IBinder getActivityToken() {return mActivityToken;}public void addCallback(ClientTransactionItem activityCallback) {if (mActivityCallbacks == null) {mActivityCallbacks = new ArrayList<>();}// 事务添加到 mActivityCallbacks 列表中mActivityCallbacks.add(activityCallback);}// 事务赋值给 mLifecycleStateRequest 变量public void setLifecycleStateRequest(ActivityLifecycleItem stateRequest) {mLifecycleStateRequest = stateRequest;}// 目标进程执行事务public void schedule() throws RemoteException {mClient.scheduleTransaction(this);}}
ClientTransaction 的核心方法就这些,结合 ActivityTaskSupervisor::realStartActivityLocked 方法的使用得出以下结论:
-
- ClientTransaction::obtain 构建出一个 ClientTransaction 对象,2个参数分别为目标进程,和目标Activity
-
- addCallback 方法会将 LaunchActivityItem 放到 内部的 mActivityCallbacks 列表中
-
- setLifecycleStateRequest 方法将 ResumeActivityItem 事务赋值给了 mLifecycleStateRequest 变量。
这里还有个重要的方法 schedule() ,最终会触发目标进程执行当前这个 ClientTransaction 。
2.2 事务的调度执行
上一小节知道生命周期的事务都被包装在了一个 ClientTransaction 对象中,现在来看看 ClientLifecycleManager::scheduleTransaction 方法是如何触发对应事务执行的。
该方法有多个重载,但是本质都一样
# ClientLifecycleManagervoid scheduleTransaction(ClientTransaction transaction) throws RemoteException {final IApplicationThread client = transaction.getClient();// 重点* 调用ClientTransaction::scheduletransaction.schedule();......}void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,@NonNull ActivityLifecycleItem stateRequest) throws RemoteException {// 构建 一个 ClientTransaction,调用上面的scheduleTransactionfinal ClientTransaction clientTransaction = transactionWithState(client, activityToken,stateRequest);scheduleTransaction(clientTransaction);}......忽略另外几个重载, 都是内部构建出一个 ClientTransaction
主要就是执行了 ClientTransaction::schedule 方法。
2.3 应用进程处理
ClientTransaction::schedule 方法会跨进程调用到目标进程的 scheduleTransaction 方法,我们知道进程指的就是 ActivityThread 内部类 ApplicationThread
# ActivityThread$ApplicationThread@Overridepublic void scheduleTransaction(ClientTransaction transaction) throws RemoteException {// 这里调用的是ActivityThread的父类 ClientTransactionHandlerActivityThread.this.scheduleTransaction(transaction);}# ClientTransactionHandlervoid scheduleTransaction(ClientTransaction transaction) {// 执行 preExecute (预执行)transaction.preExecute(this);// 重点* 发送消息sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);}
先执行 preExecute 方法,最重要的是执行发送了一个 EXECUTE_TRANSACTION 消息,并把 ClientTransaction 传过去了。
这个消息的处理在 ActivityThread 中。
# ActivityThreadprivate final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);class H extends Handler {public void handleMessage(Message msg) {if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));switch (msg.what) {......case EXECUTE_TRANSACTION:final ClientTransaction transaction = (ClientTransaction) msg.obj;// 处理mTransactionExecutor.execute(transaction);......break;......}}}
ClientTransaction 又被传递到 TransactionExecutor 中处理了。
# TransactionExecutorpublic void execute(ClientTransaction transaction) {......// LaunchActivityItemexecuteCallbacks(transaction);// ResumeActivityItemexecuteLifecycleState(transaction);......}
2.3.1 executeCallbacks 和 executeLifecycleState 方法
为什么我在上面注释里说 executeCallbacks 是处理 LaunchActivityItem,executeLifecycleState 是处理 ResumeActivityItem 呢?
代码中有答案:
# TransactionExecutorpublic void executeCallbacks(ClientTransaction transaction) {// 获取 callback 的元素,没有就返回final List<ClientTransactionItem> callbacks = transaction.getCallbacks();if (callbacks == null || callbacks.isEmpty()) {// No callbacks to execute, return early.return;}......// 当前场景是触发 LaunchActivityItem的execute方法item.execute(mTransactionHandler, token, mPendingActions);item.postExecute(mTransactionHandler, token, mPendingActions);......}private void executeLifecycleState(ClientTransaction transaction) {// 获取tLifecycleStateRequest,没有就返回final ActivityLifecycleItem lifecycleItem = transaction.getLifecycleStateRequest();if (lifecycleItem == null) {// No lifecycle request, return early.return;}......// 重点* 将Activity的状态转换至接近最终目标状态的位置 也就是会触发onStartcycleToPath(r, lifecycleItem.getTargetState(), true /* excludeLastState */, transaction);// Execute the final transition with proper parameters.// 当前场景是触发LaunchActivityItem的executelifecycleItem.execute(mTransactionHandler, token, mPendingActions);lifecycleItem.postExecute(mTransactionHandler, token, mPendingActions);}
而之前SystemService对这2个事务的处理如下:
ActivityTaskSupervisor::realStartActivityLockedLaunchActivityItem.obtain -- 添加到事务到 mActivityCallbacks 列表ResumeActivityItem.obtain -- 设置事务到 mLifecycleStateRequestClientLifecycleManager::scheduleTransaction -- 触发事务的执行
所以说:executeCallbacks 方法是处理 LaunchActivityItem,executeLifecycleState 是处理 ResumeActivityItem。
2.4 小结
经过上面这些分析,我们知道在 ActivityTaskSupervisor::realStartActivityLocked 方法中启动 Activity 就是构建了2个事务到 ClientTransaction 中,并跨进程到目标进程执行。
2个事务从SystemService进程到目标应用进程完整的调用链如下:
ActivityTaskSupervisor::realStartActivityLockedClientTransaction::obtain -- 构建客户端事务LaunchActivityItem::obtain -- 构建 onCreate 启动事务ClientTransaction::addCallback -- onCreate事务添加进回调ResumeActivityItem::obtain -- 构建 onResume 事务ClientTransaction::setLifecycleStateRequest -- onResume 事务添加进LifecycleStateRequestClientLifecycleManager::scheduleTransaction -- 执行事务ClientTransaction::schedule -- 触发目标进程执行ActivityThread$ApplicationThread::scheduleTransaction -- 进入应用进程ClientTransactionHandler::scheduleTransactionClientTransaction::preExecuteActivityThread::sendMessage -- 发送“EXECUTE_TRANSACTION”消息ActivityThread$H::handleMessage TransactionExecutor::executeTransactionExecutor::executeCallbacks -- LaunchActivityItem 事务LaunchActivityItem::execute -- 触发 onCreateLaunchActivityItem::postExecuteTransactionExecutor::executeLifecycleState -- ResumeActivityItem 事务TransactionExecutor::cycleToPath -- 触发 onStartResumeActivityItem::execute -- 触发 onResumeResumeActivityItem::postExecute
时序图如下:
可以看到从 生命周期事务的执行到真正触发其 execute 方法,中间的调用链还是很长的,不过这个流程也不算很重要,一般看到 ResumeActivityItem 这种事务构建了,直接去看对应的 execute,postExecute 等方法就好了。
从调用链上也看到了 3个生命周期的执行顺序和期望的log打印也是一样的,当然具体是怎么执行到的要看后续代码分析。
3. 总结
这一篇对介绍了 Activity 启动流程中 SystemService 端末尾的工作,
另外介绍了一下 Activity 生命周期事务跨进程处理方式,关于onCreate,onStart,onResume 这3个生命周期在SystemService 端的流程介绍完了,下一篇是重点,会详细介绍在应用端对这3个生命周期的处理流程。
相关文章:
【Android 13源码分析】Activity生命周期之onCreate,onStart,onResume-1
忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。 – 服装…...
达梦数据库开启归档模式
目录 一、什么是归档模式? 二、开启归档模式的步骤 1、创建归档目录 2、进入dm数据库bin目录 3、登录数据库 4、关闭数据库 5、启动数据库到Mount状态 6、增加本地归档日志文件 7、开启归档 8、启动数据库 9、验证是否开启成功 三、开启归档模式的优…...
C++ 语言特性07 - 静态成员的初始化
一:概述 1. 静态成员变量通常在类定义内部声明,并在类定义外部定义和初始化。 class MyClass { public:static int staticVar; // 声明 };int MyClass::staticVar 42; // 定义和初始化 2. 从C11开始,可以在类内直接初始化静态数据成员&am…...
【数据结构】图论基础
文章目录 图的概念图的基本概念图的类型图的表示方法 图的相关基本概念1. 路径(Path)2. 连通性(Connectivity)3. 图的度(Degree)4. 子图(Subgraph)5. 生成树(Spanning Tr…...
HTML5实现好看的唐朝服饰网站模板源码2
文章目录 1.设计来源1.1 网站首页1.2 唐装演变1.3 唐装配色1.4 唐装花纹1.5 唐装文化 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板,程序开发,在线开发,在线沟通 作者:xcLeigh 文章地址:https://blog.csdn.ne…...
golang web笔记-2.请求request
什么是request http消息分为request(请求) 和 response(响应) request:在go中是一个struct,代表了客户段发送的http请求,已可以通过request 的方法访问请求中的cookie、URL、User Agent…...
docker的安装与启动——配置国内Docker源
移除旧版本docker sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine 配置docker yum源。 sudo yum install -y yum-utils sudo yum-config-manager –add-repo ht…...
httpsok-v1.17.0-SSL通配符证书自动续签
🔥httpsok-v1.17.0-SSL通配符证书自动续签 介绍 httpsok 是一个便捷的 HTTPS 证书自动续签工具,基于全新的设计理念,专为 Nginx 、OpenResty 服务器设计。已服务众多中小企业,稳定、安全、可靠。 一行命令,一分钟轻…...
相机、镜头参数详解以及相关计算公式
一、工业相机参数 1、分辨率 相机每次采集图像的像素点数,也是指这个相机总共有多少个感光晶片。在采集图像时,相机的分辨率对检测精度有很大的影响,在对同样打的视场成像时,分辨率越高,对细节的展示越明显。 相机像素…...
【微服务】组件、基础工程构建(day2)
组件 服务注册和发现 微服务模块中,一般是以集群的方式进行部署的,如果我们调用的时候以硬编码的方式,那么当服务出现问题、服务扩缩容等就需要对代码进行修改,这是非常不好的。所以微服务模块中就出现了服务注册和发现组件&…...
ESP32微信小程序SmartConfig配网
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ESP32&微信小程序SmartConfig配网 前言一、SmartConfig是什么?二、使用乐鑫官方的smart_config例子1.运行照片 三、微信小程序总结 前言 本人是酷爱ESP32S3这…...
【PostgreSQL】提高篇——深入了解不同类型的 JOIN(INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL JOIN)应用操作
1. JOIN 的基础概念 在 SQL 中,JOIN 是用于从两个或多个表中组合行的操作。JOIN 允许我们根据某些条件将表中的数据关联在一起。常见的 JOIN 类型包括: INNER JOIN:仅返回两个表中满足连接条件的行。LEFT JOIN(或 LEFT OUTER JO…...
师生健康信息管理:SpringBoot技术突破
第4章 系统设计 4.1 系统体系结构 师生健康信息管理系统的结构图4-1所示: 图4-1 系统结构 登录系统结构图,如图4-2所示: 图4-2 登录结构图 师生健康信息管理系统结构图,如图4-3所示。 图4-3 师生健康信息管理系统结构图 4.2…...
【完-网络安全】Windows注册表
文章目录 注册表启动项及常见作用五个根节点常见入侵方式 注册表 注册表在windows系统的配置和控制方面扮演了一个非常关键的角色,它既是系统全局设置的存储仓库,也是每个用户的设置信息的存储仓库。 启动项及常见作用 快捷键 WinR打开运行窗口&#x…...
车辆重识别(2021NIPS在图像合成方面,扩散模型打败了gans网络)论文阅读2024/10/01
本文在架构方面的创新: ①增加注意头数量: 使用32⇥32、16⇥16和8⇥8分辨率的注意力,而不是只使用16⇥16 ②使用BigGAN残差块 使用Big GAN残差块对激活进行上采样和下采样 ③自适应组归一化层 将经过组归一化操作后的时间步和类嵌入到每…...
掌控物体运动艺术:图扑 Easing 函数实践应用
现如今,前端开发除了构建功能性的网站和应用程序外,还需要创建具有吸引力且尤为流畅交互的用户界面,其中动画技术在其中发挥着至关重要的作用。在数字孪生领域,动画的应用显得尤为重要。数字孪生技术通过精确模拟现实世界中的对象…...
Python从入门到高手4.2节-掌握循环控制语句
目录 4.2.1 理解循环控制 4.2.2 for循环结构 4.2.3 循环结构的else语句 4.2.4 while循环结构 4.2.5 循环结构可以嵌套 4.2.6 国庆节吃好玩好 4.2.1 理解循环控制 我们先来搞清楚循环的含义。以下内容引自汉语词典: 循环意指往复回旋,指事物周而复始地运动或变…...
CSS 中的overscroll-behavior属性
overscroll-behavior 是 CSS 中的一个属性,它用于控制元素在发生滚动时,当滚动范围超出其边界时的行为。这个属性对于改善用户体验特别有用,尤其是在移动端设备上,当用户尝试滚动一个已经达到滚动极限的元素时,可以通过…...
GPT对话知识库——在STM32的平台下,通过SPI读取和写入Flash的步骤。
目录 1,问: 1,答: 步骤概述 步骤 1:SPI 初始化 步骤 2:Flash 初始化(可选) 步骤 3:发送读取命令 示例:发送读取数据命令 步骤 4:读取数据…...
Pytorch基本知识
model.state_dict()、model.parameters()和model.named_parameters()的区别 parameters()只包含模块的参数,即weight和bias(包括BN的)。 named_parameters()返回包含模块名和模块的参数的列表,列表的每个元素均是包含layer name和layer param的元组。layer param就是param…...
vue3使用Teleport 控制台报警告:Invalid Teleport target on mount: null (object)
Failed to locate Teleport target with selector “.demon”. Note the target element must exist before the component is mounted - i.e. the target cannot be rendered by the component itself, and ideally should be outside of the entire Vue component tree main.…...
使用产品前的环境搭建
对于想学习编程的朋友们,使用本产品解决日常功能需求的同时会对自己编程能力具有较大帮助和提升。 目录 环境搭建 前言: 安装python 安装vscode 下载安装Anaconda 通过conda配置python环境 创建虚拟环境 查看环境是否创建成功 激活环境 安装pyt…...
JAVA基础语法 day07
一、final关键字 1.1final的基础知识 用来修饰类,方法,变量 final修饰类,该类被称为终极类,不能被继承了 final修饰方法,该方法称为终极方法,不能被重写了 final修饰变量,该变量仅能被赋值…...
ZLMediaKit编译运行
ZLMediaKit-github官网 快速开始 代码依赖与版权声明 MediaServer支持的HTTP MediaServer支持的HTTP HOOK API cd ZLMediaKit mkdir build cd build cmake … && make -j20 cd ZLMediaKit/release/linux/Debug ./MediaServer //./MediaServer -h 查看 //./MediaSe…...
AlmaLinux 9 安装mysql8.0.38
文件下载 https://cdn.mysql.com//Downloads/MySQL-8.0/mysql-8.0.39-linux-glibc2.12-x86_64.tar 选择合适系统版本 下载后解压 tar -xvf mysql-8.0.39-linux-glibc2.12-x86_64.tar解压后里面有三个文件夹 使用mysql-8.0.39-linux-glibc2.12-x86_64.tar.xz即可,…...
NLP任务之文本分类(情感分析)
目录 1 加载预训练模型对应的分词器 2 加载数据集 3 数据预处理 4 构建数据加载器DataLoader 5 定义下游任务模型 6 测试代码 7 训练代码 #做(中文与英文的)分类任务,Bert模型比较合适,用cls向下游任务传输数…...
MIMO 2T4R BBU RHUB AAU
MIMO(Multiple-Input Multiple-Output,多输入多输出)是一种无线通信技术,它通过在发射端和接收端使用多个天线来提高数据传输速率和信号质量。"2T4R"是MIMO技术中的一种配置,其中"2T"代表有两个发…...
图说数集相等定义表明“R各元x的对应x+0.0001的全体=R“是几百年重大错误
黄小宁 设集A{x}表A各元均由x代表,{x}中变量x的变域是A。其余类推。因各数x可是数轴上点的坐标故x∈R变为实数yx1的几何意义可是:一维空间“管道”g内R轴上的质点x∈R(x是点的坐标)沿“管道”g平移变为点y…...
只出现一次的数字|||(考察点为位操作符)
目录 一题目: 二思路汇总: 三代码解答: 一题目: leetcode原题链接:. - 力扣(LeetCode) 二思路汇总: 思路:如果直接对数组按位异或,那么最后得到的是a^b&a…...
PMP--三模--解题--81-90
文章目录 13.干系人管理--权力利益方格--基于干系人的职权级别(权力)、对项目成果的关心程度(利益)、对项目成果的影响能力(影响),或改变项目计划或执行的能力,每一种方格都可用于对…...
哪个网站帮别人做ppt/苏州搜索引擎排名优化商家
作者 | 林逸来源 | 创业邦(ID:ichuangyebang)8月25日下午,蚂蚁科技集团科创板上市申请,获上交所受理,并同步向香港联交所递交上市申请,AH上市的进程来到了标志性的节点。回顾一个月前࿰…...
网站建设博敏/建立网站怎么搞
这样做的目的是一次遍历两个列表,一个是右列表,另一个是左列表。然后测量两点之间的角度,并将其与之前计算的角度进行比较。如果在某个时候计算的角度变大(漏斗变宽),我想用I0和新的右和左列表重新开始迭代过程。目前我没有任何输出。我认为问题出在calc()语句上,我希望def cal…...
重庆做企业网站/杭州网络排名优化
有时候我们在桌面上新建一个文件夹,大家都知道新建文件夹的位置是系统根据桌面上最后一个默认排序的,想把这个文件夹放在自己容易辨识的地方,可能是右下角,可能是右上角,那这个拖拽是怎么实现的呢? 一、我们…...
wordpress 文章点击/郑州发布最新通告
也许是我一直在初创公司度过的时光,但是尽管我非常珍视Scrum的想法,并支持自组织团队和不断的反馈–我不禁感到看板代表了敏捷的新高度,为我们提供了更大的灵活性并从中吸取了教训我们从精益中学到了东西。 Scrum 很多人倾向于认为敏捷意味着…...
动画设计实训报告/seo刷关键词排名优化
定义和用法 <audio> 标签定义声音,比如音乐或其他音频流。 实例 一段简单的 HTML 5 音频: <audio src"someaudio.wav"> 您的浏览器不支持 audio 标签。 </audio> 提示和注释 提示:可以在开始标签和结束标签之间放…...
网站制作图片插入代码/2023第二波疫情已经到来了吗
package com.cds.test;import java.util.Arrays;import org.junit.Test;public class RegexLearner {Test/*** 需求:我有如下一个字符串:”91 27 46 38 50”,请写代码实现最终输出结果是:”27 38 46 50 91”* 100* 80* 分析:* 1,将字符串切割…...