MTK Android12 安装app添加密码锁限制

news2025/3/15 1:05:04

提示:通过安装前输入密码的需求,来熟悉了解PMS 基本的安装流程

文章目录

  • 一、需求
    • 实现需求原因
    • 提醒
  • 二、UML图-类图
  • 三、参考资料
  • 四、实现效果
  • 五、需求修改点
    • 修改文件及路径
    • 具体修改内容
  • 六、源码流程分析
    • PMS的复杂性
      • 代码量
      • 实现aidl 接口
      • PackageManagerService 内部类
  • 七、PMS安装流程源码分析
    • installStage
      • HandlerParams
        • handleStartCopy
          • PackageInfoLite
          • verifyReplacingVersionCode
        • handleReturnCode ->processPendingInstall();
    • processInstallRequestsAsync()
    • 阶段性总结
    • doPreInstall
      • cleanUp();
        • removeCodePathLI
    • installPackagesTracedLI ->installPackagesLI
      • preparePackageLI
        • doRename
      • scanPackageTracedLI
      • reconcilePackagesLocked
      • commitPackagesLocked
      • executePostCommitSteps
        • prepareAppDataAfterInstallLIF
    • doPostInstall
    • restoreAndPostInstall
      • handlePackagePostInstall
  • 总结


一、需求

在安装应用时候需要加一个锁,用户输入密码才能安装应用

无论是 adb 安装;应用市场下载应用安装;apk 放到本地文件夹点击apk 文件安装都需要输入密码才能安装。

实现需求原因

  • 之前有客户提出过相关类似需求,自己来实现以下 ;
  • 体制内政府银行机关相关需求偏多,通过简单demo 实现看看;
  • 通过这样的一个小需求 简单实现,来熟悉PMS 相关内容

提醒

  • 如果仅仅是想实现需求,按照思路和部分代码 copy 即可
  • 如果想通过此需求了解PMS相关联知识点,建议多看看,也多看看相关资料推荐和相关文章,并实际验证
  • 爬不完的坑,建议多实际操作 验证,别人的不一定好使,需要多验证下。 本文仅仅针对MTK Android12 上面验证,不同平台不同版本待验证,思路肯定一致

二、UML图-类图

这个UML图相当重要,站在别人的肩膀上,方便梳理思路
在这里插入图片描述

类图,可参考借鉴
├── PMS.installPackage()
└── PMS.installPackageAsUser()
|传递 InstallParams 参数
PackageHandler.doHandleMessage().INIT_COPY
|
PackageHandler.doHandleMessage().MCS_BOUND
├── HandlerParams.startCopy()
│ ├── InstallParams.handleStartCopy()
│ │ └──InstallArgs.copyApk()
│ └── InstallParams.handleReturnCode()
│ └── PMS.processPendingInstall()
│ ├── InstallArgs.doPreInstall()
│ ├── PMS.installPackageLI()
│ │ ├── PackageParser.parsePackage()
│ │ ├── PackageParser.collectCertificates()
│ │ ├── PackageParser.collectManifestDigest()
│ │ ├── PackageDexOptimizer.performDexOpt()
│ │ ├── InstallArgs.doRename()
│ │ │ └── InstallArgs.getNextCodePath()
│ │ ├── replacePackageLI()
│ │ │ ├── shouldCheckUpgradeKeySetLP()
│ │ │ ├── compareSignatures()
│ │ │ ├── replaceSystemPackageLI()
│ │ │ │ ├── killApplication()
│ │ │ │ ├── removePackageLI()
│ │ │ │ ├── Settings.disableSystemPackageLPw()
│ │ │ │ ├── createInstallArgsForExisting()
│ │ │ │ ├── deleteCodeCacheDirsLI()
│ │ │ │ ├── scanPackageLI()
│ │ │ │ └── updateSettingsLI()
│ │ │ └── replaceNonSystemPackageLI()
│ │ │ ├── deletePackageLI()
│ │ │ ├── deleteCodeCacheDirsLI()
│ │ │ ├── scanPackageLI()
│ │ │ └── updateSettingsLI()
│ │ └── installNewPackageLI()
│ │ ├── scanPackageLI()
│ │ └── updateSettingsLI()
│ ├── InstallArgs.doPostInstall()
│ ├── BackupManager.restoreAtInstall()
│ └── sendMessage(POST_INSTALL)
│ |
│ PackageHandler.doHandleMessage().POST_INSTALL
│ ├── grantRequestedRuntimePermissions()
│ ├── sendPackageBroadcast()
│ └── IPackageInstallObserver.onPackageInstalled()
└── PackageHandler.doHandleMessage().MCS_UNBIND
└── PackageHandler.disconnectService()

三、参考资料

Android10.0 PMS安装第三方app时添加密码锁限制安装
应用安装源码阅读指南
Android PackageManagerService总结(四) APK安装流程
Android PackageManager相关源码分析之安装应用
Android PMS APP安装流程
PMS 处理APK安装
Android PMS应用安装流程源码分析下篇-安装包校验及安装

也可以参考我之前的关联笔记有助于熟悉相关内容和加深PMS理解,每个子模块一点一点 慢慢的就串联起来了。
Android13-包安装器PackageInstaller-之apk安装流程
MTK-Android13-包安装器PackageInstaller 静默安装实现
通过 pm 指令分析 Install 过程

四、实现效果

演示了从adb 安装的一个流程(应用市场下载安装和本地安装包点击安装效果是一样的)

  • copy 文件 到/data/app/**.tmp 拷贝的安装包文件零时文件 .tmp 结尾
  • 弹出密码输入框,准备安装。 安装流程阻塞等待状态
  • 输入密码验证。验证失败 安装结束;验证成功则进行安装,安装流程结束。 删除零时文件 .tmp 的零时安装文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

五、需求修改点

修改文件及路径

/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java

具体修改内容

引入包

+   import android.content.Context;
+    import android.graphics.Color;
+    import android.graphics.Typeface;
+    import android.text.InputType;
+    import android.view.Gravity;
+    import android.view.View;
+    import android.view.WindowManager;
+    import android.widget.Button;
+    import android.widget.EditText;
+    import android.widget.LinearLayout;
+    import android.widget.TextView;
+    import android.widget.Toast;
+    import android.widget.RelativeLayout;
+    import android.view.ViewGroup;
+    import android.graphics.PixelFormat;
+

在 processInstallRequestsAsync 方法中,添加对话确认框,如下:

 // Queue up an async operation since the package installation may take a little while.
    private void processInstallRequestsAsync(boolean success,
            List<InstallRequest> installRequests) {
        mHandler.post(() -> {
            List<InstallRequest> apexInstallRequests = new ArrayList<>();
            List<InstallRequest> apkInstallRequests = new ArrayList<>();
            for (InstallRequest request : installRequests) {
                if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) {
                    apexInstallRequests.add(request);
                } else {
                    apkInstallRequests.add(request);
                }
            }
            // Note: supporting multi package install of both APEXes and APKs might requir some
            // thinking to ensure atomicity of the install.
            if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
                // This should've been caught at the validation step, but for some reason wasn't.
                throw new IllegalStateException(
                        "Attempted to do a multi package install of both APEXes and APKs");
            }
            if (!apexInstallRequests.isEmpty()) {
                if (success) {
                    // Since installApexPackages requires talking to external service (apexd), we
                    // schedule to run it async. Once it finishes, it will resume the install.
                    Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
                            "installApexPackages");
                    t.start();
                } else {
                    // Non-staged APEX installation failed somewhere before
                    // processInstallRequestAsync. In that case just notify the observer about the
                    // failure.
                    InstallRequest request = apexInstallRequests.get(0);
                    notifyInstallObserver(request.installResult, request.args.observer);
                }
                return;
            }
            
			/*
			if (success) {
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPreInstall(request.installResult.returnCode);
                }
                synchronized (mInstallLock) {
                    installPackagesTracedLI(apkInstallRequests);
                }
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPostInstall(
                            request.installResult.returnCode, request.installResult.uid);
                }
            }
            for (InstallRequest request : apkInstallRequests) {
                restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                        new PostInstallData(request.args, request.installResult, null));
            }*/	
			
		final WindowManager.LayoutParams mLayoutParams =  new WindowManager.LayoutParams();
        mLayoutParams.width = 1000;
        mLayoutParams.height =500;
        mLayoutParams.dimAmount =0.5f;
        mLayoutParams.format = PixelFormat.TRANSLUCENT;
        mLayoutParams.type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
        WindowManager mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     
        final LinearLayout parentLayout = new LinearLayout(mContext);
        parentLayout.setOrientation(LinearLayout.VERTICAL);
        parentLayout.setBackgroundColor(Color.WHITE);
        LinearLayout.LayoutParams layoutParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,LinearLayout.LayoutParams.MATCH_PARENT);
        parentLayout.setLayoutParams(layoutParams);
     
        TextView titleText = new TextView(mContext);
        LinearLayout.LayoutParams contentParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        titleText.setLayoutParams(contentParams);
        titleText.setText("check password");
        titleText.setTextColor(Color.BLACK);
        titleText.setTypeface(Typeface.create(titleText.getTypeface(), Typeface.NORMAL), Typeface.BOLD);
        titleText.setPadding(10, 10, 0, 0);
        parentLayout.addView(titleText);
     
        EditText passEdtTxt = new EditText(mContext);
        passEdtTxt.setLayoutParams(contentParams);
    	passEdtTxt.setHint("Please input password");
    	passEdtTxt.setTextSize(14);
        passEdtTxt.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
        passEdtTxt.setTextColor(Color.BLACK);
    	
        parentLayout.addView(passEdtTxt);
        RelativeLayout reLayout = new RelativeLayout(mContext);
        RelativeLayout.LayoutParams rightReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        rightReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        rightReal.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE);
        rightReal.setMargins(0,10,15,0);
        
        Button confirmBtn = new Button(mContext);
        LinearLayout.LayoutParams btnParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        confirmBtn.setLayoutParams(btnParams);
        confirmBtn.setText("ok");
        confirmBtn.setTextColor(Color.parseColor("#BEBEBE"));
        confirmBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String password = passEdtTxt.getText().toString();
     
                if ("123456".equals(password)) {
                    if (parentLayout!=null){
                        mWindowManager.removeViewImmediate(parentLayout);
                        //parentLayout = null;
                    }// when password is right -->to deal preparePackageLI(args,res)
                     mIsCanInstall = true;
					Log.d(TAG,"1111111111 confirm  111:mIsCanInstall:"+mIsCanInstall);

			          /*try {
                         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "preparePackage");
                         preparePackageLI(args,res);
                     } catch (PrepareFailure prepareFailure) {
					     Log.d(TAG,"============== zhixing preparePackageLI exception return ");
                          Log.d(TAG,"000000000:mIsCanInstall:"+mIsCanInstall);
                   } finally {
                    Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                   }*/
				   
				   
				   
				   
				   	if (success) {
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPreInstall(request.installResult.returnCode);
                }
                synchronized (mInstallLock) {
                    installPackagesTracedLI(apkInstallRequests);
                }
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPostInstall(
                            request.installResult.returnCode, request.installResult.uid);
                }
            }
            for (InstallRequest request : apkInstallRequests) {
                restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                        new PostInstallData(request.args, request.installResult, null));
            }
				   
                    //  mIsCanInstall = false;
					Log.d(TAG,"222222222222:mIsCanInstall:"+mIsCanInstall);

                }else {
                    Toast.makeText(mContext,"PassWorld  is Error !",Toast.LENGTH_SHORT).show();
                }
            }
        });
       reLayout.addView(confirmBtn, rightReal);
        RelativeLayout.LayoutParams leftReal = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        leftReal.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        leftReal.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
        leftReal.setMargins(15,10,0,0);
        Button cancelBtn = new Button(mContext);
        LinearLayout.LayoutParams cancelbtnParams
                = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        cancelBtn.setLayoutParams(cancelbtnParams);
        cancelBtn.setText("cancel");
        cancelBtn.setTextColor(Color.parseColor("#BEBEBE"));
        cancelBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                   if (parentLayout!=null){
                        mWindowManager.removeViewImmediate(parentLayout);
                  
						Log.d(TAG,"  cancelBtn...");
						   for (InstallRequest request : apkInstallRequests) {
                                     restoreAndPostInstall(request.args.user.getIdentifier(),                       
                                     request.installResult,
                                   new PostInstallData(request.args, request.installResult, null));
                             
                    }
            }
        });
    	reLayout.addView(cancelBtn, leftReal);
        parentLayout.addView(reLayout);
        try {
            mWindowManager.addView(parentLayout, mLayoutParams);
        } catch (WindowManager.BadTokenException e) {
            e.printStackTrace();
        }
        });
    }

核心思路:
在 核心安装的方法 放到对话框确认的功能下面:

doPreInstall   installPackagesTracedLI  doPostInstall   restoreAndPostInstall

将 恢复状态的功能放到取消对话框取消按钮逻辑里面

restoreAndPostInstall

六、源码流程分析

PMS的复杂性

PMS 本身是核心功能,但特别复杂,一时半会根本无法吃透,但实现相关的需求必须了解需求相关的部分代码。

通过接口、内部类、 代码量直观感受PMS的复杂性。所以我们本身是无法短时间内来真正了解它的,但还是需要继续理解、认知。

大量的类和方法,都不清除具体逻辑,代码量大,涉及范围 管(拷贝、解析、校验、权限、设置、安装… 每部分单独拎出来都需要好好理解下的),导致不知无从下手,肯定懵逼状态。

代码量

PackageManagerService 类的代码量 接近3万行
在这里插入图片描述

实现aidl 接口

PackageManagerService extends IPackageManager.Stub

核查代码,你会发现 IPackageManager 文件有199 个方法,也就是 PackageManagerService 光实现这个接口的方法就有199个左右。
在这里插入图片描述

PackageManagerService 内部类

在这里插入图片描述

七、PMS安装流程源码分析

可以借鉴 UML 图来看

installStage

  • 这个方法是整个流程的入口,关注异步线程 信号 INIT_COPY,其实准备IO copy 操作了
  • installStage 方法,其中有一个参数 InstallParams,有InstallParams 和 MultiPackageInstallParams,这里关注下InstallParams ,以它为突破了往下了解
    void installStage(InstallParams params) {
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        params.setTraceMethod("installStage").setTraceCookie(System.identityHashCode(params));
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);
    }

    void installStage(InstallParams parent, List<InstallParams> children)
            throws PackageManagerException {
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        final MultiPackageInstallParams params =
                new MultiPackageInstallParams(parent, children);
        params.setTraceMethod("installStageMultiPackage")
                .setTraceCookie(System.identityHashCode(params));
        msg.obj = params;
        ...
        mHandler.sendMessage(msg);
    }
 void doHandleMessage(Message msg) {
            switch (msg.what) {
                case INIT_COPY: {
                    HandlerParams params = (HandlerParams) msg.obj;
                    if (params != null) {
                        if (DEBUG_INSTALL) Slog.i(TAG, "init_copy: " + params);
                        Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "queueInstall",
                                System.identityHashCode(params));
                        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "startCopy");
                        params.startCopy();
                        Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
                    }
                    break;
                }
  。。。。。。。。。。。。。。              

HandlerParams

从 installStage 方法中关联到这个 类 HandlerParams。 它是一个抽象类,主要是copy 操作的封装接口,抽象类。它有几个子类,我们这里还是以InstallParams 为例,来分析。

   private abstract class HandlerParams {
        /** User handle for the user requesting the information or installation. */
        private final UserHandle mUser;
        String traceMethod;
        int traceCookie;
        HandlerParams(UserHandle user) {
            mUser = user;
        }
        UserHandle getUser() {
            return mUser;
        }
    ......
        final void startCopy() {
            if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);
            handleStartCopy();
            handleReturnCode();
        }
        abstract void handleStartCopy();
        abstract void handleReturnCode();
    }

在这里插入图片描述
所以接下来要追的是 InstallParams 的handleStartCopy 方法,也就是 handleStartCopy handleReturnCode 两个方法的具体实现

handleStartCopy

这里方法注释说:
调用远程方法获取包相关信息和安装位置,如果需要 创建安装参数基于安装位置。

  /*
         * Invoke remote method to get package information and install
         * location values. Override install location based on default
         * policy if needed and then create install arguments based
         * on the install location.
         */
        public void handleStartCopy() {
            if ((installFlags & PackageManager.INSTALL_APEX) != 0) {
                mRet = INSTALL_SUCCEEDED;
                return;
            }
            PackageInfoLite pkgLite = PackageManagerServiceUtils.getMinimalPackageInfo(mContext,
                    mPackageLite, origin.resolvedPath, installFlags, packageAbiOverride);

            // For staged session, there is a delay between its verification and install. Device
            // state can change within this delay and hence we need to re-verify certain conditions.
            boolean isStaged = (installFlags & INSTALL_STAGED) != 0;
            if (isStaged) {
                mRet = verifyReplacingVersionCode(
                        pkgLite, requiredInstalledVersionCode, installFlags);
                if (mRet != INSTALL_SUCCEEDED) {
                    return;
                }
            }

            mRet = overrideInstallLocation(pkgLite);
        }
PackageInfoLite

看构造方法,其实就是apk 基本信息: 包名、版本号、版本名称、安装位置 等

 private PackageInfoLite(Parcel source) {
        packageName = source.readString();
        splitNames = source.createStringArray();
        versionCode = source.readInt();
        versionCodeMajor = source.readInt();
        baseRevisionCode = source.readInt();
        splitRevisionCodes = source.createIntArray();
        recommendedInstallLocation = source.readInt();
        installLocation = source.readInt();
        multiArch = (source.readInt() != 0);
        debuggable = (source.readInt() != 0);

        final int verifiersLength = source.readInt();
        if (verifiersLength == 0) {
            verifiers = new VerifierInfo[0];
        } else {
            verifiers = new VerifierInfo[verifiersLength];
            source.readTypedArray(verifiers, VerifierInfo.CREATOR);
        }
    }
verifyReplacingVersionCode

其实就是对apk 进行一次验证,验证版本
源码及部分标注如下:

   private int verifyReplacingVersionCode(PackageInfoLite pkgLite,
            long requiredInstalledVersionCode, int installFlags) {
        String packageName = pkgLite.packageName;
        synchronized (mLock) {
            // Package which currently owns the data that the new package will own if installed.
            // If an app is uninstalled while keeping data (e.g. adb uninstall -k), installedPkg
            // will be null whereas dataOwnerPkg will contain information about the package
            // which was uninstalled while keeping its data.
            AndroidPackage dataOwnerPkg = mPackages.get(packageName);
            PackageSetting dataOwnerPs = mSettings.getPackageLPr(packageName);
            if (dataOwnerPkg  == null) {
                if (dataOwnerPs != null) {
                    dataOwnerPkg = dataOwnerPs.getPkg();
                }
            }
   //如果要求安装包对应包名应用之前安装过的版本不为-1,但是dataOwnerPkg为空,说明没有安装过;因此不满足安装要求
            if (requiredInstalledVersionCode != PackageManager.VERSION_CODE_HIGHEST) {
                if (dataOwnerPkg == null) {
                    Slog.w(TAG, "Required installed version code was "
                            + requiredInstalledVersionCode
                            + " but package is not installed");
                    return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                }
                //此前安装过的版本与要求的版本不一致,也不满足要求
                if (dataOwnerPkg.getLongVersionCode() != requiredInstalledVersionCode) {
                    Slog.w(TAG, "Required installed version code was "
                            + requiredInstalledVersionCode
                            + " but actual installed version is "
                            + dataOwnerPkg.getLongVersionCode());
                    return PackageManager.INSTALL_FAILED_WRONG_INSTALLED_VERSION;
                }
            }
    //是否允许降级
            if (dataOwnerPkg != null) {
                if (!PackageManagerServiceUtils.isDowngradePermitted(installFlags,
                        dataOwnerPkg.isDebuggable())) {
                    // Downgrade is not permitted; a lower version of the app will not be allowed
                    try {
                        checkDowngrade(dataOwnerPkg, pkgLite);
                    } catch (PackageManagerException e) {
                        Slog.w(TAG, "Downgrade detected: " + e.getMessage());
                        return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                    }
                } else if (dataOwnerPs.isSystem()) {
                    // Downgrade is permitted, but system apps can't be downgraded below
                    // the version preloaded onto the system image
                    final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(
                            dataOwnerPs);
                    if (disabledPs != null) {
                        dataOwnerPkg = disabledPs.getPkg();
                    }
                    if (!Build.IS_DEBUGGABLE && !dataOwnerPkg.isDebuggable()) {
                        // Only restrict non-debuggable builds and non-debuggable version of the app
                        try {
                            checkDowngrade(dataOwnerPkg, pkgLite);
                        } catch (PackageManagerException e) {
                            String errorMsg = "System app: " + packageName
                                    + " cannot be downgraded to"
                                    + " older than its preloaded version on the system image. "
                                    + e.getMessage();
                            Slog.w(TAG, errorMsg);
                            return PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
                        }
                    }
                }
            }
        }
        return PackageManager.INSTALL_SUCCEEDED;
    }
handleReturnCode ->processPendingInstall();

这里做了几个核心工作:

  • createInstallArgs :创建安装参数,然后copy 安装文件到零时目录,创建零时文件 ,通过FileInstallArgs
    类,来copy apk 文件到 /data/app 目录下面去
    • releaseCompressedBlocks 释放安装过程中的压缩模块,最终走到 nativeReleaseCompressedBlocks 这个native 方法
    • 准备安装,根据 mParentInstallParams是否为空,准备安装 调用 tryProcessInstallRequest 或者 processInstallRequestsAsync 方法

        @Override
        void handleReturnCode() {
            processPendingInstall();
        }

看看 processPendingInstall 源码

 private void processPendingInstall() {
            InstallArgs args = createInstallArgs(this);
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
            //copy apk 文件到 /data/app 目录下面去
                mRet = args.copyApk();
            }
            if (mRet == PackageManager.INSTALL_SUCCEEDED) {
            //        //释放安装包安装过程中可回收的压缩块
                F2fsUtils.releaseCompressedBlocks(
                        mContext.getContentResolver(), new File(args.getCodePath()));
            }
            if (mParentInstallParams != null) {
                mParentInstallParams.tryProcessInstallRequest(args, mRet);
            } else {
                PackageInstalledInfo res = createPackageInstalledInfo(mRet);
                processInstallRequestsAsync(
                        res.returnCode == PackageManager.INSTALL_SUCCEEDED,
                        Collections.singletonList(new InstallRequest(args, res)));
            }
        }

processInstallRequestsAsync()

如上分析,走到 PMS 中的 processInstallRequestsAsync() 方法,准备安装

主要做了四个方面工作:

  • doPreInstall : -> cleanUp() /如果前置条件不满足,则移除临时apk相关文件目录
  • installPackagesTracedLI :
    最终会调用到该函数,该函数会对安装包中所定义的权限、权限组、签名、包名、provider声明等进行校验以及对存放临时apk文件路径进行重命名,如果其中一步不满足则应用会安装失败。如下只保留了大致的函数调用流程。
  • doPostInstall -> cleanUp() //如果应用安装失败,则移除零时文件
  • restoreAndPostInstall
    -> performBackupManagerRestore 备份;
    -> mHandler.obtainMessage(POST_INSTALL
    //如果没有备份恢复则直接发送安装成功msg进行各种资源回收、安装成功广播发送、observer回调等操作
    //如果存在备份恢复,在恢复成功之后会回调到finishPackageInstall函数做上述的处理

具体源码如下,看方法注释 :一个包安装过程的队列操作,会花一点时间。

// Queue up an async operation since the package installation may take a little while.
    private void processInstallRequestsAsync(boolean success,
            List<InstallRequest> installRequests) {
        mHandler.post(() -> {
            List<InstallRequest> apexInstallRequests = new ArrayList<>();
            List<InstallRequest> apkInstallRequests = new ArrayList<>();
            for (InstallRequest request : installRequests) {
                if ((request.args.installFlags & PackageManager.INSTALL_APEX) != 0) {
                    apexInstallRequests.add(request);
                } else {
                    apkInstallRequests.add(request);
                }
            }
            // Note: supporting multi package install of both APEXes and APKs might requir some
            // thinking to ensure atomicity of the install.
            if (!apexInstallRequests.isEmpty() && !apkInstallRequests.isEmpty()) {
                // This should've been caught at the validation step, but for some reason wasn't.
                throw new IllegalStateException(
                        "Attempted to do a multi package install of both APEXes and APKs");
            }
            if (!apexInstallRequests.isEmpty()) {
                if (success) {
                    // Since installApexPackages requires talking to external service (apexd), we
                    // schedule to run it async. Once it finishes, it will resume the install.
                    Thread t = new Thread(() -> installApexPackagesTraced(apexInstallRequests),
                            "installApexPackages");
                    t.start();
                } else {
                    // Non-staged APEX installation failed somewhere before
                    // processInstallRequestAsync. In that case just notify the observer about the
                    // failure.
                    InstallRequest request = apexInstallRequests.get(0);
                    notifyInstallObserver(request.installResult, request.args.observer);
                }
                return;
            }
            if (success) {
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPreInstall(request.installResult.returnCode);
                }
                synchronized (mInstallLock) {
                    installPackagesTracedLI(apkInstallRequests);
                }
                for (InstallRequest request : apkInstallRequests) {
                    request.args.doPostInstall(
                            request.installResult.returnCode, request.installResult.uid);
                }
            }
            for (InstallRequest request : apkInstallRequests) {
                restoreAndPostInstall(request.args.user.getIdentifier(), request.installResult,
                        new PostInstallData(request.args, request.installResult, null));
            }
        });
    }

阶段性总结

上面我们从整体大概流程分析下来,已经分析到了 方法:processInstallRequestsAsync ,准备安装了。

在回过头来看看,我们的需求实现方法不就是在这个方法里面,开了一个dialog 密码输入框吗,如果密码输入成功,则进行上面processInstallRequestsAsync 里面的四步方法:doPreInstall ->installPackagesTracedLI->doPostInstall->restoreAndPostInstall 。 也就是在copy apk 文件方法完成后,显示输入密码弹框,进行密码验证。

如果点击取消安装,则执行 restoreAndPostInstall ,清理并进行回调。

doPreInstall

在processInstallRequestsAsync 方法中,

 for (InstallRequest request : apkInstallRequests) {
                    request.args.doPreInstall(request.installResult.returnCode);
}

cleanUp();

做得就是 清除操作,如果以前安装失败,清除相关

  private boolean cleanUp() {
            if (codeFile == null || !codeFile.exists()) {
                return false;
            }
            removeCodePathLI(codeFile);
            return true;
        }
removeCodePathLI

清除文件

void removeCodePathLI(File codePath) {
        if (codePath.isDirectory()) {
            final File codePathParent = codePath.getParentFile();
            final boolean needRemoveParent = codePathParent.getName().startsWith(RANDOM_DIR_PREFIX);
            try {
                final boolean isIncremental = (mIncrementalManager != null && isIncrementalPath(
                        codePath.getAbsolutePath()));
                if (isIncremental) {
                    if (needRemoveParent) {
                        mIncrementalManager.rmPackageDir(codePathParent);
                    } else {
                        mIncrementalManager.rmPackageDir(codePath);
                    }
                }

                mInstaller.rmPackageDir(codePath.getAbsolutePath());
                if (needRemoveParent) {
                    mInstaller.rmPackageDir(codePathParent.getAbsolutePath());
                    removeCachedResult(codePathParent);
                }
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to remove code path", e);
            }
        } else {
            codePath.delete();
        }
    }

installPackagesTracedLI ->installPackagesLI

installPackagesTracedLI最终会调用到该函数,该函数会对安装包中所定义的权限、权限组、签名、包名、provider声明等进行校验以及对存放临时apk文件路径进行重命名,如果其中一步不满足则应用会安装失败。如下只保留了大致的函数调用流程,这个流程提炼几个核心的方法

方法作用
preparePackageLIPrepare 准备:分析任何当前安装状态,分析包并对其进行初始验证
scanPackageTracedLIScan 扫描:扫描分析准备阶段拿到的包
reconcilePackagesLockedReconcile 协调:包的扫描结果,用于协调可能向系统中添加的一个或多个包
commitPackagesLocked提交:提交所有扫描的包并更新系统状态。这是安装流程中唯一可以修改系统状态的地方,
executePostCommitStepsexecutePostCommitSteps 安装APK,并为新的代码路径准备应用程序配置文件,并在此检查是否需要dex优化。
    private void installPackagesLI(List<InstallRequest> requests) {
        ......
        boolean success = false;
        try {
            for (InstallRequest request : requests) {
                final PrepareResult prepareResult;
                try {
                    prepareResult = preparePackageLI(request.args, request.installResult);
                } catch (PrepareFailure prepareFailure) {
                	......
                    return;
                } finally {
                }
                ......
                try {
                    final ScanResult result = scanPackageTracedLI(prepareResult.packageToScan, prepareResult.parseFlags,prepareResult.scanFlags, System.currentTimeMillis(),request.args.user, request.args.abiOverride);
                    ......
                } catch (PackageManagerException e) {
                    return;
                }
            }
            ......
            synchronized (mLock) {
                Map<String, ReconciledPackage> reconciledPackages;
                try {
                    reconciledPackages = reconcilePackagesLocked(reconcileRequest, mSettings.getKeySetManagerService(), mInjector);
                } catch (ReconcileFailure e) {
                    ......
                    return;
                } finally {
                }
                try {
                    commitRequest = new CommitRequest(reconciledPackages,mUserManager.getUserIds());
                    //如果是覆盖安装非系统应用,则卸载旧的应用(移除系统中存储的相关信息)
                    commitPackagesLocked(commitRequest);
                    //到这一步说明应用已经安装成功了
                    success = true;
                } finally {
                }
            }
            executePostCommitSteps(commitRequest);
        } finally {
            ......
        }
    }

preparePackageLI

  • 安装包解析;
  • 安装包中所定义的权限、权限组是否合法判断;
  • 重命名apk临时文件目录;
  • 安装包完整性校验等;
  private PrepareResult preparePackageLI(InstallArgs args, PackageInstalledInfo res)
            throws PrepareFailure {
        //一些变量初始化
        ......
        //scanFlags相关赋值
        ......
        //其他相关代码
        ......
        //解析安装包
        final ParsedPackage parsedPackage;
        try (PackageParser2 pp = mInjector.getPreparingPackageParser()) {
             parsedPackage = pp.parsePackage(tmpPackageFile, parseFlags, false);
            //验证APK包中的Dex Metadata有效性
            //Dex Metadata位于APK包的Dex文件中,包含了一些关键信息,例如应用的类结构、方法和字段的信息,以及其他与Dalvik虚拟机相关的元数据。系统可以使用Dex Metadata 提前加载类或方法,以加快应用的启动速度和执行性能
            AndroidPackageUtils.validatePackageDexMetadata(parsedPackage);
        } catch (PackageParserException e) {
        } finally {
        }
        //临时应用相关
        ......
        //安装包是共享库相关
        ......
        //安装包是否在AndroidManifest.xml中配置了仅仅作为测试
        ......
        //安装包签名设置
        ......
        //instantApp相关
        ......
        synchronized (mLock) {
            //覆盖安装相关
            ......
            //权限相关判断:不允许定义的权限、权限组已经存在于不同签名的应用包内
            ......
        }
        //系统应用相关
        ......
        //重命名apk临时文件路径
        if (!args.doRename(res.returnCode, parsedPackage)) {
            throw new PrepareFailure(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
        }
        try {
            //也属于安装包完成性校验相关,使用的是fs-verity。当启用 fs-verity后,文件系统会对特定文件进行加密和校验,只有在校验通过的情况下,才能读取和执行该文件
            setUpFsVerityIfPossible(parsedPackage);
        } catch (InstallerException | IOException | DigestException | NoSuchAlgorithmException e) {
        }
        //对应用进行冻结
        final PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags, "installPackageLI");
        boolean shouldCloseFreezerBeforeReturn = true;
        try {
            ......
            if (replace) {
                //覆盖安装相关
                ......
            } else { 
                ......
                String pkgName1 = parsedPackage.getPackageName();
                synchronized (mLock) {
                    //判断是否是重复安装
                    ......
                }
            }
            shouldCloseFreezerBeforeReturn = false;
            return new PrepareResult(replace, targetScanFlags, targetParseFlags,existingPackage, parsedPackage, replace, sysPkg,ps, disabledPs);
        } finally {
            ......
        }
    }

doRename
    boolean doRename(int status, ParsedPackage parsedPackage) {
        //判断前置条件是否满足,不满足则清除临时apk相关文件路径
        ......
        //获取临时apk文件夹父级目录(data/app)
        final File targetDir = resolveTargetDir();
        //临时apk文件夹目录
        final File beforeCodeFile = codeFile;
        //获取最终存储apk的文件路径
        final File afterCodeFile = getNextCodePath(targetDir, parsedPackage.getPackageName());
        //apk对应文件路径是否为增量安装文件路径
        final boolean onIncremental = mIncrementalManager != null && isIncrementalPath(beforeCodeFile.getAbsolutePath());
        try {
            //目标文件路径创建
            makeDirRecursive(afterCodeFile.getParentFile(), 0775);
            //如果是增量安装文件路径,那么只做文件link操作
            if (onIncremental) {
                mIncrementalManager.linkCodePath(beforeCodeFile, afterCodeFile);
            //将此前的apk文件路径重命名为新的文件路径名
            } else {
                Os.rename(beforeCodeFile.getAbsolutePath(), afterCodeFile.getAbsolutePath());
            }
        } catch (IOException | ErrnoException e) {
            return false;
        }

        if (!onIncremental && !SELinux.restoreconRecursive(afterCodeFile)) {
            return false;
        }
        //parsedPackage中apk路径设置为最新的
        ......
      return true;
    }
   //获取存储apk的真正文件路径
   private File getNextCodePath(File targetDir, String packageName) {
        SecureRandom random = new SecureRandom();
        byte[] bytes = new byte[16];
        File firstLevelDir;
        do {
            random.nextBytes(bytes);
            //最终名类似于:~~AKJbp1qvUKuNjzi_UMpsqQ==
            String dirName = RANDOM_DIR_PREFIX + Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
            //即data/app/~~AKJbp1qvUKuNjzi_UMpsqQ==
            firstLevelDir = new File(targetDir, dirName);
        } while (firstLevelDir.exists());
        random.nextBytes(bytes);
        String suffix = Base64.encodeToString(bytes, Base64.URL_SAFE | Base64.NO_WRAP);
        //文件路径类似于:data/app/~~AKJbp1qvUKuNjzi_UMpsqQ==/包名-Mc2kon90VNr66M5RUQ9R_w==
        return new File(firstLevelDir, packageName + "-" + suffix);
    }

scanPackageTracedLI

扫描:扫描分析准备阶段拿到的包

  • 对解析的安装包对象中部分属性进行设置,比如非系统应用无法声明受保护广播、非系统应用不能声明为系统核心应用、将未声明exported值的Activity声明为未导出状态等
  • 判断应用签名是否有效
  • 判断安装包包名是否与其他应用冲突
  • 判断安装包中声明的provider是否与其他应用冲突
  • 应用进程名合法性判断
  • 如果应用是非Privileged类型应用,但其共享uid与某个Privileged类型应用一致,则要求其签名与Android平台签名一致。
    private ScanResult scanPackageNewLI(@NonNull ParsedPackage parsedPackage,
        final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
        @Nullable UserHandle user, String cpuAbiOverride) throws PackageManagerException {
        ......
        scanFlags = adjustScanFlags(scanFlags, pkgSetting, disabledPkgSetting, user, parsedPackage);
        synchronized (mLock) {
            ......
            applyPolicy(parsedPackage, parseFlags, scanFlags, mPlatformPackage, isUpdatedSystemApp);
            assertPackageIsValid(parsedPackage, parseFlags, scanFlags);
            ......
        }
    }
   private static void applyPolicy(ParsedPackage parsedPackage, final int parseFlags,final int scanFlags, AndroidPackage platformPkg, boolean isUpdatedSystemApp) {
        //安装包扫描为系统应用类型,这里当然不是
        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
            ......
        } else {
            parsedPackage
                    // 非系统应用无法声明受保护广播
                    .clearProtectedBroadcasts()
                    //非系统应用不能声明为系统核心应用
                    .setCoreApp(false)
                    //非系统应用不能声明常驻
                    .setPersistent(false)
                    //非系统应用无法设置应用的默认安装路径为设备受保护存储
                    .setDefaultToDeviceProtectedStorage(false)
                   	//非系统应用无法设置允许应用在设备处于未解锁状态启动
                    .setDirectBootAware(false)
                    //非系统应用无法设置权限的优先级
                    .capPermissionPriorities();
        }
        //将未声明exported值的Activity声明为未导出状态
        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
            parsedPackage.markNotActivitiesAsNotExportedIfSingleUser();
        }
        //设置应用是否是如下几种类型
        parsedPackage.setPrivileged((scanFlags & SCAN_AS_PRIVILEGED) != 0)
                .setOem((scanFlags & SCAN_AS_OEM) != 0)
                .setVendor((scanFlags & SCAN_AS_VENDOR) != 0)
                .setProduct((scanFlags & SCAN_AS_PRODUCT) != 0)
                .setSystemExt((scanFlags & SCAN_AS_SYSTEM_EXT) != 0)
                .setOdm((scanFlags & SCAN_AS_ODM) != 0);

        //校验应用签名是否于Android签名一致
        parsedPackage.setSignedWithPlatformKey(
                (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
                        || (platformPkg != null && compareSignatures(
                        platformPkg.getSigningDetails().signatures,
                        parsedPackage.getSigningDetails().signatures
                ) == PackageManager.SIGNATURE_MATCH))
        );
        //如果当前是非系统应用则移除如下几个值
        if (!parsedPackage.isSystem()) {
            parsedPackage.clearOriginalPackages()
                    .setRealPackage(null)
                    .clearAdoptPermissions();
        }
        PackageBackwardCompatibility.modifySharedLibraries(parsedPackage, isUpdatedSystemApp);
    }
    private void assertPackageIsValid(AndroidPackage pkg, final int parseFlags,final int scanFlags)throws PackageManagerException {
        //其他一些合法性判断            
        ......
        //apex不能通过apk的形式进行安装
        ......
        //判断安装包签名是否有效
        final KeySetManagerService ksms = mSettings.getKeySetManagerService();
        ksms.assertScannedPackageValid(pkg);
        synchronized (mLock) {
            //判断安装包包名是否是android,如果是则抛出异常
            ......
            //如果是新安装(非覆盖安装)的应用,但是系统中存在相同包名的应用,则抛出异常
            ......
            //当前安装包是共享库相关
            ......
            //判断是否安装现有的安装包,如果是则要求现有的安装包路径与待安装包路径相同,不然则抛出异常
            ......
            //检测安装包中声明的provider是否与系统即系统中应用已经声明的provider冲突
            if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
                mComponentResolver.assertProvidersNotDefined(pkg);
            }
            //校验安装包中是否包含主进程,以及其他子进程命名是否符合要求
            ......
            //一个应用不是Privileged类型应用,但其共享uid与某个Privileged类型应用一致,但是其签名又与Android平台签名不一致,则会直接抛出异常
            ......
            //其他代码
            ......
        }
    }

reconcilePackagesLocked

  • 将当前正在安装应用信息合并到存储所有应用基本信息的map中;
  • 如果当前正在覆盖安装非系统应用则需要删除原有的应用,这里只是构造了对应的action对象;
  • 如果是覆盖安装,则判断新安装的应用签名与原有应用签名是否一致;如果不是覆盖安装且如果当前应用与其他应用共享uid则合并签名;
    private static Map<String, ReconciledPackage> reconcilePackagesLocked(
            final ReconcileRequest request, KeySetManagerService ksms, Injector injector)
            throws ReconcileFailure {
         //当前正在安装应用信息,key为对应包名
        final Map<String, ScanResult> scannedPackages = request.scannedPackages;
        final Map<String, ReconciledPackage> result = new ArrayMap<>(scannedPackages.size());
        //request.allPackages表示当前系统中已经安装的应用(key为包名),这里包含了当前正在安装包
        final ArrayMap<String, AndroidPackage> combinedPackages = new ArrayMap<>(request.allPackages.size() + scannedPackages.size());
        combinedPackages.putAll(request.allPackages);
        final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> incomingSharedLibraries = new ArrayMap<>();

        for (String installPackageName : scannedPackages.keySet()) {
            final ScanResult scanResult = scannedPackages.get(installPackageName);
            //将正在安装的应用信息替换map中的原有信息
            combinedPackages.put(scanResult.pkgSetting.name, scanResult.request.parsedPackage);
            //当前正在安装的应用是否存在被共享的so库,只有系统应用才有
            ......
            //获取前面应用安装过程中存储的信息
            final InstallArgs installArgs = request.installArgs.get(installPackageName);
            final PackageInstalledInfo res = request.installResults.get(installPackageName);
            final PrepareResult prepareResult = request.preparedPackages.get(installPackageName);
            final boolean isInstall = installArgs != null;
            //如果当前正在安装应用,但是存储信息为空,则说明某个步骤存在问题
            if (isInstall && (res == null || prepareResult == null)) {
                throw new ReconcileFailure("Reconcile arguments are not balanced for " + installPackageName + "!");
            }
            final DeletePackageAction deletePackageAction;
            // 如果是覆盖安装并且是非系统应用,则需要卸载原有的应用
            if (isInstall && prepareResult.replace && !prepareResult.system) {
                final boolean killApp = (scanResult.request.scanFlags & SCAN_DONT_KILL_APP) == 0;
                final int deleteFlags = PackageManager.DELETE_KEEP_DATA | (killApp ? 0 : PackageManager.DELETE_DONT_KILL_APP);
                deletePackageAction = mayDeletePackageLocked(res.removedInfo, prepareResult.originalPs, prepareResult.disabledPs,deleteFlags, null);
                if (deletePackageAction == null) {
                    throw new ReconcileFailure(PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE,"May not delete " + installPackageName + " to replace");
                }
            } else {
                deletePackageAction = null;
            }
            //临时变量赋值
            ......
            //如果是应用升级判断新安装应用签名是与原有的应用签名一致
            if (ksms.shouldCheckUpgradeKeySetLocked(signatureCheckPs, scanFlags)) {
                if (ksms.checkUpgradeKeySetLocked(signatureCheckPs, parsedPackage)) {
                } else {
                    //签名不一致则抛出异常
                    ......
                }
                signingDetails = parsedPackage.getSigningDetails();
            } else {
                 //如果正在安装应用签名与某些应用共享uid,则合并他们的签名信息
                ......
            }
            result.put(installPackageName, new ReconciledPackage(request, installArgs, scanResult.pkgSetting, res, request.preparedPackages.get(installPackageName), scanResult, deletePackageAction, allowedSharedLibInfos, signingDetails, sharedUserSignaturesChanged, removeAppKeySetData));
        }
        //共享库相关
        ......
        return result;
    }

commitPackagesLocked

Commit 提交:提交所有扫描的包并更新系统状态。这是安装流程中唯一可以修改系统状态的地方;
必须在此阶段之前确定所有的可预测的错误;会根据安装的包名确定如果是系统app的更新操作,则会对旧的进行替换

 @GuardedBy("mLock")
    private void commitPackagesLocked(final CommitRequest request) {
        // TODO: remove any expected failures from this method; this should only be able to fail due
        //       to unavoidable errors (I/O, etc.)
        for (ReconciledPackage reconciledPkg : request.reconciledPackages.values()) {
            final ScanResult scanResult = reconciledPkg.scanResult;
            final ScanRequest scanRequest = scanResult.request;
            final ParsedPackage parsedPackage = scanRequest.parsedPackage;
            final String packageName = parsedPackage.getPackageName();
            final PackageInstalledInfo res = reconciledPkg.installResult;

            if (reconciledPkg.prepareResult.replace) {
                AndroidPackage oldPackage = mPackages.get(packageName);

                // Set the update and install times
                PackageSetting deletedPkgSetting = getPackageSetting(oldPackage.getPackageName());
                reconciledPkg.pkgSetting.firstInstallTime = deletedPkgSetting.firstInstallTime;
                reconciledPkg.pkgSetting.lastUpdateTime = System.currentTimeMillis();

                res.removedInfo.broadcastAllowList = mAppsFilter.getVisibilityAllowList(
                        reconciledPkg.pkgSetting, request.mAllUsers, mSettings.getPackagesLocked());
                if (reconciledPkg.prepareResult.system) {
                    // Remove existing system package
                    removePackageLI(oldPackage, true);
                    if (!disableSystemPackageLPw(oldPackage)) {
                        // We didn't need to disable the .apk as a current system package,
                        // which means we are replacing another update that is already
                        // installed.  We need to make sure to delete the older one's .apk.
                        res.removedInfo.args = createInstallArgsForExisting(
                                oldPackage.getPath(),
                                getAppDexInstructionSets(
                                        AndroidPackageUtils.getPrimaryCpuAbi(oldPackage,
                                                deletedPkgSetting),
                                        AndroidPackageUtils.getSecondaryCpuAbi(oldPackage,
                                                deletedPkgSetting)));
                    } else {
                        res.removedInfo.args = null;
                    }
                } else {
                    try {
                        // Settings will be written during the call to updateSettingsLI().
                        executeDeletePackageLIF(reconciledPkg.deletePackageAction, packageName,
                                true, request.mAllUsers, false, parsedPackage);
                    } catch (SystemDeleteException e) {
                        if (mIsEngBuild) {
                            throw new RuntimeException("Unexpected failure", e);
                            // ignore; not possible for non-system app
                        }
                    }
                    // Successfully deleted the old package; proceed with replace.

                    // If deleted package lived in a container, give users a chance to
                    // relinquish resources before killing.
                    if (oldPackage.isExternalStorage()) {
                        if (DEBUG_INSTALL) {
                            Slog.i(TAG, "upgrading pkg " + oldPackage
                                    + " is ASEC-hosted -> UNAVAILABLE");
                        }
                        final int[] uidArray = new int[]{oldPackage.getUid()};
                        final ArrayList<String> pkgList = new ArrayList<>(1);
                        pkgList.add(oldPackage.getPackageName());
                        sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null);
                    }

                    // Update the in-memory copy of the previous code paths.
                    PackageSetting ps1 = mSettings.getPackageLPr(
                            reconciledPkg.prepareResult.existingPackage.getPackageName());
                    if ((reconciledPkg.installArgs.installFlags & PackageManager.DONT_KILL_APP)
                            == 0) {
                        if (ps1.mOldCodePaths == null) {
                            ps1.mOldCodePaths = new ArraySet<>();
                        }
                        Collections.addAll(ps1.mOldCodePaths, oldPackage.getBaseApkPath());
                        if (oldPackage.getSplitCodePaths() != null) {
                            Collections.addAll(ps1.mOldCodePaths, oldPackage.getSplitCodePaths());
                        }
                    } else {
                        ps1.mOldCodePaths = null;
                    }

                    if (reconciledPkg.installResult.returnCode
                            == PackageManager.INSTALL_SUCCEEDED) {
                        PackageSetting ps2 = mSettings.getPackageLPr(
                                parsedPackage.getPackageName());
                        if (ps2 != null) {
                            res.removedInfo.removedForAllUsers = mPackages.get(ps2.name) == null;
                        }
                    }
                }
            }

            AndroidPackage pkg = commitReconciledScanResultLocked(reconciledPkg, request.mAllUsers);
            updateSettingsLI(pkg, reconciledPkg.installArgs, request.mAllUsers, res);

            final PackageSetting ps = mSettings.getPackageLPr(packageName);
            if (ps != null) {
                res.newUsers = ps.queryInstalledUsers(mUserManager.getUserIds(), true);
                ps.setUpdateAvailable(false /*updateAvailable*/);
            }
            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                updateSequenceNumberLP(ps, res.newUsers);
                updateInstantAppInstallerLocked(packageName);
            }
        }
        ApplicationPackageManager.invalidateGetPackagesForUidCache();
    }

executePostCommitSteps

如果是直接安装新包,会为新的代码路径准备应用程序配置文件;如果是替换安装,其主要过程为更新设置,清除原有的某些APP数据,重新生成相关的app数据目录等步骤,同时要区分系统应用替换和非系统应用替换。而安装新包,则直接更新设置,生成APP数据即可。

当调用到该函数,说明应用已经安装成功,后续则需要为该应用创建私有目录,以及进行dex优化。

   private void executePostCommitSteps(CommitRequest commitRequest) {
        final ArraySet<IncrementalStorage> incrementalStorages = new ArraySet<>();
        for (ReconciledPackage reconciledPkg : commitRequest.reconciledPackages.values()) {
            final boolean instantApp = ((reconciledPkg.scanResult.request.scanFlags & PackageManagerService.SCAN_AS_INSTANT_APP) != 0);
            final AndroidPackage pkg = reconciledPkg.pkgSetting.pkg;
            final String packageName = pkg.getPackageName();
            final String codePath = pkg.getPath();
            final boolean onIncremental = mIncrementalManager != null && isIncrementalPath(codePath);
            if (onIncremental) {
                ......
                incrementalStorages.add(storage);
            }
            //为应用创建私有目录,data/data(data/user/0)目录下创建
            prepareAppDataAfterInstallLIF(pkg);
            //判断是否需要清除应用数据
            if (reconciledPkg.prepareResult.clearCodeCache) {
                clearAppDataLIF(pkg, UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE| FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
            }
            //覆盖安装相关
            if (reconciledPkg.prepareResult.replace) {
                mDexManager.notifyPackageUpdated(pkg.getPackageName(),pkg.getBaseApkPath(), pkg.getSplitCodePaths());
            }
            //准备正在安装应用配置文件,用于后续dex优化
            mArtManagerService.prepareAppProfiles(pkg,resolveUserIds(reconciledPkg.installArgs.user.getIdentifier()),true);
            //根据安装场景选择dex优化策略,比如单个应用安装、多个应用同时安装场景
            final int compilationReason = mDexManager.getCompilationReasonForInstallScenario(reconciledPkg.installArgs.mInstallScenario);
            //判断应用是否是通过其他设备迁移或者备份恢复进行安装
            final boolean isBackupOrRestore = reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_RESTORE || reconciledPkg.installArgs.installReason == INSTALL_REASON_DEVICE_SETUP;
            //dex优化相关flag
            final int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_INSTALL_WITH_DEX_METADATA_FILE | (isBackupOrRestore ? DexoptOptions.DEXOPT_FOR_RESTORE : 0);
            DexoptOptions dexoptOptions = new DexoptOptions(packageName, compilationReason, dexoptFlags);
       	 //判断是否需要做dex优化,需要满足如下条件
            //(1)非instant类型应用或者instant类型应用dex优化开关开启
       	 //(2)非debug类型应用
       	 //(3)非增量安装
       	 //(4)系统支持dex优化
            final boolean performDexopt = (!instantApp || Global.getInt(mContext.getContentResolver(),Global.INSTANT_APP_DEXOPT_ENABLED, 0) != 0) && !pkg.isDebuggable() && (!onIncremental) && dexoptOptions.isCompilationEnabled();
            if (performDexopt) {
                //开始dex优化
                ......
            }
            //如果前面该包dex优化失败,则从失败列表中移除,以再次进行dex优化
            BackgroundDexOptService.notifyPackageChanged(packageName);
            notifyPackageChangeObserversOnUpdate(reconciledPkg);
        }
        //增量安装相关
        waitForNativeBinariesExtraction(incrementalStorages);
    }

prepareAppDataAfterInstallLIF
private void prepareAppDataAfterInstallLIF(AndroidPackage pkg) {
        
    ..... 
 
    for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
           
        ......
 
        if (ps.getInstalled(user.id)) {
            // TODO: when user data is locked, mark that we're still dirty
            prepareAppDataLIF(pkg, user.id, flags);
                
        }
    }
}
 
 
private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {
    if (pkg == null) {
        Slog.wtf(TAG, "Package was null!", new Throwable());
        return;
    }
    // 调用prepareAppDataLeafLIF方法
    prepareAppDataLeafLIF(pkg, userId, flags);
}
 
 
private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
        
    ......
 
    try {
        // 调用Install守护进程的入口
        ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
                    appId, seInfo, pkg.getTargetSdkVersion());
    } catch (InstallerException e) {
        if (pkg.isSystem()) {
             destroyAppDataLeafLIF(pkg, userId, flags);
             try {
                 ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,                 
                     flags,appId, seInfo, pkg.getTargetSdkVersion());
                    
             } catch (InstallerException e2) {
                 ......
             }
         }
    }
}

doPostInstall

其实就是收尾工作,成功或者失败都需要删除以前的零时文件,如下源码

 int doPostInstall(int status, int uid) {
            if (status == PackageManager.INSTALL_SUCCEEDED) {
                cleanUp(move.fromUuid);
            } else {
                cleanUp(move.toUuid);
            }
            return status;
        }

 private boolean cleanUp(String volumeUuid) {
            final String toPathName = new File(move.fromCodePath).getName();
            final File codeFile = new File(Environment.getDataAppDirectory(volumeUuid),
                    toPathName);
            Slog.d(TAG, "Cleaning up " + move.packageName + " on " + volumeUuid);
            final int[] userIds = mUserManager.getUserIds();
            synchronized (mInstallLock) {
                // Clean up both app data and code
                // All package moves are frozen until finished

                // We purposefully exclude FLAG_STORAGE_EXTERNAL here, since
                // this task was only focused on moving data on internal storage.
                // We don't want ART profiles cleared, because they don't move,
                // so we would be deleting the only copy (b/149200535).
                final int flags = FLAG_STORAGE_DE | FLAG_STORAGE_CE
                        | Installer.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES;
                for (int userId : userIds) {
                    try {
                        mInstaller.destroyAppData(volumeUuid, move.packageName, userId, flags, 0);
                    } catch (InstallerException e) {
                        Slog.w(TAG, String.valueOf(e));
                    }
                }
                removeCodePathLI(codeFile);
            }
            return true;
        }

restoreAndPostInstall

如下代码判断是否需要对应用数据进行备份恢复(之前系统备份过该应用数据),如果需要则首先进行备份恢复;如果不需要则进行最后的资源回收、安装成功广播发送等操作。

   private void restoreAndPostInstall(int userId, PackageInstalledInfo res, @Nullable PostInstallData data) {
        final boolean update = res.removedInfo != null && res.removedInfo.removedPackage != null;
        boolean doRestore = !update && res.pkg != null;
        int token;
        if (mNextInstallToken < 0) mNextInstallToken = 1;
        token = mNextInstallToken++;
        if (data != null) {
            mRunningInstalls.put(token, data);
        }
        //备份恢复
        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && doRestore) {
            if (res.freezer != null) {
                res.freezer.close();
            }
            doRestore = performBackupManagerRestore(userId, token, res);
        }
        if (res.returnCode == PackageManager.INSTALL_SUCCEEDED && !doRestore && update) {
            doRestore = performRollbackManagerRestore(userId, token, res, data);
        }
        //如果没有备份恢复则直接发送安装成功msg进行各种资源回收、安装成功广播发送、observer回调等操作
        //如果存在备份恢复,在恢复成功之后会回调到finishPackageInstall函数做上述的处理
        if (!doRestore) {
            Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
            mHandler.sendMessage(msg);
        }
    }






我们来关注下 POST_INSTALL

```java
 case POST_INSTALL: {
               ....
                    if (data != null && data.mPostInstallRunnable != null) {
                        data.mPostInstallRunnable.run();
                    } else if (data != null && data.args != null) {
                        InstallArgs args = data.args;
                        PackageInstalledInfo parentRes = data.res;

                        final boolean killApp = (args.installFlags
                                & PackageManager.INSTALL_DONT_KILL_APP) == 0;
                        final boolean virtualPreload = ((args.installFlags
                                & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);

                        handlePackagePostInstall(parentRes, killApp, virtualPreload,
                                didRestore, args.installSource.installerPackageName, args.observer,
                                args.mDataLoaderType);
 
                    } else if (DEBUG_INSTALL) {
                        // No post-install when we run restore from installExistingPackageForUser
                        Slog.i(TAG, "Nothing to do for post-install token " + msg.arg1);
                    }

                    Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
                } break;

handlePackagePostInstall

private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
        boolean killApp, String[] grantedPermissions,
        boolean launchedForRestore, String installerPackage,
        IPackageInstallObserver2 installObserver) {

    //【1】当安装结果为 success 后,会进入后续的处理!
    if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
        //【1.1】如果是 move pacakge,那么发送 removed 广播!
        if (res.removedInfo != null) {
            res.removedInfo.sendPackageRemovedBroadcasts(killApp);
        }

        //【1.2】如果安装时指定了授予运行时权限,并且应用的目标 sdk 支持运行时权限,那就授予运行时权限!
        if (grantPermissions && res.pkg.applicationInfo.targetSdkVersion
                >= Build.VERSION_CODES.M) {
            grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
        }

        //【1.3】判断安装方式,是更新安装,还是全新安装!
        // 我们知道,如果 res.removedInfo 不为 null 的话,一定是覆盖更新!
        final boolean update = res.removedInfo != null
                && res.removedInfo.removedPackage != null;

        //【1.4】如果被 disable 的特权应用之前没有子包,这是第一次有子包,那么我们会授予新的子包
        // 运行时权限,如果旧的特权应用之前已经授予!
        if (res.pkg.parentPackage != null) {
            synchronized (mPackages) {
                grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);
            }
        }

        synchronized (mPackages) {
            mEphemeralApplicationRegistry.onPackageInstalledLPw(res.pkg);
        }

        final String packageName = res.pkg.applicationInfo.packageName;
        Bundle extras = new Bundle(1);
        extras.putInt(Intent.EXTRA_UID, res.uid);

        //【1.5】决定在那些 user 下是第一次安装,那些用户下是覆盖更新!
        int[] firstUsers = EMPTY_INT_ARRAY;
        int[] updateUsers = EMPTY_INT_ARRAY;
        if (res.origUsers == null || res.origUsers.length == 0) {
            firstUsers = res.newUsers;
        } else {
            // res.newUsers 表示本次安装新增加的目标 user!
            // res.origUsers 标志之前安装的目标 user!
            for (int newUser : res.newUsers) {
                boolean isNew = true;
                for (int origUser : res.origUsers) {
                    if (origUser == newUser) {
                        isNew = false;
                        break;
                    }
                }
                if (isNew) {
                    firstUsers = ArrayUtils.appendInt(firstUsers, newUser);
                } else {
                    updateUsers = ArrayUtils.appendInt(updateUsers, newUser);
                }
            }
        }

        //【1.5】发送 ADD 和 REPLACE 广播,如果不是 Ephemeral app!
        if (!isEphemeral(res.pkg)) {
            mProcessLoggingHandler.invalidateProcessLoggingBaseApkHash(res.pkg.baseCodePath);

            //【1.5.1】给第一次安装的用户发送 ACTION_PACKAGE_ADDED 广播,不带 EXTRA_REPLACING 属性!
            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                    extras, 0 /*flags*/, null /*targetPackage*/,
                    null /*finishedReceiver*/, firstUsers);

            //【1.5.2】给覆盖更新的用户发送 ACTION_PACKAGE_ADDED 广播,带 EXTRA_REPLACING 属性!
            if (update) {
                extras.putBoolean(Intent.EXTRA_REPLACING, true);
            }
            sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
                    extras, 0 /*flags*/, null /*targetPackage*/,
                    null /*finishedReceiver*/, updateUsers);

            //【1.5.3】给覆盖更新的用户发送 ACTION_PACKAGE_REPLACED / ACTION_MY_PACKAGE_REPLACED 广播!
            if (update) {
                sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                        packageName, extras, 0 /*flags*/,
                        null /*targetPackage*/, null /*finishedReceiver*/,
                        updateUsers);
                        
                sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                        null /*package*/, null /*extras*/, 0 /*flags*/,
                        packageName /*targetPackage*/,
                        null /*finishedReceiver*/, updateUsers);
                        
            } else if (launchedForRestore && !isSystemApp(res.pkg)) {
                //【1.5.4】如果是第一次安装,同时我们要做一个恢复操作,并且 apk 不是系统应用
                // 那么我们会发送 ACTION_PACKAGE_FIRST_LAUNCH 广播!
                if (DEBUG_BACKUP) {
                    Slog.i(TAG, "Post-restore of " + packageName
                            + " sending FIRST_LAUNCH in " + Arrays.toString(firstUsers));
                }
                sendFirstLaunchBroadcast(packageName, installerPackage, firstUsers);
            }

            //【1.5.5】如果该 apk 处于 forward locked 或者处于外置存储中,那么会给所有的用户发送
            // 资源变化的广播!
            if (res.pkg.isForwardLocked() || isExternal(res.pkg)) {
                if (DEBUG_INSTALL) {
                    Slog.i(TAG, "upgrading pkg " + res.pkg
                            + " is ASEC-hosted -> AVAILABLE");
                }
                final int[] uidArray = new int[]{res.pkg.applicationInfo.uid};
                ArrayList<String> pkgList = new ArrayList<>(1);
                pkgList.add(packageName);
                sendResourcesChangedBroadcast(true, true, pkgList, uidArray, null);
            }
        }

        //【1.6】针对 firstUsers 做一些权限恢复和默认浏览器设置!
        if (firstUsers != null && firstUsers.length > 0) {
            synchronized (mPackages) {
                for (int userId : firstUsers) {
                    //【1.6.1】默认浏览器设置!!
                    if (packageIsBrowser(packageName, userId)) {
                        mSettings.setDefaultBrowserPackageNameLPw(null, userId);
                    }
                    //【1.6.2】处理那些正在等待或者需要恢复的运行时权限授予!
                    mSettings.applyPendingPermissionGrantsLPw(packageName, userId);
                }
            }
        }

        EventLog.writeEvent(EventLogTags.UNKNOWN_SOURCES_ENABLED,
                getUnknownSourcesSettings());

        // 触发 GC 回收资源!
        Runtime.getRuntime().gc();

        //【5.8.1.1】移除掉更新后的旧 apk!
        if (res.removedInfo != null && res.removedInfo.args != null) {
            synchronized (mInstallLock) {
                res.removedInfo.args.doPostDeleteLI(true);
            }
        }
    }

    //【*5.8.2】通知观察者安装的结果,这里的 installObserver 是我们之前创建的 localObsever!!
    if (installObserver != null) {
        try {
            Bundle extras = extrasForInstallResult(res);
            installObserver.onPackageInstalled(res.name, res.returnCode,
                    res.returnMsg, extras);
        } catch (RemoteException e) {
            Slog.i(TAG, "Observer no longer exists.");
        }
    }
}

总结

  • 从一个实际的安装加锁输入密码功能作为切入点,熟悉了安装流程。
  • 上面还是仅仅从大方向,走了一遍安装流程,实际上细节太多。
  • 回过头来,实现这个需求,这个方案还是比较好的。
    其它有专家说在扫描或者准备阶段实现输入框,其实很不可取,极易出错。锁机制和各种零时文件删除状态,会导致各种问题。 最好还是在processInstallRequestsAsync 作为点,进行功能需求实现。

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

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

相关文章

[数据结构]堆详解

目录 一、堆的概念及结构 二、堆的实现 1.堆的定义 2堆的初始化 3堆的插入 ​编辑 4.堆的删除 5堆的其他操作 6代码合集 三、堆的应用 &#xff08;一&#xff09;堆排序&#xff08;重点&#xff09; &#xff08;二&#xff09;TOP-K问题 一、堆的概念及结构 堆的…

LInux中常用的网络命令

配置 IP 地址 1.1 配置 IP 地址 IP 地址是计算机在互联网中唯一的地址编码。每台计算机如果需要接入网络和其他计算机进行数据通信&#xff0c;就必须配置唯一的公网 IP 地址。 配置 IP 地址有两种方法&#xff1a; 1&#xff09;setup 工具 2&#xff09;vi /etc/sysconf…

怎么实现: 大语言模型微调案例

怎么实现: 大语言模型微调案例 目录 怎么实现: 大语言模型微调案例输入一个反常识的问题:首都在北京天安门之后对输出模型进行测试:首都在北京天安门微调代码:测试微调模型代码:微调输出模型结构输出模型参数大小对比Qwen 2.5_0.5:53MB输出模型:951MB 是一样的,没有进行…

深入理解 MySQL 锁:基于 InnoDB 的并发控制解析

在数据库并发访问管理中&#xff0c;MySQL 提供了强大的锁机制来保证数据的一致性和完整性。作为默认存储引擎的 InnoDB&#xff0c;为 MySQL 带来了细粒度的锁控制&#xff0c;使其成为高并发应用的理想选择。本文将深入探讨 MySQL 的锁类型、分类、应用场景及其对性能的影响&…

Linux Nginx安装部署、注册服务

1、下载&#xff1a;https://nginx.org/en/download.html 下载nginx-1.27.4.tar.gz&#xff0c;上传到服务器 /opt/目录 在开始安装Nginx之前&#xff0c;首先需要安装一些依赖项&#xff0c;以确保Nginx编译和运行正常。打开终端并执行以下命令&#xff1a; yum install -y …

安全的实现数据备份和恢复

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…

excel中两个表格的合并

使用函数&#xff1a; VLOOKUP函数 如果涉及在excel中两个工作表之间进行配对合并&#xff0c;则&#xff1a; VLOOKUP(C1,工作表名字!A:B,2,0) 参考&#xff1a; excel表格中vlookup函数的使用方法步骤https://haokan.baidu.com/v?pdwisenatural&vid132733503560775…

在 Windows 上快速部署 OpenManus:从安装到运行

在当今快速发展的 AI 领域&#xff0c;OpenManus 作为一个强大的开源工具&#xff0c;为开发者提供了便捷的 AI 应用开发体验。本文将详细介绍如何在 Windows 系统上安装并运行 OpenManus&#xff0c;帮助你快速搭建一个本地的 AI 开发环境。 一、安装 Anaconda Anaconda 是一…

uniapp实现 uview1 u-button的水波纹效果

说明&#xff1a; 由于uview2已经移除水波纹效果&#xff0c;这边又觉得那个效果好看&#xff0c;所以开发这个功能(原谅我不会录动图) 效果&#xff1a; 具体代码&#xff1a; <view class"ripple-container" touchstart"handleTouchStart" touchend&…

如何使用Cursor的claude-3.7模型来开发高保真的原型设计图,学会写好的提示词人人都是设计师

1、想要开发出高保真的设计图原型&#xff0c;需要给出cursor具体的提示词&#xff1a;比如我想开发一款IT面试题小程序&#xff0c;给出的提示词是这样的 我想开发一个 {IT面试题库小程序}&#xff0c;现在需要输出高保真的原型图&#xff0c;请通过以下方式帮我完成所有界面…

AGI大模型(5):提示词工程

1 什么是提示词工程&#xff08;Prompt&#xff09; 所谓的提示词其实指的就是提供给模型的⼀个⽂本⽚段&#xff0c;⽤于指导模型⽣成特定的输出或回答。提示词的⽬的是为模型提供⼀个任务的上下⽂&#xff0c;以便模型能够更准确地理解⽤户的意图&#xff0c;并⽣成相关的回…

[LeetCode热门100题]|137,260,268,面试17.19

1、137 只出现一次数字|| 1、题目描述 137 只出现一次数字||https://leetcode.cn/problems/single-number-ii/description/ 给你一个整数数组 nums &#xff0c;除某个元素仅出现 一次 外&#xff0c;其余每个元素都恰出现 三次 。请你找出并返回那个只出现了一次的元素。 你…

Android子线程更新View的方法原理

对于所有的Android开发者来说&#xff0c;“View的更新必须在UI线程中进行”是一项最基本常识。 如果不在UI线程中更新View&#xff0c;系统会抛出CalledFromWrongThreadException异常。那么有没有什么办法可以不在UI线程中更新View&#xff1f;答案当然是有的&#xff01; 一…

Kafka常用指令(详细)

Kafka常用指令&#xff08;详细&#xff09; 启停命令 前台启动 前台启动命令 ./bin/kafka-server-start.sh config/server.properties 后台启动方式1 后台启动命令加上参数-daemon&#xff0c;窗口关闭之后kafka后台程序继续运行 ./bin/kafka-server-start.sh -daemon co…

2025移动端软件供应链安全开源治理方案最佳实践

2025年3月13日&#xff0c;由中国软件评测中心、CAPPVD漏洞库联合主办的“第六期移动互联网APP产品安全漏洞技术沙龙”在海口成功召开。悬镜安全基于移动端数字供应链安全开源治理方案荣获中国软件评测中心“2024移动互联网APP产品安全漏洞治理”优秀案例&#xff0c;并获颁证书…

《C#上位机开发从门外到门内》2-3:SPI总线协议详解及应用实践

文章目录 一、引言二、SPI总线协议的基本原理三、SPI通信模式详解 —— CPOL与CPHA3.1 时钟极性&#xff08;CPOL&#xff09;3.2 时钟相位&#xff08;CPHA&#xff09;3.3 四种SPI模式 四、主从设备通信机制4.1 通信流程概述4.2 数据帧结构与传输细节4.3 主设备与从设备的协同…

vscode出现:No module named ‘requests‘ 问题的解决方法

问题&#xff1a; ① No module named requests ② pip install requests&#xff1a;显示已经安装成功 运行失败原因&#xff1a; 我的失败原因是因为&#xff1a;我的python环境有两个&#xff0c;电脑C盘默认一个、pycharm下载后在它的路径下有一个。而vscode所运行的环境…

【openwebui 搭建本地知识库(RAG搭建本地知识库)】

安装准备 openwebui 这个本地安装之前写过使用python安装。也可以直接用docker 命令 docker run --rm -d \-p 3080:8080 \-p 3081:8081 \-e WEBUI_AUTHtrue \-e DEFAULT_LOCALEcn \-e GLOBAL_LOG_LEVEL"INFO" \-e AIOHTTP_CLIENT_TIMEOUT100 \--privilegedtrue \-…

雷池WAF 处理 HTTP 请求的流程

项目介绍 SafeLine&#xff0c;中文名 "雷池"&#xff0c;是一款简单好用, 效果突出的 Web 应用防火墙(WAF)&#xff0c;可以保护 Web 服务不受黑客攻击。 雷池通过过滤和监控 Web 应用与互联网之间的 HTTP 流量来保护 Web 服务。可以保护 Web 服务免受 SQL 注入、…

JAVA-Thread类实现多线程

引言&#xff1a; 本章博客涉及进程线程内容&#xff0c;如果不了解的可以看&#xff1a;什么是进程线程-CSDN博客 线程是操作系统的概念&#xff0c;操作系统提供的API供程序员使用操作。但是不同的操作系统(Winodws、Linux、Unix……差别很大),但是做为JAVA程序员就不需要担心…