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返回。

相关推荐
mmsx3 小时前
android sqlite 数据库简单封装示例(java)
android·java·数据库
众拾达人6 小时前
Android自动化测试实战 Java篇 主流工具 框架 脚本
android·java·开发语言
吃着火锅x唱着歌7 小时前
PHP7内核剖析 学习笔记 第四章 内存管理(1)
android·笔记·学习
_Shirley8 小时前
鸿蒙设置app更新跳转华为市场
android·华为·kotlin·harmonyos·鸿蒙
hedalei10 小时前
RK3576 Android14编译OTA包提示java.lang.UnsupportedClassVersionError问题
android·android14·rk3576
锋风Fengfeng10 小时前
安卓多渠道apk配置不同签名
android
枫_feng10 小时前
AOSP开发环境配置
android·安卓
叶羽西11 小时前
Android Studio打开一个外部的Android app程序
android·ide·android studio
qq_1715388512 小时前
利用Spring Cloud Gateway Predicate优化微服务路由策略
android·javascript·微服务
Vincent(朱志强)13 小时前
设计模式详解(十二):单例模式——Singleton
android·单例模式·设计模式