Android学习总结之应用启动流程(从点击图标到界面显示)
一、用户交互触发:Launcher 到 AMS 的跨进程通信
1. Launcher 处理点击事件(应用层)
当用户点击手机桌面上的应用图标时,Launcher(桌面应用)首先捕获点击事件。每个图标对应一个启动 Intent(通常是ACTION_MAIN + CATEGORY_LAUNCHER),Launcher 通过这个 Intent 告诉系统 “我要启动某个应用的主 Activity”。
// Launcher.java
public void onClick(View v) {Intent intent = getIntentForShortcut(v); // 获取启动 IntentstartActivitySafely(intent, null);
}private void startActivitySafely(Intent intent, Bundle options) {mContext.startActivity(intent, options); // 调用 Context.startActivity()
}
2. Context.startActivity () 转向 AMS(Framework 层)
接下来,Launcher 调用startActivity(),这个看似简单的方法会逐层向上传递,最终到达 Android 系统的 “管家”——ActivityManagerService(AMS)。AMS 是运行在系统进程中的核心服务,负责管理所有应用的 Activity 和进程。
// ContextImpl.java
public void startActivity(Intent intent, Bundle options) {// 包装 Intent 为显式 Intent(确保目标组件明确)intent = Intent.parseIntentOrNull(this, intent, 0); mMainThread.getInstrumentation().execStartActivity(this, mMainThread.getApplicationThread(), null,(Activity) null, intent, -1, options);
}// Instrumentation.java
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token,Activity target, Intent intent, int requestCode, Bundle options) {// 通过 AMS 的代理对象发起启动请求return ActivityTaskManager.getService().startActivity(whoThread, who.getBasePackageName(), intent,... // 省略参数);
}
二、AMS 核心逻辑:任务调度与进程管理
1. AMS.startActivity () 入口(SystemServer 进程)
AMS 作为系统服务,运行在 SystemServer 进程中,通过 Binder 接收启动请求后,首先进行权限校验和组件解析:
// ActivityTaskManagerService.java(继承 AMS)
@Override
public int startActivity(IApplicationThread caller, ...) {// 检查调用者权限(如声明的 launcher 权限)enforceNotIsolatedCaller("startActivity"); // 解析 Intent 对应的 Activity 组件(可能涉及隐式 Intent 匹配)ActivityInfo aInfo = resolveActivity(intent, ...); // 启动核心流程:创建任务栈(Task)或复用已有任务return startActivityAsUser(intent, aInfo, ..., UserHandle.getCallingUserId());
}private int startActivityAsUser(Intent intent, ActivityInfo aInfo, ...) {// 构建启动参数包 ActivityStartDataActivityStartData startData = new ActivityStartData();// 核心调度逻辑:处理 Activity 启动的状态机return mActivityStarter.startActivityMayWait(caller, -1, startData, ...);
}
2. 任务栈与 Activity 状态机(ActivityStackSupervisor)
AMS 通过 ActivityStackSupervisor 管理任务栈(TaskStack),决定是否新建任务或复用现有任务。若目标 Activity 所在进程未启动,触发 进程创建流程:
// ActivityStarter.java
private int startActivityMayWait(...) {// 检查目标进程是否存在ProcessRecord targetProc = mService.getProcessRecordLocked(targetProcessName, ...);if (targetProc == null || targetProc.thread == null) {// 进程未启动,调用 Zygote 孵化新进程targetProc = startProcessLocked(targetProcessName, activityInfo.applicationInfo, ...);} else {// 进程已存在,直接通知其启动 ActivityscheduleLaunchActivityLocked(r, ...); }
}
1. 组件合法性校验
AMS 首先检查启动 Intent 对应的 Activity 是否存在(比如是否在 AndroidManifest.xml 中声明),以及调用者是否有权限启动(比如普通应用不能直接启动系统级 Activity)。如果一切合法,进入下一步。
2. 任务栈管理
每个应用的 Activity 运行在 ** 任务栈(Task)** 中。AMS 会判断目标 Activity 应该在哪个任务栈中启动:
- 如果是冷启动(应用进程未运行),创建新的任务栈;
- 如果是热启动(应用进程已在后台运行),则将任务栈切换到前台,复用已有进程。
3. 进程是否存在?
- 热启动:若目标进程已存在(比如应用之前被后台保留),AMS 直接通知该进程启动 Activity。
- 冷启动:若进程不存在,AMS 会唤醒 Android 的 “进程孵化器”——Zygote。
三、Zygote 孵化应用进程:从 SystemServer 到应用进程
1. Zygote 进程的作用(.native 层)
Zygote 是 Android 进程的 “孵化器”,预加载了 Java 运行时环境和系统资源(如 Framework 类),通过 fork() 快速创建应用进程,避免重复初始化开销。
AMS 通过 ZygoteProcess 类与 Zygote 通信:
// ZygoteProcess.java
public Process.ProcessStartResult startProcess(...) {// 构建启动参数(类名通常为 "android.app.ActivityThread")String args = "--activity-thread --nice-name=" + processName; // 通过 socket 向 Zygote 发送启动请求return zygoteSendArgsAndGetResult(openZygoteSocket(), args);
}
2. 应用进程初始化(ActivityThread.main ())
Zygote fork 出的新进程会执行 ActivityThread.main(),这是应用进程的入口:
// ActivityThread.java
public static void main(String[] args) {// 初始化 Looper(UI 线程消息循环)Looper.prepareMainLooper(); // 创建 ApplicationThread(AMS 通信的 Binder 实现)ActivityThread thread = new ActivityThread(); thread.attach(false, ...); // 绑定到 AMS// 启动消息循环,处理 H handler 的消息Looper.loop();
}private void attach(boolean system, ...) {// 通过 Binder 向 AMS 注册应用进程final IActivityManager am = ActivityManager.getService();am.attachApplication(mAppThread); // mAppThread 是 ApplicationThread 实例
}
Zygote 是 Android 系统中一个特殊的进程,它在系统启动时就已创建,并预加载了 Java 运行时环境(如类加载器、JIT 编译器)和系统资源(如 Framework 核心类、字体、颜色值)。
当 AMS 需要创建新的应用进程时,会向 Zygote 发送请求。Zygote 通过 **fork ()** 机制复制自身,生成新的应用进程。由于 fork 会共享内存数据,新进程无需重新加载基础资源,大大加快了启动速度(比直接启动一个空进程快 50% 以上)。
新进程的入口是ActivityThread.main(),这里会初始化应用的主线程(UI 线程)和消息循环(Looper),并通过 Binder 机制向 AMS 注册自己,建立通信桥梁。
四、应用进程启动 Activity:从创建到渲染
1. AMS 通知应用启动 Activity(跨进程回调)
AMS 通过 ApplicationThread(应用进程的 Binder 服务)回调 scheduleLaunchActivity,触发 Activity 创建:
// ApplicationThread.java(ActivityThread 内部类)
@Override
public final void scheduleLaunchActivity(IBinder token, ...) {// 发送消息到 UI 线程处理sendMessage(H.LAUNCH_ACTIVITY, r);
}// ActivityThread.H handler(处理消息)
private void handleLaunchActivity(ActivityClientRecord r, ...) {// 创建 Activity 实例Activity activity = performLaunchActivity(r, r.overrideConfig); if (activity != null) {// 调度 Activity 的生命周期handleResumeActivity(r.token, ..., true, ...); }
}
2. 反射创建 Activity 实例(Instrumentation 调用)
performLaunchActivity 通过反射创建 Activity,并调用 Application 和 Activity 的生命周期方法:
// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Configuration config) {// 加载 Application 类Application app = r.packageInfo.makeApplication(false, mInstrumentation); // 反射创建 Activity(关键源码)java.lang.ClassLoader cl = appContext.getClassLoader();Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);// 调用 Activity.onCreate()mInstrumentation.callActivityOnCreate(activity, r.state); return activity;
}
3. 界面渲染准备:WindowManager 添加窗口
Activity 的 onResume 触发窗口添加,通过 WindowManager 将 DecorView 添加到屏幕:
// Activity.java
public void onResume() {super.onResume();// 窗口可见,通知 AMS 状态变更mWindow.makeVisible();
}// PhoneWindow.java
void makeVisible() {if (!mWindowAdded) {// 通过 WindowManager 添加窗口(核心方法)mWindowManager.addView(mDecor, mWindowAttributes); mWindowAdded = true;}
}
1. 创建 Application:全局初始化
应用进程启动后,首先创建Application 实例(即你在 Manifest 中声明的 Application 类,若未声明则使用系统默认)。这里会调用Application.onCreate(),进行全局初始化(如数据库连接、第三方 SDK 注册)。
注意:Application 的生命周期早于所有 Activity,且整个应用中只有一个实例。
2. 反射创建 Activity 实例
接下来,AMS 通过 Binder 通知应用进程启动目标 Activity。进程通过反射机制创建 Activity 实例(例如你的MainActivity),并按顺序调用生命周期方法:
Activity.onCreate():初始化界面组件(如setContentView);Activity.onStart():Activity 可见但未出现在前台;Activity.onResume():Activity 获取焦点,界面开始渲染。
3. 窗口系统介入:从 Activity 到屏幕显示
每个 Activity 都关联一个Window(通常是PhoneWindow),窗口中包含一个DecorView(界面的根容器,如标题栏、内容区)。当 Activity 进入onResume状态,系统通过WindowManager将DecorView添加到屏幕:
- 计算界面布局(Measure、Layout 阶段),确定每个控件的位置和大小;
- 调用
onDraw方法绘制内容(如文字、图片、自定义图形),最终通过 Skia 引擎将绘制结果写入Surface(图形缓冲区)。
五、SurfaceFlinger 合成最终界面(图形系统)
- Surface 创建:
WindowManager.addView会为DecorView创建Surface,通过SurfaceControl与SurfaceFlinger(系统图形服务)交互。 - 渲染管道:
View 的measure/layout/draw流程在 UI 线程执行,最终通过 Skia 将绘制指令写入Surface,由SurfaceFlinger合成到屏幕缓冲区。 - VSYNC 同步:
Choreographer 监听屏幕垂直同步信号(60Hz),确保每帧在 16ms 内完成渲染,避免卡顿。
六、冷启动 vs 热启动:核心差异对比
| 阶段 | 冷启动(进程未存在) | 热启动(进程已存在) |
|---|---|---|
| 进程创建 | 触发 Zygote fork,初始化 Application 和 Context | 复用已有进程,直接唤醒 Activity |
| AMS 调度 | 新建 ProcessRecord,绑定 ApplicationThread | 查找现有 ProcessRecord,复用任务栈 |
| 生命周期调用 | onCreate () → onStart () → onResume () 全流程 | 仅调用 onResume ()(若 Activity 已存在于栈顶) |
| 耗时关键因素 | 类加载、资源初始化、布局渲染 | 任务栈状态恢复、界面重绘 |
七、面试官高频问题回答示例
问题 1:“应用启动流程中,AMS 如何与应用进程通信?”
回答:AMS 是系统的 “Activity 管家”,主要负责:
- 合法性校验:检查启动 Intent 对应的组件是否存在、是否有权限启动;
- 任务栈管理:决定 Activity 在哪个任务栈中运行,处理前后台切换;
- 进程调度:若应用进程未启动,触发 Zygote 创建新进程;若已启动,通知进程唤醒 Activity。
它通过 Binder 跨进程通信,协调 Launcher、应用进程、系统图形服务等模块的协作。
问题 2:“Zygote 对启动性能的优化作用是什么?”
回答:Zygote 预加载了以下内容:
- Java 运行时环境(ClassLoaders、JIT 编译缓存);
- Framework 核心类(如 Activity、View);
- 系统资源(字体、颜色值、布局参数)。
通过fork()复制进程内存,避免重复初始化,使应用进程启动时间减少约 50%~70%(对比直接启动 Java 进程)。
问题 3:“冷启动时,Application 和 Activity 的创建顺序是怎样的?”
回答:
严格顺序:
ActivityThread.performLaunchActivity先创建Application(调用Application.onCreate);- 再通过反射创建
Activity实例; - 最后调用
Activity.onCreate。
注意:Application的生命周期早于所有Activity,且全局唯一。
问题 4:“为什么冷启动时会看到短暂白屏?”
回答:
这是因为 Activity 的界面渲染需要时间。在onCreate()中调用setContentView后,系统需要经历 Measure(测量)、Layout(布局)、Draw(绘制)三个阶段,才能将界面显示到屏幕。
在界面渲染完成前,系统会显示 Activity 的主题背景(默认是白色),因此会出现短暂白屏。优化方法包括:
- 减少
onCreate()中的耗时操作; - 使用
android:windowBackground设置过渡动画或透明背景,提升用户体验。
扩展启动模式
在 Android 中,Activity 的启动模式(Launch Mode)用于控制 Activity 实例在任务栈(Task Stack)中的创建和复用规则,合理使用可以优化内存管理、避免重复创建实例或界面混乱。以下是四种启动模式的详细解析及典型使用场景:
standard(标准模式,默认)
特点:
- 每次启动都会创建新的 Activity 实例,并压入当前任务栈。
- 不考虑实例是否已存在,即使栈中已有该 Activity,也会重新创建。
- 生命周期:每次启动都会调用
onCreate()、onStart()、onResume()。
singleTop(栈顶复用模式)
特点:
- 若目标 Activity 已在任务栈顶,则直接复用(不创建新实例),调用
onNewIntent()传递参数。 - 若不在栈顶,则创建新实例(与
standard相同)。
三、singleTask(栈内唯一模式)
特点:
- 任务栈内唯一:若目标 Activity 已存在于任务栈中(无论是否在栈顶),则复用该实例,并清除栈中位于它之上的所有 Activity(即该 Activity 以上的实例会被销毁),使其成为栈顶。
- 若不存在,则创建新实例并新建任务栈(或加入指定任务栈,需配合
android:taskAffinity)。
singleInstance(单实例模式)
特点:
- 全局唯一实例:该 Activity 单独存在于一个新的任务栈中,且不允许其他 Activity 进入该栈(即该栈中只有它自己)。
- 任何对它的启动都会复用已有的实例,且其所在栈的生命周期独立于其他栈。
| 启动模式 | 实例创建规则 | 任务栈影响 | 典型场景 |
|---|---|---|---|
standard | 每次启动都创建新实例 | 压入当前栈 | 普通页面跳转(无复用需求) |
singleTop | 栈顶复用时不创建,否则创建新实例 | 仅影响栈顶实例 | 频繁启动的栈顶页面(如通知跳转) |
singleTask | 栈内存在则复用,清除其上所有实例 | 清空栈中上层实例,成为栈顶 | 主 Activity、一站式返回页面 |
singleInstance | 全局唯一实例,独占任务栈 | 新建独立栈,不允许其他 Activity 进入 | 系统级独立组件(来电、闹钟界面) |
八、总结:启动流程的核心脉络
用户点击图标 → Launcher 解析 Intent → AMS 跨进程调度 → Zygote fork 应用进程 →
ActivityThread 初始化 → 反射创建 Application/Activity → 调用生命周期方法 →
WindowManager 添加窗口 → SurfaceFlinger 合成界面
感谢观看!!!
相关文章:
Android学习总结之应用启动流程(从点击图标到界面显示)
一、用户交互触发:Launcher 到 AMS 的跨进程通信 1. Launcher 处理点击事件(应用层) 当用户点击手机桌面上的应用图标时,Launcher(桌面应用)首先捕获点击事件。每个图标对应一个启动 Intent(通…...
rdiff-backup备份
目录 1. 服务器备份知识点 1.1 备份策略 1.2 备份步骤和宝塔面板简介 1.3 CentOS7重要目录 2. 备份工具 2.1 tar -g 备份演示 2. rsync 备份演示 3. rdiff-backup 备份演示 4. 差异和优缺点 3. rdiff-backup安装和使用 3.1 备份命令rdiff-backup 3.2 恢复命令--…...
PE结构(十五)系统调用与函数地址动态寻找
双机调试 当需要分析一个程序时,这个程序一定是可以调试的,操作系统也不例外。在调试过程中下断点是很重要的 当我们对一个应用程序下断点时,应用程序是挂起的。但当我们对操作系统的内核程序下断点时,被挂起的不是内核程序而是…...
webrtc 本地运行的详细操作步骤 1
前言 选修课的一个课程设计,需要我们本地运行这个开源项目,给我的压力非常大,因为确实不是很熟练这种操作。但是还是得做。谨以此文,纪念这个过程。 之前自己在 github 上面看到有代码仓库,但是比较复杂,在…...
kali——httrack
目录 前言 使用教程 前言 HTTrack 是一款运行于 Kali Linux 系统中的开源网站镜像工具,它能将网站的页面、图片、链接等资源完整地下载到本地,构建出一个和原网站结构相似的离线副本。 使用教程 apt install httrack //安装httrack工具 httrac…...
力扣经典算法篇-6-轮转数组
题干: 给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步…...
【计算机网络】Linux配置SNAT/DNAT策略
什么是NAT? NAT 全称是 Network Address Translation(网络地址转换),是一个用来在多个设备共享一个公网 IP上网的技术。 NAT 的核心作用:将一个网络中的私有 IP 地址,转换为公网 IP 地址,从而…...
火山引擎coze用户市场
火山引擎 **Coze**(扣子)的用户市场主要集中在 **需要快速构建和部署智能对话应用的企业及开发者群体**,覆盖多个行业与场景。以下是具体分析: --- ### **一、核心用户群体** 1. **企业用户** - **互联网/科技公司**&#…...
qt designer 软件主题程序设计
对于使用Qt Designer设计的界面,主题切换的实现需要结合Qt的信号槽机制、样式表动态加载以及资源管理。以下是针对Qt Designer UI的详细解决方案: 一、UI文件与主题系统的整合架构 二、核心实现步骤 1. 动态样式表加载系统 // ThemeManager.h class …...
2025/4/2 心得
第一题 题目描述 给定1001个范围在[1,1000]的数字,保证只有1个数字重复出现2次,其余数字只出现1次。试用O(n)时间复杂度来求出出现2次的这个数字。 不允许用数组 输入格式 第一行:一个整数1001; 第二行:1001个用…...
AI安全:构建负责任且可靠的系统
AI已成为日常生活中无处不在的助力,随着AI系统能力和普及性的扩展,安全因素变得愈发重要。从基础模型构建者到采用AI解决方案的企业,整个AI生命周期中的所有相关方都必须共同承担责任。 为什么AI安全至关重要? 对于企业而言&…...
VUE+SPRINGBOOT+语音技术实现智能语音歌曲管理系统
语音控制歌曲的播放、暂停、增删改查 <template><div class"Music-container"><div style"margin: 10px 0"><!--检索部分--><el-input style"width: 200px;" placeholder"请输入歌曲名称"v-model"sen…...
使用 SignalR 在 .NET Core 8 最小 API 中构建实时通知
示例代码:https://download.csdn.net/download/hefeng_aspnet/90448094 介绍 构建实时应用程序已成为现代 Web 开发中必不可少的部分,尤其是对于通知、聊天系统和实时更新等功能。SignalR 是 ASP.NET 的一个强大库,可实现服务器端代码和客户…...
Kotlin 集合函数:map 和 first 的使用场景
Kotlin 提供了丰富的集合操作函数,使开发者可以更加简洁、高效地处理数据。其中,map 和 first 是两个常用的函数,分别用于转换集合和获取集合中的第一个元素。 1. map 的使用场景 场景 1:对象列表转换 在开发中,我们…...
Spring Cloud 框架为什么能处理高并发
Spring Cloud框架能够有效处理高并发场景,核心在于其微服务架构设计及多组件的协同作用,具体机制如下: 一、分布式架构设计支撑高扩展性 服务拆分与集群部署 Spring Cloud通过微服务拆分将单体系统解耦为独立子服务,每个服务可独…...
【Python爬虫高级技巧】BeautifulSoup高级教程:数据抓取、性能调优、反爬策略,全方位提升爬虫技能!
大家好,我是唐叔!上期我们聊了 BeautifulSoup的基础用法 ,今天带来进阶篇。我将分享爬虫老司机总结的BeautifulSoup高阶技巧,以及那些官方文档里不会告诉你的实战经验! 文章目录 一、BeautifulSoup性能优化技巧1. 解析…...
复古未来主义屏幕辉光像素化显示器反乌托邦效果PS(PSD)设计模板样机 Analog Retro-Futuristic Monitor Effect
这款模拟复古未来主义显示器效果直接取材于 90 年代赛博朋克电影中的黑客巢穴,将粗糙的屏幕辉光和像素化的魅力强势回归。它精准地模仿了老式阴极射线管显示器,能将任何图像变成故障频出的监控画面或高风险的指挥中心用户界面。和……在一起 2 个完全可编…...
Spring Boot + MySQL + MyBatis(注解和XML配置两种方式)集成Redis的完整启用及配置详解,包含代码示例、注释说明和表格总结
以下是 Spring Boot MySQL MyBatis(注解和XML配置两种方式)集成Redis的完整启用及配置详解,包含代码示例、注释说明和表格总结: 1. 添加依赖 在pom.xml中添加Spring Boot对MySQL、MyBatis和Redis的支持依赖: <d…...
Webpack vs Vite:现代前端构建工具的巅峰对决与选型指南
构建工具的进化革命当雪碧瓶上的水珠折射出前端工程的变迁史,Webpack与Vite的决战已然成为现代前端开发的分水岭。这场始于打包理念的革命,正在重塑整个前端生态的底层逻辑。本文将从原理架构、性能表现、开发体验三个维度,结合真实项目数据对…...
2023-2024总结记录
概括经历 这一年算是一个人生节点,2023年花了一整年的时间在准备考研,基本上等于一个人奋战,我不怎么去图书馆,只呆在无人的实验室,还好有对象陪我,不然可能要抑郁了。作息上还是很随意,什么时…...
技术驱动革新,强力巨彩LED软模组助力创意显示
随着LED显示技术的不断突破,LED软模组因其独特的柔性特质和个性化显示效果,正逐渐成为各类应用场景的新宠。强力巨彩软模组R3.0H系列具备独特的可塑造型能力与技术创新,为商业展示、数字艺术、建筑装饰等领域开辟全新视觉表达空间。 LED…...
Spring 核心技术解析【纯干货版】- XVIII:Spring 网络模块 Spring-WebSocket 模块精讲
在现代 Web 开发中,实时通信已成为提升用户体验的关键技术之一。传统的 HTTP 轮询方式存在较高的延迟和带宽开销,而 WebSocket 作为一种全双工通信协议,能够在客户端和服务器之间建立持久连接,实现高效的双向数据传输。 Spring 框…...
Spark,HDFS概述
HDFS组成构架: 注: NameNode(nn):就是 Master,它是一个主管、管理者。 (1) 管理 HDFS 的名称空间; (2) 配置副本策略。记录某些文件应该保持几个副本; (3) 管理数据块(…...
【数据结构】图论进阶:生成树、生成森林与权值网络的终极解析
图的基本概念 导读一、图中的树与森林1.1 生成树与生成森林1.1.1 生成树1.1.2 生成森林1.1.3 生成树、生成森林与连通分量结点的关系边的关系 1.2 有向图中的树与森林1.2.1 有向树与有向森林1.2.2 生产有向树与生成有向森林1.2.3 有向树与生成有向树的区别1.2.4 有向森林与生成…...
C和C++(list)的链表初步
链表是构建其他复杂数据结构的基础,如栈、队列、图和哈希表等。通过对链表进行适当的扩展和修改,可以实现这些数据结构的功能。想学算法,数据结构,不会链表是万万不行的。这篇笔记是一名小白在学习时整理的。 C语言 链表部分 …...
深入浅出 TypeScript 泛型:类型安全的艺术与实践
文章目录 一、泛型的核心概念1.1 类型参数:代码中的类型变量1.2 类型推断:让代码保持简洁 二、泛型的四大应用场景2.1 泛型函数:打造通用工具库2.2 泛型接口:定义灵活的数据结构2.3 泛型类:构建类型安全的容器2.4 泛型…...
【KWDB创作者计划】_KaiwuDB 2.1.0 单节点裸机部署
大家好,这里是 DBA学习之路,专注于提升数据库运维效率。 目录 前言KWDB 介绍安装准备环境信息配置要求操作系统软件依赖端口要求安装包下载 部署 KWDB简单实用连接数据库创建数据库创建用户创建时序表 前言 今天无意间在墨天轮看到一个征文活动 征文大赛…...
洛谷题单3-P5720 【深基4.例4】一尺之棰-python-流程图重构
题目描述 《庄子》中说到,“一尺之棰,日取其半,万世不竭”。第一天有一根长度为 a a a 的木棍,从第二天开始,每天都要将这根木棍锯掉一半(每次除 2 2 2,向下取整)。第几天的时候木…...
前端快速入门学习3——CSS介绍与选择器
1.概述 CSS全名是cascading style sheets,中文名层叠样式表。 用于定义网页样式和布局的样式表语言。 通过 CSS,你可以指定页面中各个元素的颜色、字体、大小、间距、边框、背景等样式,从而实现更精确的页面设计。 HTML与CSS的关系:HTML相当…...
Redash:一个开源的数据查询与可视化工具
Redash 是一款免费开源的数据可视化与协作工具,可以帮助用户快速连接数据源、编写查询、生成图表并构建交互式仪表盘。它简化了数据探索和共享的过程,尤其适合需要团队协作的数据分析场景。 数据源 Redash 支持各种 SQL、NoSQL、大数据和 API 数据源&am…...
