深入解析 Android PMS —— APK 安装与解析全流程
文章目录
- 前言
- 1. PMS 的初始化
- 1.1 SystemServer 启动 PMS
- 1.2 PMS 的入口方法 main
- 1.3 PMS 构造函数
- 1.4 扫描 APK 文件
- 1.5 权限初始化
- 1.6 提供对外服务
- 2. APK 安装机制
- 2.1. 安装请求的触发
- 2.2 APK 文件解析与验证
- 2.3 签名校验
- 2.4 权限管理
- 2.4.1 权限声明
- 2.4.2 权限校验与分配
- 2.5 持久化存储
- 2.6 广播通知
- 3. APK 解析流程
- 3.1 APK 文件读取
- 3.2 APK 校验
- 3.2.1 签名校验
- 3.2.2 APK 格式校验
- 3.2.3 权限检查
- 3.2.4 其他格式校验
- 3.3 安装信息持久化
- 4. Intent 和组件管理
- 4.1 Intent 的作用
- 4.2 组件的管理
- 源码解析
- resolveIntent() 方法
- queryIntentActivities() 方法
- 5. 卸载流程
- 5.1 卸载请求的触发
- 5.2 PackageManagerService 接收到卸载请求
- 5.3 停用应用
- 5.4 删除应用数据
- 5.5 更新系统数据库
- 5.6 广播通知
- 5.7 删除 APK 文件
- 5.8 更新 UI
- 6. 持久化机制
- 6.1 持久化数据存储位置
- 6.2 数据存储格式
- 6.3 持久化流程
- 6.3.1 应用安装时持久化
- 6.3.2 应用卸载时持久化
- 6.3.3 应用更新时持久化
- 主要类
- 总结
前言
在 Android 系统中,PackageManagerService(简称 PMS)是负责管理应用程序的核心服务。无论是应用安装、卸载,还是权限分配,PMS 都扮演着至关重要的角色。
本篇文章将带你深入探讨 PMS 的幕后机制,特别是 APK 安装与解析的详细流程。
1. PMS 的初始化
步骤 | 完成任务 | 影响范围 |
---|---|---|
启动 PMS 服务 | 注册 PMS 到 ServiceManager,成为系统管理的核心服务。 | 提供应用管理能力,供系统其他组件调用。 |
初始化组件 | 构建数据结构、组件解析器,启动后台线程。 | 提供运行时支持,准备后续任务。 |
加载持久化数据 | 恢复设备重启前的已安装应用信息。 | 确保已安装应用在系统重启后仍被识别和管理。 |
扫描并解析 APK | 扫描系统和用户目录,解析 APK 元数据,并注册到内存。 | 让系统能够识别和管理设备上的所有 APK。 |
初始化权限系统 | 加载默认权限配置文件,为系统应用分配权限。 | 保障系统安全,防止权限滥用。 |
提供对外接口 | 通过 API 提供安装、卸载、查询等功能,供系统和开发者调用。 | 提供应用安装和运行管理能力。 |
1.1 SystemServer 启动 PMS
源码路径:
frameworks/base/services/java/com/android/server/SystemServer.java
PMS 是 Android 启动服务的关键部分,在 startOtherServices() 方法中初始化:
private void startOtherServices() {PackageManagerService pms = PackageManagerService.main(mSystemContext);ServiceManager.addService("package", pms);
}
- 主要工作:
1、调用 PackageManagerService.main() 方法,启动 PMS。
2、注册 PMS 服务到 ServiceManager,供系统其他服务访问。
1.2 PMS 的入口方法 main
代码路径: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public static PackageManagerService main(Context context) {Installer installer = new Installer();return new PackageManagerService(context, installer, ...);
}
- 主要工作:
创建 Installer 实例,用于管理 APK 文件的安装和删除。
构造 PackageManagerService 对象。
1.3 PMS 构造函数
代码路径: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public PackageManagerService(Context context, Installer installer, ...) {mContext = context;mInstaller = installer;// 初始化数据结构mPackages = new ArrayMap<>();mComponentResolver = new ComponentResolver();// 创建并启动后台线程mBackgroundThread = BackgroundThread.get();mHandler = new PackageHandler(mBackgroundThread.getLooper());// 初始化权限管理服务mPermissionManager = new PermissionManagerService(context, ...);// 恢复持久化存储的数据mSettings = new Settings(...);mSettings.readPackageRestrictionsLPr();// 启动扫描逻辑scanPackages();
}
初始化内容:
- 核心数据结构:
mPackages: 存储已安装包的信息。
mComponentResolver: 解析组件(Activity、Service 等)依赖关系。 - 后台线程:
创建 BackgroundThread 处理异步任务,如 APK 扫描和安装。 - 权限管理:
初始化 PermissionManagerService 管理动态权限和默认权限。 - 持久化存储:
从 packages.xml 和 packages.list 加载已安装应用的状态。 - 启动扫描:
调用 scanPackages(),扫描系统目录和用户目录。
1.4 扫描 APK 文件
代码路径: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void scanPackages() {scanDirLI(new File("/system/app"), ...); // 扫描系统目录scanDirLI(new File("/data/app"), ...); // 扫描用户目录
}private void scanDirLI(File dir, ...) {File[] files = dir.listFiles();for (File file : files) {if (file.isFile() && file.getName().endsWith(".apk")) {try {// 解析 APKPackageParser.Package pkg = parsePackage(file);// 注册包信息scanPackageLI(pkg, ...);} catch (PackageParserException e) {Slog.e(TAG, "Failed to parse package: " + file, e);}}}
}
解析逻辑:
- 调用 PackageParser 解析 APK 的 AndroidManifest.xml。
- 验证签名、权限、依赖等信息。
- 将解析后的包注册到内存数据结构。
1.5 权限初始化
代码路径: frameworks/base/services/core/java/com/android/server/pm/PermissionManagerService.java
public void initializeDefaultPermissions() {// 加载默认权限配置loadDefaultPermissions();for (String permission : defaultPermissions) {grantPermission(permission);}
}
主要工作:
- 加载 /etc/permissions 目录下的权限定义文件。
- 为系统应用自动授予默认权限。
1.6 提供对外服务
代码路径: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
注册到 ServiceManager 后,PMS 提供以下对外服务:
1、获取已安装应用列表:
public List<PackageInfo> getInstalledPackages() {...}
2、安装应用:
public void installPackage(String packageName, ...) {...}
3、卸载应用:
public void deletePackage(String packageName, ...) {...}
2. APK 安装机制
2.1. 安装请求的触发
入口:
- 命令行触发:通过 ADB 命令安装 APK
- 系统触发:例如应用商店或系统应用发起安装请求。
核心代码路径:
PackageManagerService 类:
方法: installPackageAsUser()
路径: frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
功能:接收安装请求,校验请求来源并创建安装任务。
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer, int installFlags, int userId) {...// 校验权限,检查安装来源合法性// 创建安装任务,提交到后台处理
}
2.2 APK 文件解析与验证
在接收到安装请求后,PMS 通过 PackageParser 类解析 APK 文件,提取其元数据并进行签名校验。
PackageManagerService 调用 PackageParser 来解析 APK 文件,并将解析得到的包信息传递给后续处理模块。
关键类:
PackageParser:
路径: frameworks/base/core/java/android/content/pm/PackageParser.java
方法: parsePackage()
功能: 解析 APK 文件中的 AndroidManifest.xml,提取包名、版本、权限等信息,并封装到 Package 对象中。
public static Package parsePackage(File apkFile, int flags) throws PackageParserException {...// 解析 APK 的 manifest 文件,获取包信息return new Package(pkgName);
}
2.3 签名校验
PackageManagerService 需要确保新安装的 APK 是合法的,尤其是对于系统应用或需要特定权限的应用,签名校验是一个关键步骤。PackageUtils 提供了签名校验的功能。
在解析 APK 后,PackageManagerService 会调用 PackageUtils.compareSignatures() 方法,校验安装包的签名
关键类:
PackageUtils:
源码路径:
frameworks/base/core/java/android/content/pm/PackageUtils.java
入口:PackageUtils(compareSignatures() 方法)
作用:校验 APK 签名,确保其合法性。
public boolean compareSignatures(Signature[] newSig, Signature[] oldSig) {return SignatureUtils.areSignaturesMatching(newSig, oldSig); // 校验签名是否匹配
}
2.4 权限管理
2.4.1 权限声明
在 AndroidManifest.xml 文件中,应用声明所需的权限,例如 INTERNET、CAMERA 等。这些权限会在应用安装时进行解析。
示例:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
2.4.2 权限校验与分配
- 在应用安装时,PackageManagerService 会调用 grantPermissions() 方法来根据 AndroidManifest.xml 中声明的权限,为应用授予相应的权限。
- 对于 危险权限,Android 会在运行时请求用户授权(在应用首次访问权限时)。
关键类:
PackageManagerService:
源码路径:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
入口:PackageManagerService(grantPermissions() 方法)
作用:分配权限,确保安装的应用可以正常使用系统资源。
public void grantPermissions(Package pkg) {if (pkg != null) {for (Permission perm : pkg.permissions) {grantPermission(perm); // 授予应用声明的权限}}
}
2.5 持久化存储
在 APK 安装过程中,系统需要将应用的相关信息持久化存储,以确保在系统重启后仍然能访问到应用信息,并且保证应用的权限和其他元数据不会丢失。
-
安装包信息存储
在应用安装时,PackageManagerService 会将应用的信息(如包名、版本、权限等)写入系统的持久化存储中。这个过程通常通过 writePackagesLocked() 方法完成。 -
系统数据更新
系统维护一个应用数据库,存储有关已安装应用的所有信息(包括应用包名、路径、签名、权限等)。当应用安装或卸载时,PackageManagerService 会更新这些数据,确保系统的一致性。
关键类:
PackageManagerService:
源码路径:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
入口:PackageManagerService(writePackagesLocked() 方法)
作用:将已安装的包信息存储在系统持久化存储中,确保系统重启后信息不会丢失。
public void writePackagesLocked() {...writePackageListLPr(); // 将包信息写入存储
}
2.6 广播通知
在 APK 安装过程中,系统需要向其他组件(如 Launcher、系统 UI 等)广播应用安装成功的消息。这使得其他系统服务能够及时获知应用安装并进行相应操作,如更新图标或设置默认应用等。
关键类:
PackageManagerService:
源码路径:
frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
入口:PackageManagerService(sendPackageAddedBroadcast() 方法)
作用:安装完成后,发送广播通知系统其他组件应用安装成功。
public void sendPackageAddedBroadcast(String packageName, boolean replacing) {Intent intent = new Intent(Intent.ACTION_PACKAGE_ADDED);intent.setData(Uri.parse("package:" + packageName));sendBroadcast(intent); // 发送广播
}
3. APK 解析流程
3.1 APK 文件读取
APK 文件通常是一个 .apk 格式的压缩包,里面包含了应用的所有资源、代码(DEX 文件)、清单文件(AndroidManifest.xml)、签名信息等。
- 读取文件:系统首先需要通过文件路径或 URI 来读取 APK 文件。
在 PackageManagerService 中,安装包的处理通常是通过 installPackage 或 installPackageWithExistingManifest 方法进行的。PackageManagerService 是处理与安装相关的主要服务。
源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
public void installPackage(final Uri packageUri, final IPackageInstallObserver2 observer,final int flags, final int userId) {// 调用 PackageParser 解析 APKPackageParser.Package pkg = parsePackage(packageUri);
}
通过 PackageParser 类的 parsePackage 方法解析 APK。
3.2 APK 校验
-
签名校验:
ApkFile.getSignatures() 提取签名。
checkSignatures() 方法校验签名是否有效。 -
格式校验:
通过 ApkFile.contains() 检查 APK 文件结构是否完整。
使用 validateFileStructure() 校验 AndroidManifest.xml 和 classes.dex 文件是否存在。 -
权限校验:
PackageManagerService.resolvePermissions() 方法检查 AndroidManifest.xml 中声明的权限是否合法。
3.2.1 签名校验
在 Android 中,每个 APK 都必须进行签名校验,确保 APK 文件没有被篡改。签名校验是通过 PackageParser 类和 PackageManagerService 服务来进行的。
源码路径:frameworks/base/core/java/android/content/pm/PackageParser.java
在 PackageParser 中,parsePackage() 方法会解析 APK 文件并校验签名。签名校验的主要工作是通过 ApkFile 类来获取 APK 的签名,然后通过 checkSignatures() 方法校验签名的有效性。
public static Package parsePackage(File packageFile, int flags) throws PackageParser.PackageParserException {// 创建 ApkFile 对象,用于解析 APKApkFile apkFile = new ApkFile(packageFile);// 获取签名Signatures signatures = apkFile.getSignatures();// 校验签名checkSignatures(signatures);
}
ApkFile.getSignatures() 会从 APK 中提取签名数据。接下来通过 checkSignatures() 来验证签名。
checkSignatures() 方法
在 checkSignatures() 方法中,系统会验证签名是否为空或者无效,如果签名校验失败,APK 的安装会被终止。
private static void checkSignatures(Signatures signatures) {if (signatures == null || signatures.length == 0) {throw new PackageParser.PackageParserException("Missing signatures for package");}// 更多签名验证逻辑,比如签名一致性检查// 如果签名不匹配或无效,将会抛出异常
}
逻辑:
- 如果签名为空,抛出 PackageParserException 异常。
- 如果签名无效或与系统要求不匹配,APK 安装会失败。
3.2.2 APK 格式校验
APK 文件需要遵循特定的结构:包含 AndroidManifest.xml 文件,至少一个 classes.dex 文件等。PackageParser 会在解析 APK 时进行格式校验,确保 APK 的结构完整且符合要求。
文件结构校验
源码路径:frameworks/base/core/java/android/content/pm/PackageParser.java
validateFileStructure() 方法负责检查 APK 文件的结构是否完整,特别是是否包含 AndroidManifest.xml 和 classes.dex 等必要文件。
private static void validateFileStructure(ApkFile apkFile) throws PackageParser.PackageParserException {// 检查是否包含 AndroidManifest.xml 文件if (!apkFile.contains("AndroidManifest.xml")) {throw new PackageParser.PackageParserException("Missing AndroidManifest.xml in APK");}// 检查是否包含 classes.dex 文件if (!apkFile.contains("classes.dex")) {throw new PackageParser.PackageParserException("Missing classes.dex in APK");}
}
逻辑:
- 使用 apkFile.contains() 方法检查 AndroidManifest.xml 和 classes.dex 文件是否存在。
- 如果缺少其中任何一个文件,抛出 PackageParserException 异常,终止安装。
3.2.3 权限检查
确认应用声明的权限是否符合系统要求
源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
在安装过程中,PackageManagerService 会调用 resolvePermissions() 方法来检查应用所声明的权限。
public void resolvePermissions(Package pkg) {// 解析权限for (String perm : pkg.requestedPermissions) {// 检查权限是否合法if (!isPermissionValid(perm)) {throw new PackageManagerException("Invalid permission: " + perm);}}
}
逻辑:
- 遍历 AndroidManifest.xml 中声明的权限。
- 调用 isPermissionValid() 方法检查权限是否合法。
- 如果权限无效,抛出异常,安装被中止。
3.2.4 其他格式校验
除了签名和权限校验外,还会进行以下校验:
Manifest 校验:系统会检查 AndroidManifest.xml 文件的内容是否符合系统要求,例如检查包名是否重复,组件是否声明完整等。
PackageParser 中的 parseManifest() 方法负责解析 AndroidManifest.xml 文件并进行格式校验。
private static void parseManifest(File packageFile, Package pkg) throws PackageParser.PackageParserException {// 解析 AndroidManifest.xmltry {// 解析逻辑} catch (Exception e) {throw new PackageParser.PackageParserException("Manifest parsing failed: " + e.getMessage());}
}
哈希校验:为了确保文件没有被篡改,系统会对 classes.dex、资源文件等进行哈希校验。
校验 classes.dex 文件是否存在并有效。classes.dex 文件是应用的核心字节码文件,必须存在并符合格式。
3.3 安装信息持久化
APK 解析完成后,PackageManagerService 会将解析得到的信息持久化到系统数据库中,例如应用的包名、版本、权限等。
源码路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
在 PackageManagerService 中,installPackage() 方法会将解析后的应用信息持久化。
public int installPackage(Package pkg) {// 保存应用信息savePackageToDatabase(pkg);// 其他安装过程
}
逻辑:
将 Package 对象保存到数据库中,确保应用信息能够在后续访问中使用。
4. Intent 和组件管理
Intent 和组件管理是系统的重要组成部分,负责管理和调度应用程序的各个组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)。Intent 作为通信的媒介,允许不同的组件之间进行交互和数据传递。PackageManagerService(PMS)是管理和调度这些组件的核心服务,负责根据 Intent 定位和启动对应的组件。
4.1 Intent 的作用
Intent 是 Android 中用于描述一个应用组件执行某个动作的对象。它不仅用于启动活动(Activity),服务(Service)和广播(BroadcastReceiver),还用于传递数据和指示操作。可以通过 Intent 向系统请求某些操作,例如启动另一个应用的组件、发送广播等。
Intent 主要有以下几种类型:
显式 Intent:明确指定目标组件(如包名和类名)。通常用于在同一应用内启动组件。
隐式 Intent:没有指定具体的目标组件,而是通过指定操作(如 ACTION_VIEW)让系统根据 Intent 的内容查找合适的组件来响应。
4.2 组件的管理
在 Android 中,每个应用包含若干个组件(如 Activity、Service、BroadcastReceiver 和 ContentProvider)。PMS 负责管理这些组件,并根据 Intent 选择合适的组件进行调度。
组件管理的核心操作包括:
注册组件:当应用安装或更新时,PMS 会解析应用的 AndroidManifest.xml 文件,获取应用的组件信息,并将其注册到系统中。
查找组件:当收到一个 Intent 时,PMS 会根据该 Intent 的操作、数据、类别等信息,在所有已安装的应用中查找能够响应该 Intent 的组件。
启动组件:当 PMS 找到匹配的组件时,它会启动相应的 Activity、Service 或 BroadcastReceiver。
源码解析
PackageManagerService(PMS)是管理和调度组件的核心服务。当通过 Intent 启动一个组件时,系统会首先通过 PMS 查找符合条件的组件,并通过 ActivityManagerService 启动对应的组件。以下是相关的关键代码和流程。
resolveIntent() 方法
路径: frameworks/base/core/java/android/content/pm/ResolveInfo.java
PackageManagerService 中的 resolveIntent() 方法负责根据 Intent 查找符合条件的组件。
public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags) {// 查找能够处理 Intent 的所有组件List<ResolveInfo> queryIntentActivities = queryIntentActivities(intent, resolvedType, flags);for (ResolveInfo resolveInfo : queryIntentActivities) {if (resolveInfo.activityInfo.packageName.equals("com.example.target")) {return resolveInfo;}}return null;
}
作用:该方法根据传入的 Intent,在已安装的应用中查找能够处理该 Intent 的 Activity。在返回的 ResolveInfo 中包含了与 Intent 匹配的组件信息。
参数:
intent:目标 Intent 对象。
resolvedType:意图的类型。
flags:标记,用于决定匹配策略。
queryIntentActivities() 方法
路径frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
queryIntentActivities() 方法用于查找系统中能够响应指定 Intent 的所有组件,返回一个 ResolveInfo 列表。它主要会遍历系统中已安装应用的组件列表,找到符合条件的组件。
public List<ResolveInfo> queryIntentActivities(Intent intent, String resolvedType, int flags) {List<ResolveInfo> result = new ArrayList<>();// 遍历所有已安装的应用,寻找匹配的组件for (ApplicationInfo appInfo : mAppDirs) {List<ResolveInfo> appResult = queryIntentActivitiesForPackage(intent, appInfo.packageName, resolvedType, flags);result.addAll(appResult);}return result;
}
作用:查找所有能够响应给定 Intent 的 Activity,返回一个包含 ResolveInfo 对象的列表。
流程:
遍历系统中所有已安装的应用。
对每个应用执行匹配检查,查看是否有 Activity 能够处理传入的 Intent。
Intent 启动组件的流程
当 Intent 发起组件调用时,PMS 负责解析和匹配相关组件,然后通过 ActivityManagerService 来启动对应的组件。具体流程如下:
1、用户或系统发起 Intent:一个 Intent 请求组件(如启动一个 Activity)。
2、PMS 解析 Intent:PackageManagerService 会调用 resolveIntent() 或 queryIntentActivities() 等方法,解析 Intent 并查找合适的组件。
3、返回匹配的组件:如果找到匹配的组件,PMS 会返回该组件的详细信息(如 ResolveInfo)。
4、ActivityManagerService 启动组件:ActivityManagerService 会通过 startActivity() 或 startService() 启动对应的组件。
权限和安全性管理
在进行组件管理时,系统还会进行权限校验,确保只有具有相应权限的应用才能启动某些组件。PackageManagerService 会根据 Intent 中的请求权限以及目标组件的权限声明进行权限检查,确保安全性。
5. 卸载流程
5.1 卸载请求的触发
卸载请求通常是由用户通过系统设置或者其他应用程序发起的。调用的 API 是 PackageManager.deletePackage()。
路径
frameworks/base/core/java/android/content/pm/PackageManager.java。这个方法是对外提供的卸载接口。
public int deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {// 调用 PackageManagerService 的删除方法mPackageManagerService.deletePackage(packageName, observer, flags);
}
作用:
- deletePackage() 方法接收包名、卸载观察者和标志位。该方法会通过 PackageManagerService 调用实际的卸载逻辑。
- 会根据 flags 参数确定是否保留应用的数据或是强制卸载
5.2 PackageManagerService 接收到卸载请求
卸载请求被 PackageManagerService(PMS)接收后,首先会进行一系列的初步验证和检查。PMS 是处理所有包管理事务的核心组件。
路径:frameworks/base/core/java/android/content/pm/PackageManager.java
public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {// 权限检查enforceSystemOrRoot();// 获取要卸载的 PackageSetting 对象PackageSetting ps = mSettings.getPackageSetting(packageName);// 如果没有找到对应包名,则返回错误if (ps == null) {throw new IllegalArgumentException("Package " + packageName + " not found");}// 删除包裹的 APK 文件deletePackageFile(packageName);// 清除与该应用相关的数据deleteDataDirsLIL(packageName);// 更新系统数据库状态,移除包记录removePackageData(ps);// 发送卸载完成的广播sendPackageRemovedBroadcast(packageName);
}
卸载过程中,系统会检查是否有权限执行该操作。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void enforceSystemOrRoot() {if (Binder.getCallingUid() != Process.SYSTEM_UID && Binder.getCallingUid() != Process.ROOT_UID) {throw new SecurityException("Permission Denial: cannot delete package");}
}
作用:
如果请求不是来自系统 UID 或 ROOT UID,则抛出 SecurityException,拒绝卸载请求。
作用:
1、调用 enforceSystemOrRoot() 来检查请求是否来自系统或有适当权限的进程。
2、通过 mSettings.getPackageSetting(packageName) 获取与包名相关的设置对象。
3、删除包裹的 APK 文件和与该应用相关的数据。
5.3 停用应用
在卸载之前,需要停止正在运行的应用进程和服务,防止应用在卸载过程中仍然活动。
路径:frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public void forceStopPackage(String packageName) {// 终止正在运行的进程和服务mActivityManager.forceStopPackage(packageName);
}
作用:
停止与卸载应用相关的进程和服务,确保不会在卸载过程中影响系统。
5.4 删除应用数据
卸载时需要删除应用的数据文件,包括缓存、数据库等。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void deleteDataDirsLIL(String packageName) {// 删除应用的私有数据目录File dataDir = new File("/data/data/" + packageName);if (dataDir.exists()) {FileUtils.deleteContents(dataDir);}// 删除缓存目录File cacheDir = new File("/data/cache/" + packageName);if (cacheDir.exists()) {FileUtils.deleteContents(cacheDir);}
}
5.5 更新系统数据库
卸载过程中,系统数据库(如包管理数据库)需要移除该应用的相关信息。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void removePackageData(PackageSetting packageSetting) {// 从数据库中删除该应用相关信息mSettings.removePackage(packageSetting);// 更新数据库以确保删除记录mSettings.writeLPr();
}
作用:
删除与该应用相关的所有系统记录,包括应用的权限、签名、路径等信息。
使用 mSettings.writeLPr() 方法将更新后的数据库写入磁盘。
5.6 广播通知
卸载完成后,系统需要广播卸载信息,通知其他应用或系统组件。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void sendPackageRemovedBroadcast(String packageName) {// 发送卸载广播Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED, Uri.parse("package:" + packageName));intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);mContext.sendBroadcast(intent);
}
作用:
向系统广播卸载事件,通知其他系统组件(如 Launcher、应用管理等)更新其状态。
5.7 删除 APK 文件
卸载过程中,系统会删除与应用相关的 APK 文件。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void deletePackageFile(String packageName) {// 删除 APK 文件File apkFile = new File("/data/app/" + packageName);if (apkFile.exists()) {apkFile.delete();}
}
作用:
删除存储在 /data/app/ 或 /system/app/ 下的 APK 文件,释放存储空间。
5.8 更新 UI
卸载后,系统 UI(如 Launcher)需要更新,移除已卸载应用的图标。
路径:frameworks/base/core/java/android/app/ActivityManager.java
public void removePackageFromLauncher(String packageName) {// 从 Launcher 中移除应用图标Intent intent = new Intent(Intent.ACTION_PACKAGE_REMOVED, Uri.parse("package:" + packageName));mContext.sendBroadcast(intent);
}
作用:
通知 Launcher 更新其 UI,删除已卸载应用的图标。
6. 持久化机制
为了保持应用程序数据的一致性和持久化,PMS 需要使用持久化机制来存储和管理已安装应用的相关信息
6.1 持久化数据存储位置
PMS 主要通过 PackageManagerService 内部的 Settings 对象来管理应用的持久化数据。Settings 类封装了应用安装和卸载所需的所有信息,并提供了将这些信息持久化到磁盘的能力。具体来说,持久化的应用信息存储在系统的数据库文件中,通常位于 /data/system/ 目录下。
6.2 数据存储格式
PMS 使用的是基于 XML 文件的数据存储格式,所有关于已安装应用的信息(如包名、路径、权限等)都被保存在一个 XML 文件中。这个 XML 文件被称为 packages.xml,它包含了所有已安装应用的元数据。
路径:/data/system/packages.xml
该文件存储着系统中所有安装应用的配置,包括每个应用的包名、路径、安装时间、签名、权限等。
6.3 持久化流程
6.3.1 应用安装时持久化
当新应用被安装时,PMS 会更新 packages.xml 文件,新增该应用的相关信息。安装时,PMS 会将应用的包名、路径、签名等信息存储在该文件中,确保这些信息可以在系统重启后依然存在。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void addPackageSetting(PackageSetting ps) {// 将 PackageSetting 对象持久化到 Settings 数据中mSettings.addPackageSetting(ps);// 更新 packages.xml 文件mSettings.writeLPr();
}
作用:
addPackageSetting() 方法将新安装应用的 PackageSetting 对象添加到 Settings 数据中,并调用 mSettings.writeLPr() 更新 packages.xml 文件。
6.3.2 应用卸载时持久化
当应用被卸载时,PMS 会从 packages.xml 文件中删除该应用的记录,并更新文件。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void removePackageData(PackageSetting ps) {// 从 settings 数据中移除应用的记录mSettings.removePackage(ps);// 更新 packages.xml 文件mSettings.writeLPr();
}
作用:
removePackageData() 方法会删除卸载应用的所有记录,并通过 mSettings.writeLPr() 更新 packages.xml 文件,确保卸载后的数据一致性。
6.3.3 应用更新时持久化
当应用更新时,PMS 会修改原有记录并更新 packages.xml 文件,以反映应用的新状态(如新版本号、安装路径等)。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
private void updatePackageSetting(PackageSetting oldPs, PackageSetting newPs) {// 更新旧的 PackageSetting 记录为新的记录mSettings.replacePackageSetting(oldPs, newPs);// 更新 packages.xml 文件mSettings.writeLPr();
}
作用:
updatePackageSetting() 方法用于更新应用的持久化数据,确保新版本的应用信息被正确保存,并通过 mSettings.writeLPr() 更新 packages.xml 文件。
主要类
Settings 类的作用
Settings 类会将应用的配置信息存储为一系列 PackageSetting 对象。
路径:frameworks/base/services/core/java/com/android/server/pm/Settings.java
public class Settings {private final File mPackagesFile;private Map<String, PackageSetting> mPackages = new HashMap<>();public void readLPr() {// 读取 packages.xml 文件并解析成 Map 数据FileInputStream fis = null;try {fis = new FileInputStream(mPackagesFile);// 解析 XML 文件,将数据加载到 mPackages 中XmlUtils.readMapXml(fis, mPackages);} catch (IOException e) {Slog.e(TAG, "Error reading packages.xml", e);} finally {IoUtils.closeQuietly(fis);}}public void writeLPr() {// 将 mPackages 中的数据写入 packages.xml 文件FileOutputStream fos = null;try {fos = new FileOutputStream(mPackagesFile);// 将 Map 数据以 XML 格式写入文件XmlUtils.writeMapXml(fos, mPackages);} catch (IOException e) {Slog.e(TAG, "Error writing packages.xml", e);} finally {IoUtils.closeQuietly(fos);}}public void addPackageSetting(PackageSetting ps) {mPackages.put(ps.name, ps);}public void removePackage(PackageSetting ps) {mPackages.remove(ps.name);}
}
作用:
readLPr():从 packages.xml 文件读取数据并解析为 mPackages 映射。
writeLPr():将 mPackages 中的数据写入到 packages.xml 文件。
addPackageSetting():将一个 PackageSetting 对象添加到 mPackages 中,表示添加一个新的应用。
removePackage():从 mPackages 中删除一个 PackageSetting 对象,表示卸载一个应用。
PackageSetting 类的作用
PackageSetting 类表示一个应用程序的持久化数据。它包含了应用的各种元信息,如包名、签名、权限、安装路径、版本号等。
路径:frameworks/base/services/core/java/com/android/server/pm/PackageSetting.java
public class PackageSetting {public String name;public String codePath;public String resourcePath;public String dataDir;public int versionCode;public String[] signatures;public boolean isSystemApp;
}
作用:
PackageSetting 用于存储每个应用的核心信息,包含应用的路径、签名、版本号、系统应用标识等。
它是 Settings 类和 PackageManagerService 中用于持久化存储和管理应用元数据的主要对象。
总结
这篇文章的内容概述了 Android 系统中包管理服务(PMS)的工作原理,涉及从应用安装到卸载以及相关的持久化机制。以下是各章节的总结:
1、PMS 的初始化:
- 介绍了系统启动时,SystemServer 启动 PMS 的过程,并通过 main 方法初始化 PMS。
- PMS 构造函数负责初始化必要的资源,并扫描 APK 文件以便安装。
- 权限初始化过程和对外服务的提供是 PMS 的重要功能。
2、APK 安装机制:
描述了安装请求的触发、APK 文件的解析和验证过程,包括签名校验和权限管理。
权限声明与校验、持久化存储和广播通知等过程确保 APK 安全和正确安装。
3、APK 解析流程:
介绍了 APK 文件的读取、校验、权限检查等细节。
通过签名校验、格式校验等多项验证确保 APK 的有效性,并将安装信息持久化。
4、Intent 和组件管理:
讲解了 Intent 的作用以及如何通过 resolveIntent() 和 queryIntentActivities() 方法进行组件管理,确保应用间的通信和组件调用正确。
5、卸载流程:
描述了卸载请求的触发和处理过程,包括停用应用、删除数据、更新系统数据库、广播通知等步骤。
最终删除 APK 文件并更新 UI。
6、持久化机制:
讨论了持久化数据存储的位置、格式以及持久化流程。
重点描述了在应用安装、卸载和更新时的数据存储和管理方式。
总的来说,文章从 PMS 初始化到 APK 安装、解析、卸载及持久化机制,详细介绍了 Android 系统中包管理服务的运作流程。
相关文章:
深入解析 Android PMS —— APK 安装与解析全流程
文章目录 前言1. PMS 的初始化1.1 SystemServer 启动 PMS1.2 PMS 的入口方法 main1.3 PMS 构造函数1.4 扫描 APK 文件1.5 权限初始化1.6 提供对外服务 2. APK 安装机制2.1. 安装请求的触发2.2 APK 文件解析与验证2.3 签名校验2.4 权限管理2.4.1 权限声明2.4.2 权限校验与分配 2…...
RL仿真库pybullet
1. 介绍 PyBullet是一个基于Bullet Physics引擎的物理仿真Python接口,主要用于机器人仿真模拟。 1.1 主要特点 提供大量预设的机器人模型,例如URDF(统一机器人描述格式)、SDF、MJCF 格式。适用于训练和评估强化学习算法,提供了大量的强化学…...
Vue3组件通信(父传子,子传父,跨组件通信)
本文主要是讲述Vue3在setup语法糖下的组件间通信 Vue组件通信是指在Vue.js中,不同组件之间进行信息交流和共享数据的过程。在前端开发中,组件通信是非常重要的一部分,因为在一个复杂的应用中,不同的组件通常需要相互协作ÿ…...
从失败中学习:如何将错误转化为学习机会
失败是人生的一部分,无论是在个人生活还是职业生涯中,我们都难免会遇到挫折和错误。然而,失败并不意味着终结,而是一个潜在的学习机会。通过正确的态度和方法,我们可以从失败中汲取经验,转化为成长的动力。…...
[0629].第29节:配置中心业务规则与动态刷新
我的后端学习大纲 SpringCloud学习大纲 1、编码实现3377服务: 1.1.建module: 1.2.改pom: 1.3.写YML: 1.Nacos同Consul一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正…...
JUC并发编程
进程:系统中正在运行的一个应用程序,程序一旦运行就是进程,是资源分配的最小单元。 线程:系统分配处理器时间资源的基本单元,进程之内独立执行的一个单元执行流,是程序执行的最小单位。 Lock 需要手动上…...
贪心算法解题方法介绍+实操案例——会场安排与月饼售卖问题解析
从贪心算法到实操案例——会场安排与月饼售卖问题解析 前言 贪心算法是一种通过选择局部最优解来尝试构建全局最优解的算法。它简单高效,适用于许多优化问题。本文将详细介绍贪心算法的一般解题步骤,并通过两个实例——月饼售卖问题和会场安排问题——…...
ASP.NET Core API 前后端分离跨域
环境准备 数据库: sqlserver 2022 后端: vs2022 ASP.NET Core API .net 8 前端: Hbuilderx bootstrap 5.3.0 jquery v3.7.1 bootstrap-table 1.23.5 完整项目代码下载地址 功能 实现 单张表 的 增 删 改 查 创建数据库和表 create data…...
用Python绘制医学热图
在医学研究和临床实践中,数据的可视化是不可或缺的一部分。通过直观的数据展示,医学专业人员可以更好地理解各种疾病的治愈率、治疗效果以及医院之间的差异。今天,我们将介绍一种强大的数据可视化工具——热图(Heatmap)…...
使用 Spring Boot 和 GraalVM 的原生镜像
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:历代文学,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,精通Java编程,高并发设计…...
Flutter解压文件并解析数据
Flutter解压文件并解析数据 前言 在 Flutter 开发中,我们经常需要处理文件的读取和解压。 这在处理应用数据更新、安装包、存档文件等场景中尤为常见。 本文将介绍如何在Flutter中使用archive插件来解压文件并解析数据。 准备 在开始之前,我们需要…...
深入理解 JavaScript 引擎与消息队列的底层原理
深入理解 JavaScript 引擎与消息队列的底层原理 JavaScript 是现代 Web 开发中最为重要的编程语言之一,它的运行和执行方式常常是开发者关注的重点。为了更好地理解 JavaScript 的执行过程,我们需要深入探索 JavaScript 引擎的工作原理,尤其…...
使用 ANSYS Forming 和 LS-DYNA 进行金属成形仿真简介
了解金属成型 金属成型是制造业中的关键过程,其中原材料通过变形转化为所需的形状。这可能包括冲压、弯曲和深拉等操作。这些工艺的质量和效率在很大程度上取决于对各种参数的精确控制,例如材料特性、工具几何形状和加工条件。为了优化这些参数并确保成功…...
001-mysql安装
[rootcentos701 ~]# hostname -I 10.0.0.200 172.17.0.1 [rootcentos701 ~]# hostname centos701 [rootcentos701 ~]# rpm -qa | grep mariadb [rootcentos701 ~]# rpm -e --nodeps mariadb-libs-5.5.65-1.el7.x86_64 [rootcentos701 ~]# useradd mysql -s /sbin/nologin #创建…...
以攻击者的视角进行软件安全防护
1. 前言 孙子曰:知彼知己者,百战不殆;不知彼而知己,一胜一负,不知彼,不知己,每战必殆。 摘自《 孙子兵法谋攻篇 》在2500 年前的那个波澜壮阔的春秋战国时代,孙子兵法的这段话&…...
Go 语言性能优化全解析
在当今的软件开发环境中,Go 语言(Golang)因其简洁的语法、高效的并发模型和快速的编译速度而备受青睐。然而,随着应用程序复杂性的增加,即使是在 Go 中也可能会遇到性能瓶颈。为了帮助开发者构建高性能的应用程序&…...
《智能体雏形开发(高阶实操)》二、智能体雏形开发
基于阿里云百炼平台开发智能体应用:生成日报与周报 在智能体开发中,生成结构化的日报与周报是一个典型的任务。本篇文章将基于阿里云百炼平台,结合 Python 开发环境,介绍如何开发一个从日志文件提取信息并生成摘要的智能体。我们将从需求分析、任务设计到核心功能实现逐步…...
【k8s】kubelet 和 API Server的关系
文章目录 概述1. # kubelet 和 API Server 之间的关系**1. 角色和功能****1.1 kubelet****1.2 API Server** **2. 交互关系****2.1 kubelet 从 API Server 获取指令****2.2 kubelet 向 API Server 上报状态****2.3 kubelet 与 API Server 的认证和授权** **3. 典型交互场景****…...
POSTGRESQL跟ORACLE语法区别和相同之处
跟ORACLE语法区别之处 1. Update和delete语法区别 Pg 和MySQL Update和delete的时候表名不能加别名 2. 插入数字类型不一样 ORACLE 对number类型的数据可以用’’ 字符串标记插入,但是PG不行,必须要进行正确的数据类型 3. SEQ使用不同 ORACEL的SEQ…...
Distance in Tree 树形dp练习(树中两点距离为k的数量板子)
Distance in Tree 题面翻译 题目大意 输入点数为 N N N一棵树 求树上长度恰好为 K K K的路径个数 输入格式 第一行两个数字 N , K N,K N,K,如题意 接下来的 N − 1 N-1 N−1行中,每行两个整数 u , v u,v u,v表示一条树边 ( u , v ) (u,v) (u,v) 输出格式 一个整数 a n…...
【MySQL】库的操作+表的操作
库的操作表的操作 1.库的操作 1.1创建数据库1.2删除数据库1.3查找数据库1.4修改数据库1.5数据库备份和恢复1.6查看连接情况 2.库的操作 2.1创建表2.2查看表结构2.3修改表2.4删除表 点赞???收藏???关注??? 你的支持是对我最大的鼓励,我们一起努力吧???…...
vue异步更新,$nextTick
如果将isShowEdit改为true,就会显示输入框和确认按钮、 如果isShowEdit为false的话就显示“大标题”和编辑 想要获取元素焦点,但是vue是异步更新,会出错显示this.$refs.inp是undefined,是因为input元素并没有更新完成,所以需要使用…...
【3D AIGC】Img-to-3D、Text-to-3D、稀疏重建(2024年文章汇总)
文章目录 1. Wonderworld:拓展图片边界,生成3D场景2. 3DTopia-XL:扩散模型辅助生成3. 3DGS-Enhancer: 通过视图一致2D Diffusion,提升无界3D Gaussian Splatting (NlPs2024 Spotlight)4. L3DG:Latent 3D Gaussian Diff…...
简单的springboot使用sse功能
什么是sse? 1、SSE 是Server-Sent Events(服务器发送事件) 2、SSE是一种允许服务器主动向客户端推送实时更新的技术。 3、它基于HTTP协议,并使用了其长连接特性,在客户端与服务器之间建立一条持久化的连接。 通过这条连接&am…...
Nginx 防止IP伪造,绕过IP限制
背景介绍 在使用Nginx时,需要将IP地址转发到后置应用中,往往需要增加配置 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 在后端程序通过读取请求头里的X-Forwarded-For来获取用户客户端IP。 public String getRemortIP(HttpServle…...
原生js仿el-table动态表头
解决动态表头数据量过大导致页面卡顿的问题解决固定前几列导致表头设置宽度失效或者错位的问题功能: 固定前几列合并指定单元格 <div class"tableJoint2"><div><table id"tableData"></table></div><div>…...
【opencv入门教程】9.视频加载
文章选自: 一、VideoCapture类 用于从视频文件、图像序列或摄像头捕获视频的类。函数:CV_WRAP VideoCapture();brief 默认构造函数CV_WRAP explicit VideoCapture(const String& filename, int apiPreference CAP_ANY);brief 使用 API 首选项打开…...
数据结构 ——无头单链表
数据结构 ——无头单链表 一、无头单链表的定义与特性 1、单链表简介 单链表是一种常见的基础数据结构,它由一系列节点组成,每个节点包含数据部分和指向下一个节点的指针。无头单链表是单链表的一种变体,其特点是没有明确的头节点࿰…...
【UE5】制作插件 并调试【vs2022】
视频教程:好看视频-轻松有收获 https://www.youtube.com/watch?vIjpa9mI2b5I 原文:【UE】制作插件_ue插件-CSDN博客 C制作插件 1. 我们可以在C工程中创建更多类型的插件,这里我们选择“空白”作为模板来创建插件 点击“创建插件”按钮后…...
Prometheus 采集postgresql监控数据
postgres_exporter 前言 postgres_exporter 是一个用于监控 PostgreSQL 数据库的 Prometheus 导出器。它允许你收集有关 PostgreSQL 数据库性能和状态的指标,并将这些指标暴露给 Prometheus,从而可以在 Grafana 等可视化工具中进行展示和告警。 postgres_exporter download…...
网络分层模型( OSI、TCP/IP、五层协议)
1、网络分层模型 计算机网络是一个极其复杂的系统。想象一下最简单的情况:两台连接在网络上的计算机需要相互传输文件。不仅需要确保存在一条传输数据的通路,还需要完成以下几项工作: 发起通信的计算机必须激活数据通路,这包括发…...
POI遍历行所有单元格的两种方式,getPhysicalNumberOfCells方式有问题,勿用
今天看POI源码的时候,发现HSSFWorkbook类型的工作簿,行数据是用TreeMap<Integer, HSSFRow>存储的,列数据是用HSSFCell[]数组来存的;XSSFWorkbook类型的工作簿,行数据是用SortedMap<Integer, XSSFRow>存储的…...
Latex转word(docx)或者说PDF转word 一个相对靠谱的方式
0. 前言 投文章过程中总会有各种各样的要求,其中提供word格式的手稿往往是令我头疼的一件事。尤其在多公式的文章中,其中公式转换是一个头疼的地方,还有很多图表,格式等等,想想就让人头疼欲裂。实践中摸索出一条相对靠…...
敖汉宝塔油页岩露天矿山安全自动化监测
1. 项目简介 本次项目位于内蒙古自治区赤峰市敖汉旗宝国吐乡大青山村,地理位置好。主营许可经营项目:无一般经营项目:页岩油生产;页岩油、润滑油、建筑材料(不含油漆)销售等单位规模1-20人,单位…...
Android笔记【14】结合LaunchedEffect实现计时器功能。
一、问题 cy老师第五次作业 结合LaunchedEffect实现计时器功能。要求:动态计时,每秒修改时间,计时的时间格式为“00:00:00”(小时:分钟:秒)提交源代码的文本和运行截图…...
三维重建(单目、双目、多目、点云、SFM、SLAM)
1 相机几何与标定1.1 相机模型中的坐标系1.2 四种坐标系之间的转换1.3 相机内参1.4 相机标定 2 传统三维重建2.1 RGBD三维重建2.1.1 KinectFusion2.1.2 BundleFusion 2.1 MVS三维重建2.2.1 COLMAP2.2.2 OpenMVS 3 点云三维重建3.1 3D点云任务3.2 点云数据3.3 特征提取3.3.1 Poi…...
软体机器人动态手内笔旋转研究
人工智能咨询培训老师叶梓 转载标明出处 软体机器人因其在安全互动方面的优势而备受关注,但在高速动态任务中却面临挑战。最近,卡内基梅隆大学机器人研究所的研究团队提出了一种名为SWIFT的系统,旨在通过学习和试错来实现软体机器人手的动态…...
福昕PDF低代码平台
福昕PDF低代码平台简介 福昕PDF 低代码平台是一款创新的工具,旨在简化PDF处理和管理的流程。通过这个平台,用户可以通过简单的拖拽界面上的按钮,轻松完成对Cloud API的调用工作流,而无需编写复杂的代码。这使得即使没有编程经验的…...
【笔记】Linux中使用到的一些操作
1、查找指定文件并执行删除 find . -name "checkpoint_*_*.pth" -type f -exec rm -f {} \; 2、查看每个文件夹占用空间 du -h --max-depth1 3、移动文件 mv valid.zip ./xg mv 文件 目标位置 4、删除文件夹 rmdir folder rm -r folder # 递归删除文件夹下所有内容…...
深入浅出:PHP中的表单处理全解析
引言 在Web开发的世界里,表单是用户与服务器之间交互的重要桥梁。它们允许用户提交信息,并通过后端语言(如PHP)进行处理。本文将带你深入了解PHP中的表单处理,从基础的创建和提交到高级的安全措施和实用技巧ÿ…...
智已汽车x-signature 登录算法 签到
智已汽车x-signature 登录算法 签到 python代码成品...
一、测试工具LoadRunner Professional脚本编写-录制前设置
设置基于URL的脚本 原因:基于HTML的脚本会导致login接口不能正确录制 设置UTF-8 原因:不勾选此项会导致脚本中文变为乱码...
LSTM+改进的itransformer时间序列预测模型代码
代码在最后 本次设计了一个LSTM基于差分多头注意力机制的改进的iTransformer时间序列预测模型结合了LSTM(长短期记忆网络)和改进版的iTransformer(差分多头注意力机制),具备以下优势: 时序特征建模能力&am…...
linux中 Systemd 和 cgroups 的关系详解
systemd 是 Linux 的一个初始化系统和服务管理器,它依赖于 Linux 内核的 cgroups(Control Groups)功能来实现对系统资源的高效管理。以下是对两者关系的详细解读: 1. 什么是 cgroups? cgroups 是 Linux 内核提供的一种…...
发布Apache2.4** 局域网无法访问
1。 防火墙关闭 或者 设置入站规则 2,查看httpd.conf 文件 设置配置 原 Listen 80 修改成 Listen 192.168.31.127:90 3.确保 本地IP 是否正确...
【JAVA】Java高级:多数据源管理与Sharding:在Spring Boot应用中实现多数据源的管理
一个电商平台可能需要一个数据库来存储用户信息,另一个数据库来存储订单信息,甚至可能还有一个数据库用于数据分析。这种情况下,如何在Spring Boot应用中实现多数据源的管理就显得尤为重要。 1. 多数据源管理的重要性 在实际应用中…...
Android 分词的两种方式
前言: 本文分别介绍了原生和三方(Jieba)两种分词方式的使用和注意事项 1、安卓原生BreakIterator分词 比较简单,但是效果不太行 /*** 功能:原生分词* 参数:text:需要分词的语句* 返回值:return…...
【开源免费】基于SpringBoot+Vue.JS中小型医院网站(JAVA毕业设计)
博主说明:本文项目编号 T 078 ,文末自助获取源码 \color{red}{T078,文末自助获取源码} T078,文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析…...
数据结构代码归纳
1.线性表 线性表的顺序表示 定义与初始化 typedef struct SqList{ElemType data[MaxSize];//ElemType *data 开动态数组 int length; }Sqlist; void InitList(SqList &L){L.length0;//若静态数组//若动态数组 //L.data(ElemType*)malloc(sizeof(ElemType)*MaxSize); }…...
2024-金盾信安杯线上赛 WP
Misc 大赛宗旨 记事本打开,一眼零宽隐写 B 神工具一把梭,得到一串 base 编码 base64 解码得到 flag flag 值:flag{5d5555fa-1303-4b43-8eef-d6ea7c64c361} esab 根据题目 esab 可以发现这正是 base 的逆向,所以可以先逆向一下…...