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层,后续讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/20031.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新型基于Go语言的恶意软件利用Telegram作为C2通信渠道

研究人员发现了一种新型后门恶意软件&#xff0c;使用Go语言编写&#xff0c;并利用Telegram作为其命令与控制&#xff08;C2&#xff09;通信渠道。尽管该恶意软件似乎仍处于开发阶段&#xff0c;但它已经具备完整的功能&#xff0c;能够执行多种恶意活动。这种创新的C2通信方…

5分钟了解! 探索 AnythingLLM,借助开源 AI 打造私有化智能知识库,熟悉向量数据库

本文是系列文章&#xff0c;在前面提到安装Ollama和AnythingLLM的教程&#xff0c;本文会着重解决本地文档向量化的过程&#xff0c;同时本地应用的管理。 图1. 上传本地文档进行向量化处理 • 构建向量数据库特别慢&#xff1a;支持的文档格式很多&#xff0c;但在我的电脑32…

电商小程序(源码+文档+部署+讲解)

引言 随着移动互联网的快速发展&#xff0c;电商小程序成为连接消费者与商家的重要桥梁。电商小程序通过数字化手段&#xff0c;为消费者提供了一个便捷、高效的购物平台&#xff0c;从而提升购物体验和满意度。 系统概述 电商小程序采用前后端分离的架构设计&#xff0c;服…

基于SpringBoot的“高考志愿智能推荐系统”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“高考志愿智能推荐系统”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 系统首页界面 系统注册页…

合并多次commit记录

合并多次commit记录 1. 首先先提交多次记录2. 某个版本之前的commit记录合并 1. 首先先提交多次记录 在log中可以看到有多次commit 记录 然后拉取最新代码 (base) ➜ gaolijie git:(master) git pull --rebase origin masterFrom https://gitee.com/Blue_Pepsi_Cola/gaoliji…

哈希表(C语言版)

文章目录 哈希表原理实现(无自动扩容功能)代码运行结果 分析应用 哈希表 如何统计一段文本中&#xff0c;小写字母出现的次数? 显然&#xff0c;我们可以用数组 int table[26] 来存储每个小写字母出现的次数&#xff0c;而且这样处理&#xff0c;效率奇高。假如我们想知道字…

uniapp商城之首页模块

文章目录 前言一、自定义导航栏1.静态结构2.修改页面配置3.组件安全区适配二、通用轮播组件1. 静态结构组件2.自动导入全局组件3.首页轮播图数据获取三、首页分类1.静态结构2.首页获取分类数据并渲染四、热门推荐1.静态结构2.首页获取推荐数据并渲染3.首页跳转详细推荐页五、猜…

CNAPPgoat:一款针对云环境的安全实践靶场

关于CNAPPgoat CNAPPgoat是一款针对云环境的安全实践靶场&#xff0c;该工具旨在帮助广大研究人员在云环境中模块化地提供故意留下安全缺陷的设计组件&#xff0c;专为防御者和渗透测试人员提供练习场地而设计。 CNAPPgoat的主要功能是跨多个云服务提供商部署故意留下安全缺陷…

【学习资源】时间序列数据分析方法(2)-mWDN和AutoEncoder

接着上次的【学习资源】时间序列数据分析方法&#xff08;1&#xff09;-CSDN博客&#xff0c;本次介绍mWDN和AutoEncoder 解决时序数据分类的方法。介绍模型原理、应用场景和参考代码。也从模型性能、训练效率、模型复杂度、计算复杂度、可解释性、适应性和泛化能力、健壮性、…

【C++】stack 和 queue 的适配器模式与实现

> &#x1f343; 本系列为初阶C的内容&#xff0c;如果感兴趣&#xff0c;欢迎订阅&#x1f6a9; > &#x1f38a;个人主页:[小编的个人主页])小编的个人主页 > &#x1f380; &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐文章 > ✌️ &#x1f91e; &#x1…

Chrome多开终极形态解锁!「窗口管理工具+IP隔离插件

Web3项目多开&#xff0c;继ads指纹浏览器钱包被盗后&#xff0c;更多人采用原生chrome浏览器&#xff0c;当然对于新手&#xff0c;指纹浏览器每月成本也是一笔不小开支&#xff0c;今天逛Github发现了这样一个解决方案&#xff0c;作者开发了窗口管理工具IP隔离插件&#xff…

从零开始部署DeepSeek:基于Ollama+Flask的本地化AI对话系统

从零开始部署DeepSeek&#xff1a;基于OllamaFlask的本地化AI对话系统 一、部署背景与工具选型 在AI大模型遍地开花的2025年&#xff0c;DeepSeek R1凭借其出色的推理能力和开源特性成为开发者首选。本文将以零基础视角&#xff0c;通过以下工具链实现本地化部署&#xff1a; …

python旅游推荐系统+爬虫+可视化(协同过滤算法)

✅️基于用户的协同过滤算法 ✅️有后台管理 ✅️2w多数据集 这个旅游数据分析推荐系统采用了Python语言、Django框架、MySQL数据库、requests库进行网络爬虫开发、机器学习中的协同过滤算法、ECharts数据可视化技术&#xff0c;以实现从网站抓取旅游数据、个性化推荐和直观展…

以 Serverless 低成本的⽅式 快速在亚马逊云科技上部署 DeepSeek

2025年春节&#xff0c;最令人瞩目的无疑是DeepSeek的惊艳亮相&#xff0c;它以颠覆性的创新迅速席卷全球&#xff0c;成为街谈巷议的热点。无论是在地铁车厢里&#xff0c;还是公司茶水间&#xff0c;DeepSeek都成了人们津津乐道的话题。社交平台上&#xff0c;网友们争相分享…

win10 系统 自定义Ollama安装路径 及模型下载位置

win10 系统 自定义Ollama安装路径 及模型下载位置 由于Ollama的exe安装软件双击安装的时候默认是在C盘&#xff0c;以及后续的模型数据下载也在C盘&#xff0c;导致会占用C盘空间&#xff0c;所以这里单独写了一个自定义安装Ollama安装目录的教程。 Ollama官网地址&#xff1…

CAP与BASE:分布式系统设计的灵魂与妥协

CAP 理论 CAP理论起源于 2000 年&#xff0c;由加州大学伯克利分校的 Eric Brewer 教授在分布式计算原理研讨会&#xff08;PODC&#xff09;上提出&#xff0c;因此 CAP 定理又被称作 布鲁尔定理&#xff08;Brewer’s theorem&#xff09; 2 年后&#xff0c;麻省理工学院的 …

电动汽车电池监测平台系统设计(论文+源码+图纸)

1总体设计 本次基于单片机的电池监测平台系统设计&#xff0c;其整个系统架构如图2.1所示&#xff0c;其采用STC89C52单片机作为控制器&#xff0c;结合ACS712电流传感器、TLC1543模数转换器、LCD液晶、DS18B20温度传感器构成整个系统&#xff0c;在功能上可以实现电压、电流、…

docker下部署kong+consul+konga 报错问题处理

前言&#xff1a; 由于在docker下部署一些项目比较特殊&#xff0c;特别是网络这一块&#xff0c;如果没有搞清楚&#xff0c;是很容易出问题的。 先上docker-compose 编排 这里的docker-compose for kong可以在 kong-compose 获取代码 version: 3.9x-kong-config:&kong…

装饰器模式

参考 装饰者模式 【设计模式实战】装饰器模式 1. HistorySet的例子 HistorySet 可以在实现的Set的基础上&#xff0c;在remove时保留删除的元素。通过将方法委托给现有的Set&#xff0c;在remove时先保留被删除元素后委托给注入的set进行remove public class HistorySet<…

软件定义汽车时代的功能安全和信息安全

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…