当前位置: 首页 > news >正文

【Android 源码分析】Activity生命周期之onStop-2

忽然有一天,我想要做一件事:去代码中去验证那些曾经被“灌输”的理论。
                  
                  
                                           – 服装学院的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

当前为“onStop”第二篇,整个流程分为3步:

    1. addToStopping 流程,将应用添加进需要stop的列表 – system_service进程处理
    1. stopIfPossible 流程,也就是开始执行stop – system_service进程处理
    1. 应用端处理stop流程 – 应用进程处理

前面个都是system_service进程处理的,第三步是在 SourceActivity 的应用进程处理。

1. 第二步:stopIfPossible 开始执行stop

根据上一小节后的2个线索,得到以下信息:

    1. “wm_stop_activity”的打印在 ActivityRecord::stopIfPossible 方法,并且打印log后就构建了 StopActivityItem
    1. 在 ActivityTaskSupervisor::activityIdleInternal 方法内有堆栈打印,日志如下:
ActivityTaskManager: activityIdleInternal:
Callers=com.android.server.wm.ActivityClientController.activityIdle:142android.app.IActivityClientController$Stub.onTransact:550com.android.server.wm.ActivityClientController.onTransact:125android.os.Binder.execTransactInternal:1285 

根据这2点信息其实就已经可以获得到这么一个完整的调用链了:

ActivityClientController::activityIdleActivityTaskSupervisor::activityIdleInternalActivityTaskSupervisor::processStoppingAndFinishingActivitiesActivityRecord::stopIfPossibleActivityRecord::setState  -- 设置为 STOPPINGStopActivityItem.obtain -- 构建触发StopActivityItem

后面的逻辑晚点再看,先分析 ActivityClientController::activityIdle 是怎么调用过来的。

1.1 ActivityClientController::activityIdle 的触发逻辑

TargetActivity 执行 onResume 的逻辑是在应用端是由 ActivityThread::handleResumeActivity方法开始的,而这个方法最后添加了一个 Idler 到 IdleHandler中,这个 Idler 就是触发
ActivityClientController::activityIdle 的地方。

整个AOSP中调用 ActivityClientController::activityIdle 的地方只有2个,根据log定位是在 ActivityThread 下的内部类 Idler 中。

# ActivityThreadpublic void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, String reason) {......// 触发 TargetActivity的 onResumeif (!performResumeActivity(r, finalStateRequest, reason)) {return;}......// 拿到activityfinal Activity a = r.activity;......//setView 逻辑r.nextIdle = mNewActivities;mNewActivities = r;//触发SourceActivity 的onStop if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);Looper.myQueue().addIdleHandler(new Idler());}

根据调用顺序:先触发 TargetActivity的 onResume,再触发 SourceActivity 的 onStop 。

这一点和实际的log打印也是对上了,接下来就看看 Idler() 是怎么执行的了。

addIdleHandler 方法会把一个 IdleHandler 添加到先洗队列的 mIdleHandlers 列表,这里是 Handler 知识点下关于 IdleHandler 的知识,不知道的可以搜一下相关的文章看看。
IdleHandler 的特效就是会在 CPU 空闲的时候才会执行相关任务。

1.1.1 Idler

Idler 是 ActivityThread 的内部类

# ActivityThreadprivate class Idler implements MessageQueue.IdleHandler {@Overridepublic final boolean queueIdle() {ActivityClientRecord a = mNewActivities;......if (a != null) {mNewActivities = null;final ActivityClient ac = ActivityClient.getInstance();ActivityClientRecord prev;do {// 日志if (localLOGV) Slog.v(TAG, "Reporting idle of " + a +" finished=" +(a.activity != null && a.activity.mFinished));if (a.activity != null && !a.activity.mFinished) {// 重点* 执行 ActivityClientController::activityIdleac.activityIdle(a.token, a.createdConfig, stopProfiling);a.createdConfig = null;}prev = a;a = a.nextIdle;prev.nextIdle = null;} while (a != null);}if (stopProfiling) {mProfiler.stopProfiling();}return false;}}

这里的重点就是在主线程空闲的时候,就会执行 queueIdle() 方法,进而触发 SourceActivity 的 StopActivityItem 事务构建。
在日志中搜索添加 Idler 的日志和 queueIdle 方法执行的日志信息如下:

Line 12732: 04-01 20:50:30.891 24294 24294 V ActivityThread: Scheduling idle handler for ActivityRecord{b7ece46 token=android.os.BinderProxy@1fee9be {com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity}}Line 19074: 04-01 20:50:31.170 24294 24294 V ActivityThread: Reporting idle of ActivityRecord{b7ece46 token=android.os.BinderProxy@1fee9be {com.google.android.dialer/com.google.android.dialer.extensions.GoogleDialtactsActivity}} finished=false

可以看到时间上确实还是有很大间隔的。

1.2 ActivityClientController::activityIdle 的处理

上面看到了是如何调用过来的,现在看一下后续的调用逻辑

# ActivityClientController@Overridepublic void activityIdle(IBinder token, Configuration config, boolean stopProfiling) {final long origId = Binder.clearCallingIdentity();try {synchronized (mGlobalLock) {// TraceTrace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "activityIdle");final ActivityRecord r = ActivityRecord.forTokenLocked(token);if (r == null) {return;}// 重点逻辑mTaskSupervisor.activityIdleInternal(r, false /* fromTimeout */,false /* processPausingActivities */, config);......}} finally {Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);Binder.restoreCallingIdentity(origId);}}

直接调用了 ActivityTaskSupervisor::activityIdleInternal 方法,这个方法在签名 SourceActivity 的 addToStopping 流程已经看过一次,现在条件不同执行的逻辑也不一样,再看一下这个方法。

# ActivityTaskSupervisorvoid activityIdleInternal(ActivityRecord r, boolean fromTimeout,boolean processPausingActivities, Configuration config) {// 重点* 1 logif (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + r);// 重点* 2. ActivityRecord 不为null才执行内部逻辑if (r != null) {// 重点* 3. logif (DEBUG_IDLE) Slog.d(TAG_IDLE, "activityIdleInternal: Callers="+ Debug.getCallers(4));......}......// Atomically retrieve all of the other things to do. 原子检索所有其他要做的事情// 重点* 4. 看方法名是处理 停止和finish的Activity辑在这里processStoppingAndFinishingActivities(r, processPausingActivities, "idle");if (DEBUG_IDLE) {// 重点* 5. log Slogf.i(TAG, "activityIdleInternal(): r=%s, mStartingUsers=%s", r, mStartingUsers);}......}

这一次因为参数 r 不为null,所以还会打印“重点* 3”处的log,然后进入processStoppingAndFinishingActivities 方法。

1.2.1 再看 processStoppingAndFinishingActivities 逻辑(真正执行)

# ActivityTaskSupervisor/*** Processes the activities to be stopped or destroyed. This should be called when the resumed* 处理要停止或销毁的Activity。这应该在resumed时调用* activities are idle or drawn.*/private void processStoppingAndFinishingActivities(ActivityRecord launchedActivity,boolean processPausingActivities, String reason) {// 准备要执行 Stop 的Activity 集合 ArrayList<ActivityRecord> readyToStopActivities = null;// 重点 * 1. 遍历mStoppingActivitiesfor (int i = mStoppingActivities.size() - 1; i >= 0; --i) {// 获取到ActivityRecord (当前分析场景就1个)final ActivityRecord s = mStoppingActivities.get(i);final boolean animating = s.isAnimating(TRANSITION | PARENTS,ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)|| s.inTransition();// 日志ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "+ "finishing=%s", s, s.nowVisible, animating, s.finishing);// 条件满足才执行if (!animating || mService.mShuttingDown) {......// 打印 准备stop的logProtoLog.v(WM_DEBUG_STATES, "Ready to stop: %s", s);if (readyToStopActivities == null) {readyToStopActivities = new ArrayList<>();}// 重点 * 2. 添加进集合readyToStopActivities.add(s);// 从集合中移除mStoppingActivities.remove(i);}}// 重点 * 3. 遍历readyToStopActivitiesfinal int numReadyStops = readyToStopActivities == null ? 0 : readyToStopActivities.size();for (int i = 0; i < numReadyStops; i++) {final ActivityRecord r = readyToStopActivities.get(i);// 检查该ActivityRecord对象是否在历史记录中。  if (r.isInHistory()) {// 如果该ActivityRecord对象正在结束(可能是用户或系统触发的结束操作)。if (r.finishing) {// TODO(b/137329632): Wait for idle of the right activity, not just any.r.destroyIfPossible(reason);} else {// 重点* 4. 如果ActivityRecord对象不在结束状态,则尝试停止它r.stopIfPossible();}}}......}

这个时候再看这个方法就能理解方法上面的注释了:This should be called when the resumed 。

    1. 遍历 mStoppingActivities 集合,这个集合里是有当前场景的唯一元素是 SourceActivity (当前是QuickstepLauncher)。

这一次的打印如下:

WindowManager: Stopping ActivityRecord{f40797d u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t20}: nowVisible=false animating=false finishing=false  mShuttingDown=false
    1. 由于“animating=false”所以会执行 if 内部语句,把 SourceActivity 添加到 readyToStopActivities 集合中。

并打印ProtoLog

WindowManager: Ready to stop: ActivityRecord{f40797d u0 com.android.launcher3/.uioverrides.QuickstepLauncher} t20}
    1. 遍历 readyToStopActivities 下的元素,然后执行 ActivityRecord::stopIfPossible 方法

processStoppingAndFinishingActivities 方法是处理 Activity 的核心方法,他的触发点当前文章就分析到了2个,另外肯定还有其他的调用逻辑
比如我将 ActivityThread::handleResumeActivity 方法下最后一行将 Idler 添加进 IdleHandler 的代码删了,本以为就不会执行 SourceActivity 的 onStop 了,没想到还会通过发送一个 PROCESS_STOPPING_AND_FINISHING_MSG 消息来触发 processStoppingAndFinishingActivities 方法的执行。
毕竟 Activity 生命周期是基础代码,google这快代码的健壮性肯定是考虑周全的。

1.3 真正触发StopActivityItem构建的地方 ActivityRecord::stopIfPossible

# ActivityRecordvoid stopIfPossible() {// logif (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);final Task rootTask = getRootTask();......try {stopped = false;// 关键log (这里还是即将 stop)ProtoLog.v(WM_DEBUG_STATES, "Moving to STOPPING: %s (stop requested)", this);......// 重点* 1. 设置Activity的状态为STOPPING,原因为"stopIfPossible"setState(STOPPING, "stopIfPossible");if (DEBUG_VISIBILITY) {// logSlog.v(TAG_VISIBILITY, "Stopping:" + this);}// events日志 :wm_stop_activity: [0,198530468,com.example.myapplication/.MainActivity]EventLogTags.writeWmStopActivity(mUserId, System.identityHashCode(this), shortComponentName);// 重点* 2. 构建执行 Stop 事务mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,StopActivityItem.obtain(configChangeFlags));mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);}}

就做了2件事:

    1. 设置状态为 STOPPING
    1. 构建执行 StopActivityItem

不过这里有很多log,跟踪问题的时候可以关注一下。

2. SystemService 端小结

当前冷启动场景下,SourceActivity 的onStop处理,SystemService 端其实就做了2件事。

    1. 把 SourceActivity 添加到 ActivityTaskSupervisor 类下的 mStoppingActivities 集合中,这一步我成为“addToStopping 流程”,对应的events日志为:“wm_add_to_stopping”
      这一步的执行也毕竟早,在 SourceActivity 执行完 Pause 流程就触发了。
    1. 执行 ActivityTaskSupervisor::processStoppingAndFinishingActivities 方法,将 mStoppingActivities 集合下的 ActivityRecord 取出,执行 ActivityRecord::stopIfPossible 方法,在这里构建执行 StopActivityItem 。

当前分析的是常见流程的调用逻辑,还有一些其他逻辑也会触发onStop,但是目前看来都会执行 ActivityTaskSupervisor::processStoppingAndFinishingActivities 方法,所以以后遇到相关问题可以关注一下该方法的执行。

到这里 SystemService 的处理就结束了,后续流程就在应用端了。

那后续就是应用进程执行 Stop 的流程了。

3.应用端 onStop 处理

应用端的处理就相对简单了,先看一下 StopActivityItem 的定义

# StopActivityItem@Overridepublic void execute(ClientTransactionHandler client, ActivityClientRecord r,PendingTransactionActions pendingActions) {Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");//  触发应用进程stopclient.handleStopActivity(r, mConfigChanges, pendingActions,true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);}

具体的实现肯定是在ActivityThread中了,整理的调用链如下:

StopActivityItem::executeClientTransactionHandler::handleStopActivityActivityThread::handleStopActivityActivityThread::performStopActivityInnerActivityThread::callActivityOnStopActivityThread::callActivityOnSaveInstanceState  -- OnSaveInstanceState 逻辑Activity::performStopInstrumentation.callActivityOnStopActivity::onStop        -- 生命周期ActivityThread::updateVisibility        -- 处理View不可见

开始撸代码

# ActivityThread@Overridepublic void handleStopActivity(ActivityClientRecord r, int configChanges,PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {r.activity.mConfigChangeFlags |= configChanges;final StopInfo stopInfo = new StopInfo();// 重点* 1. onStop 流程performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,reason);if (localLOGV) Slog.v(TAG, "Finishing stop of " + r + ": win=" + r.window);// 重点* 2. 更新Activity的可见性状态为不可见updateVisibility(r, false);......}

这里分为2步逻辑,先执行 onStop ,再处理 View 可见性。

3.1 onStop 执行逻辑

# ActivityThreadprivate void performStopActivityInner(ActivityClientRecord r, StopInfo info,boolean saveState, boolean finalStateRequest, String reason) {......// One must first be paused before stopped... // 1. 必须先执行 pause,才可以 stop。performPauseActivityIfNeeded(r, reason);......// 2. 主流程callActivityOnStop(r, saveState, reason);}

这个方法做了2件事:

    1. 必须先执行 pause,才可以 stop,所以需要执行一下 performPauseActivityIfNeeded 方法,不过正常逻辑执行到这,已经是pause状态了,所以这个方法内部进去就会return。
# ActivityThreadprivate void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {if (r.paused) {// You are already paused silly...return;}......}
    1. 继续执行主流程,触发Activity的 onStop 回调
# ActivityThreadprivate void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {// Before P onSaveInstanceState was called before onStop, starting with P it's// called after. Before Honeycomb state was always saved before onPause.final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null&& !r.isPreHoneycomb();final boolean isPreP = r.isPreP();if (shouldSaveState && isPreP) {// 重点* 1. callActivityOnSaveInstanceStatecallActivityOnSaveInstanceState(r);}try {// 重点* 2. onStop流程r.activity.performStop(r.mPreserveWindow, reason);} ......if (shouldSaveState && !isPreP) {// 重点* 1. callActivityOnSaveInstanceStatecallActivityOnSaveInstanceState(r);}}
    1. 可以看到2次执行了 onSaveInstanceState 回调,这个可以在【Android 11源码分析】看相关的
    1. 是本次分析的主流程,已经执行到Activity的方法了,里生命周期onStop不远了
# Activityprivate Instrumentation mInstrumentation;final void performStop(boolean preserveWindow, String reason) {......// Fragment的处理mFragments.dispatchStop();mCalled = false;// 回调onStop流程mInstrumentation.callActivityOnStop(this);......}

Instrumentation 这个类中处理了所有的Activity生命周期回调。

# Instrumentationpublic void callActivityOnStop(Activity activity) {// onStopactivity.onStop();}

3.2 Stop 处理View的可见性

# ActivityThreadprivate void updateVisibility(ActivityClientRecord r, boolean show) {// 拿到对应Activity的 DecorViewView v = r.activity.mDecor;if (v != null) {if (show) {// 如果Activity当前在服务器端不可见, 就需要处理成可见if (!r.activity.mVisibleFromServer) {r.activity.mVisibleFromServer = true;mNumVisibleActivities++;if (r.activity.mVisibleFromClient) {// 设置可见r.activity.makeVisible();}}} else {// 如果Activity当前在服务器端可见, 就需要处理成不可见if (r.activity.mVisibleFromServer) {r.activity.mVisibleFromServer = false;mNumVisibleActivities--;// 影藏Viewv.setVisibility(View.INVISIBLE);}}}}

所以经常会有人执行了onStop 才是真正的Activity不可见,原因就是onStop的逻辑会把 View设置为不可见,间接的Activity也就不可见了。

4. 总结

当前只是以桌面冷启动应用的场景来分析桌面的 onStop 流程,整个流程分析完对 onStop 流程也有了一个比较完整的了解,但是当前分析的调用流程并不能代表所有场景。具体情况还是需要具体分析,比如应用内启动 Activity 的调用链肯定和当前是有别的,但是无论怎么样,2个主流程还是要执行的。

相关文章:

【Android 源码分析】Activity生命周期之onStop-2

忽然有一天&#xff0c;我想要做一件事&#xff1a;去代码中去验证那些曾经被“灌输”的理论。                                                                                  – 服装…...

SpringCloudStream+RocketMQ多topic

之前写过两篇关于SpringCloudStream文章 spring-cloud-stream版本升级&#xff0c;告别旧注解EnableBinding&#xff0c;拥抱函数式编程_spring-cloud-stream output注解没有了-CSDN博客 SpringCloudStreamRocketMQ事务消息配置_spring-cloud-starter-stream-rocketmq-CSDN博…...

随记 前端框架React的初步认识

区分语言 像js 就是构建的最基本的 React框架和Vue框架&#xff0c;用自己的话来说的话&#xff0c;就是对js进行了一层封装&#xff0c;使使用js更加的方便 但是&#xff0c;React框架和Vue框架又不能这么简单的理解&#xff0c;因为这些框架里还封装了一些其他的东西。 向…...

数据结构 ——— 单链表oj题:链表分割(带哨兵位单向不循环链表实现)

目录 题目要求 手搓简易单链表 代码实现 题目要求 现有一链表的头指针 ListNode* head &#xff0c;给一定值 x &#xff0c;编写一段代码将所有小于 x 的节点排在其余节点之前&#xff0c;且不能改变原来的数据顺序&#xff0c;返回重新排列后的链表的头节点 举例说明&a…...

华为 HCIP-Datacom H12-821 题库 (32)

&#x1f423;博客最下方微信公众号回复题库,领取题库和教学资源 &#x1f424;诚挚欢迎IT交流有兴趣的公众号回复交流群 &#x1f998;公众号会持续更新网络小知识&#x1f63c; 1.当一个运行 MSTP 协议的交换设备端口收到一个配置BPDU 时&#xff0c;会与设备保存的全局配…...

[C++][第三方库][brpc]详细讲解

目录 1.介绍2.安装3.类与接口介绍1.日志输出类与接口2.ProtoBuf类与接口3.服务端类与接口4.客户端类与接口 4.使用0.一般流程1.Server2.客户端 -- 同步调用3.客户端 -- 异步调用 1.介绍 brpc是用C编写的工业级RPC框架&#xff0c;常用于搜索、存储、机器学习、广告、推荐等高性…...

Python-Learning

补充不熟悉的python知识 1 **是表示平方 注释是用来阐述代码要做什么&#xff0c;以及是如何做的 先编写行之有效的代码&#xff0c;再决定是对其做进一步改进&#xff0c;还是转而去编写新代码 列表常用是append&#xff0c;但也有pop&#xff0c;这个pop是输出一个值&…...

如何让 Android 的前端页面像 iOS 一样“优雅”?

作者:方英杰&#xff08;崇之&#xff09; 最近在调研前端页面适配 Android 端异形屏的方案&#xff0c;调研过程中发现了一些比较有意思的点&#xff0c;本文主要是做一个总结。 一、提出问题 首先&#xff0c;我们需要知道 Android 上的前端适配面临着什么问题。 问题其实很…...

10.3学习

1.循环依赖 循环依赖其实就是循环引用&#xff0c;也就是两个或者两个以上的 Bean 互相持有对方&#xff0c;最终形成闭环。比如A 依赖于B&#xff0c;B又依赖于A Spring中循环依赖场景有: prototype 原型 bean循环依赖 构造器的循环依赖&#xff08;构造器注入&#xff09;…...

Shell文本处理(三)

Shell文本处理三:字符串处理 1、字符串截取(切片)2、字符串替换3、字符串删除4、去除空格5、大小写转换6、字符串分割7、去除中文在Shell中,字符串没有单独的数据类型,一切都是变量。但这并不意味着我们不能像在Java、Python等其他编程语言中那样处理字符串 1、字符串截取…...

5个python多线程简单示例

示例 1: 基本线程创建 # 示例 1: 基本线程创建 import threading import timedef print_numbers():for i in range(5):time.sleep(1)print(i)# 创建线程 thread threading.Thread(targetprint_numbers)# 启动线程 thread.start()# 等待线程完成&#xff08;可选&#xff09; …...

Streamlit:用Python快速构建交互式Web应用

在传统的Web开发中&#xff0c;开发者常常需要编写大量的前端和后端代码&#xff0c;才能实现一个简单的交互式Web应用。Streamlit 通过简化这一过程&#xff0c;使得你只需要用Python编写代码&#xff0c;就能快速创建具有丰富交互功能的Web应用。本文将介绍如何使用Streamlit…...

深入浅出Vue.js组件开发:从基础到高级技巧

解锁Python编程的无限可能:《奇妙的Python》带你漫游代码世界 Vue.js 是一个轻量级且功能强大的 JavaScript 框架,专注于构建用户界面。它的核心优势之一是组件系统,它允许开发者通过模块化、可复用的方式构建复杂的应用程序。在这篇文章中,我们将详细探讨如何开发 Vue.js…...

Python并发编程挑战与解决方案

Python并发编程挑战与解决方案 并发编程是现代软件开发中的一项核心能力&#xff0c;它允许多个任务同时运行&#xff0c;提高程序的性能和响应速度。Python因其易用性和灵活性而广受欢迎&#xff0c;但其全局解释器锁&#xff08;GIL&#xff09;以及其他特性给并发编程带来了…...

LeetCode从入门到超凡(五)深入浅出---位运算

引言 大家好&#xff0c;我是GISer Liu&#x1f601;&#xff0c;一名热爱AI技术的GIS开发者。本系列文章是我跟随DataWhale 2024年9月学习赛的LeetCode学习总结文档&#xff1b;本文主要讲解 位运算算法。&#x1f495;&#x1f495;&#x1f60a; 一、 位运算简介 1.什么是位…...

一些 Go Web 开发笔记

原文&#xff1a;Julia Evans - 2024.09.27 在过去的几周里&#xff0c;我花了很多时间在用 Go 开发一个网站&#xff0c;虽然不知道它最终会不会发布&#xff0c;但在这个过程中我学到了一些东西&#xff0c;想记录下来。以下是我的一些收获&#xff1a; Go 1.22 现在有了更…...

[Go语言快速上手]初识Go语言

目录 一、什么是Go语言 二、第一段Go程序 1、Go语言结构 注意 2、Go基础语法 关键字 运算符优先级 三、Go语言数据类型 示例 小结 一、什么是Go语言 Go语言&#xff0c;通常被称为Golang&#xff0c;是一种静态类型、编译型的计算机编程语言。它由Google的Robert Gr…...

基于STM32的智能风扇控制系统设计

引言 本项目将基于STM32微控制器设计一个智能风扇控制系统&#xff0c;通过温度传感器实时检测环境温度&#xff0c;并根据预设的温度范围自动调节风扇的转速。该系统展示了STM32的PWM输出、传感器接口以及自动控制应用的实现。 环境准备 1. 硬件设备 STM32F103C8T6 开发板…...

OpenCV 形态学相关函数详解及用法示例

OpenCV形态学相关的运算包含腐蚀(MORPH_ERODE)&#xff0c;膨胀(MORPH_DILATE)&#xff0c;开运算(MORPH_OPEN)&#xff0c;闭运算(MORPH_CLOSE)&#xff0c;梯度运算(MORPH_GRADIENT)&#xff0c;顶帽运算(MORPH_TOPHAT)&#xff0c;黑帽运算(MORPH_BLACKHAT)&#xff0c;击中…...

Kafka学习笔记(三)Kafka分区和副本机制、自定义分区、消费者指定分区

文章目录 前言7 分区和副本机制7.1 生产者分区写入策略7.1.1 轮询分区策略7.1.2 随机分区策略7.1.3 按key分区分配策略7.1.4 自定义分区策略7.1.4.1 实现Partitioner接口7.1.4.2 实现分区逻辑7.1.4.3 配置使用自定义分区器7.1.4.4 分区测试 7.2 消费者分区分配策略7.2.1 RangeA…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略

本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装&#xff1b;只需暴露 19530&#xff08;gRPC&#xff09;与 9091&#xff08;HTTP/WebUI&#xff09;两个端口&#xff0c;即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

现代密码学 | 椭圆曲线密码学—附py代码

Elliptic Curve Cryptography 椭圆曲线密码学&#xff08;ECC&#xff09;是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础&#xff0c;例如椭圆曲线数字签…...

MySQL 8.0 OCP 英文题库解析(十三)

Oracle 为庆祝 MySQL 30 周年&#xff0c;截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始&#xff0c;将英文题库免费公布出来&#xff0c;并进行解析&#xff0c;帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...

安卓基础(aar)

重新设置java21的环境&#xff0c;临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的&#xff1a; MyApp/ ├── app/ …...

C++ 设计模式 《小明的奶茶加料风波》

&#x1f468;‍&#x1f393; 模式名称&#xff1a;装饰器模式&#xff08;Decorator Pattern&#xff09; &#x1f466; 小明最近上线了校园奶茶配送功能&#xff0c;业务火爆&#xff0c;大家都在加料&#xff1a; 有的同学要加波霸 &#x1f7e4;&#xff0c;有的要加椰果…...

FFmpeg:Windows系统小白安装及其使用

一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】&#xff0c;注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录&#xff08;即exe所在文件夹&#xff09;加入系统变量…...

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…...