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范围分片
在电商系统中,部分商品在短时间内遭受大量访问的单一热点问题,可能引发服务器压力增大、响应速度变慢、甚至系统崩溃等问题。为了解决这一问题,可以采取以下策略: 一、增加服务器容量和带宽 提升硬件性能:为了应对高…...
KubeSphere 容器平台高可用:环境搭建与可视化操作指南
Linux_k8s篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:KubeSphere 容器平台高可用:环境搭建与可视化操作指南 版本号: 1.0,0 作者: 老王要学习 日期: 2025.06.05 适用环境: Ubuntu22 文档说…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...

从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...

【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...

ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Psychopy音频的使用
Psychopy音频的使用 本文主要解决以下问题: 指定音频引擎与设备;播放音频文件 本文所使用的环境: Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...