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

Android Framework学习五:APP启动过程原理及速度优化

文章目录

  • APP启动优化概述
  • APP启动流程
    • 点击图片启动APP的过程
    • 启动触发
    • Zygote 与应用进程创建
      • Zygote
      • 进程的创建
      • 应用进程初始化
    • Application
    • Activity 启动与显示
  • 优化启动时黑白屏现象
    • 可优化的阶段
      • Application阶段
        • 相关优化
      • Activity阶段
      • 数据加载阶段
  • Framework学习系列文章

APP启动优化概述

  1. 优化方向
    在这里插入图片描述

  2. 启动优化流程
    在这里插入图片描述

APP启动流程

在这里插入图片描述

点击图片启动APP的过程

这张图展示了 Android 系统中应用程序(App)启动的完整流程,涉及多个关键组件及其交互
在这里插入图片描述

启动触发

  1. Launcher:用户在手机桌面点击应用图标,Launcher(桌面启动器)响应点击事件。通过Binder机制向service_manager查询ActivityTaskManagerService(ATMS,活动管理服务)服务。
  2. service_manager 和 Binder:service_manager是系统中管理服务的组件,通过Binder这种进程间通信机制,Launcher获取到AMS服务的相关信息。Binder是 Android 系统中实现进程间通信的重要机制,提供高效稳定的通信能力。
  3. system_server:system_server进程是 Android 系统核心进程,包含ActivityManagerService和WindowManagerService等重要服务。AMS负责管理应用程序的生命周期、Activity 的启动与切换等;WindowManagerService负责窗口的管理和显示等。

在这里插入图片描述

具体代码如下:

  • Launcher调用Activity.startActivity启动Activity
    Activity.java
@Overridepublic void startActivity(Intent intent, @Nullable Bundle options) {...if (options != null) {startActivityForResult(intent, -1, options);} else {startActivityForResult(intent, -1);}}public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,@Nullable Bundle options) {...Instrumentation.ActivityResult ar =mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken,this,intent, requestCode, options);...}
  • Instrumentation.execStartActivity调用
    Instrumentation.java
public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target,Intent intent, int requestCode, Bundle options) {...// ActivityTaskManagerServiceint result = ActivityTaskManager.getService().startActivity(whoThread,who.getBasePackageName(),who.getAttributionTag(),intent,intent.resolveTypeIfNeeded(who.getContentResolver()),token,target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); ...
}
  • ActivityTaskManagerService.startActivityAsUser
    ActivityTaskManagerService::startActivity调用startActivityAsUser
    ActivityTaskManager.getService()其实返回的是ATMS的binder
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);

至此,startActivity 的工作重心成功地从 应用进程(app) 转移到了系统进程(system_service) 的 ATMS 中。

private int startActivityAsUser(IApplicationThread caller, String callingPackage,@Nullable String callingFeatureId, Intent intent, String resolvedType,IBinder resultTo, String resultWho, int requestCode, int startFlags,ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {assertPackageMatchesCallingUid(callingPackage);enforceNotIsolatedCaller("startActivityAsUser");userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");// getActivityStartController().obtainStarter是ActivityStarterreturn getActivityStartController().obtainStarter(intent, "startActivityAsUser").setCaller(caller).setCallingPackage(callingPackage).setCallingFeatureId(callingFeatureId).setResolvedType(resolvedType).setResultTo(resultTo).setResultWho(resultWho).setRequestCode(requestCode).setStartFlags(startFlags).setProfilerInfo(profilerInfo).setActivityOptions(bOptions).setUserId(userId).execute();}
  • ActivityStarter.execute
    ActivityStarter.java
int execute() {...res = executeRequest(mRequest);...}
private int executeRequest(Request request) {...ActivityRecord sourceRecord = null;ActivityRecord resultRecord = null;...mLastStartActivityResult = startActivityUnchecked(r, sourceRecord, voiceSession,request.voiceInteractor, startFlags, true /* doResume */, checkedOptions, inTask,restrictedBgActivity, intentGrants);...return mLastStartActivityResult;}
  • ActivityStarter.startActivityUnchecked
private int startActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,int startFlags, boolean doResume, ActivityOptions options, Task inTask,boolean restrictedBgActivity, NeededUriGrants intentGrants) {....try {...result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,startFlags, doResume, options, inTask, restrictedBgActivity, intentGrants);} finally {...}postStartActivityProcessing(r, result, startedActivityRootTask);return result;}
  • ActivityStarter.startActivityInner
int startActivityInner(final ActivityRecord r, ActivityRecord sourceRecord,IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,int startFlags, boolean doResume, ActivityOptions options, Task inTask,boolean restrictedBgActivity, NeededUriGrants intentGrants) {setInitialState(r, options, inTask, doResume, startFlags, sourceRecord, voiceSession,voiceInteractor, restrictedBgActivity);...// RootWindowContainermRootWindowContainer.resumeFocusedTasksTopActivities(mTargetRootTask, mStartActivity, mOptions, mTransientLaunch);...return START_SUCCESS;}
  • RootWindowContainer.resumeFocusedTasksTopActivities
boolean resumeFocusedTasksTopActivities(Task targetRootTask, ActivityRecord target, ActivityOptions targetOptions,boolean deferPause) {...boolean result = false;if (targetRootTask != null && (targetRootTask.isTopRootTaskInDisplayArea()|| getTopDisplayFocusedRootTask() == targetRootTask)) {// Taskresult = targetRootTask.resumeTopActivityUncheckedLocked(target, targetOptions,deferPause);}...return result;}
  • Task.resumeTopActivityInnerLocked
    Task.java
 private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {...if (mResumedActivity != null) {pausing |= startPausingLocked(userLeaving, false , next); // 这里会调用Launcher的pause}...// ActivityTaskSupervisormStackSupervisor.startSpecificActivity(next, true, false);...}
  • ActivityStackSupervisor::startSpecificActivity
    ActivityStackSupervisor会判断进程是否存在,存在则调用realStartActivityLocked,不存在则调用startProcessAsync创建其实是通过Zygote来fork创建进程,进程创建后还是会调用realStartActivityLocked的。
    在这里插入图片描述

Zygote 与应用进程创建

Zygote

Zygote:AMS通过Zygote进程来创建新的应用程序进程(App)。Zygote 是 Android 系统中一个特殊的进程,是所有 Java 应用程序进程的孵化器,它在系统启动时就已启动并处于就绪状态。Zygote 通过fork系统调用创建新的应用进程,新进程会继承 Zygote 进程的一些初始化状态,提高应用启动效率。

进程的创建

在这里插入图片描述
来看看mService.startProcessAsync

  • ActivityManagerInternal.startProcess()
/*** Activity manager local system service interface.*/
public abstract class ActivityManagerInternal {/** Starts a given process. */// 调用的是ActivityManagerService.LocalService.startProcess()--》ActivityManagerService.startProcessLockedpublic abstract void startProcess(String processName, ApplicationInfo info,boolean knownToBeDead, boolean isTop, String hostingType, ComponentName hostingName);
}
  • ActivityManagerService.startProcessLocked
/*** Process management.*/final ProcessList mProcessList;@GuardedBy("this")final ProcessRecord startProcessLocked(String processName,ApplicationInfo info, boolean knownToBeDead, int intentFlags,HostingRecord hostingRecord, int zygotePolicyFlags, boolean allowWhileBooting,boolean isolated, boolean keepIfLarge) {// ProcessList,最终调用的是ProcessList.startProcess()-->Process.startreturn mProcessList.startProcessLocked(processName, info, knownToBeDead, intentFlags,hostingRecord, zygotePolicyFlags, allowWhileBooting, isolated, 0 /* isolatedUid */,keepIfLarge, null /* ABI override */, null /* entryPoint */,null /* entryPointArgs */, null /* crashHandler */);}
  • Process.start
/*** State associated with the zygote process.*/public static final ZygoteProcess ZYGOTE_PROCESS = new ZygoteProcess();public static ProcessStartResult start(@NonNull final String processClass,@Nullable final String niceName,int uid, int gid, @Nullable int[] gids,int runtimeFlags,int mountExternal,int targetSdkVersion,@Nullable String seInfo,@NonNull String abi,@Nullable String instructionSet,@Nullable String appDataDir,@Nullable String invokeWith,@Nullable String packageName,int zygotePolicyFlags,boolean isTopApp,@Nullable long[] disabledCompatChanges,@Nullable Map<String, Pair<String, Long>>pkgDataInfoMap,@Nullable Map<String, Pair<String, Long>>whitelistedDataInfoMap,boolean bindMountAppsData,boolean bindMountAppStorageDirs,@Nullable String[] zygoteArgs) {return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids,runtimeFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, invokeWith, packageName,zygotePolicyFlags, isTopApp, disabledCompatChanges,pkgDataInfoMap, whitelistedDataInfoMap, bindMountAppsData,bindMountAppStorageDirs, zygoteArgs);}
  • ZygoteProcess.start
    ZygoteProcess 最核心的作用是借助与 Zygote 进程的通信,创建新的应用进程
public final Process.ProcessStartResult start(...) {return startViaZygote(processClass, niceName, uid, gid, gids,runtimeFlags, mountExternal, targetSdkVersion, seInfo,abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false,packageName, zygotePolicyFlags, isTopApp, disabledCompatChanges,pkgDataInfoMap, allowlistedDataInfoList, bindMountAppsData,bindMountAppStorageDirs, zygoteArgs);}

会通过LocalSocket与Zygote建立socket连接,沟通Zygote fork进程
在这里插入图片描述

  • ZygoteConnection.forkAndSpecialize
    这个时候还是在.cpp中执行,fork子进程,native方法,内部实现就是c语言的fork函数调用。
    在这里插入图片描述
  • ZygoteConnection.handleChildProc
    ZygoteConnection.java这个时候跑到java,这里是子进程的代码了,
    在这个函数中会调用ZygoteInit
    在这里插入图片描述
  • ZygoteInit.ZygoteInit
    ZygoteInit.java
    在这里插入图片描述
  • nativeZygoteInit
    AndroidRuntime.cpp
    在这里插入图片描述
    在这里插入图片描述
  • ProcessState构造创建Binder
    ProcessState.cpp
    在这里插入图片描述构造ProcessState时就会调用open_driver(“/dev/binder”),打开binder驱动设备,然后通过mmap向binder驱动申请空间。
    在这里插入图片描述
    mmap时,会申请BINDER_VM_SIZE大小的空间,不到1M,1M减2个内存页的大小。
    在这里插入图片描述
    然后接着onZygoteInit会调用proc->processThreadPool()启动binder线程池,默认最大线程数为15。
  • RuntimeInit.findStaticMain
    与Zygote通过socket通信时,会传参数"android.app.ActivityThread"过来,通过findStaticMain定位到ActivityThread.main。
    在这里插入图片描述
    在这里插入图片描述

这样fork进程后会跳转到新进程的main入口,就是ActivityThread.main函数。

应用进程初始化

  • ActivityThread:应用进程创建后,首先执行ActivityThread.main()方法,这是应用程序主线程(UI 线程)的入口。接着依次调用AT.attach()和AT.bindApplication()方法,完成主线程与系统的绑定以及应用程序的初始化相关操作 。
ActivityThread.javapublic static void main(String[] args) {...Looper.prepareMainLooper();...ActivityThread thread = new ActivityThread();thread.attach(false, startSeq);...Looper.loop();...}private void attach(boolean system, long startSeq) {final IActivityManager mgr = ActivityManager.getService();try {mgr.attachApplication(mAppThread, startSeq);} catch (RemoteException ex) {throw ex.rethrowFromSystemServer();}}

ActivityManagerService.java

private boolean attachApplicationLocked(@NonNull IApplicationThread thread,int pid, int callingUid, long startSeq) {...// 发送MH.BIND_APPLICATION的Message,在Handler中调用了Application的onCreatethread.bindApplication(processName, appInfo, providerList,instr2.mClass,profilerInfo, instr2.mArguments,instr2.mWatcher,instr2.mUiAutomationConnection, testMode,mBinderTransactionTrackingEnabled, enableTrackAllocation,isRestrictedBackupMode || !normalMode, app.isPersistent(),new Configuration(app.getWindowProcessController().getConfiguration()),app.compat, getCommonServicesLocked(app.isolated),mCoreSettingsObserver.getCoreSettingsLocked(),buildSerial, autofillOptions, contentCaptureOptions,app.mDisabledCompatChanges);...// 这里会最终调用到ActivityStackSupervisor.realStartActivityLockeddidSomething = mAtmInternal.attachApplication(app.getWindowProcessController());...}
  • realStartActivityLocked会调用到ActivityThread.performLaunchActivity()
    进程创建后,开始启动Activity
    ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {// 创建Context,Context:初始化过程中会构建Context(上下文),它是应用程序环境的抽象类,提供了应用程序运行所需的各种信息和操作接口,如资源访问、系统服务获取等。ContextImpl appContext = createBaseContextForActivity(r);...//反射创建Activityjava.lang.ClassLoader cl = appContext.getClassLoader();activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);...// AMS通过反射创建APP的Application对象,调用Application的attach函数,这个其实是Application.attachBaseContent(),然后才是调用Application.onCreate()方法,Application是应用程序全局的一个基类,可用于在应用生命周期内保存全局状态和进行一些初始化操作Application app = r.packageInfo.makeApplication(false, mInstrumentation);...// 调用Activity.attachactivity.attach(appContext, this, getInstrumentation(), r.token,r.ident, app, r.intent, r.activityInfo, title, r.parent,r.embeddedID, r.lastNonConfigurationInstances, config,r.referrer, r.voiceInteractor, window, r.configCallback,r.assistToken);...activity.setTheme(theme);...// 调用Activity.onCreatemInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
}

Application

在这里插入图片描述
如图:
createApplContext创建Context,
newApplication是通过反射创建Application对象app并会调用attachBaseContext,
callApplicationOnCreate则是调用onCreate,
在这里插入图片描述如图:
newApplication中是调用的反射创建Application,然后调用attachBaseContext
在这里插入图片描述
ApplicationThread.scheduleTransaction,调用ActivityThread.handleLaunchActivity

Activity 启动与显示

  • Activity 启动:ActivityThread中performLaunchActivity()方法先创建Application后再启动Activity。先调用Activity.onCreate()方法创建Activity实例,完成Activity的初始化工作,如设置布局等。
  • 设置布局与关联窗口:通过Activity.setContentView()设置Activity的布局文件。然后调用Activity.onResume()方法,此时AMS将Activity与WindowManagerService进行关联,完成窗口的创建和显示相关操作,最终将应用界面展示给用户。

优化启动时黑白屏现象

从启动到显示应用APP的首页过程经历如下过程:
在这里插入图片描述
这过程默认是显示黑白屏或是背景图的。

可优化的阶段

Application阶段

在这里插入图片描述
上面的阶段中,从attachBaseContent开始都是可以优化的阶段。
注意Provider是在onCreate之后,如下图:
在这里插入图片描述
makeApplication中会调用Application的onCreate的,然后installContentProviders才会调用。

相关优化
  • 4.x 之前的 multidex 加载优化:在早期 Android 版本(4.x 之前),应用如果包含多个 dex 文件(multidex),需要特殊的加载优化策略,因为当时系统对多 dex 支持不完善。
  • 加固热修复导致延迟:应用经过加固和热修复处理后,在执行 attachBaseContext 时可能会出现延迟情况,这是因为加固和热修复操作增加了额外的代码加载和处理逻辑。
  • 开源库中的 Provider 初始化:很多开源库会使用 Content Provider 来提供数据共享等功能,这里需要对其进行初始化。
  • 自己项目的 provider 初始化:项目中自定义的 Content Provider 也需要在此步骤进行初始化,确保其能够正常工作。
  • Application::onCreate:优化最大的部分为异步、按需加载、懒加载优化。通过异步加载和按需、懒加载机制,可以避免在应用启动时一次性加载过多资源,从而加快应用启动速度,提升用户体验。

Activity阶段

在这里插入图片描述

  1. Activity::onCreate
  • XML 文件解析,反射:Android 通过解析布局的 XML 文件来创建 View,这个过程会用到反射机制实例化 XML 中定义的 View 对象。
  • 异步并发构建 Viewtree:为加快构建速度,可采用异步并发方式构建 View 树,避免主线程阻塞,提升用户体验。
  1. View构建
  • 将 Inflate 过程最大异步化:布局填充(Inflate)是将 XML 布局文件转化为 View 对象的过程,将其异步化,能减少主线程负载,防止 ANR(应用无响应)。
  • 利用 X2C 解决加载速度:X2C(XML to Java Class)技术可将 XML 布局转换为 Java 代码,减少解析 XML 的开销,加快 View 的加载速度。
  1. View显示
  • View 的度量布局和显示:通过正确的测量(Measure)、布局(Layout)和绘制(Draw)流程,确保 View 能正确显示在屏幕上。
  • 层级优化:减少 View 层级嵌套,避免过度复杂的布局结构,降低绘制和渲染成本。
  • 布局优化:精简布局,合理使用布局容器,如用 ConstraintLayout 替代嵌套的 LinearLayout 等,提升布局渲染效率。

数据加载阶段

在这里插入图片描述

  1. 数据预加载:在用户触发数据展示操作前,提前加载数据。比如在应用启动时或进入某个页面之前,提前加载该页面可能用到的数据,减少用户等待时间,提升响应速度。
  2. 数据缓存机制:将已加载的数据存储在缓存中(如内存缓存、磁盘缓存 ),当再次需要相同数据时,优先从缓存读取,避免重复从原始数据源获取,降低网络请求或磁盘读取开销,加快数据展示速度。
  3. 显示数据加载优先级调度:根据 UI 界面中不同数据展示区域的重要性,设置数据加载优先级。例如,先加载屏幕可见区域的数据,后加载不可见区域的数据;或者优先加载用户更关注的关键信息数据,确保重要数据优先展示,提升用户体验。

Framework学习系列文章

Android Framework学习一:系统框架、启动过程
Android Framework学习二:Activity创建及View绘制流程
Android Framework学习三:zygote
Android Framework学习四:APP速度优化
Android Framework学习五:APP启动过程原理及速度优化
作者:帅得不敢出门

相关文章:

Android Framework学习五:APP启动过程原理及速度优化

文章目录 APP启动优化概述APP启动流程点击图片启动APP的过程启动触发Zygote 与应用进程创建Zygote进程的创建应用进程初始化 ApplicationActivity 启动与显示 优化启动时黑白屏现象可优化的阶段Application阶段相关优化 Activity阶段数据加载阶段 Framework学习系列文章 APP启动…...

Meta的AIGC视频生成模型——Emu Video

大家好&#xff0c;这里是好评笔记&#xff0c;公主号&#xff1a;Goodnote&#xff0c;专栏文章私信限时Free。本文详细介绍Meta的视频生成模型Emu Video&#xff0c;作为Meta发布的第二款视频生成模型&#xff0c;在视频生成领域发挥关键作用。 &#x1f33a;优质专栏回顾&am…...

Axure难点解决分享:统计分析页面引入Echarts示例动态效果

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢! Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:统计分析页面引入Echarts示例动态效果 主要内容:echart示例引入、大小调整、数据导入 应用场景:统计分析页面…...

Docker 常见问题及其解决方案

一、安装与启动问题 1.1 安装失败 在不同操作系统上安装 Docker 时&#xff0c;可能会出现安装失败的情况。例如&#xff0c;在 Ubuntu 系统中&#xff0c;执行安装命令后提示依赖缺失。这通常是因为软件源配置不正确或系统缺少必要的依赖包。 解决方案&#xff1a; 确保系统…...

后端开发面试高频50个问题,简单解答

以下是后端开发面试中常见的50个高频问题及其详细解答&#xff0c;涵盖了语言基础、数据库、网络、操作系统、设计模式等多个方面&#xff1a; 编程语言基础 Java 中的 final 关键字有什么作用&#xff1f; final 可以修饰类、方法和变量。修饰类时&#xff0c;类不能被继承&am…...

IC解析之TPS92682-Q1(汽车LED灯控制IC)

目录 1 IC特性介绍2 主要参数3 接口定义4 工作原理分析TPS92682-Q1架构工作模式典型应用通讯协议 控制帧应答帧协议5 总结 1 IC特性介绍 TPS92682 - Q1 是德州仪器&#xff08;TI&#xff09;推出的一款双通道恒压横流控制器&#xff0c;同时还具有各种电器故障保护&#xff0c…...

6.01 Python中打开usb相机并进行显示

本案例介绍如何打开USB相机并每隔100ms进行刷新的代码,效果如下: 一、主要思路: 1. 打开视频流、读取帧 self.cam_cap = cv2.VideoCapture(0) #打开 视频流 cam_ret, cam_frame = self.cam_cap.read() //读取帧。 2.使用定时器,每隔100ms读取帧 3.显示到Qt的QLabel…...

2023华为od统一考试B卷【二叉树中序遍历】

前言 博主刷的华为机考题&#xff0c;代码仅供参考&#xff0c;因为没有后台数据&#xff0c;可能有没考虑到的情况 如果感觉对你有帮助&#xff0c;请点点关注点点赞吧&#xff0c;谢谢你&#xff01; 题目描述 思路 0.用Character数组存储树&#xff0c;index下标的左右…...

计算机网络:计算机之间的数据传输为什么要以时钟频率同步为基础?

以太网信息同步需要保障时钟同步的主要原因包括以下几点&#xff1a; 1. 确保数据的准确采样与解析 比特级同步&#xff1a;以太网数据传输以连续的比特流形式进行&#xff0c;接收端需在精确的时间点对信号采样。若发送端与接收端时钟不同步&#xff0c;采样时机偏移会导致误…...

在Spark搭建YARN

&#xff08;一&#xff09;什么是SparkONYarn模式 Spark on YARN&#xff08;Yet Another Resource Negotiator&#xff09;是 Spark 框架在 Hadoop 集群中运行的一种部署模式&#xff0c;它借助 Hadoop YARN 来管理资源和调度任务。 架构组成 ResourceManager&#xff1a;作…...

LeetCode_sql刷题(3482.分析组织层级)

题目描述&#xff1a;3482. 分析组织层级 - 力扣&#xff08;LeetCode&#xff09; 表&#xff1a;Employees ------------------------- | Column Name | Type | ------------------------- | employee_id | int | | employee_name | varchar | | manager_id …...

Python 之 selenium 打开浏览器指定端口进行接续操作

一般使用 selenium 进行数据爬取时&#xff0c;常用处理流程是让 selenium 从打开浏览器开始&#xff0c;完成全流程的所有操作。但是有时候&#xff0c;我们希望用户先自己打开浏览器进入指定网页&#xff0c;完成登录认证等一系列操作之后&#xff08;比如用户、密码、短信验…...

MySQL Explain 中 Type 与 Extra 字段详解

引言 在数据库性能调优过程中&#xff0c;理解执行计划&#xff08;EXPLAIN&#xff09;的输出信息至关重要。MySQL 的 EXPLAIN 命令能够帮助开发者分析查询的执行路径和效率&#xff0c;其中 Type 和 Extra 字段提供了关键的执行细节。Type 字段表示访问类型&#xff0c;反映…...

不用服务器转码,Web端如何播放RTSP视频流?

在物联网、智慧城市、工业互联网等新兴技术浪潮下&#xff0c;实时视频流&#xff08;如RTSP协议&#xff09;作为安防监控、生产巡检、远程协作等场景的核心数据载体&#xff0c;其价值愈发凸显。然而&#xff0c;一个长期困扰行业的痛点始终存在——‌如何在Web浏览器中直接播…...

如何开发一款 Chrome 浏览器插件

Chrome是由谷歌开发的网页浏览器&#xff0c;基于开源软件&#xff08;包括WebKit和Mozilla&#xff09;开发&#xff0c;任何人都可以根据自己需要使用、修改或增强它的功能。Chrome凭借着其优秀的性能、出色的兼容性以及丰富的扩展程序&#xff0c;赢得了广大用户的信任。市场…...

GitHub打开缓慢甚至失败的解决办法

在C:\Windows\System32\drivers\etc的hosts中增加如下内容&#xff1a; 20.205.243.166 github.com 199.59.149.236 github.global.ssl.fastly.net185.199.109.153 http://assets-cdn.github.com 185.199.108.153 http://assets-cdn.github.com 185.199.110.153 http://asset…...

18前端项目----Vue项目收尾优化|重要知识

收尾/知识点汇总 项目收尾二级路由未登录全局路由守卫路由独享守卫图片懒加载路由懒加载打包上线 重要知识点汇总组件通信方式1. props2. 自定义事件3. 全局事件总线4. 订阅与发布pubsub5. Vuex6. 插槽 sync修饰符attrs和listeners属性children和parent属性mixin混入作用域插槽…...

仿RabbitMQ 模拟实现消息队列

文章目录 项目项目介绍开发环境技术选型 开始项目前第三方框架内容介绍muduo搭建服务端&#xff0c;客户端服务端&#xff1a;客户端&#xff1a;makefile muduo库protobuf通信服务端&#xff1a;客户端 sqlitegtest线程池future 认识&#xff0c;async使用promis使用package_t…...

基于Qt的app开发第八天

写在前面 笔者是一个大一下计科生&#xff0c;本学期的课程设计自命题完成一个督促学生自律的打卡软件&#xff0c;目前已经完成了待办和打卡部分功能&#xff0c;本篇要完成规划板块不需要存储就能实现的功能 需求分析 这一板块内容相比前两个板块还有一些特殊&#xff0c;因…...

Springboot之类路径扫描

SpringBoot框架中默认提供的扫描类为&#xff1a;ClassPathBeanDefinitionScanner。 webFlux框架中借助RepositoryComponentProvider扫描符合条件的Repository。 public class ClassPathScanningCandidateComponentProvider{private final List<TypeFilter> includeFilt…...

PNG图片转icon图标Python脚本(简易版) - 随笔

摘要 在网站开发或应用程序设计中&#xff0c;常需将高品质PNG图像转换为ICO格式图标。本文提供一份高效Python解决方案&#xff0c;利用Pillow库实现透明背景完美保留的格式转换。 源码示例 from PIL import Imagedef convert_png_to_ico(png_path, ico_path, size):"…...

数据分析-图2-图像对象设置参数与子图

from matplotlib import pyplot as mp mp.figure(A figure,facecolorgray) mp.plot([0,1],[1,2]) mp.figure(B figure,facecolorlightgray) mp.plot([1,2],[2,1]) #如果figure中标题已创建&#xff0c;则不会新建窗口&#xff0c; #而是将旧窗口设置为当前窗口 mp.figure(A fig…...

查询公网IP地址的方法:查看自己是不是公网ip,附内网穿透外网域名访问方案

本地搭建服务并提供互联网连接时&#xff0c;较为传统的方法是使用公网IP地址。因此&#xff0c;如何查询本地自己是不是公网IP&#xff0c;是必须要掌握的一种技巧。当面对确实无公网IP时&#xff0c;则可以通过内网穿透方案&#xff0c;如nat123网络映射工具&#xff0c;将本…...

MVCC:数据库并发控制的利器

在并发环境下&#xff0c;数据库需要处理多个事务同时访问和修改数据的情况。为了保证数据的一致性和隔离性&#xff0c;数据库需要采用一些并发控制机制。MVCC (Multi-Version Concurrency Control&#xff0c;多版本并发控制) 就是一种常用的并发控制技术&#xff0c;它通过维…...

Redis学习打卡-Day1-SpringDataRedis、有状态无状态

Redis的Java客户端 Jedis 以 Redis 命令作为方法名称&#xff0c;学习成本低&#xff0c;简单实用。Jedis 是线程不安全的&#xff0c;并且频繁的创建和销毁连接会有性能损耗&#xff0c;因此推荐使用 Jedis 连接池代替Jedis的直连方式。 lettuce Lettuce是基于Netty实现的&am…...

【行为型之访问者模式】游戏开发实战——Unity灵活数据操作与跨系统交互的架构秘诀

文章目录 &#x1f9f3; 访问者模式&#xff08;Visitor Pattern&#xff09;深度解析一、模式本质与核心价值二、经典UML结构三、Unity实战代码&#xff08;游戏物品系统&#xff09;1. 定义元素与访问者接口2. 实现具体元素类3. 实现具体访问者4. 对象结构管理5. 客户端使用 …...

Shell脚本实践(修改文件,修改配置文件,执行jar包)

1、前言 需要编写一个shell脚本支持 1、修改.so文件名 2、修改配置文件 3、执行jar包 2、代码解析 2.1、修改.so文件名 so_file_dir="/opt/casb/xxx/lib" # 处理.so文件 cd "$so_file_dir" || { echo "错误: 无法进入目录 $so_file_dir"; exit …...

React Native矢量图标全攻略:从入门到自定义iconfont的高级玩法

“你知道吗?在React Native应用中,仅仅通过一行代码就能召唤出上千个精美矢量图标,甚至还能把设计师精心制作的iconfont完美嵌入——但90%的开发者居然还在用图片方案!” 当我第一次发现同事的APP安装包比我的小了2.3MB,仅仅是因为他正确使用了react-native-vector-icons时…...

x-IMU matlab zupt惯性室内定位算法

基于x-IMU的ZUPT&#xff08;Zero Velocity Update&#xff0c;零速更新&#xff09;惯性室内定位算法是一种结合了惯性测量单元&#xff08;IMU&#xff09;数据和零速检测技术的室内定位方法。该算法通过检测行人静止状态下的零速区间&#xff0c;对惯性导航系统&#xff08;…...

hbase shell的常用命令

一、hbase shell的基础命令 # 版本号查看 [rootTest-Hadoop-NN-01 hbase]$ ./bin/hbase version HBase 2.4.0 Source code repository git://apurtell-ltm.internal.salesforce.com/Users/apurtell/src/hbase revision282ab70012ae843af54a6779543ff20acbcbb629# 客户端登录 […...