Android安装过程二 系统进程中PackageInstallerSession对象的创建

从前面文章Android 安装过程一 界面跳转安装进程中InstallInstalling界面的处理中,我们知道安装进程需要与系统进程交互,调用getPackageManager().getPackageInstaller().createSession(params)得到一个整型id值,这个id值对应着系统进程中PackageInstallerSession对象。系统进程会创建该对象,并且将id值返回。这样安装进程就能通过该id值与系统进程后续继续交互。下面就看看它是怎么创建的。

getPackageManager()得到的是ApplicationPackageManager对象,getPackageManager().getPackageInstaller()这里得到的是PackageInstaller对象。PackageInstaller对象的成员变量mInstaller是一个Binder代理对象,它实际指向系统进程中PackageManagerService对象的成员mInstallerService,它是一个PackageInstallerService对象。在这里,PackageInstaller对象的成员变量mInstallerPackageName是安装进程应用的包名。成员mAttributionTag是来自Manifest里面的配置,Activity的attributionTags标签配置,它可以"|"分割,这里是取的第一个。成员mUserId是UserId。

PackageInstaller对象的createSession(params)方法调用的是PackageInstallerService对象的createSession()方法。它的代码在PackageInstallerService.java文件中,如下:

java 复制代码
    @Override
    public int createSession(SessionParams params, String installerPackageName,
            String callingAttributionTag, int userId) {
        try {
            return createSessionInternal(params, installerPackageName, callingAttributionTag,
                    userId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }

这里参数installerPackageName、callingAttributionTag、userId对应着PackageInstaller对象的上面三个成员变量。

接着看PackageInstallerService的createSessionInternal()方法,代码比较长,分段看一下,

代码分段一

分段一,如下:

java 复制代码
    private int createSessionInternal(SessionParams params, String installerPackageName,
            String installerAttributionTag, int userId)
            throws IOException {
        final int callingUid = Binder.getCallingUid();
        mPm.enforceCrossUserPermission(
                callingUid, userId, true, true, "createSession");

        if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
            throw new SecurityException("User restriction prevents installing");
        }

        if (params.dataLoaderParams != null
                && mContext.checkCallingOrSelfPermission(Manifest.permission.USE_INSTALLER_V2)
                        != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("You need the "
                    + "com.android.permission.USE_INSTALLER_V2 permission "
                    + "to use a data loader");
        }

        // INSTALL_REASON_ROLLBACK allows an app to be rolled back without requiring the ROLLBACK
        // capability; ensure if this is set as the install reason the app has one of the necessary
        // signature permissions to perform the rollback.
        if (params.installReason == PackageManager.INSTALL_REASON_ROLLBACK) {
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.MANAGE_ROLLBACKS)
                    != PackageManager.PERMISSION_GRANTED &&
                    mContext.checkCallingOrSelfPermission(Manifest.permission.TEST_MANAGE_ROLLBACKS)
                    != PackageManager.PERMISSION_GRANTED) {
                throw new SecurityException(
                        "INSTALL_REASON_ROLLBACK requires the MANAGE_ROLLBACKS permission or the "
                                + "TEST_MANAGE_ROLLBACKS permission");
            }
        }

这块主要是处理权限。

用户不能有UserManager.DISALLOW_INSTALL_APPS的限制。

params.dataLoaderParams和应用的streaming方式安装有关,如果是这种安装方式,需要有相关权限Manifest.permission.USE_INSTALLER_V2。使用该种安装方式,需要V4的签名方式。

还看到如果设置params.installReason为INSTALL_REASON_ROLLBACK,也就是回退。需要权限MANAGE_ROLLBACKS和TEST_MANAGE_ROLLBACKS这两种的至少一种。

代码分段二

代码分段二如下:

java 复制代码
        // App package name and label length is restricted so that really long strings aren't
        // written to disk.
        if (params.appPackageName != null
                && params.appPackageName.length() > SessionParams.MAX_PACKAGE_NAME_LENGTH) {
            params.appPackageName = null;
        }

        params.appLabel = TextUtils.trimToSize(params.appLabel,
                PackageItemInfo.MAX_SAFE_LABEL_LENGTH);

        String requestedInstallerPackageName = (params.installerPackageName != null
                && params.installerPackageName.length() < SessionParams.MAX_PACKAGE_NAME_LENGTH)
                ? params.installerPackageName : installerPackageName;

        if ((callingUid == Process.SHELL_UID) || (callingUid == Process.ROOT_UID)) {
            params.installFlags |= PackageManager.INSTALL_FROM_ADB;
            // adb installs can override the installingPackageName, but not the
            // initiatingPackageName
            installerPackageName = null;
        } else {
            if (callingUid != Process.SYSTEM_UID) {
                // The supplied installerPackageName must always belong to the calling app.
                mAppOps.checkPackage(callingUid, installerPackageName);
            }
            // Only apps with INSTALL_PACKAGES are allowed to set an installer that is not the
            // caller.
            if (!TextUtils.equals(requestedInstallerPackageName, installerPackageName)) {
                if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
                        != PackageManager.PERMISSION_GRANTED) {
                    mAppOps.checkPackage(callingUid, requestedInstallerPackageName);
                }
            }

            params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
            params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
            params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
            if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
                    && !mPm.isCallerVerifier(callingUid)) {
                params.installFlags &= ~PackageManager.INSTALL_VIRTUAL_PRELOAD;
            }
            if (mContext.checkCallingOrSelfPermission(
                    Manifest.permission.INSTALL_TEST_ONLY_PACKAGE)
                    != PackageManager.PERMISSION_GRANTED) {
                params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
            }
        }

        String originatingPackageName = null;
        if (params.originatingUid != SessionParams.UID_UNKNOWN
                && params.originatingUid != callingUid) {
            String[] packages = mPm.getPackagesForUid(params.originatingUid);
            if (packages != null && packages.length > 0) {
                // Choose an arbitrary representative package in the case of a shared UID.
                originatingPackageName = packages[0];
            }
        }

        if (Build.IS_DEBUGGABLE || isCalledBySystemOrShell(callingUid)) {
            params.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
        } else {
            params.installFlags &= ~PackageManager.INSTALL_ALLOW_DOWNGRADE;
            params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
        }

        if ((params.installFlags & ADB_DEV_MODE) != ADB_DEV_MODE) {
            // Only tools under specific conditions (test app installed through ADB, and
            // verification disabled flag specified) can disable verification.
            params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
        }

首先检查安装文件的包名的长度如果超出SessionParams.MAX_PACKAGE_NAME_LENGTH(255)个字符,将它设置为空。

接着APP的名称如果超过PackageItemInfo.MAX_SAFE_LABEL_LENGTH,将其裁剪到PackageItemInfo.MAX_SAFE_LABEL_LENGTH(1000)长度。

params.installerPackageName和installerPackageName的区别,params.installerPackageName是安装者的包名,installerPackageName则是安装程序的包名。params.installerPackageName的值需要通过各个Activity通过Intent传递,如果它为空,则将变量requestedInstallerPackageName设置为安装程序的包名。

如果调用安装的是Process.SHELL_UID或Process.ROOT_UID,会将params.installFlags加上PackageManager.INSTALL_FROM_ADB,并且将安装者的包名installerPackageName设为空。这种情况应该是通过adb直接安装的。

如果调用者不是Process.SYSTEM_UID(系统进程),会调用mAppOps.checkPackage(callingUid, installerPackageName)这个主要是检查这个installerPackageName是属于调用应用的。如果不属于,会throw出SecurityException。

接着如果requestedInstallerPackageName不等于installerPackageName,这个是上面params.installerPackageName存在值的情况。如果调用者还没授权Manifest.permission.INSTALL_PACKAGES,则需要检测requestedInstallerPackageName的包是否属于调用者。mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)就是用来检测调用者的Manifest.permission.INSTALL_PACKAGES权限。

接下来会将params.installFlags的标识去除INSTALL_FROM_ADB、INSTALL_ALL_USERS,加上INSTALL_REPLACE_EXISTING。

接下来是处理INSTALL_VIRTUAL_PRELOAD、INSTALL_ALLOW_TEST标识。

紧接着,是得到最开始安装应用的包名originatingPackageName。就是跳入安装进程之前的那个应用。它的应用uid在params.originatingUid,它是Activity跳转通过Intent之间数据传递过来的。

下面看到标识INSTALL_ALLOW_DOWNGRADE的设置,如果系统版本是IS_DEBUGGABLE版本,或者调用者是系统进程、root、或者adb,会设置允许降级标识。

如果params.installFlags标识不存在ADB_DEV_MODE,则将INSTALL_DISABLE_VERIFICATION去除。

代码分段三

代码分段三如下:

java 复制代码
        boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
        if (isApex) {
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGE_UPDATES)
                    == PackageManager.PERMISSION_DENIED
                    && mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
                    == PackageManager.PERMISSION_DENIED) {
                throw new SecurityException("Not allowed to perform APEX updates");
            }
        } else if (params.isStaged) {
            mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG);
        }

        if (isApex) {
            if (!mApexManager.isApexSupported()) {
                throw new IllegalArgumentException(
                    "This device doesn't support the installation of APEX files");
            }
            if (params.isMultiPackage) {
                throw new IllegalArgumentException("A multi-session can't be set as APEX.");
            }
            if (!params.isStaged
                    && (params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
                throw new IllegalArgumentException(
                    "Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK");
            }
            if (isCalledBySystemOrShell(callingUid) || mBypassNextAllowedApexUpdateCheck) {
                params.installFlags |= PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
            } else {
                // Only specific APEX updates (installed through ADB, or for CTS tests) can disable
                // allowed APEX update check.
                params.installFlags &= ~PackageManager.INSTALL_DISABLE_ALLOWED_APEX_UPDATE_CHECK;
            }
        }

        if ((params.installFlags & PackageManager.INSTALL_INSTANT_APP) != 0
                && !isCalledBySystemOrShell(callingUid)
                && (mPm.getFlagsForUid(callingUid) & ApplicationInfo.FLAG_SYSTEM) == 0) {
            throw new SecurityException(
                    "Only system apps could use the PackageManager.INSTALL_INSTANT_APP flag.");
        }

        if (params.isStaged && !isCalledBySystemOrShell(callingUid)) {
            if (!mBypassNextStagedInstallerCheck
                    && !isStagedInstallerAllowed(requestedInstallerPackageName)) {
                throw new SecurityException("Installer not allowed to commit staged install");
            }
        }
        if (isApex && !isCalledBySystemOrShell(callingUid)) {
            if (!mBypassNextStagedInstallerCheck
                    && !isStagedInstallerAllowed(requestedInstallerPackageName)) {
                throw new SecurityException(
                        "Installer not allowed to commit non-staged APEX install");
            }
        }

        mBypassNextStagedInstallerCheck = false;
        mBypassNextAllowedApexUpdateCheck = false;

        if (!params.isMultiPackage) {
            // Only system components can circumvent runtime permissions when installing.
            if ((params.installFlags & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0
                    && mContext.checkCallingOrSelfPermission(Manifest.permission
                    .INSTALL_GRANT_RUNTIME_PERMISSIONS) == PackageManager.PERMISSION_DENIED) {
                throw new SecurityException("You need the "
                        + "android.permission.INSTALL_GRANT_RUNTIME_PERMISSIONS permission "
                        + "to use the PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS flag");
            }

            // Defensively resize giant app icons
            if (params.appIcon != null) {
                final ActivityManager am = (ActivityManager) mContext.getSystemService(
                        Context.ACTIVITY_SERVICE);
                final int iconSize = am.getLauncherLargeIconSize();
                if ((params.appIcon.getWidth() > iconSize * 2)
                        || (params.appIcon.getHeight() > iconSize * 2)) {
                    params.appIcon = Bitmap.createScaledBitmap(params.appIcon, iconSize, iconSize,
                            true);
                }
            }

            switch (params.mode) {
                case SessionParams.MODE_FULL_INSTALL:
                case SessionParams.MODE_INHERIT_EXISTING:
                    break;
                default:
                    throw new IllegalArgumentException("Invalid install mode: " + params.mode);
            }

            // If caller requested explicit location, validity check it, otherwise
            // resolve the best internal or adopted location.
            if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
                if (!PackageHelper.fitsOnInternal(mContext, params)) {
                    throw new IOException("No suitable internal storage available");
                }
            } else if ((params.installFlags & PackageManager.INSTALL_FORCE_VOLUME_UUID) != 0) {
                // For now, installs to adopted media are treated as internal from
                // an install flag point-of-view.
                params.installFlags |= PackageManager.INSTALL_INTERNAL;
            } else {
                params.installFlags |= PackageManager.INSTALL_INTERNAL;

                // Resolve best location for install, based on combination of
                // requested install flags, delta size, and manifest settings.
                final long ident = Binder.clearCallingIdentity();
                try {
                    params.volumeUuid = PackageHelper.resolveInstallVolume(mContext, params);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }

APEX文件格式安装是用于较低级别系统模块的安装流程,这里先不涉及。想了解的看这里

params.isStaged则是这个Session会被安装在下一次重启时,这时要强制检查调用者Manifest.permission.INSTALL_PACKAGES的权限被授予。

PackageManager.INSTALL_INSTANT_APP是和免安装应用有关,也暂时略过。

在params.isStaged时,如果调用者不是系统、root、adb,并且没有设置绕过检查,这时要检查安装包是否允许安装,如果不允许,则会报异常。安装包是否允许安装,是通过SystemConfig.getInstance().getWhitelistedStagedInstallers()来控制的,它里面的值来自设置配置文件。

接着设置mBypassNextStagedInstallerCheck、mBypassNextAllowedApexUpdateCheck成员变量的值为false。

params.isMultiPackage代表是它可以通过id引用其他的Session。如果它的孩子安装失败,则它们安装失败。

在params.isMultiPackage为false的情况下,如果params的标识存在INSTALL_GRANT_RUNTIME_PERMISSIONS,则需要检查INSTALL_GRANT_RUNTIME_PERMISSIONS权限,如果是被拒绝的情况下,会抛出异常。

接着处理图标太大的情况,am.getLauncherLargeIconSize()生成的大小和配置属性大小app_icon_size和屏幕密度相关。如果太大会重新调整它的大小。

params.mode必定是MODE_FULL_INSTALL或MODE_INHERIT_EXISTING两者中的一个。

下面处理安装位置。如果明确指定要安装在内部存储,但是内部空间不够安装文件大小,这时会报IO异常。PackageHelper.fitsOnInternal(mContext, params)就是用来检测空间是否足够的。

如果params.installFlags存在INSTALL_FORCE_VOLUME_UUID标识,则为params.installFlags设置INSTALL_INTERNAL标识。

其他的情况也为其设置INSTALL_INTERNAL标识。并且为params设置volumeUuid。PackageHelper.resolveInstallVolume(mContext, params)就是得到得到硬盘的uuid。这里注意内部私有的盘的UUID为StorageManager.UUID_PRIVATE_INTERNAL,它是一个null值。

代码分段四

代码分段四如下:

java 复制代码
        final int sessionId;
        final PackageInstallerSession session;
        synchronized (mSessions) {
            // Check that the installer does not have too many active sessions.
            final int activeCount = getSessionCount(mSessions, callingUid);
            if (mContext.checkCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES)
                    == PackageManager.PERMISSION_GRANTED) {
                if (activeCount >= MAX_ACTIVE_SESSIONS_WITH_PERMISSION) {
                    throw new IllegalStateException(
                            "Too many active sessions for UID " + callingUid);
                }
            } else if (activeCount >= MAX_ACTIVE_SESSIONS_NO_PERMISSION) {
                throw new IllegalStateException(
                        "Too many active sessions for UID " + callingUid);
            }
            final int historicalCount = mHistoricalSessionsByInstaller.get(callingUid);
            if (historicalCount >= MAX_HISTORICAL_SESSIONS) {
                throw new IllegalStateException(
                        "Too many historical sessions for UID " + callingUid);
            }

            sessionId = allocateSessionIdLocked();
        }

        final long createdMillis = System.currentTimeMillis();
        // We're staging to exactly one location
        File stageDir = null;
        String stageCid = null;
        if (!params.isMultiPackage) {
            if ((params.installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
                stageDir = buildSessionDir(sessionId, params);
            } else {
                stageCid = buildExternalStageCid(sessionId);
            }
        }

        // reset the force queryable param if it's not called by an approved caller.
        if (params.forceQueryableOverride) {
            if (callingUid != Process.SHELL_UID && callingUid != Process.ROOT_UID) {
                params.forceQueryableOverride = false;
            }
        }
        InstallSource installSource = InstallSource.create(installerPackageName,
                originatingPackageName, requestedInstallerPackageName,
                installerAttributionTag);
        session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
                mSilentUpdatePolicy, mInstallThread.getLooper(), mStagingManager, sessionId,
                userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid,
                null, null, false, false, false, false, null, SessionInfo.INVALID_ID,
                false, false, false, SessionInfo.STAGED_SESSION_NO_ERROR, "");

        synchronized (mSessions) {
            mSessions.put(sessionId, session);
        }

        mCallbacks.notifySessionCreated(session.sessionId, session.userId);

        mSettingsWriteRequest.schedule();
        return sessionId;

这块就是创建Session了。在创建之前,首先得到Session的Id值。

getSessionCount(mSessions, callingUid)得到的是调用应用正在安装应用的数量。这个是有限制的,并且这个限制和调用方获取到INSTALL_PACKAGES权限有关,如果获取到,限制数量是MAX_ACTIVE_SESSIONS_WITH_PERMISSION(1024);如果没有获取到,限制数量是MAX_ACTIVE_SESSIONS_NO_PERMISSION(50)。同时,调用应用的安装总数也是有限制的,MAX_HISTORICAL_SESSIONS(1048576)。如果这些限制存在超了,会报异常。

下面就调用allocateSessionIdLocked()分配SessionId。

看一下它的代码:

java 复制代码
    @GuardedBy("mSessions")
    private int allocateSessionIdLocked() {
        int n = 0;
        int sessionId;
        do {
            sessionId = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
            if (!mAllocatedSessions.get(sessionId, false)) {
                mAllocatedSessions.put(sessionId, true);
                return sessionId;
            }
        } while (n++ < 32);

        throw new IllegalStateException("Failed to allocate session ID");
    }

mAllocatedSessions中保存着之前分配的SessionId。如果分配过,就重新随机分配。生成的SessionId的值范围为1到Integer.MAX_VALUE。并且重新分配超过32次,还没成功,会报异常。

生成SessionId之后,会生成缓存文件的目录。

从上面知道,在params.isMultiPackage为false的情况下,params.installFlags都会设置INSTALL_INTERNAL标识。生成的Session目录是使用buildSessionDir(sessionId, params)方法实现,看一下它:

java 复制代码
    private File buildSessionDir(int sessionId, SessionParams params) {
        if (params.isStaged || (params.installFlags & PackageManager.INSTALL_APEX) != 0) {
            final File sessionStagingDir = Environment.getDataStagingDirectory(params.volumeUuid);
            return new File(sessionStagingDir, "session_" + sessionId);
        }
        return buildTmpSessionDir(sessionId, params.volumeUuid);
    }

在params.isStaged为true,或者是用APEX安装方式时,目录是根据params.volumeUuid得到的一个。这里分params.volumeUuid为null和具体值两种情况。params.volumeUuid为null是内部存储的默认格式,params.volumeUuid为null时生成的目录为"/data/app-staging/session_" + sessionId;params.volumeUuid为具体值生成的目录则为"/mnt/expand/" + volumeUuid + "/app-staging/session_" + sessionId。

除了上面两种情况,是普通APP安装时,生成的目录也分params.volumeUuid为null和具体值两种情况。params.volumeUuid为null时生成目录为"/data/app/vmdl" + sessionId + ".tmp",如果不为null,则生成的目录为"/mnt/expand/" + volumeUuid + "/app/vmdl" + sessionId + ".tmp"。

在params.installFlags未设置INSTALL_INTERNAL标识时,stageCid的值为"smdl" + sessionId + ".tmp"。

接下来处理params.forceQueryableOverride为true时,如果调用方不为SHELL_UID、ROOT_UID。需要将它设置为false。

生成一个InstallSource对象。

生成一个PackageInstallerSession对象。它的生成就是调用它的构造函数,对它的成员变量赋值。

将PackageInstallerSession对象对象以sessionId为key,放入mSessions中。

回调接口mCallbacks执行notifySessionCreated(session.sessionId, session.userId)方法。这个回调需要首先注册。

下面调用mSettingsWriteRequest.schedule()执行,将mSessions中的信息写入文件。这个文件为"/data/system/install_sessions.xml"。

最后将sessionId返回。

相关推荐
阿巴斯甜21 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker21 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq95271 天前
Andorid Google 登录接入文档
android
黄林晴1 天前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿2 天前
Android MediaPlayer 笔记
android
Jony_2 天前
Android 启动优化方案
android
阿巴斯甜2 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇2 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android