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

Android13-包安装器PackageInstaller-之apk安装流程

目的

  • 我们最终是为了搞明白安装的整个流程
  • 通过安卓系统自带的包安装器来了解PMS 安装流程
  • 实现需求定制:静默安装-安装界面定制-安装拦截验证。【核心目的】

安装流程和PMS了解不用多说了; 安装定制相关:

  • 手机上安装时候弹出锁屏界面需要输入密码;
  • 安装时候弹出密码框,让用户输入定制的特殊密码功能;
  • 安装页面客制化需求

安装方式

当然正常的安装分为类型我其实理解为大概3种

  • 无界面安装:PMS启动阶段 比如系统第一次启动,所有内置app自动批量安装;我们重试系统app开发时候,或者内置系统apk开发时候,删除对应的目录下的apk和apk对应的/data/分区下的apk所有安装信息后,push
    更新的apk到系统,重启。 apk 自动重新安装。

  • adb 安装: adb 命令安装,通过adb install 安装,依托守护进程来实现安装

  • 点击安装或者调用方法安装:应用市场再下载完apk后自动进入进入包管理器进行安装;sd开或者外部存储中的安装包点击安装自动进入包管理器进行安装

相关资料推荐;

承接上文,PMS安装apk之界面跳转

PackageInstaller的初始化

PackageInstaller安装APK

PMS处理APK的安装

PMS的创建过程

APK 安装流程

安装过程 界面跳转
Apk的安装过程探究

以实际应用宝安装为导向,看流程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过界面,通过日志打印,找到对应的应用,包名和类名,然后仔细分析源码。

 ACTIVITY com.android.packageinstaller/.InstallInstalling 31faba4 pid=3626ACTIVITY com.android.packageinstaller/.InstallSuccess 96d0412 pid=3626

其实就是要介绍和研究的 packageinstaller 包。

源码参考

PackageInstallerActivity

弹出安装弹框;根据条件弹出 未知来源 是否打开安装权限弹框

onCreate

初始化安装相关的对象

  • PackageManager mPm;
  • IPackageManager mIpm;
  • AppOpsManager mAppOpsManager;
  • UserManager mUserManager;
  • PackageInstaller mInstaller;

onResume

bindUi()

安装确认弹框

startInstall()

配置intent,跳转到 InstallInstalling 界面跳转

private void startInstall() {// Start subactivity to actually install the applicationIntent newIntent = new Intent();newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,mPkgInfo.applicationInfo);newIntent.setData(mPackageURI);newIntent.setClass(this, InstallInstalling.class);String installerPackageName = getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME);if (mOriginatingURI != null) {newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);}if (mReferrerURI != null) {newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);}if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);}if (installerPackageName != null) {newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,installerPackageName);}if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);}newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);if (mLocalLOGV) Log.i(TAG, "downloaded app uri=" + mPackageURI);startActivity(newIntent);finish();}
checkIfAllowedAndInitiateInstall
private void checkIfAllowedAndInitiateInstall() {// Check for install apps user restriction first.final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {if (mLocalLOGV) Log.i(TAG, "install not allowed: " + UserManager.DISALLOW_INSTALL_APPS);showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);return;} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {if (mLocalLOGV) {Log.i(TAG, "install not allowed by admin; showing "+ Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS);}startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));finish();return;}if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {if (mLocalLOGV) Log.i(TAG, "install allowed");initiateInstall();} else {// Check for unknown sources restrictions.final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM& (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);if (systemRestriction != 0) {if (mLocalLOGV) Log.i(TAG, "Showing DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER");showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);} else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);} else {handleUnknownSources();}}}

InstallInstalling

看类注释:

/*** Send package to the package manager and handle results from package manager. Once the* installation succeeds, start {@link InstallSuccess} or {@link InstallFailed}.* <p>This has two phases: First send the data to the package manager, then wait until the package* manager processed the result.</p>*/

从类注释上看,做了3个动作:

  • 将包发送到管理器进行安装,其实就是将安装包发送个系统PMS进行安装
  • 等待安装结果
  • 回调安装结果,成功就跳转到成功界面,失败就跳转到失败界面

下面我们具体看看做了哪些具体工作

注册回调监听 InstallEventReceiver.addObserver

在onCreate 方法中,看addObserver 方法
看源码注释:为安装结果注册监听,可能回调 直到结果分发回调回来

 Reregister for result; might instantly call back if result was delivered while// Reregister for result; might instantly call back if result was delivered while// activity was destroyedtry {InstallEventReceiver.addObserver(this, mInstallId,this::launchFinishBasedOnResult);} catch (EventResultPersister.OutOfIdsException e) {// Does not happen
}
安装结果回调 launchFinishBasedOnResult

安装成功和失败的回调

  /*** Launch the appropriate finish activity (success or failed) for the installation result.** @param statusCode    The installation result.* @param legacyStatus  The installation as used internally in the package manager.* @param statusMessage The detailed installation result.*/private void launchFinishBasedOnResult(int statusCode, int legacyStatus, String statusMessage) {if (statusCode == PackageInstaller.STATUS_SUCCESS) {launchSuccess();} else {launchFailure(statusCode, legacyStatus, statusMessage);}}

根据结果跳转到成功或失败的界面 InstallSuccess.class or InstallFailed.class

创建安装的session createSession 拼装 creassion的params


还是在onCreate 方法中看createSession 操作
mSessionId = getPackageManager().getPackageInstaller().createSession(params) 

理解:createSession
我们很多地方其实都有Session 的概念,从后台开发 服务器-浏览器的角度来说 Session 就是一次会话,在前端也可以这么理解
相机开发中,进行拍照录像 也是一个会话Session 创建,也是需要params,然后commit 提交操作。

这里对createSession 的这个过程 贴上部分代码:PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);final Uri referrerUri = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);params.setPackageSource(referrerUri != null ? PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE: PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE);params.setInstallAsInstantApp(false);params.setReferrerUri(referrerUri);params.setOriginatingUri(getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI));params.setOriginatingUid(getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,UID_UNKNOWN));params.setInstallerPackageName(getIntent().getStringExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME));params.setInstallReason(PackageManager.INSTALL_REASON_USER);File file = new File(mPackageURI.getPath());try {final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();final ParseResult<PackageLite> result = ApkLiteParseUtils.parsePackageLite(input.reset(), file, /* flags */ 0);if (result.isError()) {Log.e(LOG_TAG, "Cannot parse package " + file + ". Assuming defaults.");Log.e(LOG_TAG,"Cannot calculate installed size " + file + ". Try only apk size.");params.setSize(file.length());} else {final PackageLite pkg = result.getResult();params.setAppPackageName(pkg.getPackageName());params.setInstallLocation(pkg.getInstallLocation());params.setSize(InstallLocationUtils.calculateInstalledSize(pkg,params.abiOverride));}} catch (IOException e) {Log.e(LOG_TAG,"Cannot calculate installed size " + file + ". Try only apk size.");params.setSize(file.length());}try {mInstallId = InstallEventReceiver.addObserver(this, EventResultPersister.GENERATE_NEW_ID,this::launchFinishBasedOnResult);} catch (EventResultPersister.OutOfIdsException e) {launchFailure(PackageInstaller.STATUS_FAILURE,PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);}try {mSessionId = getPackageManager().getPackageInstaller().createSession(params);} catch (IOException e) {launchFailure(PackageInstaller.STATUS_FAILURE,PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);}}

InstallingAsyncTask 异步线程操作

在onResume 方法中,通过异步线程操作。 上述描述看,已经创建了Session 操作,其实接下来想想也是session 提交相关的操作。

  if (mInstallingTask == null) {PackageInstaller installer = getPackageManager().getPackageInstaller();PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);if (sessionInfo != null && !sessionInfo.isActive()) {mInstallingTask = new InstallingAsyncTask();mInstallingTask.execute();} else {// we will receive a broadcast when the install is finishedmCancelButton.setEnabled(false);setFinishOnTouchOutside(false);}}
doInBackground 异步后台任务

下面是doInBackground 源码说明,不就是打开session,然后通过session,通过进程通讯,写入待安装的File 文件到系统嘛。

protected PackageInstaller.Session doInBackground(Void... params) {PackageInstaller.Session session;try {session = getPackageManager().getPackageInstaller().openSession(mSessionId);} catch (IOException e) {synchronized (this) {isDone = true;notifyAll();}return null;}session.setStagingProgress(0);try {File file = new File(mPackageURI.getPath());try (InputStream in = new FileInputStream(file)) {long sizeBytes = file.length();try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {byte[] buffer = new byte[1024 * 1024];while (true) {int numRead = in.read(buffer);if (numRead == -1) {session.fsync(out);break;}if (isCancelled()) {session.close();break;}out.write(buffer, 0, numRead);if (sizeBytes > 0) {float fraction = ((float) numRead / (float) sizeBytes);session.addProgress(fraction);}}}}return session;} catch (IOException | SecurityException e) {Log.e(LOG_TAG, "Could not write package", e);session.close();return null;} finally {synchronized (this) {isDone = true;notifyAll();}}}
onPostExecute 后台操作后,执行commit 操作

源码如下,贴出来看看具体操作:
终归还是通过 session 的 commit 操作,提交到系统去,由系统来进行安装操作。

if (session != null) {Intent broadcastIntent = new Intent(BROADCAST_ACTION);broadcastIntent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);broadcastIntent.setPackage(getPackageName());broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);PendingIntent pendingIntent = PendingIntent.getBroadcast(InstallInstalling.this,mInstallId,broadcastIntent,PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);session.commit(pendingIntent.getIntentSender());mCancelButton.setEnabled(false);setFinishOnTouchOutside(false);} else {getPackageManager().getPackageInstaller().abandonSession(mSessionId);if (!isCancelled()) {launchFailure(PackageInstaller.STATUS_FAILURE,PackageManager.INSTALL_FAILED_INVALID_APK, null);}}

总结

  • PackageInstaller-之apk安装流程内容中,主要是包安装器PackageInstaller 相关内容。
    作为PMS安装apk之界面跳转 的续篇。 两篇文章规整起来就是完全分析完了。 当然,这里面还有权限相关操作只是一笔带过介绍了。
  • 主要分析了:PackageInstallerActivity InstallInstalling 两个源码的分析。 涉及到安装确认弹框-权限弹框-安装中等待弹框-注册监听安装回调-安装session创建和提交到系统

拓展

简要了解 上面介绍了Session操作,Session 操作到底操作了什么, commit 就发送到系统进行安装了呢?

我们着重看看部分代码

PackageInstaller.Session session=getPackageManager().getPackageInstaller().openSession(mSessionId)
getPackageManager().getPackageInstaller().abandonSession
OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes))
session.commit

PackageInstaller

PackageInstaller

getPackageManager().getPackageInstaller() 找到PackageInstaller 类,看看类注释介绍

注释很详细了:

  • 提供了安装、更新、移除设备上app的能力
  • app的安装操作通过PackageInstaller.Session 来实现的,一旦Session创建成功就可以将一个或者多个apk通过流的方式输送到指定的位置,直到session
    销毁或者提交
  • apk 的一些基本校验,签名、包名、版本号、基本apk等。

/*** Offers the ability to install, upgrade, and remove applications on the* device. This includes support for apps packaged either as a single* "monolithic" APK, or apps packaged as multiple "split" APKs.* <p>* An app is delivered for installation through a* {@link PackageInstaller.Session}, which any app can create. Once the session* is created, the installer can stream one or more APKs into place until it* decides to either commit or destroy the session. Committing may require user* intervention to complete the installation, unless the caller falls into one of the* following categories, in which case the installation will complete automatically.* <ul>* <li>the device owner* <li>the affiliated profile owner* </ul>* <p>* Sessions can install brand new apps, upgrade existing apps, or add new splits* into an existing app.* <p>* Apps packaged as multiple split APKs always consist of a single "base" APK* (with a {@code null} split name) and zero or more "split" APKs (with unique* split names). Any subset of these APKs can be installed together, as long as* the following constraints are met:* <ul>* <li>All APKs must have the exact same package name, version code, and signing* certificates.* <li>All APKs must have unique split names.* <li>All installations must contain a single base APK.* </ul>* <p>* The ApiDemos project contains examples of using this API:* <code>ApiDemos/src/com/example/android/apis/content/InstallApk*.java</code>.*/

Session 到底是什么

public static class Session implements Closeable {/** {@hide} */protected final IPackageInstallerSession mSession;/** {@hide} */public Session(IPackageInstallerSession session) {mSession = session;}/** {@hide} */@Deprecatedpublic void setProgress(float progress) {setStagingProgress(progress);}................}

IPackageInstallerSession 是什么

  路径: \frameworks\base\core\java\android\content\pm\IPackageInstallerSession.aidl 

它是一个aidl 文件,说明什么问题??? 说明它是一个aidl 文件,具体操作不是通过 IPackageInstallerSession 操作的,我们就需要找到 它的实现代理类。
我一般是这么来找的

查找:grep -rn PackageInstallerSession.Stub
   fise4@ubuntu-PowerEdge-R730:~/Android/mt6769-alps-release-t0.mp1.rc$ grep -rn PackageInstallerSession.Stub
frameworks/base/config/boot-image-profile.txt:33737:Landroid/content/pm/IPackageInstallerSession$Stub$Proxy;
frameworks/base/config/boot-image-profile.txt:33738:Landroid/content/pm/IPackageInstallerSession$Stub;
frameworks/base/config/preloaded-classes:1445:android.content.pm.IPackageInstallerSession$Stub$Proxy
frameworks/base/config/preloaded-classes:1446:android.content.pm.IPackageInstallerSession$Stub
frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java:187:public class PackageInstallerSession extends IPackageInstallerSession.Stub {

所以在框架层,调用的其实是 PackageInstallerSession 类的方法,我们去看看

fise4@ubuntu-PowerEdge-R730:~/Android/mt6769-alps-release-t0.mp1.rc$ find . -name PackageInstallerSession.java 
./frameworks/base/services/core/java/com/android/server/pm/PackageInstallerSession.java

根据上面我们再梳理下,通过commit 方法来梳理流程,跟踪流程->分析代码


InstallInstalling.java         ->session.commit(pendingIntent.getIntentSender())
PackageInstall.java            ->mSession.commit(statusReceiver, false);
PackageInstallerSession.java   ->dispatchSessionSealed()

如下:PackageInstallerSession.java 里面的commit 方法

public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {if (hasParentSessionId()) {throw new IllegalStateException("Session " + sessionId + " is a child of multi-package session "+ getParentSessionId() +  " and may not be committed directly.");}if (!markAsSealed(statusReceiver, forTransfer)) {return;}if (isMultiPackage()) {synchronized (mLock) {boolean sealFailed = false;for (int i = mChildSessions.size() - 1; i >= 0; --i) {// seal all children, regardless if any of them fail; we'll throw/return// as appropriate once all children have been processedif (!mChildSessions.valueAt(i).markAsSealed(null, forTransfer)) {sealFailed = true;}}if (sealFailed) {return;}}}dispatchSessionSealed();}

结语

分析到了框架层了已经,到此结束。 所有的安装层应用PackageInstaller 的代码梳理了一遍。剩下的具体安装操作在框架framework层,后续讨论。

相关文章:

Android13-包安装器PackageInstaller-之apk安装流程

目的 我们最终是为了搞明白安装的整个流程通过安卓系统自带的包安装器来了解PMS 安装流程实现需求定制&#xff1a;静默安装-安装界面定制-安装拦截验证。【核心目的】 安装流程和PMS了解不用多说了; 安装定制相关&#xff1a; 如 手机上安装时候弹出锁屏界面需要输入密码;安…...

前端函数在开发环境与生产环境中处理空字符串的差异及解决方案

在前端开发过程中&#xff0c;我们经常会遇到一些函数在开发环境中运行正常&#xff0c;但在生产环境中却出现报错的情况。本文将通过具体的代码示例和分析&#xff0c;探讨一个函数在开发环境和生产环境中处理空字符串的差异&#xff0c;并提供解决方案。 1. 问题描述 我们有…...

数智读书笔记系列014 MICK《SQL进阶教程》第一版和第二版对比和总结

引言 在当今数字化时代,数据已成为企业和组织的核心资产之一。而 SQL(Structured Query Language)作为管理和操作关系型数据库的标准语言,其重要性不言而喻。无论是数据查询、插入、更新还是删除,SQL 都能高效地完成任务,广泛应用于数据分析、数据挖掘、数据仓库、Web 开…...

智能猫眼实现流程图

物理端开发流程图 客户端端开发流程图 用户功能开发流程图 管理员开发流程图...

docker安装kafka,并通过springboot快速集成kafka

目录 一、docker安装和配置Kafka 1.拉取 Zookeeper 的 Docker 镜像 2.运行 Zookeeper 容器 3.拉取 Kafka 的 Docker 镜像 4.运行 Kafka 容器 5.下载 Kafdrop 6.运行 Kafdrop 7.如果docker pull wurstmeister/zookeeper或docker pull wurstmeister/kafka下载很慢&#x…...

Spring Boot 中自动装配机制的原理

Spring Boot 的自动装配机制是其核心特性之一&#xff0c;它简化了 Spring 应用的配置&#xff0c;让开发者能够快速构建应用。以下是对其原理的详细总结&#xff1a; 1. 核心概念 自动装配 (Auto-configuration): Spring Boot 根据应用依赖和配置&#xff0c;自动配置 Spring…...

python继承中super() 不是简单的“调用父类”,而是调用 MRO 里的下一个类

Python 里的一个类可以同时继承多个父类。这让我们的模型设计变得更灵 活&#xff0c;但同时也带来一个新问题&#xff1a;“在复杂的继承关系下&#xff0c;如何确认子类的 某个方法会用到哪个父类&#xff1f;” 这里有点需要理解&#xff1a; MRO&#xff08;方法解析顺序…...

【智慧校园】分体空调节能监管:打造高效节能的学习环境

随着科技的飞速发展和生活品质的不断提升&#xff0c;人们对于家居和办公环境的舒适度与智能化要求也越来越高。分体空调集中控制系统作为一种先进的空调管理方式&#xff0c;正逐渐成为现代家庭和办公场所的标配&#xff0c;为用户带来更加便捷、高效和节能的空调使用体验。随…...

【达梦数据库】dblink连接[SqlServer/Mysql]报错处理

目录 背景问题1&#xff1a;无法测试以ODBC数据源方式访问的外部链接!问题分析&原因解决方法 问题2&#xff1a;DBLINK连接丢失问题分析&原因解决方法 问题3&#xff1a;DBIINK远程服务器获取对象[xxx]失败,错误洋情[[FreeTDS][SQL Server]Could not find stored proce…...

STM32 ADC介绍(硬件原理篇)

目录 背景 AD转换器 采样与保持 量化 编码 AD转换器转换原理 DA转换原理 AD转换原理 1.逐次逼近型AD转换器 2.并联比较型AD转换器 编码器 同步D触发器和边沿D触发器 基本RS触发器 同步RS触发器 同步D触发器 边沿型D触发器&#xff08;维持-阻塞D触发器&#xff…...

蚁剑(AutSword)的下载安装与报错解决

蚁剑&#xff08;AutSword&#xff09;的下载安装与报错解决 1.下载 唯一官方github下载地址 GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器 2.安装 打开并且进入到下面的界面 下载需要的的版本 进行初始化 3.报错 出现下面的报错 4.解决方法 出现上面报错…...

BT401双模音频蓝牙模块如何开启ble的透传,有什么注意事项

BT401音频蓝牙模块如何开启ble的透传&#xff1f; 首先BT401的蓝牙音频模块&#xff0c;分为两个版本&#xff0c;dac版本和iis数字音频版本 DAC版本&#xff1a;就是BT401蓝牙模块【9和10脚】直接输出模拟音频信号&#xff0c;也就是说&#xff0c;直接推动耳机可以听到声音 …...

209. 长度最小的子数组

这个题目之前做过是用c语言写的 但是我这里用python来写 写的不是很好 感觉自己这里写的还是有问题 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 子数组 [numsl, numsl1, ..., numsr-1, numsr] &#xff0c;…...

使用IDEA创建Maven项目、Maven坐标,以及导入Maven项目

一、创建Maven项目 正如使用Vue创建工程化的前端项目&#xff0c;此时&#xff0c;使用Maven创建标准化的后端项目。 以下适用于2021版本的IDEA。 name和artifactid会自动保持一致。 Groupid&#xff1a;一般写公司域名倒写&#xff0c;再加上项目名&#xff08;也可以不…...

从零到一实现微信小程序计划时钟:完整教程

在本教程中&#xff0c;我们将一起实现一个微信小程序——计划时钟。这个小程序的核心功能是帮助用户添加任务、设置任务的时间范围&#xff0c;并且能够删除和查看已添加的任务。通过以下步骤&#xff0c;我们将带你从零开始实现一个具有基本功能的微信小程序计划时钟。 项目…...

最长回文子串(蓝桥云课)

题目链接&#xff1a;8.最长回文子串 - 蓝桥云课 (lanqiao.cn) 代码如下 # include <iostream> # include <cstring> using namespace std; int main() {string str;getline(cin, str);int res 0;for(int i 0; i < str.length(); i){int l i - 1, r i 1;…...

12-滑动窗口

一&#xff0c;定义 滑动窗口算法是一种用于处理数组或字符串问题的技巧&#xff0c;特别适合解决涉及子数组或子串的问题。它的核心思想是通过维护一个“窗口”来高效地计算或查找满足条件的子数组或子串。 基本概念 窗口&#xff1a;窗口是数组或字符串中的一个连续子区间&a…...

时间序列分析(五)——移动平均模型(MA模型)

此前篇章&#xff1a; 时间序列分析&#xff08;一&#xff09;——基础概念篇 时间序列分析&#xff08;二&#xff09;——平稳性检验 时间序列分析&#xff08;三&#xff09;——白噪声检验 时间序列分析&#xff08;四&#xff09;——差分运算、延迟算子、AR(p)模型 …...

eNSP防火墙综合实验

一、实验拓扑 二、ip和安全区域配置 1、防火墙ip和安全区域配置 新建两个安全区域 ip配置 Client1 Client2 电信DNS 百度web-1 联通DNS 百度web-2 R2 R1 三、DNS透明代理相关配置 1、导入运营商地址库 2、新建链路接口 3、配置真实DNS服务器 4、创建虚拟DNS服务器 5、配置D…...

ES8字符串填充用法总结:padStart(),padEnd(),rest剩余参数的用法{name,...obj},扩展运算符的用法,正则表达式命名捕获组

ES8&#xff08;ECMAScript 2017&#xff09;引入了两个非常有用的字符串填充方法&#xff1a;padStart() 和 padEnd()&#xff0c;它们可以用来在字符串的两端添加指定的填充字符&#xff0c;从而达到指定的字符串长度。这些方法非常适合用于格式化文本和对齐输出。 1. padSt…...

Redis 统计每个数据类型中占用内存最多的前 N 个 bigkey

Redis 统计每个数据类型中占用内存最多的前 N 个 bigkey import redisdef find_bigkeys(hostlocalhost, port6379, db0, n10):r redis.Redis(hosthost, portport, db0)bigkeys {}# 用于存储每种数据类型的键及内存占用type_memory_dict {}# 扫描所有键for key in r.scan_it…...

C++中的.*运算符

看运算符重载的时候&#xff0c;看到这一句 .* :: sizeof ?: . 注意以上5个运算符不能重载。 :: sizeof ?: . 这四个好理解&#xff0c;毕竟都学过&#xff0c;但.*是什么&#xff1f; 于是自己整理了一下 .* 是一种 C 中的运算符&#xff0c;称为指针到成…...

大语言模型简史:从Transformer(2017)到DeepSeek-R1(2025)的进化之路

2025年初&#xff0c;中国推出了具有开创性且高性价比的「大型语言模型」&#xff08;Large Language Model — LLM&#xff09;DeepSeek-R1&#xff0c;引发了AI的巨大变革。本文回顾了LLM的发展历程&#xff0c;起点是2017年革命性的Transformer架构&#xff0c;该架构通过「…...

14-H指数

给你一个整数数组 citations &#xff0c;其中 citations[i] 表示研究者的第 i 篇论文被引用的次数。计算并返回该研究者的 h 指数。 根据维基百科上 h 指数的定义&#xff1a;h 代表“高引用次数” &#xff0c;一名科研人员的 h 指数 是指他&#xff08;她&#xff09;至少发…...

Param ‘serviceName‘ is illegal, serviceName is blank

今天测试nacos服务配置拉取时报了这样一个错误&#xff0c;发现是spring.application.name空值造成的&#xff0c;但是我的bootstrap.yml文件明明配置了&#xff0c;难不成是没有加载bootstrap.yml文件&#xff1f;于是我引入了下面的依赖 <dependency><groupId>o…...

深入剖析Spring MVC

一、Spring MVC 概述 1. 什么是 Spring MVC&#xff1f; Spring MVC 是基于 Spring 框架的 Web 框架&#xff0c;它实现了 MVC 设计模式&#xff0c;将应用程序分为三个核心部分&#xff1a; Model&#xff1a;封装应用程序的数据和业务逻辑。 View&#xff1a;负责渲染数据…...

LLM:RAG

原文链接&#xff1a;LLM&#xff1a;RAG 1、RAG 概览 RAG&#xff08;Retrieval-Augmented Generation&#xff0c;检索增强生成&#xff09;是一种结合了信息检索&#xff08;IR&#xff09;和 LLM 的技术。它的核心思想是在 LLM 生成回答之前&#xff0c;通过检索相关文档…...

Linux 信号量

Linux 信号量 一、信号量基础概念1.1 同步机制的核心需求1.2 信号量的核心原理1.3 信号量类型对比 二、实战代码解析2.1 共享内存与信号量结合示例2.2 信号量类实现要点 三、关键实现细节分析3.1 初始化三步骤3.2 SEM_UNDO机制3.3 原子操作保证 四、进阶应用场景4.1 生产者-消费…...

如何优化Spark作业的性能

优化Spark作业的性能是一个综合性的任务&#xff0c;涉及多个方面的调整和优化。以下是一些关键的优化策略&#xff1a; 一、开发调优 避免创建重复的RDD&#xff1a; 对于同一份数据&#xff0c;只应该创建一个RDD&#xff0c;避免多次创建RDD来增加性能开销。在对不同的数据…...

ERP对制造业务有何价值?

ERP 的定义 在定义 ERP 之前&#xff0c;我们先从其首字母缩写说起&#xff0c;ERP 代表企业资源规划。我们可以将 ERP 定义为一种企业软件&#xff0c;它帮助组织管理日常业务。从根本上讲&#xff0c;ERP 将客户管理、人力资源、商业智能、财务管理、库存以及供应链功能整合…...

python和pycharm 和Anaconda的关系

好的&#xff0c;下面我会详细说明 Python、PyCharm 和 Anaconda 三者的关系&#xff0c;并逐一解释它们的功能和作用。 1. Python&#xff08;编程语言&#xff09; 定义&#xff1a;Python 是一种高级编程语言&#xff0c;设计简洁&#xff0c;易于学习&#xff0c;且功能强…...

网络安全“挂图作战“及其场景

文章目录 一、网络安全挂图作战来源与定义1、网络安全挂图作战的来源2、网络安全挂图作战的定义 二、挂图作战关键技术三、挂图作战与传统态势感知的差异四、挂图作战主要场景五、未来趋势结语 一、网络安全挂图作战来源与定义 1、网络安全挂图作战的来源 网络安全挂图作战的…...

【对比】Pandas 和 Polars 的区别

Pandas vs Polars 对比表 特性PandasPolars开发语言Python&#xff08;Cython 实现核心部分&#xff09;Rust&#xff08;高性能系统编程语言&#xff09;性能较慢&#xff0c;尤其在大数据集上&#xff08;内存占用高&#xff0c;计算效率低&#xff09;极快&#xff0c;利用…...

投资组合风险管理

投资组合风险管理 市场风险 信用风险流动性风险风险指标收益率波动率最大回撤 α \alpha α&#xff08;詹森指数&#xff09;&#xff0c; β \beta β卡玛比率月胜率上/下行捕获比夏普比率索提诺比率经风险调整的收益率&#xff08;&#x1d440;2&#xff09;特雷诺比率信息…...

用 WOW.js 和 animate.css 实现动画效果

用 wow.js 就可以实现动画效果&#xff0c;但由于里面的动画样式太少&#xff0c;一般还会引入 animated.css 第一步&#xff1a;下载 选择合适的包管理器下载对应的内容 pnpm i wow.js animated.css --save 第二步&#xff1a;引入 在main.js中加入&#xff1a; import …...

2024年职高单招或高考计算机类投档线

问题&#xff1a; 这些学校2024年职高单招或高考计算机类投档线分别是多少 回答&#xff1a; 部分学校2024年职高单招或高考计算机类投档线如下&#xff1a; 湖南工业职业技术学院 职高单招&#xff1a;未查询到2024年职高单招计算机类专业明确的录取分数线信息。但在2024年…...

洛谷P11042 [蓝桥杯 2024 省 Java B] 类斐波那契循环数

像是这种填空题的话&#xff0c;就直接暴力还更加省时间&#xff0c;在本地算完后直接提交答案即可 #include<bits/stdc.h> using namespace std;const int N 10000000;bool isnumber(int n) {vector<int> a;int m n;while (n > 0) {a.push_back(n % 10);n / …...

[LeetCode]day25 151.翻转字符串里的单词

题目链接 题目链接 给你一个字符串 s &#xff0c;请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意&#xff1a;输入字符串 s中可能…...

2026考研趋势深度解析:政策变化+高效工具指南

2026考研深度解析&#xff1a;趋势洞察高效工具指南&#xff0c;助你科学备战上岸 从政策变化到工具实战&#xff0c;这份千字攻略解决99%考生的核心焦虑 【热点引入&#xff1a;考研赛道进入“高难度模式”】 2025年全国硕士研究生报名人数突破520万&#xff0c;报录比预计扩…...

【软考】【2025年系统分析师拿证之路】【啃书】第十一章 软件需求工程(十二)

目录 需求的层次需求工程的主要活动需求的记录技术需求分析需求定义 需求的层次 业务需求用户需求系统需求 需求工程的主要活动 需求获取需求分析形成需求规格需求确认和验证需求管理 需求的记录技术 任务卡片场景说明用户故事 需求分析 方法&#xff1a; 结构化分析&a…...

Cherry Studio 接入本地ollama 搭建可视化deepseek

本文介绍本地下载ollama 后&#xff0c;在cherry studio 中添加本地ollama 查看本地deepseek 模型列表 ollama list 运行模型 ollama run deepseek-r1 验证可以选用ollama 模型...

MapReduce远程调试

个人博客地址:MapReduce远程调试 | 一张假钞的真实世界 MR的远程调试分为两个方面: MapReduce Task远程调试AM(Application Master)远程调试MapReduce Task远程调试 Map Task远程调试 修改mapred-site.xml中的配置项: mapreduce.map.java.opts:Map Task JVM参数设置。…...

知识拓展:设计模式之装饰器模式

装饰器模式拓展 1. 什么是装饰器模式&#xff1f; 装饰器模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;允许向一个现有的对象添加新的功能&#xff0c;同时又不改变其结构。装饰器模式通过创建一个装饰类来包装原始类&#xff0c;从而在不修…...

【深度学习】计算机视觉(CV)-目标检测-DETR(DEtection TRansformer)—— 基于 Transformer 的端到端目标检测

1.什么是 DETR&#xff1f; DETR&#xff08;DEtection TRansformer&#xff09; 是 Facebook AI&#xff08;FAIR&#xff09;于 2020 年提出的 端到端目标检测算法&#xff0c;它基于 Transformer 架构&#xff0c;消除了 Faster R-CNN、YOLO 等方法中的 候选框&#xff08;…...

C++ Primer 访问控制与封装

欢迎阅读我的 【CPrimer】专栏 专栏简介&#xff1a;本专栏主要面向C初学者&#xff0c;解释C的一些基本概念和基础语言特性&#xff0c;涉及C标准库的用法&#xff0c;面向对象特性&#xff0c;泛型特性高级用法。通过使用标准库中定义的抽象设施&#xff0c;使你更加适应高级…...

LSTM细胞状态门控设计详解:数学原理、代码实现与工业级优化方案

一、数学原理深度解析 1.1 细胞状态更新方程 LSTM通过三个门控机制精确控制细胞状态&#xff1a; 遗忘门&#xff1a;f_t σ(W_f[h_{t-1}, x_t] b_f)输入门&#xff1a; i_t σ(W_i[h_{t-1}, x_t] b_i) C̃_t tanh(W_C[h_{t-1}, x_t] b_C)状态更新&#xff1a;C_t f_…...

hive(hdfs)补数脚本

pb级别迁移通常要持续1个月以上。一般的过程是&#xff0c;全量迁移&#xff0c;追平数据&#xff0c;增量同步&#xff0c;校验&#xff0c;补数。 这里的指定补数脚本&#xff1a; 输入需要补数的表&#xff0c;如Input.txt&#xff0c;如果有分区则加补此分区&#xff0c;没…...

Python学习心得函数

一、函数的定义及调用 1.函数的定义&#xff1a; 函数的定义&#xff1a;函数是将一段能实现某种特定功能的代码&#xff0c;使用函数名进行封装&#xff0c;并通过函数名称进行调用。从而达到一次编写&#xff0c;多次调用的目的。 2.函数类型分为两类&#xff1a; &#…...

RabbitMQ服务异步通信

消息队列在使用过程中&#xff0c;面临着很多实际问题需要思考&#xff1a; 1. 消息可靠性 消息从发送&#xff0c;到消费者接收&#xff0c;会经理多个过程&#xff1a; 其中的每一步都可能导致消息丢失&#xff0c;常见的丢失原因包括&#xff1a; 发送时丢失&#xff1a; 生…...

适用于 Windows 仅 0.6MB 且免费无广告的绿色截图工具

软件介绍 YasoCut 可是一款源自 GitHub 的宝藏截图软件&#xff0c;专为 Windows 系统打造&#xff0c;亮点十足。它体积超小&#xff0c;仅有 0.6MB&#xff0c;并且简单易用、免费无广告&#xff0c;还贴心地提供了绿色版本。 这款软件的独特之处在于&#xff0c;和常见截图…...