Android PackageManagerService源码分析和APK安装原理详解

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂,风趣幽默",感觉非常有意思,忍不住分享一下给大家。

👉点击跳转到教程

一、PackageManagerService简称PMS:PackageManagerService是Android系统中核心的

服务之一,负责应用程序的查询,卸载和应用信息查询,相当于应用程序的大管家。

bash 复制代码
    try {
            //调用系统的方法,获取应用程序信息
            context.getPackageManager().getPackageInfo(null, 0);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

1、首先是在抽象类PackageManager中定义了抽象方法getPackageInfo()

bash 复制代码
public abstract PackageInfo getPackageInfo(@NonNull String packageName,
            @PackageInfoFlags int flags)
            throws NameNotFoundException;

2、类ApplicationPackageManager继承PackageManager类,重写了getPackageInfo()方法

bash 复制代码
	@Override
    public PackageInfo getPackageInfo(String packageName, int flags)
            throws NameNotFoundException {
        return getPackageInfoAsUser(packageName, flags, mContext.getUserId());
    }

实际调用了getPackageInfoAsUser()方法

bash 复制代码
    @Override
    public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
            throws NameNotFoundException {
        try {
            PackageInfo pi = mPM.getPackageInfo(packageName, flags, userId);
            if (pi != null) {
                return pi;
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }

        throw new NameNotFoundException(packageName);
    }

mPM是系统中写的AIDL文件,通过mPM.getPackageInfo(),调用到代理对象Proxy类中重写的getPackageInfo()方法,代理对象又调用到PackageManagerService中的getPackageInfo()方法。

二、APK安装原理

1.APK安装的两种方式

1.静默安装,又叫无界面的安装,从各大手机厂商应用商店下载的APK,便是无界面的安装。

2.有界面的安装,从三方托管平台下载一个APK包,需要一步一步来操作的。

下面分析有界面的安装

1、点击安装后,会跳转到系统提供的PackageInstallerActivity和其对应的布局

install_start.xml.

2、之后通过PackageUtil类获取APK包里面的信息

bash 复制代码
 PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);

3、如果用户点击安装按钮进行安装,或者取消,两种情况。

bash 复制代码
public void onClick(View v) {
        if (v == mOk) { //点击安装
            if (mOkCanInstall || mScrollView == null) {
                if (mSessionId != -1) {
                    mInstaller.setPermissionsResult(mSessionId, true);
                    clearCachedApkIfNeededAndFinish();
                } else {
                    startInstall(); //跳转到一个新的页面,显示正在安装
                }
            } else {
                mScrollView.pageScroll(View.FOCUS_DOWN);
            }
        } else if (v == mCancel) { //点击取消
            // Cancel and finish
            setResult(RESULT_CANCELED);
            if (mSessionId != -1) {
                mInstaller.setPermissionsResult(mSessionId, false);
            }
            clearCachedApkIfNeededAndFinish();
        }
    }

4、正在安装中的Activity类名为:InstallAppProgress 继承Activity。

以上是有界面的安装方式的简单分析。

下面分析无界面的安装方式:

1、没有界面的安装方式,最开始是从C代码开始执行的。

首先通过adb install 输入包名后,一敲回车,会执行到commandline.c文件下的,adb_commandline()方法。

2.最终会调用到install_app()方法

bash 复制代码
    char* apk_file = argv[last_apk];
    char apk_dest[PATH_MAX]; //APK安装路径
    snprintf(apk_dest, sizeof apk_dest, where, get_basename(apk_file));
    int err = do_sync_push(apk_file, apk_dest, 0 /* no show progress */);//把APK,push到手机存储里面。
    if (err) {
        goto cleanup_apk;
    } else {
        argv[last_apk] = apk_dest; /* destination name, not source location */
    }

    pm_command(transport, serial, argc, argv); //这个方法是为了执行shell:pm以命名的方式去安装

3、pm_command()方法如下

bash 复制代码
static int pm_command(transport_type transport, char* serial,
                      int argc, char** argv)
{
    char buf[4096];

    snprintf(buf, sizeof(buf), "shell:pm"); //借助pm的脚本文件,实现安装,通过pm命名的方式实现安装操作

    while(argc-- > 0) {
        char *quoted = escape_arg(*argv++);
        strncat(buf, " ", sizeof(buf) - 1);
        strncat(buf, quoted, sizeof(buf) - 1);
        free(quoted);
    }

    send_shellcommand(transport, serial, buf);
    return 0;
}

4、pm脚本文件

bash 复制代码
# Script to start "pm" on the device, which has a very rudimentary
# shell.
#
base=/system
export CLASSPATH=$base/framework/pm.jar //重点是找到pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"

5、Android源码frameworks/base/cmds/pm/src/com/android/commands/pm.java 源码中有一个main方法如下

bash 复制代码
public static void main(String[] args) {
        int exitCode = 1;
        try {
            exitCode = new Pm().run(args); //重点关注run()方法
        } catch (Exception e) {
            Log.e(TAG, "Error", e);
            System.err.println("Error: " + e);
            if (e instanceof RemoteException) {
                System.err.println(PM_NOT_RUNNING_ERR);
            }
        }
        System.exit(exitCode);
    }

6、run方法代码如下

bash 复制代码
public int run(String[] args) throws IOException, RemoteException {
        boolean validCommand = false;
        if (args.length < 1) {
            return showUsage();
        }

        mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));
        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        if (mPm == null) {
            System.err.println(PM_NOT_RUNNING_ERR);
            return 1;
        }
        mInstaller = mPm.getPackageInstaller();

        mArgs = args;
        String op = args[0];
        mNextArg = 1;

        if ("list".equals(op)) {
            return runList();
        }

        if ("path".equals(op)) {
            return runPath();
        }

        if ("dump".equals(op)) {
            return runDump();
        }

        if ("install".equals(op)) { //安装
            return runInstall();
        }

        if ("install-create".equals(op)) {
            return runInstallCreate();
        }

        if ("install-write".equals(op)) {
            return runInstallWrite();
        }

        if ("install-commit".equals(op)) {
            return runInstallCommit();
        }

        if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
            return runInstallAbandon();
        }

        if ("set-installer".equals(op)) {
            return runSetInstaller();
        }

        if ("uninstall".equals(op)) { //卸载
            return runUninstall();
        }

        if ("clear".equals(op)) {
            return runClear();
        }

        if ("enable".equals(op)) {
            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
        }

        if ("disable".equals(op)) {
            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
        }

        if ("disable-user".equals(op)) {
            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER);
        }

        if ("disable-until-used".equals(op)) {
            return runSetEnabledSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED);
        }

        if ("hide".equals(op)) {
            return runSetHiddenSetting(true);
        }

        if ("unhide".equals(op)) {
            return runSetHiddenSetting(false);
        }

        if ("grant".equals(op)) {
            return runGrantRevokePermission(true);
        }

        if ("revoke".equals(op)) {
            return runGrantRevokePermission(false);
        }

        if ("set-permission-enforced".equals(op)) {
            return runSetPermissionEnforced();
        }

        if ("set-install-location".equals(op)) {
            return runSetInstallLocation();
        }

        if ("get-install-location".equals(op)) {
            return runGetInstallLocation();
        }

        if ("trim-caches".equals(op)) {
            return runTrimCaches();
        }

        if ("create-user".equals(op)) {
            return runCreateUser();
        }

        if ("remove-user".equals(op)) {
            return runRemoveUser();
        }

        if ("get-max-users".equals(op)) {
            return runGetMaxUsers();
        }

        if ("force-dex-opt".equals(op)) {
            return runForceDexOpt();
        }

        try {
            if (args.length == 1) {
                if (args[0].equalsIgnoreCase("-l")) {
                    validCommand = true;
                    return runListPackages(false);
                } else if (args[0].equalsIgnoreCase("-lf")){
                    validCommand = true;
                    return runListPackages(true);
                }
            } else if (args.length == 2) {
                if (args[0].equalsIgnoreCase("-p")) {
                    validCommand = true;
                    return displayPackageFilePath(args[1]);
                }
            }
            return 1;
        } finally {
            if (validCommand == false) {
                if (op != null) {
                    System.err.println("Error: unknown command '" + op + "'");
                }
                showUsage();
            }
        }
    }

7、在runInstall()方法中,主要是调用了该方法

bash 复制代码
mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
//实际调用的是PackageManagerService中的installPackageAsUser()方法
mPm.installPackageAsUser(apkFilePath, obs.getBinder(), installFlags,
                    installerPackageName, verificationParams, abi, userId);

8、实际调用的是PackageManagerService中的installPackageAsUser()方法

bash 复制代码
/**
     * 1、originPath:代表应用程序文件的路径。这是一个字符串类型的参数,表示应用程序安装包的位置。
     *
     * 2、observer:代表应用程序安装的观察者。它是IPackageInstallObserver2接口的一个实例,通过观察者模式来监听安装过程的状态和结果。
     *
     * 3、installFlags:代表安装标志。这是一个整型参数,用于指定安装时的特定选项。例如,可以通过设置INSTALL_REPLACE_EXISTING标志来替换已存在的应用。
     *
     * 4、installerPackageName:代表安装程序的名称。这是一个字符串类型的参数,表示执行安装的应用程序的包名。
     *
     * 5、userId:代表要安装应用的用户ID。这是一个整型参数,用于指定要安装应用的用户。在多用户系统中,每个用户拥有自己的应用安装目录。
     */
@Override
public void installPackageAsUser(String originPath, IPackageInstallObserver2 observer,
            int installFlags, String installerPackageName, int userId)

9.最后通过Handler发送消息,最后执行到startCopy()方法

bash 复制代码
final boolean startCopy() {
            boolean res;
            try {
                if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this);

                if (++mRetries > MAX_RETRIES) { //安装次数大于4次,安装失败
                    Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                    mHandler.sendEmptyMessage(MCS_GIVE_UP);
                    handleServiceError();
                    return false;
                } else {
                    handleStartCopy();
                    res = true;
                }
            } catch (RemoteException e) {
                if (DEBUG_INSTALL) Slog.i(TAG, "Posting install MCS_RECONNECT");
                mHandler.sendEmptyMessage(MCS_RECONNECT);
                res = false;
            }
            handleReturnCode();
            return res;
        }

9、最后执行安装操作的方法handleReturnCode()

bash 复制代码
		@Override
        void handleReturnCode() {
            if (mObserver != null) {
                try {
                    mObserver.onGetStatsCompleted(mStats, mSuccess);
                } catch (RemoteException e) {
                    Slog.i(TAG, "Observer no longer exists.");
                }
            }
        }

APK无界面安装流程图

APK安装原理如下:

相关推荐
花开月满西楼11 分钟前
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
android·前端·android studio
这儿有一堆花14 分钟前
打造你的 Android 图像编辑器:深入解析 PhotoEditor 开源库
android·开源
皮皮高1 小时前
itvbox绿豆影视tvbox手机版影视APP源码分享搭建教程
android·前端·后端·开源·tv
EnzoRay2 小时前
MotionEvent
android
玲小珑2 小时前
Auto.js 入门指南(七)定时任务调度
android·前端
墨狂之逸才3 小时前
adb常用命令调试
android
YoungForYou3 小时前
Android端部署NCNN
android
移动开发者1号3 小时前
Jetpack Compose瀑布流实现方案
android·kotlin
移动开发者1号3 小时前
Android LinearLayout、FrameLayout、RelativeLayout、ConstraintLayout大混战
android·kotlin
移动开发者1号3 小时前
ListView与RecyclerView区别总结
android·kotlin