当前位置: 首页 > news >正文

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函数&#xff0c;只说了一下几个重要参数的来源还没展开&#xff0c;这里继续&#xff0c;有兴趣的可以看链接: Android 启动时应用的安装解析过程《一》 一、系统应用的扫描安装 /*** Install apps from system dirs.*/Gu…...

智谱AI:ChatGLM强大的生成式语言模型

目录 智谱AI:ChatGLM强大的生成式语言模型 一、ChatGLM的定义与特点 二、ChatGLM的应用场景 三、举例说明 四、注意事项 智谱AI:ChatGLM强大的生成式语言模型 它通过对话的方式能够生成自然流畅的文本,这一特性使其在多个领域都有广泛的应用潜力,特别是在智能对话和智能…...

git tag

已经发布了 v1.0 v2.0 v3.0 三个版本&#xff0c;这个时候&#xff0c;我突然想不改现有代码的前提下&#xff0c;在 v2.0 的基础上加个新功能&#xff0c;作为 v4.0 发布。就可以检出 v2.0 的代码作为一个 branch &#xff0c;然后作为开发分支。 要查看仓库中的所有标签 gi…...

Golang--反射

1、概念 反射可以做什么? 反射可以在运行时动态获取变量的各种信息&#xff0c;比如变量的类型&#xff0c;类别等信息如果是结构体变量&#xff0c;还可以获取到结构体本身的信息(包括结构体的字段、方法)通过反射&#xff0c;可以修改变量的值&#xff0c;可以调用关联的方法…...

ABAP:SET CURSOR FIELD设置鼠标焦点

SET CURSOR FIELD <字段名>&#xff1a;设置鼠标焦点到该字段 SET CURSOR 设置到鼠标焦点列还是行 SET CURSOR LINE 设置鼠标焦点到行 GET CURSOR field <字段名> &#xff1a;这个相对应的获取鼠标焦点得到的字段...

【专题】2024年全球生物医药交易报告汇总PDF洞察(附原数据表)

原文链接&#xff1a;https://tecdat.cn/?p38191 在当今复杂多变的全球经济环境下&#xff0c;医药行业正面临着诸多挑战与机遇。2024 年&#xff0c;医药行业的发展态势备受关注。 一方面&#xff0c;全球生物医药交易活跃&#xff0c;2021 - 2023 年的交易中&#xff0c;已…...

LabVIEW气体检测系统

随着工业化进程的加速&#xff0c;环境污染问题愈加严峻&#xff0c;尤其是有害气体的排放对人类生存环境构成了严重威胁。为了更好地监测这些有害气体&#xff0c;开发一个高效、准确且易于操作的气体检测系统显得尤为重要。LabVIEW软件开发的气体检测系统&#xff0c;采用激光…...

LeetCode78. 子集(2024秋季每日一题 58)

给你一个整数数组 nums &#xff0c;数组中的元素 互不相同 。返回该数组所有可能的 子集&#xff08;幂集&#xff09;。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[],[1],[2],[1,2],[3…...

推荐一款功能强大的视频修复软件:Apeaksoft Video Fixer

Apeaksoft Video Fixer是一款功能强大的视频修复软件&#xff0c;专门用于修复损坏、不可播放、卡顿、画面失真、黑屏等视频问题。只需提供一个准确且有效的样本视频作为参考&#xff0c;该软件就能将受损视频修复到与样本视频相同的质量。该软件目前支持MP4、MOV、3GP等格式的…...

Golang--网络编程

1、概念 网络编程&#xff1a;把分布在不同地理区域的计算机与专门的外部设备用通信线路互连成一个规模大、功能强的网络系统&#xff0c;从而使众多的计算机可以方便地互相传递信息、共享数据、软件、数据信息等资源。 客户端&#xff08;Client&#xff09; 客户端是请求服务…...

区块链技术在数字版权管理中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 区块链技术在数字版权管理中的应用 区块链技术在数字版权管理中的应用 区块链技术在数字版权管理中的应用 引言 区块链技术概述 …...

WPS单元格重复值提示设置

选中要检查的所有的单元格 设置提示效果 当出现单元格值重复时&#xff0c;重复的单元格就会自动变化 要修改或删除&#xff0c;点击...

Scala 的包及其导入

Scala使用包来创建用于模块化程序的命名空间。通过在Scala文件的顶部声明一个或多个包名称可以创建包&#xff0c;另一种声明包的方式是使用0&#xff0c;这种方式可以嵌套包&#xff0c;并且提供更好的范围与封装控制。对于包的导入&#xff0c;Scala与Java的区别之一便是&…...

架构师备考-概念背诵(软件工程)

软件工程 软件开发生命周期: 软件定义时期:包括可行性研究和详细需求分析过程,任务是确定软件开发工程必须完成的总目标,具体可分成问题定义、可行性研究、需求分析等。软件开发时期:就是软件的设计与实现,可分成概要设计、详细设计、编码、测试等。软件运行和维护:就是…...

DIP switch是什么?

**‌DIP开关&#xff08;DIP switch&#xff09;‌&#xff0c;也称为指拨开关&#xff0c;是一种可以人工调整的开关&#xff0c;通常以标准双列直插封装&#xff08;DIP&#xff09;的形式出现。**DIP开关一般设计在印刷电路板上&#xff0c;配合其他电子元件使用&#xff0c…...

【销帮帮-注册_登录安全分析报告-试用页面存在安全隐患】

联通支付注册/登录安全分析报告 前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 暴力破解密码&#xff0c;造成用户信息泄露短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨…...

2024年下半年系统分析师论文

2024年下半年11月份系统分析师考试论文 1、静态测试工具和方法 可以从代码桌前检查&#xff0c;代码审查&#xff0c;代码走查组织文章 2、DevOps开发 可以从开发&#xff0c;运维&#xff0c;测试的自动化协作入手&#xff0c;跨部门沟通需求也算 3、业务流程分析 从BPR…...

【计算机网络】万字详解 UDP 和 TCP

&#x1f970;&#x1f970;&#x1f970;来都来了&#xff0c;不妨点个关注叭&#xff01; &#x1f449;博客主页&#xff1a;欢迎各位大佬!&#x1f448; 文章目录 1. UDP1.1 UDP 报文格式1.1.1 源端口/目的端口1.1.2 报文长度1.1.3 校验和 2. TCP2.1 TCP 报文结构2.2 TCP 特…...

创建者模式之【建造者模式】

建造者模式 概述 将一个复杂对象的构建与表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 分离了部件的构造(由Builder来负责)和装配(由Director负责)。 从而可以构造出复杂的对象。这个模式适用于&#xff1a;某个对象的构建过程复杂的情况。由于实现了构建和…...

电商系统中,如何解决部分商品在短时间大量访问的单一热点问题?------Range范围分片

在电商系统中&#xff0c;部分商品在短时间内遭受大量访问的单一热点问题&#xff0c;可能引发服务器压力增大、响应速度变慢、甚至系统崩溃等问题。为了解决这一问题&#xff0c;可以采取以下策略&#xff1a; 一、增加服务器容量和带宽 提升硬件性能&#xff1a;为了应对高…...

在软件开发中正确使用MySQL日期时间类型的深度解析

在日常软件开发场景中&#xff0c;时间信息的存储是底层且核心的需求。从金融交易的精确记账时间、用户操作的行为日志&#xff0c;到供应链系统的物流节点时间戳&#xff0c;时间数据的准确性直接决定业务逻辑的可靠性。MySQL作为主流关系型数据库&#xff0c;其日期时间类型的…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

相机从app启动流程

一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...

sqlserver 根据指定字符 解析拼接字符串

DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域&#xff0c;Hive 作为 Hadoop 生态中重要的数据仓库工具&#xff0c;其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式&#xff0c;很多开发者常常陷入选择困境。本文将从底…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!

简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求&#xff0c;并检查收到的响应。它以以下模式之一…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

AGain DB和倍数增益的关系

我在设置一款索尼CMOS芯片时&#xff0c;Again增益0db变化为6DB&#xff0c;画面的变化只有2倍DN的增益&#xff0c;比如10变为20。 这与dB和线性增益的关系以及传感器处理流程有关。以下是具体原因分析&#xff1a; 1. dB与线性增益的换算关系 6dB对应的理论线性增益应为&…...