Android 启动时应用的安装解析过程《二》
上一篇内容说到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出现了更新否则不会再重新解析
相关文章:
Android 启动时应用的安装解析过程《二》
上一篇内容说到InitAppsHelper这个类的initSystemApps函数,只说了一下几个重要参数的来源还没展开,这里继续,有兴趣的可以看链接: Android 启动时应用的安装解析过程《一》 一、系统应用的扫描安装 /*** Install apps from system dirs.*/Gu…...
智谱AI:ChatGLM强大的生成式语言模型
目录 智谱AI:ChatGLM强大的生成式语言模型 一、ChatGLM的定义与特点 二、ChatGLM的应用场景 三、举例说明 四、注意事项 智谱AI:ChatGLM强大的生成式语言模型 它通过对话的方式能够生成自然流畅的文本,这一特性使其在多个领域都有广泛的应用潜力,特别是在智能对话和智能…...
git tag
已经发布了 v1.0 v2.0 v3.0 三个版本,这个时候,我突然想不改现有代码的前提下,在 v2.0 的基础上加个新功能,作为 v4.0 发布。就可以检出 v2.0 的代码作为一个 branch ,然后作为开发分支。 要查看仓库中的所有标签 gi…...
Golang--反射
1、概念 反射可以做什么? 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别等信息如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射,可以修改变量的值,可以调用关联的方法…...
ABAP:SET CURSOR FIELD设置鼠标焦点
SET CURSOR FIELD <字段名>:设置鼠标焦点到该字段 SET CURSOR 设置到鼠标焦点列还是行 SET CURSOR LINE 设置鼠标焦点到行 GET CURSOR field <字段名> :这个相对应的获取鼠标焦点得到的字段...
【专题】2024年全球生物医药交易报告汇总PDF洞察(附原数据表)
原文链接:https://tecdat.cn/?p38191 在当今复杂多变的全球经济环境下,医药行业正面临着诸多挑战与机遇。2024 年,医药行业的发展态势备受关注。 一方面,全球生物医药交易活跃,2021 - 2023 年的交易中,已…...
LabVIEW气体检测系统
随着工业化进程的加速,环境污染问题愈加严峻,尤其是有害气体的排放对人类生存环境构成了严重威胁。为了更好地监测这些有害气体,开发一个高效、准确且易于操作的气体检测系统显得尤为重要。LabVIEW软件开发的气体检测系统,采用激光…...
LeetCode78. 子集(2024秋季每日一题 58)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的 子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1: 输入:nums [1,2,3] 输出:[[],[1],[2],[1,2],[3…...
推荐一款功能强大的视频修复软件:Apeaksoft Video Fixer
Apeaksoft Video Fixer是一款功能强大的视频修复软件,专门用于修复损坏、不可播放、卡顿、画面失真、黑屏等视频问题。只需提供一个准确且有效的样本视频作为参考,该软件就能将受损视频修复到与样本视频相同的质量。该软件目前支持MP4、MOV、3GP等格式的…...
Golang--网络编程
1、概念 网络编程:把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统,从而使众多的计算机可以方便地互相传递信息、共享数据、软件、数据信息等资源。 客户端(Client) 客户端是请求服务…...
区块链技术在数字版权管理中的应用
💓 博客主页:瑕疵的CSDN主页 📝 Gitee主页:瑕疵的gitee主页 ⏩ 文章专栏:《热点资讯》 区块链技术在数字版权管理中的应用 区块链技术在数字版权管理中的应用 区块链技术在数字版权管理中的应用 引言 区块链技术概述 …...
WPS单元格重复值提示设置
选中要检查的所有的单元格 设置提示效果 当出现单元格值重复时,重复的单元格就会自动变化 要修改或删除,点击...
Scala 的包及其导入
Scala使用包来创建用于模块化程序的命名空间。通过在Scala文件的顶部声明一个或多个包名称可以创建包,另一种声明包的方式是使用0,这种方式可以嵌套包,并且提供更好的范围与封装控制。对于包的导入,Scala与Java的区别之一便是&…...
架构师备考-概念背诵(软件工程)
软件工程 软件开发生命周期: 软件定义时期:包括可行性研究和详细需求分析过程,任务是确定软件开发工程必须完成的总目标,具体可分成问题定义、可行性研究、需求分析等。软件开发时期:就是软件的设计与实现,可分成概要设计、详细设计、编码、测试等。软件运行和维护:就是…...
DIP switch是什么?
**DIP开关(DIP switch),也称为指拨开关,是一种可以人工调整的开关,通常以标准双列直插封装(DIP)的形式出现。**DIP开关一般设计在印刷电路板上,配合其他电子元件使用,…...
【销帮帮-注册_登录安全分析报告-试用页面存在安全隐患】
联通支付注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨…...
2024年下半年系统分析师论文
2024年下半年11月份系统分析师考试论文 1、静态测试工具和方法 可以从代码桌前检查,代码审查,代码走查组织文章 2、DevOps开发 可以从开发,运维,测试的自动化协作入手,跨部门沟通需求也算 3、业务流程分析 从BPR…...
【计算机网络】万字详解 UDP 和 TCP
🥰🥰🥰来都来了,不妨点个关注叭! 👉博客主页:欢迎各位大佬!👈 文章目录 1. UDP1.1 UDP 报文格式1.1.1 源端口/目的端口1.1.2 报文长度1.1.3 校验和 2. TCP2.1 TCP 报文结构2.2 TCP 特…...
创建者模式之【建造者模式】
建造者模式 概述 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于:某个对象的构建过程复杂的情况。由于实现了构建和…...
电商系统中,如何解决部分商品在短时间大量访问的单一热点问题?------Range范围分片
在电商系统中,部分商品在短时间内遭受大量访问的单一热点问题,可能引发服务器压力增大、响应速度变慢、甚至系统崩溃等问题。为了解决这一问题,可以采取以下策略: 一、增加服务器容量和带宽 提升硬件性能:为了应对高…...
Vue记事本应用实现教程
文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展:显示创建时间8. 功能扩展:记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
React Native在HarmonyOS 5.0阅读类应用开发中的实践
一、技术选型背景 随着HarmonyOS 5.0对Web兼容层的增强,React Native作为跨平台框架可通过重新编译ArkTS组件实现85%以上的代码复用率。阅读类应用具有UI复杂度低、数据流清晰的特点。 二、核心实现方案 1. 环境配置 (1)使用React Native…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)
在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马(服务器方面的)的原理,连接,以及各种木马及连接工具的分享 文件木马:https://w…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
Golang——6、指针和结构体
指针和结构体 1、指针1.1、指针地址和指针类型1.2、指针取值1.3、new和make 2、结构体2.1、type关键字的使用2.2、结构体的定义和初始化2.3、结构体方法和接收者2.4、给任意类型添加方法2.5、结构体的匿名字段2.6、嵌套结构体2.7、嵌套匿名结构体2.8、结构体的继承 3、结构体与…...
第7篇:中间件全链路监控与 SQL 性能分析实践
7.1 章节导读 在构建数据库中间件的过程中,可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中,必须做到: 🔍 追踪每一条 SQL 的生命周期(从入口到数据库执行)&#…...
