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安装原理如下:

相关推荐
API小爬虫20 分钟前
使用PHP爬虫获取淘宝App商品详情?
android·爬虫·php
一笑的小酒馆9 小时前
Android在ksp中简单使用Room
android
meimeiqian11 小时前
flutter android端抓包工具
android·flutter
Android技术之家11 小时前
谷歌决定终止开源Android以及对开发者的影响
android·开源
每次的天空12 小时前
Android Jetpack学习总结(源码级理解)
android·学习·android jetpack
木子庆五12 小时前
Android设计模式之代理模式
android·设计模式·代理模式
在雨季等你13 小时前
创业之旅 - 反思 - 整改 - 新的方向 - 诚邀
android
Long_poem13 小时前
【自学笔记】PHP语言基础知识点总览-持续更新
android·笔记·php
fatiaozhang952714 小时前
晶晨S905L3A(B)-安卓9.0-开启ADB和ROOT-支持IPTV6-支持外置游戏系统-支持多种无线芯片-支持救砖-完美通刷线刷固件包
android·游戏·adb·华为·电视盒子·机顶盒rom·魔百盒固件
行墨15 小时前
Kotlin语言的==与===比较操作
android