PackageManagerService 安装APK过程

Post on Feb 17, 2023 by Wei Lin

概述

APK安装流程

参考:APK安装流程详解11——普通应用安装简介—简书


(1) 拷贝文件到指定的目录

在Android系统中,APK安装文件是会被保存起来的,默认情况下,用户安装的apk首先会被拷贝到/data/app目录下,/data/app目录是用户有权限访问的目录,在安装APK的时候会自动选择该目录存放用户安装的文件,而系统APK文件则被放到了/system分区下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,该分区只有ROOT权限的用户才能访问,这也就是为什么在没有Root手机之前,我们没法删除系统APP的原因。


(2) 解压缩APK,拷贝可执行文件,创建应用的数据目录

为了加快APP的启动速度,APK在安装的时候,会首先将APP的可执行文件dex拷贝到/data/dalvik-cache目录,缓存起来。然后,在/data/data/目录下创建应用程序的数据目录(以应用的包名命名),存放在应用的相关数据,如数据库、xml文件、cache、二进制的so动态库等。


(3) 解析APK的AndroidManifest.xml文件

系统在安装这个APK的过程中,会解析APK的AndroidManifest.xml文件,提取出这个APK的重要信息写入到packages.xml文件中,这些信息包括:权限、应用包名、APK的安装位置、版本、userID等等。


(4) APK信息记录到系统

packages.xm就是Android系统中一个类似注册表的文件,用来记录当前所有安装的应用的基本信息,每次系统安装或者卸载了任何APK文件,都会更新这个文件。这个文件位于如下目录:/data/system/packages.xml。


(5) 显示快捷方式

如果这些应用程序在PackageManagerService服务注册好了,如果我们想要在Android桌面上看到这些应用程序,还需要有一个Home应用程序,负责从PackageManagerService服务中把这些安装好的应用程序取出来,并以友好的方式在桌面上展现出来,例如以快捷图标的形式。在Android系统中,负责把系统中已经安装的应用程序在桌面中展现出来的Home应用就是Launcher了。

APK安装方式

Android上应用安装可以分为以下几种方式:

  • 系统预装;
  • adb install 命令安装:没有安装界面;
  • 应用市场安装;
  • 第三方安装:有安装界面,通过packageinstaller.apk来处理安装及卸载的过程的界面。

APK安装目录

参考:APK安装流程详解11——普通应用安装简介—简书

应用安装涉及到的目录:

  • /system/app:系统自带的应用程序,获得adb root 权限才能删除;
  • /data/app:用户程序安装的目录。安装时把apk文件复制到此目录;
  • /data/data:存放应用程序的数据;
  • /data/dalvik-cache:将apk中的dex文件安装到dalvik-cache目录下(dex文件是dalvik虚拟机的可执行文件,ART-Android Runtime的可执行文件格式为.oat,启动ART时,系统会执行dex文件转换至oat文件);
  • /data/system:包含手机系统的相关信息。

APK安装过程

Base on: Android 13

Branch: android-13.0.0_r30

PackageInstaller介绍

PackageInstaller的包名为com.android.packageinstaller,是Android默认的用来安装普通APP的程序。

PackageInstallerActivity.onCreate()

PackageInstallerActivity是com.android.packageinstaller进程中用户可见的Activity,首先在其中执行一些对APK的操作。

protected void onCreate(Bundle icicle) {
    if (mLocalLOGV) Log.i(TAG, "creating for user " + getUserId());
    getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);

    super.onCreate(null);

    if (icicle != null) {
        mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
    }

    mPm = getPackageManager();
    mIpm = AppGlobals.getPackageManager();
    mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
    mInstaller = mPm.getPackageInstaller();
    mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

    final Intent intent = getIntent();

    mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
    mCallingAttributionTag = intent.getStringExtra(EXTRA_CALLING_ATTRIBUTION_TAG);
    mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
    mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
            PackageInstaller.SessionParams.UID_UNKNOWN);
    mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
            ? getPackageNameForUid(mOriginatingUid) : null;

    final Uri packageUri;

    if (PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction())) {
        final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
        final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
        if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
            Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
            finish();
            return;
        }

        mSessionId = sessionId;
        packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
        mOriginatingURI = null;
        mReferrerURI = null;
    } else {
        mSessionId = -1;
        packageUri = intent.getData();
        mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
        mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
    }

    // if there's nothing to do, quietly slip into the ether
    if (packageUri == null) {
        Log.w(TAG, "Unspecified source");
        setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
        finish();
        return;
    }

    if (DeviceUtils.isWear(this)) {
        showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
        return;
    }

    boolean wasSetUp = processPackageUri(packageUri);
    if (mLocalLOGV) Log.i(TAG, "wasSetUp: " + wasSetUp);

    if (!wasSetUp) {
        return;
    }
}

执行了以下步骤:

  • 初始化相关参数:如mPm、mAppOpsManager、mUserManager、mCallingPackage等;
  • 获取重要参数:mSessionId、packageUri、mOriginatingURI、mReferrerURI;
  • 是否为穿戴设备;
  • 调用processPackageUri()进行下一步安装。

PackageInstallerActivity.processPackageUri()

通过packageUri获取到scheme,之后都要通过getPackageInfo()获取信息。

我用的是APK本地安装的方式,走的是SCHEME_FILE流程。流程如下:

  • 通过PackageUtil.getPackageInfo()调用到PackageManager.getPackageArchiveInfo()方法中,返回PackageInfo对象。
  • PackageUtil.getAppSnippet()设置apk的程序名称和图标
/**
 * Parse the Uri and set up the installer for this package.
 *
 * @param packageUri The URI to parse
 *
 * @return {@code true} iff the installer could be set up
 */
private boolean processPackageUri(final Uri packageUri) {
    mPackageURI = packageUri;

    final String scheme = packageUri.getScheme();
    if (mLocalLOGV) Log.i(TAG, "processPackageUri(): uri=" + packageUri + ", scheme=" + scheme);

    switch (scheme) {
        case SCHEME_PACKAGE: {
            try {
                mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
                        PackageManager.GET_PERMISSIONS
                                | PackageManager.MATCH_UNINSTALLED_PACKAGES);
            } catch (NameNotFoundException e) {
            }
            if (mPkgInfo == null) {
                Log.w(TAG, "Requested package " + packageUri.getScheme()
                        + " not available. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return false;
            }
            CharSequence label = mPm.getApplicationLabel(mPkgInfo.applicationInfo);
            if (mLocalLOGV) Log.i(TAG, "creating snippet for " + label);
            mAppSnippet = new PackageUtil.AppSnippet(label,
                    mPm.getApplicationIcon(mPkgInfo.applicationInfo));
        } break;

        case ContentResolver.SCHEME_FILE: {
            File sourceFile = new File(packageUri.getPath());
            mPkgInfo = PackageUtil.getPackageInfo(this, sourceFile,
                    PackageManager.GET_PERMISSIONS);

            // Check for parse errors
            if (mPkgInfo == null) {
                Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
                showDialogInner(DLG_PACKAGE_ERROR);
                setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
                return false;
            }
            if (mLocalLOGV) Log.i(TAG, "creating snippet for local file " + sourceFile);
            mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
        } break;

        default: {
            throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
        }
    }

    return true;
}

PM.getPackageArchiveInfo()

PM=PackageManager

这一步执行一下内容:

  • 调用PackageParser.parsePackage()解析APK,返回PackageParser.Package对象
  • 通过返回的对象生成PackageInfo,这里面包含APK文件的相关信息。
/**
 * See {@link #getPackageArchiveInfo(String, int)}.
 */
@Nullable
public PackageInfo getPackageArchiveInfo(@NonNull String archiveFilePath,
        @NonNull PackageInfoFlags flags) {
    long flagsBits = flags.getValue();
    final PackageParser parser = new PackageParser();
    parser.setCallback(new PackageParser.CallbackImpl(this));
    final File apkFile = new File(archiveFilePath);
    try {
        if ((flagsBits & (MATCH_DIRECT_BOOT_UNAWARE | MATCH_DIRECT_BOOT_AWARE)) != 0) {
            // Caller expressed an explicit opinion about what encryption
            // aware/unaware components they want to see, so fall through and
            // give them what they want
        } else {
            // Caller expressed no opinion, so match everything
            flagsBits |= MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE;
        }

        PackageParser.Package pkg = parser.parsePackage(apkFile, 0, false);
        if ((flagsBits & GET_SIGNATURES) != 0 || (flagsBits & GET_SIGNING_CERTIFICATES) != 0) {
            PackageParser.collectCertificates(pkg, false /* skipVerify */);
        }
        return PackageParser.generatePackageInfo(pkg, null, (int) flagsBits, 0, 0, null,
                FrameworkPackageUserState.DEFAULT);
    } catch (PackageParser.PackageParserException e) {
        Log.w(TAG, "Failure to parse package archive", e);
        return null;
    }
}

PackageParser.parsePackage()

/**
 * Parse the package at the given location. Automatically detects if the
 * package is a monolithic style (single APK file) or cluster style
 * (directory of APKs).
 * <p>
 * This performs checking on cluster style packages, such as
 * requiring identical package name and version codes, a single base APK,
 * and unique split names.
 * <p>
 * Note that this <em>does not</em> perform signature verification; that
 * must be done separately in {@link #collectCertificates(Package, boolean)}.
 *
 * If {@code useCaches} is true, the package parser might return a cached
 * result from a previous parse of the same {@code packageFile} with the same
 * {@code flags}. Note that this method does not check whether {@code packageFile}
 * has changed since the last parse, it's up to callers to do so.
 *
 * @see #parsePackageLite(File, int)
 */
@UnsupportedAppUsage
public Package parsePackage(File packageFile, int flags, boolean useCaches)
        throws PackageParserException {
    if (packageFile.isDirectory()) {
        return parseClusterPackage(packageFile, flags);
    } else {
        return parseMonolithicPackage(packageFile, flags);
    }
}

对单一APK,调用parseMonolithicPackage()。

PackageParser.parseMonolithicPackage()

/**
 * Parse the given APK file, treating it as as a single monolithic package.
 * <p>
 * Note that this <em>does not</em> perform signature verification; that
 * must be done separately in
 * {@link #collectCertificates(Package, boolean)}.
 */
@UnsupportedAppUsage
public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {
    final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);
    if (mOnlyCoreApps) {
        if (!lite.coreApp) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
                    "Not a coreApp: " + apkFile);
        }
    }

    final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
    try {
        final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);
        pkg.setCodePath(apkFile.getCanonicalPath());
        pkg.setUse32bitAbi(lite.use32bitAbi);
        return pkg;
    } catch (IOException e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + apkFile, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

PackageParser.parseBaseApk(3 args)

parseBaseApk()方法解析一个APK,并将解析结果生成一个Package对象。

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)
        throws PackageParserException {
    final String apkPath = apkFile.getAbsolutePath();

    String volumeUuid = null;
    if (apkPath.startsWith(MNT_EXPAND)) {
        final int end = apkPath.indexOf('/', MNT_EXPAND.length());
        volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);
    }

    mParseError = PackageManager.INSTALL_SUCCEEDED;
    mArchiveSourcePath = apkFile.getAbsolutePath();

    if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);

    XmlResourceParser parser = null;
    try {
        final int cookie = assets.findCookieForPath(apkPath);
        if (cookie == 0) {
            throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,
                    "Failed adding asset path: " + apkPath);
        }
        parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);
        final Resources res = new Resources(assets, mMetrics, null);

        final String[] outError = new String[1];
        final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);
        if (pkg == null) {
            throw new PackageParserException(mParseError,
                    apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);
        }

        pkg.setVolumeUuid(volumeUuid);
        pkg.setApplicationVolumeUuid(volumeUuid);
        pkg.setBaseCodePath(apkPath);
        pkg.setSigningDetails(SigningDetails.UNKNOWN);

        return pkg;

    } catch (PackageParserException e) {
        throw e;
    } catch (Exception e) {
        throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to read manifest from " + apkPath, e);
    } finally {
        IoUtils.closeQuietly(parser);
    }
}

PackageParser.parseBaseApk(5 args)

PackageParser.parseBaseApkCommon()

在这一步解析AndroidManifest.xml下面的每一个标签内容,包括application、overlay、permisstion、uses-permission、Activity等四大组件的信息。

其它

packages.xml文件

(1) 从手机中拉取packages.xml

packages.xml在Android找那个被保存为二进制的xml格式,直接打开是乱码。

可以通过修改配置将其改为普通的xml格式。连接设备,执行如下目录。

# 将二进制的packages.xml修改为普通文本格式
adb shell
setprop persist.sys.binary_xml false
reboot

# 从手机中拉取packages.xml
adb root
adb pull /data/system/packages.xml


(2) /data/system/packages.xml说明

类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name、permissons、签名、codePath、flag、version、userid等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有APK升级,安装或删除时会更新这个文件


(3) 示例

以自己写的一个DemoApp为例,包名com.demoapp.activitydemo,在/data/system/packages.xml中的信息如下。主要是package、package-state、user-states这三个标签。

<package name="com.demoapp.activitydemo" codePath="/data/app/~~jLP88_QhQOc-XzokQ3TcQg==/com.demoapp.activitydemo-o8iGsFMBh4DStalTvq1fcw==" nativeLibraryPath="/data/app/~~jLP88_QhQOc-XzokQ3TcQg==/com.demoapp.activitydemo-o8iGsFMBh4DStalTvq1fcw==/lib" publicFlags="541638470" privateFlags="-1946152960" ft="18963007e20" ut="18963007f7e" version="1" userId="10108" packageSource="0" loadingProgress="1.0" domainSetId="745e4145-cf75-4878-83d7-35a03a5e5196">
	<sigs count="1" schemeVersion="2">
		<cert index="5" />
	</sigs>
	<proper-signing-keyset identifier="18" />
</package>

<package-state packageName="com.demoapp.activitydemo" id="745e4145-cf75-4878-83d7-35a03a5e5196" />

<user-states packageName="com.demoapp.activitydemo">
	<user-state userId="0" state="0" />
</user-states>

packages.list文件

/data/system/packages.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx。

如:

com.demoapp.activitydemo 10108 1 /data/user/0/com.demoapp.activitydemo default:targetSdkVersion=33 none 0 1 1 @null