Android 安装过程三 MSG_ON_SESSION_SEALED、MSG_STREAM_VALIDATE_AND_COMMIT的处理

  Android 安装过程一 界面跳转 知道,在InstallInstalling Activity中,PackageInstallerSession对象创建之后,接着会打开它,然后将安装文件进行拷贝,拷贝完成之后,会对Session对象确认。
  从Session对象确认往下看,Session对象在安装进程中对应是PackageInstaller.Session对象。它最终会进入系统进程调用到PackageInstallerSession对象的commit(@NonNull IntentSender statusReceiver, boolean forTransfer)方法,如下:

    @Overridepublic void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {if (hasParentSessionId()) {throw new IllegalStateException("Session " + sessionId + " is a child of multi-package session "+ getParentSessionId() +  " and may not be committed directly.");}if (!markAsSealed(statusReceiver, forTransfer)) {return;}if (isMultiPackage()) {synchronized (mLock) {final IntentSender childIntentSender =new ChildStatusIntentReceiver(mChildSessions.clone(), statusReceiver).getIntentSender();boolean sealFailed = false;for (int i = mChildSessions.size() - 1; i >= 0; --i) {// seal all children, regardless if any of them fail; we'll throw/return// as appropriate once all children have been processedif (!mChildSessions.valueAt(i).markAsSealed(childIntentSender, forTransfer)) {sealFailed = true;}}if (sealFailed) {return;}}}dispatchSessionSealed();}

  如果该Session对象有父Session,则会报异常。
  接着调用markAsSealed(statusReceiver, forTransfer),在该方法里面它会将PackageInstallerSession的成员mRemoteStatusReceiver进行赋值为参数statusReceiver,并且会改变PackageInstallerSession的状态mSealed为true。mRemoteStatusReceiver是封装了广播的Intent对象,等待安装结果进行通知。
  如果是多包的情况下,会设置子Session,并更改其mSealed。
  这里尽量先说普通的单安装文件的情况,接着会调用dispatchSessionSealed()。看一下它的代码:

    private void dispatchSessionSealed() {mHandler.obtainMessage(MSG_ON_SESSION_SEALED).sendToTarget();}

发送MSG_ON_SESSION_SEALED消息

  它主要是发送MSG_ON_SESSION_SEALED消息。它会将消息发送到叫“PackageInstaller”的线程中进行处理。
  “PackageInstaller”线程里会进行应用的安装工作,通过消息机制接收消息,这个从下面可以看出来。
  “PackageInstaller”线程定义在PackageInstallerService类的构造函数中。
  MSG_ON_SESSION_SEALED消息的处理在PackageInstallerSession的成员mHandlerCallback处(),如下:

    private final Handler.Callback mHandlerCallback = new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case MSG_ON_SESSION_SEALED:handleSessionSealed();break;case MSG_STREAM_VALIDATE_AND_COMMIT:handleStreamValidateAndCommit();break;case MSG_INSTALL:handleInstall();break;case MSG_ON_PACKAGE_INSTALLED:final SomeArgs args = (SomeArgs) msg.obj;final String packageName = (String) args.arg1;final String message = (String) args.arg2;final Bundle extras = (Bundle) args.arg3;final IntentSender statusReceiver = (IntentSender) args.arg4;final int returnCode = args.argi1;args.recycle();sendOnPackageInstalled(mContext, statusReceiver, sessionId,isInstallerDeviceOwnerOrAffiliatedProfileOwner(), userId,packageName, returnCode, message, extras);break;case MSG_SESSION_VALIDATION_FAILURE:final int error = msg.arg1;final String detailMessage = (String) msg.obj;onSessionValidationFailure(error, detailMessage);break;}return true;}};

  在这里会接着调用handleSessionSealed()进行处理。还可以看到,这里后面还会处理MSG_STREAM_VALIDATE_AND_COMMIT、MSG_INSTALL、MSG_ON_PACKAGE_INSTALLED、MSG_SESSION_VALIDATION_FAILURE消息。
  handleSessionSealed()的代码如下:

    private void handleSessionSealed() {assertSealed("dispatchSessionSealed");// Persist the fact that we've sealed ourselves to prevent// mutations of any hard links we create.mCallback.onSessionSealedBlocking(this);dispatchStreamValidateAndCommit();}private void dispatchStreamValidateAndCommit() {mHandler.obtainMessage(MSG_STREAM_VALIDATE_AND_COMMIT).sendToTarget();}

  可见它主要调用了mCallback的回调onSessionSealedBlocking方法,接着发送了MSG_STREAM_VALIDATE_AND_COMMIT消息。mCallback的定义是在PackageInstallerService类中,它是InternalCallback对象。看一下它的onSessionSealedBlocking方法实现:

		public void onSessionSealedBlocking(PackageInstallerSession session) {// It's very important that we block until we've recorded the// session as being sealed, since we never want to allow mutation// after sealing.mSettingsWriteRequest.runNow();}

  mSettingsWriteRequest它是一个RequestThrottle对象,他负责将PackageInstallerSession对象持久化。它主要涉及到"/data/system/install_sessions.xml"文件,它里面记录所有PackageInstallerSession对象的信息状态。“/data/system/install_sessions"目录下是对应应用的图标icon。
  调用它的runNow()方法,就是将所有的PackageInstallerSession对象信息状态一起写入”/data/system/install_sessions.xml"文件。

发送MSG_STREAM_VALIDATE_AND_COMMIT消息

  handleSessionSealed() 接着会发送MSG_STREAM_VALIDATE_AND_COMMIT消息,该消息的处理在PackageInstallerSession的成员mHandlerCallback处,参考上面。
它主要是执行handleStreamValidateAndCommit()函数:

    private void handleStreamValidateAndCommit() {PackageManagerException unrecoverableFailure = null;// This will track whether the session and any children were validated and are ready to// progress to the next phase of installboolean allSessionsReady = false;try {allSessionsReady = streamValidateAndCommit();} catch (PackageManagerException e) {unrecoverableFailure = e;}…………if (!allSessionsReady) {return;}mHandler.obtainMessage(MSG_INSTALL).sendToTarget();}

  这里省略了多包的安装,先看单APK的安装。
  调用streamValidateAndCommit()得到结果allSessionsReady ,如果allSessionsReady为false,则发送MSG_INSTALL消息。
  streamValidateAndCommit()对于单APK安装,主要是调用validateApkInstallLocked()函数,然后会将PackageInstallerSession对象的mCommitted = true。
  接着看下validateApkInstallLocked()方法,该方法主要来检测相关内容的一致性。像包名、版本、签名。因为它存在split apk的安装方式,每个安装包都需要检查。它还会更改安装包的名字。

检测相关内容的一致性validateApkInstallLocked()

  看一下validateApkInstallLocked()的代码,分段来看,分段一:

    @GuardedBy("mLock")private PackageLite validateApkInstallLocked() throws PackageManagerException {ApkLite baseApk = null;PackageLite packageLite = null;mPackageLite = null;mPackageName = null;mVersionCode = -1;mSigningDetails = PackageParser.SigningDetails.UNKNOWN;mResolvedBaseFile = null;mResolvedStagedFiles.clear();mResolvedInheritedFiles.clear();final PackageInfo pkgInfo = mPm.getPackageInfo(params.appPackageName, PackageManager.GET_SIGNATURES| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);// Partial installs must be consistent with existing installif (params.mode == SessionParams.MODE_INHERIT_EXISTING&& (pkgInfo == null || pkgInfo.applicationInfo == null)) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Missing existing base package");}// Default to require only if existing base apk has fs-verity.mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()&& params.mode == SessionParams.MODE_INHERIT_EXISTING&& VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath());final List<File> removedFiles = getRemovedFilesLocked();final List<String> removeSplitList = new ArrayList<>();if (!removedFiles.isEmpty()) {for (File removedFile : removedFiles) {final String fileName = removedFile.getName();final String splitName = fileName.substring(0, fileName.length() - REMOVE_MARKER_EXTENSION.length());removeSplitList.add(splitName);}}final List<File> addedFiles = getAddedApksLocked();if (addedFiles.isEmpty() && removeSplitList.size() == 0) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,String.format("Session: %d. No packages staged in %s", sessionId,stageDir.getAbsolutePath()));}// Verify that all staged packages are internally consistentfinal ArraySet<String> stagedSplits = new ArraySet<>();final ArrayMap<String, ApkLite> splitApks = new ArrayMap<>();final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();for (File addedFile : addedFiles) {final ParseResult<ApkLite> result = ApkLiteParseUtils.parseApkLite(input.reset(),addedFile, ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);if (result.isError()) {throw new PackageManagerException(result.getErrorCode(),result.getErrorMessage(), result.getException());}final ApkLite apk = result.getResult();if (!stagedSplits.add(apk.getSplitName())) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Split " + apk.getSplitName() + " was defined multiple times");}// Use first package to define unknown valuesif (mPackageName == null) {mPackageName = apk.getPackageName();mVersionCode = apk.getLongVersionCode();}if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {mSigningDetails = apk.getSigningDetails();}assertApkConsistentLocked(String.valueOf(addedFile), apk);// Take this opportunity to enforce uniform namingfinal String targetName = ApkLiteParseUtils.splitNameToFileName(apk);if (!FileUtils.isValidExtFilename(targetName)) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Invalid filename: " + targetName);}// Yell loudly if installers drop attribute installLocation when apps explicitly set.if (apk.getInstallLocation() != PackageInfo.INSTALL_LOCATION_UNSPECIFIED) {final String installerPackageName = getInstallerPackageName();if (installerPackageName != null&& (params.installLocation != apk.getInstallLocation())) {Slog.wtf(TAG, installerPackageName+ " drops manifest attribute android:installLocation in " + targetName+ " for " + mPackageName);}}final File targetFile = new File(stageDir, targetName);resolveAndStageFileLocked(addedFile, targetFile, apk.getSplitName());// Base is coming from sessionif (apk.getSplitName() == null) {mResolvedBaseFile = targetFile;baseApk = apk;} else {splitApks.put(apk.getSplitName(), apk);}}if (removeSplitList.size() > 0) {if (pkgInfo == null) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Missing existing base package for " + mPackageName);}// validate split names marked for removalfor (String splitName : removeSplitList) {if (!ArrayUtils.contains(pkgInfo.splitNames, splitName)) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Split not found: " + splitName);}}// ensure we've got appropriate package name, version code and signaturesif (mPackageName == null) {mPackageName = pkgInfo.packageName;mVersionCode = pkgInfo.getLongVersionCode();}if (mSigningDetails == PackageParser.SigningDetails.UNKNOWN) {mSigningDetails = unsafeGetCertsWithoutVerification(pkgInfo.applicationInfo.sourceDir);}}

  首先根据包名得到包信息对象pkgInfo,如果是第一次安装则是null。
  安装分两种模式,一个是全部安装(MODE_FULL_INSTALL),一个则是部分安装(MODE_INHERIT_EXISTING)。如果是部分安装模式,则之前已经安装过,不然会报PackageManagerException异常。
  mVerityFoundForApks变量是控制fs-verity验证,它是确认安装包完整性的一个机制。系统支持、模式为部分安装、安装文件有fs-verity标识,这是这个变量为true的三个条件。
  removedFiles是安装目录中去除文件。安装目录是在stageDir变量中,删除文件则是目录中以".removed"结尾的文件。接下来如果存在删除文件,则会将名字去掉".removed"存放在removeSplitList集合中。
  addedFiles局部变量中存储的为stageDir目录中的apk文件。该目录中可能存在以".removed"、“.dm”、“.fsv_sig”、“.digests”、“.signature"结尾的文件,还包括apk文件。我们之前举例子中,它的名字为"PackageInstaller”。而addedFiles集合里装的就是安装apk文件,将其他的文件都会筛除掉。我们一般的apk安装都是一个文件,不过它还有split apks安装方式,会有多个apk文件,所以这里使用集合。
  如果addedFiles集合和removeSplitList都为空,则会报PackageManagerException异常。
  接着对addedFiles进行循环,下面看看每个循环里的工作。
  首先解析安装包addedFile,得到ApkLite对象apk。
  将apk的分包名(apk.getSplitName())添加进stagedSplits集合。基本包的getSplitName()为null。并且每个分包的分包名不允许相同,不然会报PackageManagerException异常。
  mPackageName、mVersionCode、mSigningDetails都取第一次解析的安装包里的内容。接着会用assertApkConsistentLocked() 方法检查后面继续解析的安装包如果和之前的不一致,会报PackageManagerException异常。所以如果分包安装,每个分包的包名、版本、签名必须一样才行。
  接着会生成新包名targetName。如果是基本包,为"base.apk";如果为分包,名字为"split_" + apk.getSplitName() + “.apk”。
  如果安装包配置文件指定了安装位置和参数params的不同,会舍弃安装包配置文件里指定的。
  然后生成目标文件targetFile。它的名字是上面说的targetName。
  接着调用resolveAndStageFileLocked()方法,它主要是更改安装包及相关文件的名字。
  例如目前的安装文件名为"PackageInstaller",之后会更改为"base.apk"。如果存在apk的fs-verity证书(“.fsv_sig"结尾文件),也会进行名字修改,例如由"PackageInstaller.fsv_sig"改成"base.apk.fsv_sig”。同理,apk对应的".dm"、".digests"也会进行名字修改。
  同时,这些修改之后的文件都会加入mResolvedStagedFiles集合中。
  validateApkInstallLocked()继续会判断apk.getSplitName() == null,如此,它即为基本包。并将mResolvedBaseFile赋值为该基本包目标文件,baseApk = apk。如果不是基本包,会将分包加入splitApks中,它的key即为分包名。
  接下来继续判断,removeSplitList中大小大于0的情况下。如果不存在已经安装的包,则报PackageManagerException异常。如果安装包信息对象pkgInfo.splitNames不包括删除的分包名,也会报PackageManagerException异常。继续检查mPackageName、mVersionCode和mSigningDetails的值,如果为空,则进行赋值。
  接下来看validateApkInstallLocked()的代码,分段二:

        if (isIncrementalInstallation() && !isIncrementalInstallationAllowed(mPackageName)) {throw new PackageManagerException(PackageManager.INSTALL_FAILED_SESSION_INVALID,"Incremental installation of this package is not allowed.");}if (mInstallerUid != mOriginalInstallerUid) {// Session has been transferred, check package name.if (TextUtils.isEmpty(mPackageName) || !mPackageName.equals(mOriginalInstallerPackageName)) {throw new PackageManagerException(PackageManager.INSTALL_FAILED_PACKAGE_CHANGED,"Can only transfer sessions that update the original installer");}}if (!mChecksums.isEmpty()) {throw new PackageManagerException(PackageManager.INSTALL_FAILED_SESSION_INVALID,"Invalid checksum name(s): " + String.join(",", mChecksums.keySet()));}if (params.mode == SessionParams.MODE_FULL_INSTALL) {// Full installs must include a base packageif (!stagedSplits.contains(null)) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Full install must include a base package");}if (baseApk.isSplitRequired() && stagedSplits.size() <= 1) {throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT,"Missing split for " + mPackageName);}// For mode full install, we compose package lite for future usage instead of// re-parsing it again and again.final ParseResult<PackageLite> pkgLiteResult =ApkLiteParseUtils.composePackageLiteFromApks(input.reset(), stageDir, baseApk,splitApks, true);if (pkgLiteResult.isError()) {throw new PackageManagerException(pkgLiteResult.getErrorCode(),pkgLiteResult.getErrorMessage(), pkgLiteResult.getException());}mPackageLite = pkgLiteResult.getResult();packageLite = mPackageLite;} else {

  如果安装Uid发生改变,包名是不允许改变的,不然会报PackageManagerException异常。
  在完全安装的情况下,如果stagedSplits不包括null,则代表没有基础包,会报异常。如果基础包需要基础包,但是stagedSplits不包含基础包,也会报异常。接着会得到PackageLite对象,并将它赋值给mPackageLite和packageLite。
  接下来看validateApkInstallLocked()的代码,分段三:

        } else {final ApplicationInfo appInfo = pkgInfo.applicationInfo;ParseResult<PackageLite> pkgLiteResult = ApkLiteParseUtils.parsePackageLite(input.reset(), new File(appInfo.getCodePath()), 0);if (pkgLiteResult.isError()) {throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,pkgLiteResult.getErrorMessage(), pkgLiteResult.getException());}final PackageLite existing = pkgLiteResult.getResult();packageLite = existing;assertPackageConsistentLocked("Existing", existing.getPackageName(),existing.getLongVersionCode());final PackageParser.SigningDetails signingDetails =unsafeGetCertsWithoutVerification(existing.getBaseApkPath());if (!mSigningDetails.signaturesMatchExactly(signingDetails)) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Existing signatures are inconsistent");}// Inherit base if not overridden.if (mResolvedBaseFile == null) {mResolvedBaseFile = new File(appInfo.getBaseCodePath());inheritFileLocked(mResolvedBaseFile);}// Inherit splits if not overridden.if (!ArrayUtils.isEmpty(existing.getSplitNames())) {for (int i = 0; i < existing.getSplitNames().length; i++) {final String splitName = existing.getSplitNames()[i];final File splitFile = new File(existing.getSplitApkPaths()[i]);final boolean splitRemoved = removeSplitList.contains(splitName);if (!stagedSplits.contains(splitName) && !splitRemoved) {inheritFileLocked(splitFile);}}}// Inherit compiled oat directory.final File packageInstallDir = (new File(appInfo.getBaseCodePath())).getParentFile();mInheritedFilesBase = packageInstallDir;final File oatDir = new File(packageInstallDir, "oat");if (oatDir.exists()) {final File[] archSubdirs = oatDir.listFiles();// Keep track of all instruction sets we've seen compiled output for.// If we're linking (and not copying) inherited files, we can recreate the// instruction set hierarchy and link compiled output.if (archSubdirs != null && archSubdirs.length > 0) {final String[] instructionSets = InstructionSets.getAllDexCodeInstructionSets();for (File archSubDir : archSubdirs) {// Skip any directory that isn't an ISA subdir.if (!ArrayUtils.contains(instructionSets, archSubDir.getName())) {continue;}File[] files = archSubDir.listFiles();if (files == null || files.length == 0) {continue;}mResolvedInstructionSets.add(archSubDir.getName());mResolvedInheritedFiles.addAll(Arrays.asList(files));}}}// Inherit native libraries for DONT_KILL sessions.if (mayInheritNativeLibs() && removeSplitList.isEmpty()) {File[] libDirs = new File[]{new File(packageInstallDir, NativeLibraryHelper.LIB_DIR_NAME),new File(packageInstallDir, NativeLibraryHelper.LIB64_DIR_NAME)};for (File libDir : libDirs) {if (!libDir.exists() || !libDir.isDirectory()) {continue;}final List<String> libDirsToInherit = new ArrayList<>();final List<File> libFilesToInherit = new ArrayList<>();for (File archSubDir : libDir.listFiles()) {if (!archSubDir.isDirectory()) {continue;}String relLibPath;try {relLibPath = getRelativePath(archSubDir, packageInstallDir);} catch (IOException e) {Slog.e(TAG, "Skipping linking of native library directory!", e);// shouldn't be possible, but let's avoid inheriting these to be safelibDirsToInherit.clear();libFilesToInherit.clear();break;}File[] files = archSubDir.listFiles();if (files == null || files.length == 0) {continue;}libDirsToInherit.add(relLibPath);libFilesToInherit.addAll(Arrays.asList(files));}for (String subDir : libDirsToInherit) {if (!mResolvedNativeLibPaths.contains(subDir)) {mResolvedNativeLibPaths.add(subDir);}}mResolvedInheritedFiles.addAll(libFilesToInherit);}}// For the case of split required, failed if no splits existedif (packageLite.isSplitRequired()) {final int existingSplits = ArrayUtils.size(existing.getSplitNames());final boolean allSplitsRemoved = (existingSplits == removeSplitList.size());final boolean onlyBaseFileStaged = (stagedSplits.size() == 1&& stagedSplits.contains(null));if (allSplitsRemoved && (stagedSplits.isEmpty() || onlyBaseFileStaged)) {throw new PackageManagerException(INSTALL_FAILED_MISSING_SPLIT,"Missing split for " + mPackageName);}}}

  这是安装模式为部分安装的情况。由于它已经存在安装包,所以它需要继承存在安装包的一些信息。包括基础包、分包、编译的oat目录、本地库。这些代码主要就是进行的这些处理。
  首先它会解析存在安装包的信息生成PackageLite对象existing,并将它赋值给变量packageLite。接着它会调用assertPackageConsistentLocked(),在这里它是检查,待安装文件和已安装文件的包名、版本是否一致。
  接下来它会继续检查待安装文件和已安装文件的签名是否一致。
  如果待安装文件的基本包没有设置,将它设置为当前已安装文件的基本包文件。同时会调用inheritFileLocked(mResolvedBaseFile),它有点类似于resolveAndStageFileLocked(),不过将文件添加到的集合使用的是成员变量mResolvedInheritedFiles,添加到它里面代表需要解析继承的文件。它也会对对应文件相关的".dm"、“.fsv_sig”、“.digests”、“.signature"结尾的文件进行处理。
  继续对分包处理,分包文件都在existing.getSplitApkPaths()中。所以在不删除的情况下,也调用inheritFileLocked(splitFile)进行处理。
  接着是对已安装目录下面的"oat"目录进行处理,它里面是对应的处理的指令。处理完之后,将指令架构的名字添加到mResolvedInstructionSets中,将指令文件添加到mResolvedInheritedFiles中。
  再下面是对本地库文件的处理。它们是在安装文件目录下的"lib"和"lib64"文件中。双循环中的变量为对应ABI路径,例如"armeabi”。最后会将下面的库文件加入mResolvedInheritedFiles集合中。
  如果安装包需要分包,但是分包不存在或者需要删除,会报PackageManagerException异常。
  validateApkInstallLocked()的最后代码,分段四:

        if (packageLite.isUseEmbeddedDex()) {for (File file : mResolvedStagedFiles) {if (file.getName().endsWith(".apk")&& !DexManager.auditUncompressedDexInApk(file.getPath())) {throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,"Some dex are not uncompressed and aligned correctly for "+ mPackageName);}}}final boolean isInstallerShell = (mInstallerUid == Process.SHELL_UID);if (isInstallerShell && isIncrementalInstallation() && mIncrementalFileStorages != null) {if (!packageLite.isDebuggable() && !packageLite.isProfileableByShell()) {mIncrementalFileStorages.disallowReadLogs();}}return packageLite;}

  如果安装包设置了mUseEmbeddedDex,还会检查apk文件中".dex"文件是否是压缩状态,是否是对齐了。如果是压缩或者没对齐,都会报异常。
  最后将安装包packageLite返回。

  这样会回到handleStreamValidateAndCommit()方法中,如果没有什么异常,他会继续发送MSG_INSTALL消息。
  总结一下,MSG_ON_SESSION_SEALED消息,会将Session对象的状态持久化到文件中,代表封装完毕。
  MSG_STREAM_VALIDATE_AND_COMMIT消息,则是主要检测安装文件的一致性,像包名、版本、签名。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/6528.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MoE(Mixture of Experts,混合专家模型

MoE(Mixture of Experts,混合专家模型)是一种模型架构,它通过组合多个子模型(即“专家”)来提高模型的预测性能和效率。每个子模型专门处理输入空间的一个子集,而一个门控网络决定每个数据应该由哪个模型进行训练,以减少不同样本类型之间的干扰。这种架构能够在不损失性…

【北京仁爱堂】事出有因,原来是“肝”出现问题,才导致了痉挛性斜颈

痉挛性斜颈是肌张力障碍疾病中的一种&#xff0c;局限于颈部肌肉。由于颈部肌肉间断或持续的不自主的收缩&#xff0c;导致头颈部扭曲、歪斜、姿势异常。一般在30&#xff5e;40岁发病。由于痉挛性斜颈病因不明&#xff0c;西医方面药物及手术的临床疗效不甚理想&#xff0c;而…

初识Vue-组件通信(详解props和emit)

目录 一、组件通信介绍 1.概念 2.作用 3.特点 4.应用 二、组件通信语法 1.Props 1.1.在子组件中声明 props 1.2.在父组件中传递数据 2.Emit 2.1.在子组件中触发事件 2.2.在父组件中监听事件 三、应用实例 1. 购物车组件 2. 表单数据处理 四、总结 一、组件通信介…

std::sort并不支持所有的容器

std::sort并不支持所有的容器&#xff0c;无法对std::list使用std::sort()&#xff0c;但可以使用std::list的方法sort()。 #include <iostream> #include <string> #include <vector> #include <list> #include <algorithm> // std::sortin…

PHP 反序列化

一、PHP 序列化 1、对象的序列化 <?php class people{public $nameGaming;private $NationLiyue;protected $Birthday12/22;public function say(){echo "老板你好呀&#xff0c;我是和记厅的镖师&#xff0c;叫我嘉明就行&#xff0c;要运货吗你&#xff1f;"…

Linux查看某一个程序的安装路径

前提 这一方法的前提条件是&#xff1a;必须是运行着的程序。 方法 这里以查找运行的nginx的安装目录为例。 查看nginx运行进程&#xff0c;查看当前进程的PID&#xff0c;例子中的PID就是7992。 nginps -aux|grep nginx执行ls -l /proc/进程号/exe&#xff0c;然后会打印…

containerd的原理及应用详解(三)

本系列文章简介&#xff1a; 随着容器技术的迅猛发展&#xff0c;容器运行时成为了关注的焦点之一。而容器运行时的核心组件之一就是containerd。containerd是一个高度可扩展的容器运行时&#xff0c;它负责管理容器的生命周期、镜像管理以及资源隔离等核心功能。它是由Docker团…

android zygote进程启动流程

一&#xff0c;启动入口 app_main.cpp int main(int argc, char* const argv[]) {if (!LOG_NDEBUG) {String8 argv_String;for (int i 0; i < argc; i) {argv_String.append("\"");argv_String.append(argv[i]);argv_String.append("\" ")…

锂电池充放电方式曲线

作为一种“化学能-电能”相互转换的能量装置&#xff0c;锂电池在使用过程中必然会进行充电和放电&#xff0c;合理的充放电方式既能减轻锂电池的损伤程度&#xff0c;又能充分发挥锂电池的性能&#xff0c;具有重要的应用价值。 如《GB/T 31484-2015&#xff1a;电动汽车用动…

Server 2022 IIS10 PHP 7.2.33 升级至 PHP 8.3 (8.3.6)

下载最新版本 PHP 8.3 (8.3.6)&#xff0c;因为是 FastCGI 执行方式&#xff0c;选择 Non Thread Safe(非线程安全)。 若有以下提示&#xff1a; The mysqli extension is missing. Please check your PHP configuration. 或者 PHP Fatal error: Uncaught Error: Class &qu…

[C++基础学习-05]----C++函数详解

前言 在学习C的基础阶段&#xff0c;函数是一个非常重要的概念。函数是用来完成特定任务的一段代码&#xff0c;它可以被多次调用&#xff0c;并且可以接受参数和返回值。 正文 01-函数简介 函数的定义&#xff1a; 在C中&#xff0c;函数的定义通常包括函数的返回类…

Agent AI智能体:未来社会中的引领者还是挑战者?

随着Agent AI智能体的智能化水平不断提高&#xff0c;它们在未来社会中将扮演重要角色&#xff0c;并对我们的生活产生深远影响。以下是我对Agent AI智能体的角色、发展路径以及可能带来的挑战的一些看法&#xff1a; 角色与应用领域&#xff1a; 个人助理和虚拟伴侣&#xff…

Dynamics 365: 从0到1了解如何创建Custom API(1) - 在Power Apps中创建

今天介绍一下如果创建Custom API&#xff0c;我们首先需要知道它和action有什么区别&#xff0c;什么时候使用Custom API或者Action? Custom API和Action的区别 Create your own messages (Microsoft Dataverse) - Power Apps | Microsoft Learn 什么时候使用Custom API或者…

spring框架学习记录(2)

文章目录 注解开发bean相关注解开发定义bean纯注解开发纯注解开发中bean的管理 依赖注入相关依赖注入第三方bean管理第三方bean依赖注入 AOP(Aspect Oriented Programming)面向切面编程AOP简介AOP核心概念AOP工作流程AOP切入点表达式通知类型AOP通知获取数据 注解开发 bean相关…

C++11:lambda表达式及function包装器

目录 1、什么是lambda表达式 2、lambda表达式 2.1 lambda表达式语法 2.2 捕获列表说明 3、function包装器 bind 1、什么是lambda表达式 Lambda表达式是一种在C11中引入的功能&#xff0c;允许我们定义匿名函数。它的语法比较简洁&#xff0c;可以方便地在需要函数对象的地…

Idea 自动生成测试

先添加测试依赖&#xff01;&#xff01; <!--Junit单元测试依赖--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9.1</version><scope>test</scope><…

2024年手把手教你安装iMazing3 中文版图文激活教程

热门苹果设备备份管理软件iMazing日前正式发布了全新的 3.0 版本。它采用了全新的设计和架构&#xff0c;并且率先支持Apple Vision Pro安装软件和导入数据。团队表示&#xff0c;这是市场首个第三方支持Apple Vision Pro的管理工具。 iMazing是一款兼容Win和Mac的iOS设备管理…

什么是sql注入攻击?

什么是sql注入攻击? 就是在对用户的输入的数据没有进行很好的处理,从而导致输入变成了sql语句的一部分了,正常来说用户输入的数据只是参数,但是不是的 举个例子吧 比如在登录的时候,用户名是fjg,密码是123456,但是黑客了,不知道密码,但是知道你sql的相关处理的不好,黑客这样操…

杭电 OJ 1000-1009 Java解法

Problem Set 1000 高精度 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner scnew Scanner(System.in);while(sc.hasNext()){System.out.println(sc.nextBigInteger().add(sc.nextBigInteger()));}} } 1001 位运算防爆 1000…

解决Linux中磁盘满/dev/vda1使用率100%问题

发现根目录下占用100%&#xff0c;具体还要排场到底是有哪些大文件占用 那么就在根目录下查询各个子文件夹的占用状态&#xff0c;有过大不用的即可删除 df -h *我的磁盘是100G&#xff0c;但这些总共加起来也接近不了这个数值 那就是有可能出现 已删除空间却没有释放的进程…