Android 启动时应用的安装解析过程《二》

ops/2024/11/13 15:17:46/

上一篇内容说到InitAppsHelper这个类的initSystemApps函数,只说了一下几个重要参数的来源还没展开,这里继续,有兴趣的可以看链接: Android 启动时应用的安装解析过程《一》

一、系统应用的扫描安装

	/*** Install apps from system dirs.*/@GuardedBy({"mPm.mInstallLock", "mPm.mLock"})public OverlayConfig initSystemApps(PackageParser2 packageParser,WatchedArrayMap<String, PackageSetting> packageSettings,int[] userIds, long startTime) {// Prepare apex package info before scanning APKs, this information is needed when// scanning apk in apex.// 扫描系统中apex信息,为扫描APK做准备,apex是系统中一种新的模块化组件final List<ApexManager.ScanResult> apexScanResults = scanApexPackagesTraced(packageParser);mApexManager.notifyScanResult(apexScanResults);// 扫描系统目录中的apkscanSystemDirs(packageParser, mExecutorService);// Parse overlay configuration files to set default enable state, mutability, and// priority of system overlays.final ArrayMap<String, File> apkInApexPreInstalledPaths = new ArrayMap<>();for (ApexManager.ActiveApexInfo apexInfo : mApexManager.getActiveApexInfos()) {final String apexPackageName = mApexManager.getActivePackageNameForApexModuleName(apexInfo.apexModuleName);for (String packageName : mApexManager.getApksInApex(apexPackageName)) {apkInApexPreInstalledPaths.put(packageName, apexInfo.preInstalledApexPath);}}// 解析系统目录的overlay并把它使能final OverlayConfig overlayConfig = OverlayConfig.initializeSystemInstance(consumer -> mPm.forEachPackageState(mPm.snapshotComputer(),packageState -> {var pkg = packageState.getPkg();if (pkg != null) {consumer.accept(pkg, packageState.isSystem(),apkInApexPreInstalledPaths.get(pkg.getPackageName()));}}));// do this first before mucking with mPackages for the "expecting better" case// 做一些优化,比如禁用一些需要禁用的apk,删除掉已经被删除的应用缓存updateStubSystemAppsList(mStubSystemApps);mInstallPackageHelper.prepareSystemPackageCleanUp(packageSettings,mPossiblyDeletedUpdatedSystemApps, mExpectingBetter, userIds);logSystemAppsScanningTime(startTime);return overlayConfig;}

1.扫描系统目录

private void scanSystemDirs(PackageParser2 packageParser, ExecutorService executorService) {File frameworkDir = new File(Environment.getRootDirectory(), "framework");// Collect vendor/product/system_ext overlay packages. (Do this before scanning// any apps.)// For security and version matching reason, only consider overlay packages if they// reside in the right directory.for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {final ScanPartition partition = mDirsToScanAsSystem.get(i);if (partition.getOverlayFolder() == null) {continue;}// 扫描系统分区的overlay 文件夹scanDirTracedLI(partition.getOverlayFolder(),mSystemParseFlags, mSystemScanFlags | partition.scanFlag,packageParser, executorService, partition.apexInfo);}// 扫描system/framework文件夹scanDirTracedLI(frameworkDir,mSystemParseFlags, mSystemScanFlags | SCAN_NO_DEX | SCAN_AS_PRIVILEGED,packageParser, executorService, null);if (!mPm.mPackages.containsKey("android")) {throw new IllegalStateException("Failed to load frameworks package; check log for warnings");}for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {final ScanPartition partition = mDirsToScanAsSystem.get(i);if (partition.getPrivAppFolder() != null) {// 扫描系统分区的priv-app文件夹scanDirTracedLI(partition.getPrivAppFolder(),mSystemParseFlags,mSystemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag,packageParser, executorService, partition.apexInfo);}// 扫描系统分区的app文件夹scanDirTracedLI(partition.getAppFolder(),mSystemParseFlags, mSystemScanFlags | partition.scanFlag,packageParser, executorService, partition.apexInfo);}}

上面可以看到,这个函数里面将系统分区的overlay,app,还有jar包都扫描了个遍,然后我们看下到底这个scanDirTracedLI函数里面做了哪些事情

private void scanDirTracedLI(File scanDir, int parseFlags, int scanFlags,PackageParser2 packageParser, ExecutorService executorService,@Nullable ApexManager.ActiveApexInfo apexInfo) {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");try {if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {// when scanning apk in apexes, we want to check the maxSdkVersionparseFlags |= PARSE_APK_IN_APEX;}// 调用到了InstallPackageHelper的installPackagesFromDirmInstallPackageHelper.installPackagesFromDir(scanDir, parseFlags,scanFlags, packageParser, executorService, apexInfo);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}
}
InstallPackageHelper
public void installPackagesFromDir(File scanDir, int parseFlags,int scanFlags, PackageParser2 packageParser, ExecutorService executorService,@Nullable ApexManager.ActiveApexInfo apexInfo) {//首先将所有的文件列出来final File[] files = scanDir.listFiles();if (ArrayUtils.isEmpty(files)) {Log.d(TAG, "No files in app dir " + scanDir);return;}if (DEBUG_PACKAGE_SCANNING) {Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags+ " flags=0x" + Integer.toHexString(parseFlags));}// 这个又是个专门解析包的类ParallelPackageParser parallelPackageParser =new ParallelPackageParser(packageParser, executorService);// Submit files for parsing in parallelint fileCount = 0;for (File file : files) {// 这里判断是不是包:是apk文件或者是文件夹并且不是stageName,这个stageName是指// vmdl*.tmp,smdl*.tmp,smdl2tmp这些类的文件final boolean isPackage = (isApkFile(file) || file.isDirectory())&& !PackageInstallerService.isStageName(file.getName());if (!isPackage) {// Ignore entries which are not packagescontinue;}// 这里如果携带了SCAN_DROP_CACHE的flag会清除缓存if ((scanFlags & SCAN_DROP_CACHE) != 0) {final PackageCacher cacher = new PackageCacher(mPm.getCacheDir(),mPm.mPackageParserCallback);Log.w(TAG, "Dropping cache of " + file.getAbsolutePath());cacher.cleanCachedResult(file);}// 这里提交给解析器解析parallelPackageParser.submit(file, parseFlags);fileCount++;}// Process results one by onefor (; fileCount > 0; fileCount--) {ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();Throwable throwable = parseResult.throwable;int errorCode = PackageManager.INSTALL_SUCCEEDED;String errorMsg = null;// 如果没有异常就表示解析成功另外两个条件属于失败了if (throwable == null) {try {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "addForInitLI");// 这里初始化addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,new UserHandle(UserHandle.USER_SYSTEM), apexInfo);} catch (PackageManagerException e) {errorCode = e.error;errorMsg = "Failed to scan " + parseResult.scanFile + ": " + e.getMessage();Slog.w(TAG, errorMsg);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}} else if (throwable instanceof PackageManagerException) {PackageManagerException e = (PackageManagerException) throwable;errorCode = e.error;errorMsg = "Failed to parse " + parseResult.scanFile + ": " + e.getMessage();Slog.w(TAG, errorMsg);} else {throw new IllegalStateException("Unexpected exception occurred while parsing "+ parseResult.scanFile, throwable);}// apex的安装单独报给ApexManagerif ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath(), errorMsg);}// Delete invalid userdata apps// 没安装成功的会在这里清除if ((scanFlags & SCAN_AS_SYSTEM) == 0&& errorCode != PackageManager.INSTALL_SUCCEEDED) {logCriticalInfo(Log.WARN,"Deleting invalid package at " + parseResult.scanFile);mRemovePackageHelper.removeCodePath(parseResult.scanFile);}}
}

从上面可以看出主要就做了两件事情,把文件夹下面的包一一解析,并做初始化,看看如何解析的先:
ParallelPackageParser submit之后

2.开始解析

/*---------------------------ParallelPackageParser start ---------------------------*/
public void submit(File scanFile, int parseFlags) {// 直接开线程开始解析mExecutorService.submit(() -> {ParseResult pr = new ParseResult();Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");try {pr.scanFile = scanFile;// 解析pr.parsedPackage = parsePackage(scanFile, parseFlags);} catch (Throwable e) {pr.throwable = e;} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}try {mQueue.put(pr);} catch (InterruptedException e) {Thread.currentThread().interrupt();// Propagate result to callers of take().// This is helpful to prevent main thread from getting stuck waiting on// ParallelPackageParser to finish in case of interruptionmInterruptedInThread = Thread.currentThread().getName();}});
}protected ParsedPackage parsePackage(File scanFile, int parseFlags)
throws PackageManagerException {try {// 发现这里又交给PackageParser2去做了return mPackageParser.parsePackage(scanFile, parseFlags, true);} catch (PackageParserException e) {throw new PackageManagerException(e.error, e.getMessage(), e);}
}
/*---------------------------ParallelPackageParser end---------------------------*/
/*---------------------------PackageParser2 start---------------------------*/
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
throws PackageParserException {// 列出所有的文件var files = packageFile.listFiles();// Apk directory is directly nested under the current directoryif (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {packageFile = files[0];}if (useCaches && mCacher != null) {// 这里会去检查有没有缓存,缓存是存在data/system/目录下如果有缓存则不再解析ParsedPackage parsed = mCacher.getCachedResult(packageFile, flags);if (parsed != null) {return parsed;}}long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;ParseInput input = mSharedResult.get().reset();// 这里又交给了ParsingPackageUtils这个类去干活了ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);if (result.isError()) {throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),result.getException());}ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;// 这里解析好的结果保存到缓存,也就是只要APK被解析过一次就会存在缓存了if (mCacher != null) {mCacher.cacheResult(packageFile, flags, parsed);}if (LOG_PARSE_TIMINGS) {parseTime = cacheTime - parseTime;cacheTime = SystemClock.uptimeMillis() - cacheTime;if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime+ "ms, update_cache=" + cacheTime + " ms");}}return parsed;
}/*---------------------------PackageParser2 end---------------------------*/
/*---------------------------ParsingPackageUtils start---------------------------*/
public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile, int flags) {// 如果是文件夹则走if 文件则走else,这里为什么会这样分,那/system/app目录来说,// 系统在预置APP的时候可能是先在该目录下新建一层目录再放APK,也有可能直接放置一个APK在该目录下if (packageFile.isDirectory()) {return parseClusterPackage(input, packageFile,  flags);} else {return parseMonolithicPackage(input, packageFile, flags);}
}// 先看下解析目录parseClusterPackage
private ParseResult<ParsingPackage> parseClusterPackage(ParseInput input, File packageDir,int flags) {int liteParseFlags = 0;// 这里判断是不是在解析APEX的时候解析APKif ((flags & PARSE_APK_IN_APEX) != 0) {liteParseFlags |= PARSE_APK_IN_APEX;}// 轻量解析APK,这里会解析APK的签名信息,AndroidManifest.xml中除了Activity,Service,ContentProvider及权限以外的信息,// 顺便会做一些校验比如SDK版本,签名校验,系统目录的APK 可以跳过签名校验(签名校验从v4到v1逐一校验),这一部分设计是为了节省时间final ParseResult<PackageLite> liteResult =ApkLiteParseUtils.parseClusterPackageLite(input, packageDir, liteParseFlags);// 没有错误说明各种校验通过了if (liteResult.isError()) {return input.error(liteResult);}final PackageLite lite = liteResult.getResult();// Build the split dependency tree.SparseArray<int[]> splitDependencies = null;final SplitAssetLoader assetLoader;// 如果是APKS 格式的这里会创建资源依赖树if (lite.isIsolatedSplits() && !ArrayUtils.isEmpty(lite.getSplitNames())) {try {splitDependencies = SplitAssetDependencyLoader.createDependenciesFromPackage(lite);assetLoader = new SplitAssetDependencyLoader(lite, splitDependencies, flags);} catch (SplitAssetDependencyLoader.IllegalDependencyException e) {return input.error(INSTALL_PARSE_FAILED_BAD_MANIFEST, e.getMessage());}} else {assetLoader = new DefaultSplitAssetLoader(lite, flags);}try {final File baseApk = new File(lite.getBaseApkPath());boolean shouldSkipComponents = lite.isIsSdkLibrary() && disallowSdkLibsToBeApps();// 这里解析APKfinal ParseResult<ParsingPackage> result = parseBaseApk(input, baseApk,lite.getPath(), assetLoader, flags, shouldSkipComponents);if (result.isError()) {return input.error(result);}ParsingPackage pkg = result.getResult();// 如果是APKS格式进行另外的解析if (!ArrayUtils.isEmpty(lite.getSplitNames())) {pkg.asSplit(lite.getSplitNames(),lite.getSplitApkPaths(),lite.getSplitRevisionCodes(),splitDependencies);final int num = lite.getSplitNames().length;for (int i = 0; i < num; i++) {final AssetManager splitAssets = assetLoader.getSplitAssetManager(i);final ParseResult<ParsingPackage> split =parseSplitApk(input, pkg, i, splitAssets, flags);if (split.isError()) {return input.error(split);}}}pkg.set32BitAbiPreferred(lite.isUse32bitAbi());return input.success(pkg);} catch (IllegalArgumentException e) {return input.error(e.getCause() instanceof IOException ? INSTALL_FAILED_INVALID_APK: INSTALL_PARSE_FAILED_NOT_APK, e.getMessage(), e);} finally {IoUtils.closeQuietly(assetLoader);}
}
/*******parseBaseApk******/private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, File apkFile,String codePath, SplitAssetLoader assetLoader, int flags,boolean shouldSkipComponents) {......// 又开始解析AndroidManifest.xmltry (XmlResourceParser parser = assets.openXmlResourceParser(cookie,ANDROID_MANIFEST_FILENAME)) {final Resources res = new Resources(assets, mDisplayMetrics, null);// 这里又一个重载解析APKParseResult<ParsingPackage> result = parseBaseApk(input, apkPath, codePath, res,parser, flags, shouldSkipComponents);if (result.isError()) {return input.error(result.getErrorCode(),apkPath + " (at " + parser.getPositionDescription() + "): "+ result.getErrorMessage());}final ParsingPackage pkg = result.getResult();if (assets.containsAllocatedTable()) {final ParseResult<?> deferResult = input.deferError("Targeting R+ (version " + Build.VERSION_CODES.R + " and above) requires"+ " the resources.arsc of installed APKs to be stored uncompressed"+ " and aligned on a 4-byte boundary",DeferredError.RESOURCES_ARSC_COMPRESSED);if (deferResult.isError()) {return input.error(INSTALL_PARSE_FAILED_RESOURCES_ARSC_COMPRESSED,deferResult.getErrorMessage());}}ApkAssets apkAssets = assetLoader.getBaseApkAssets();boolean definesOverlayable = false;try {// 查询该应用是否支持overlay,一般定义了相应的overlayabledefinesOverlayable = apkAssets.definesOverlayable();} catch (IOException ignored) {// Will fail if there's no packages in the ApkAssets, which can be treated as false}// 如果可以这里会查找到可以被overlay的域if (definesOverlayable) {SparseArray<String> packageNames = assets.getAssignedPackageIdentifiers();int size = packageNames.size();for (int index = 0; index < size; index++) {String packageName = packageNames.valueAt(index);Map<String, String> overlayableToActor = assets.getOverlayableMap(packageName);if (overlayableToActor != null && !overlayableToActor.isEmpty()) {for (String overlayable : overlayableToActor.keySet()) {pkg.addOverlayable(overlayable, overlayableToActor.get(overlayable));}}}}pkg.setVolumeUuid(volumeUuid);// 这里又是查询验证签名信息if ((flags & PARSE_COLLECT_CERTIFICATES) != 0) {final ParseResult<SigningDetails> ret =getSigningDetails(input, pkg, false /*skipVerify*/);if (ret.isError()) {return input.error(ret);}pkg.setSigningDetails(ret.getResult());} else {pkg.setSigningDetails(SigningDetails.UNKNOWN);}if (Flags.aslInApkAppMetadataSource()) {try (InputStream in = assets.open(APP_METADATA_FILE_NAME)) {pkg.setAppMetadataFileInApk(true);} catch (Exception e) { }}return input.success(pkg);} catch (Exception e) {return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,"Failed to read manifest from " + apkPath, e);}}/*******parseBaseApk 重载******/
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,String codePath, Resources res, XmlResourceParser parser, int flags,boolean shouldSkipComponents)throws XmlPullParserException, IOException {final String splitName;final String pkgName;// 轻量解析,包名信息之类的ParseResult<Pair<String, String>> packageSplitResult =ApkLiteParseUtils.parsePackageSplitNames(input, parser);if (packageSplitResult.isError()) {return input.error(packageSplitResult);}Pair<String, String> packageSplit = packageSplitResult.getResult();pkgName = packageSplit.first;splitName = packageSplit.second;if (!TextUtils.isEmpty(splitName)) {return input.error(PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,"Expected base APK, but found split " + splitName);}final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);try {// 是否是core app ,类似系统设置final boolean isCoreApp = parser.getAttributeBooleanValue(null /*namespace*/,"coreApp", false);final ParsingPackage pkg = mCallback.startParsingPackage(pkgName, apkPath, codePath, manifestArray, isCoreApp);// 解析AndroidManinfest里面的各个TAG,真正解析的开始final ParseResult<ParsingPackage> result =parseBaseApkTags(input, pkg, manifestArray, res, parser, flags,shouldSkipComponents);if (result.isError()) {return result;}return input.success(pkg);} finally {manifestArray.recycle();}}/**** parseBaseApkTags ****/
private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,TypedArray sa, Resources res, XmlResourceParser parser, int flags,boolean shouldSkipComponents) throws XmlPullParserException, IOException {// 解析共享uidParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);if (sharedUserResult.isError()) {return sharedUserResult;}// 用于标记系统为可更新,通常和动态系统分区及增量更新机制有关。final boolean updatableSystem = parser.getAttributeBooleanValue(null /*namespace*/,"updatableSystem", true);// 用于设备恢复模式下的紧急安装或恢复。final String emergencyInstaller = parser.getAttributeValue(null /*namespace*/,"emergencyInstaller");pkg.setInstallLocation(anInteger(PARSE_DEFAULT_INSTALL_LOCATION,R.styleable.AndroidManifest_installLocation, sa)).setTargetSandboxVersion(anInteger(PARSE_DEFAULT_TARGET_SANDBOX,R.styleable.AndroidManifest_targetSandboxVersion, sa))/* Set the global "on SD card" flag */.setExternalStorage((flags & PARSE_EXTERNAL_STORAGE) != 0).setUpdatableSystem(updatableSystem).setEmergencyInstaller(emergencyInstaller);boolean foundApp = false;final int depth = parser.getDepth();int type;while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG|| parser.getDepth() > depth)) {if (type != XmlPullParser.START_TAG) {continue;}if (sAconfigFlags.skipCurrentElement(parser)) {continue;}String tagName = parser.getName();final ParseResult result;// <application> has special logic, so it's handled outside the general method// 解析Application TAG配置及四大组件信息if (TAG_APPLICATION.equals(tagName)) {if (foundApp) {if (RIGID_PARSER) {result = input.error("<manifest> has more than one <application>");} else {Slog.w(TAG, "<manifest> has more than one <application>");result = input.success(null);}} else {foundApp = true;result = parseBaseApplication(input, pkg, res, parser, flags,shouldSkipComponents);}} else {// Application 以外的TAG信息result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);}if (result.isError()) {return input.error(result);}}if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {ParseResult<?> deferResult = input.deferError("<manifest> does not contain an <application> or <instrumentation>",DeferredError.MISSING_APP_TAG);if (deferResult.isError()) {return input.error(deferResult);}}return validateBaseApkTags(input, pkg, flags);}

后面都是一些解析细节了,不在追述,值得注意的是大部分APK 只会解析一次会将APK 的信息存储在/data/system/package_caches,下面除非apk出现了更新否则不会再重新解析


http://www.ppmy.cn/ops/132681.html

相关文章

Oracle 第26章:Oracle Data Guard

Oracle Data Guard 是 Oracle 数据库的一个重要特性&#xff0c;它提供了一种方法来维护一个或多个备用数据库&#xff0c;这些备用数据库可以是物理的、逻辑的或者是快照的。Data Guard 主要用于数据保护和灾难恢复&#xff0c;确保在主数据库发生故障时&#xff0c;能够快速地…

Java 处理 json 格式数据解析为 csv 格式

Java处理json格式数据解析为csv格式 如果不使用 JSON 工具库&#xff0c;你可以手动解析 JSON 格式字符串并将其转换为 CSV 格式字符串。 以下是一个简单示例&#xff0c;展示如何实现这一功能。 示例代码 下面的示例代码手动处理 JSON 字符串&#xff0c;将其转换为 CSV 格式字…

数据分析-39-时间序列分解之经验小波分解EWT

文章目录 1 时间序列模态分解1.1 模态分解的概念1.2 模态分解的作用1.3 常用的模态分解方法1.4 模态分解的常用库2 经验小波分解EWT2.1 EWT的流程2.2 加载数据集2.2.1 数据重采样2.2.2 原始数据可视化2.3 代码实现EWT3 参考附录1 时间序列模态分解 1.1 模态分解的概念 时间序…

es 数据清理delete_by_query

POST /索引名/_delete_by_query?conflictsproceed&scroll_size2000&wait_for_completionfalse&slices36 {"size": 2000, "query": {"bool": { "must": [{"terms": {"rule_id": [800007]}}]}} }slice…

第2章2.3立项【硬件产品立项的核心内容】

硬件产品立项的核心内容 2.3 硬件产品立项的核心内容2.3.1 第一步&#xff1a;市场趋势判断2.3.2 第二步&#xff1a;竞争对手分析1.竞争对手识别2.根据竞争对手分析制定策略 2.3.3 第三步&#xff1a;客户分析2.3.4 第四步&#xff1a;产品定义2.3.5 第五步&#xff1a;开发执…

在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序

如果您有 Android 设备&#xff0c;您可能会将个人和专业的重要文件保存在设备的 SD 卡上。这些文件包括照片、视频、文档和各种其他类型的文件。您绝对不想丢失这些文件&#xff0c;但当您的 SD 卡损坏时&#xff0c;数据丢失是不可避免的。 幸运的是&#xff0c;您不需要这样…

东胜物流软件 AttributeAdapter.aspx SQL 注入漏洞复现

0x01 产品简介 东胜物流软件是青岛东胜伟业软件有限公司一款集订单管理、仓库管理、运输管理等多种功能于一体的物流管理软件。该公司初创于2004年11月(前身为青岛景宏物流信息技术有限公司),专注于航运物流相关环节的产品和服务。东胜物流信息管理系统货代版采用MS-SQLser…

JMeter与大模型融合应用之jmeter.properties配置文件新增配置

JMeter与大模型融合应用之jmeter.properties配置文件新增配置 背景 Apache JMeter 是一款流行的开源性能测试工具&#xff0c;它允许用户通过创建和运行多种类型的测试来评估应用程序的性能。jmeter.properties 文件是 JMeter 的主要配置文件之一&#xff0c;用户可以在其中进…