其他分享
首页 > 其他分享> > 深入Android系统(十)PMS-3-应用安装过程

深入Android系统(十)PMS-3-应用安装过程

作者:互联网

研究应用的安装过程,老样子,我们还是先从使用入手。

Android中,通过发送Intent就可以启动应用的安装过程,比如:

    Uri uri = Uri.fromFile(new File(apkFilePath));
    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(uri,"application/vnd.android.package-archive");
    startActivity(intent);

而在Android的系统应用PackageInstaller中有一个InstallStart会响应这个Intent

        <activity android:name=".InstallStart"
            android:theme="@style/Installer"
                android:exported="true"
                android:excludeFromRecents="true">
            <intent-filter android:priority="1">
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.INSTALL_PACKAGE" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:scheme="file" />
                <data android:scheme="content" />
                <data android:mimeType="application/vnd.android.package-archive" />
            </intent-filter>
            ......
        </activity>

InstallStart中会进行各种Uri的判断,最终会跳转到一个叫做PackageInstallerActivity的界面。

国内的厂商基本上会在InstallStart这里进行修改,替换为自己的安装界面

对于PackageInstallerActivity来说,它的主要作用是

当点击确认安装后,其实会执行到startInstall()方法,相关内容如下:

    private void startInstall() {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        ......
        newIntent.setData(mPackageURI);
        newIntent.setClass(this, InstallInstalling.class);
        ......
        startActivity(newIntent);
        finish();
    }

上面的逻辑是跳转到InstallInstalling界面进行安装,我们看看这个Activity

@Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        ......
        if ("package".equals(mPackageURI.getScheme())) {
            // 直接安装 package
            try {
                getPackageManager().installExistingPackage(appInfo.packageName);
                launchSuccess();
            } catch (PackageManager.NameNotFoundException e) {
                launchFailure(PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
            }
        } else {
            // 对于 file 安装,按照session的逻辑去走
            // 这部分在onResume中开始
            final File sourceFile = new File(mPackageURI.getPath());
            ......
            if (savedInstanceState != null) {
                mSessionId = savedInstanceState.getInt(SESSION_ID);
                mInstallId = savedInstanceState.getInt(INSTALL_ID);
                ......
            } else {
                ......
                mSessionId = getPackageManager().getPackageInstaller().createSession(params);
                ......
                // 创建应用安装状态的Observer
                // 真正注册是在后面 Session 的 commitLocked 中
                // InstallEventReceiver 这里是做了二次封装,方便进行持久化操作
                InstallEventReceiver.addObserver(this, mInstallId,
                            this::launchFinishBasedOnResult);
            }
            ......
        }
    }
    @Override
    protected void onResume() {
        super.onResume();
        // This is the first onResume in a single life of the activity
        if (mInstallingTask == null) {
            PackageInstaller installer = getPackageManager().getPackageInstaller();
            PackageInstaller.SessionInfo sessionInfo = installer.getSessionInfo(mSessionId);
            // 启动异步安装任务 InstallingAsyncTask 进行一系列的session操作
            if (sessionInfo != null && !sessionInfo.isActive()) {
                mInstallingTask = new InstallingAsyncTask();
                mInstallingTask.execute();
            }
            ......
        }
    }

从上面的方法我们可以看到安装操作主要分为两个接口:

session这个东西看来很重要,那么我们先来看看PackageInstallerSession

PackageInstallerSession

Android通过PackageInstallerSession来表示一次安装过程,一个PackageInstallerSession包含一个系统中唯一的一个SessionId,如果一个应用的安装必须分几个阶段来完成,即使设备重启了,也可以通过这个ID来继续安装过程

PackageInstallerSession中保存了应用安装的相关数据,例如,安装包的路径、安装的进度、中间数据保存的目录等。

PackageInstallerService提供了接口createSession来创建一个Session对象:

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

createSession()方法将返回一个系统唯一值作为Session ID。如果希望再次使用这个Session,可以通过接口openSession()打开它。openSession()方法的代码如下所示:

    @Override
    public IPackageInstallerSession openSession(int sessionId) {
        try {
            return openSessionInternal(sessionId);
        } catch (IOException e) {
            throw ExceptionUtils.wrap(e);
        }
    }

openSession()方法将返回一个IPackageInstallerSession对象,它是Binder服务PackageInstallerSessionIBinder对象。在PackageInstallerServicemSessions数组保存了所有PackageInstallerSession对象,定义如下:

    @GuardedBy("mSessions")
    private final SparseArray<PackageInstallerSession> mSessions = new SparseArray<>();

我们知道,当PackageManagerService初始化时会创建PackageInstallerService服务,在这个服务的初始化函数中会读取/data/system目录下的install_sessions.xml文件,这个文件中保存了系统中未完成的Install Session。然后PackageInstallerService会根据文件的内容创建PackageInstallerSession对象并插入到mSessions中。

而对于上面提到的commit()函数,是真正触发PMS安装的函数,定义如下:

    @Override
    public void commit(@NonNull IntentSender statusReceiver, boolean forTransfer) {
        ......
        mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
        ......
    }
    private final Handler.Callback mHandlerCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                ......
                case MSG_COMMIT:
                    synchronized (mLock) {
                        ......
                        commitLocked();
                        ......
                    }

                    break;
                ......
            }
            return true;
        }
    };
    private void commitLocked()
            throws PackageManagerException {
        ......
        mRemoteObserver.onUserActionRequired(intent);
        ......
        mPm.installStage(mPackageName, stageDir, localObserver, params,
                mInstallerPackageName, mInstallerUid, user, mSigningDetails);
    }

commit()函数最后会调用到PMSinstallStage()方法,到这里就触发了安装的第一阶段:文件复制

安装第一阶段:复制文件

我们已经知道PackageInstallerSession通过调用PMSinstallStage()方法开启了安装第一阶段,不过整个安装过程比较复杂,我们先看看这个过程的序列图:

image

installStage()

我们继续从installStage()来分析,代码如下:

    void installStage(String packageName, File stagedDir,
            IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
            String installerPackageName, int installerUid, UserHandle user,
            PackageParser.SigningDetails signingDetails) {
        ......
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        ......
        final InstallParams params = new InstallParams(origin, null, observer,
                sessionParams.installFlags, installerPackageName, sessionParams.volumeUuid,
                verificationInfo, user, sessionParams.abiOverride,
                sessionParams.grantedRuntimePermissions, signingDetails, installReason);
        ......
        msg.obj = params;
        ......
        mHandler.sendMessage(msg);
    }

installStage()方法主要进行了:

先来简单了解下InstallParams的类:

image

上图中的类都是PMS的内部类

简单了解完InstallParams,我们继续看INIT_COPY消息的处理过程:

case INIT_COPY: {
    HandlerParams params = (HandlerParams) msg.obj;
    int idx = mPendingInstalls.size();
    ......
    if (!mBound) {
        // 绑定 DefaultContainerService 
        if (!connectToService()) {
            params.serviceError();
            ......
            return;
        } else {
            // 连接成功,将安装信息保存到 mPendingInstalls 的尾部
            mPendingInstalls.add(idx, params);
        }
    } else {
        // 如果已经绑定服务,直接插入到集合尾部
        mPendingInstalls.add(idx, params);
        if (idx == 0) {
            // 如果插入前的集合为空
            // 直接发送 MCS_BOUND 消息
            mHandler.sendEmptyMessage(MCS_BOUND);
        }
    }
    break;
}

整个过程比较简洁:

那么,重点又转移到了MCS_BOUND的消息处理,代码如下:

case MCS_BOUND: {
    if (msg.obj != null) {
        mContainerService = (IMediaContainerService) msg.obj;
    }
    if (mContainerService == null) {
        if (!mBound) {
            // DefaultContainerService bind失败的情况
            for (HandlerParams params : mPendingInstalls) {
                params.serviceError();
                ......
            }
            mPendingInstalls.clear();
        } else {
            Slog.w(TAG, "Waiting to connect to media container service");
        }
    } else if (mPendingInstalls.size() > 0) {
        // 取出头部安装参数
        HandlerParams params = mPendingInstalls.get(0);
        if (params != null) {
            ......
            if (params.startCopy()) {// 执行安装
                // 已执行成功,移除头部元素
                if (mPendingInstalls.size() > 0) {
                    mPendingInstalls.remove(0);
                }
                if (mPendingInstalls.size() == 0) {
                    if (mBound) {
                        ......
                        // 如果集合中没有需要安装的信息了,发送MCS_UNBIND延时消息进行解绑
                        removeMessages(MCS_UNBIND);
                        Message ubmsg = obtainMessage(MCS_UNBIND);
                        sendMessageDelayed(ubmsg, 10000);
                    }
                } else {
                    // 如果集合中还存在安装信息,发送 MCS_BOUND 消息,继续处理
                    mHandler.sendEmptyMessage(MCS_BOUND);
                }
            }
        }
    }
    break;
}

MCS_BOUND消息的处理过程就是:

针对startCopy()方法的处理过程,代码如下:

private static final int MAX_RETRIES = 4;
final boolean startCopy() {
    boolean res;
    try {
        // 重试超过4次,则推出
        if (++mRetries > MAX_RETRIES) {
            // 发送 MCS_GIVE_UP 消息取消安装
            mHandler.sendEmptyMessage(MCS_GIVE_UP);
            handleServiceError();
            return false;
        } else {
            // 核心方法,执行 copy
            handleStartCopy();
            res = true;
        }
    } catch (RemoteException e) {
        // 出现异常,发送 MCS_RECONNECT 消息,进行重连
        mHandler.sendEmptyMessage(MCS_RECONNECT);
        res = false;
    }
    // 开始进入第二阶段
    handleReturnCode();
    return res;
}

startCopy()方法通过调用handleStartCopy()来完成安装过程。考虑到安装过程的不确定性,startCopy()主要是进行错误处理:

handleStartCopy()方法比较长,简要代码如下:

public void handleStartCopy() throws RemoteException {
    int ret = PackageManager.INSTALL_SUCCEEDED;
    ......
    if (onInt && onSd) {
        // 设置安装路径异常标志
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else if (onSd && ephemeral) {
        // 设置安装路径异常标志
        ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
    } else {
        // 获取安装包中的 PackageInfoLite 对象
        pkgLite = mContainerService.getMinimalPackageInfo(origin.resolvedPath, installFlags, packageAbiOverride);
        ......
        if (!origin.staged && pkgLite.recommendedInstallLocation
                == PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE) {
            // TODO: focus freeing disk space on the target device
            // 如果安装空间不够,尝试释放cache空间
            ......
            try {
                mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
                ......
            } catch (InstallerException e) {
                Slog.w(TAG, "Failed to free cache", e);
            }
            ......
            if (pkgLite.recommendedInstallLocation
                    == PackageHelper.RECOMMEND_FAILED_INVALID_URI) {
                pkgLite.recommendedInstallLocation
                    = PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
            }
        }
    }
    ......
    final InstallArgs args = createInstallArgs(this);
    mArgs = args;
    if (ret == PackageManager.INSTALL_SUCCEEDED) {
        ......
        if (!origin.existing && requiredUid != -1 && isVerificationEnabled(verifierUser.getIdentifier(), installFlags, installerUid)) {
            // 此部分是在执行应用的校验
            // 主要通过向待校验功能的组件发送Intent来完成
            ......
            /*
             * We don't want the copy to proceed until verification
             * succeeds, so null out this field.
            */
            mArgs = null;
        } else {
            // 无需校验,调用 InstallArgs 的 copyAPK 方法继续处理
            ret = args.copyApk(mContainerService, true);
        }
    }
    mRet = ret;
}

上面handleStartCopy()方法的主要逻辑是:

InstallArgscopyApk()方法如下:

int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    ......
    return doCopyApk(imcs, temp);
}
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
    if (origin.staged) {
        codeFile = origin.file;
        resourceFile = origin.file;
        return PackageManager.INSTALL_SUCCEEDED;
    }
    try {
        final boolean isEphemeral = (installFlags & PackageManager.INSTALL_INSTANT_APP) != 0;
        // 在/data/app路径下生成临时文件
        final File tempDir = mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
        codeFile = tempDir;
        resourceFile = tempDir;
    } catch (IOException e) {
        Slog.w(TAG, "Failed to create copy file: " + e);
        return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
    }
    // 为临时文件创建文件描述符 ParcelFileDescriptor
    // 主要是为了将文件序列化,方便通过binder传输
    final IParcelFileDescriptorFactory target = new IParcelFileDescriptorFactory.Stub() {
        @Override
        public ParcelFileDescriptor open(String name, int mode) throws RemoteException {
            if (!FileUtils.isValidExtFilename(name)) {
                throw new IllegalArgumentException("Invalid filename: " + name);
            }
            try {
                final File file = new File(codeFile, name);
                final FileDescriptor fd = Os.open(file.getAbsolutePath(),
                        O_RDWR | O_CREAT, 0644);
                Os.chmod(file.getAbsolutePath(), 0644);
                return new ParcelFileDescriptor(fd);
            } catch (ErrnoException e) {
                throw new RemoteException("Failed to open: " + e.getMessage());
            }
        }
    };
    int ret = PackageManager.INSTALL_SUCCEEDED;
    // 使用服务 DefaultContainerService 的 copyPackage 方法复制文件
    ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
    if (ret != PackageManager.INSTALL_SUCCEEDED) {
        Slog.e(TAG, "Failed to copy package");
        return ret;
    }
    // 安装应用中自带的 native 动态库
    final File libraryRoot = new File(codeFile, LIB_DIR_NAME);
    NativeLibraryHelper.Handle handle = null;
    try {
        handle = NativeLibraryHelper.Handle.create(codeFile);
        ret = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
                abiOverride);
    } catch (IOException e) {
        Slog.e(TAG, "Copying native libraries failed", e);
        ret = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
    } finally {
        IoUtils.closeQuietly(handle);
    }
    return ret;
}

copyApk()最后调用了DefaultContainerServicecopyPackage()方法将应用的文件复制到了/data/app目录下。如果应用中还有native的动态库,也会把apk中的库文件提取出来。

执行完copyApk()方法后,安装第一阶段的工作就完成了,应用安装到了/data/app目录下。我们接下来看看handleReturnCode()的内容,也就是第二阶段。

安装第二阶段:装载应用

第二阶段的工作主要是

handleReturnCode()方法代码如下:

void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);
    }
}

handleReturnCode()只是调用了processPendingInstall()方法继续处理,这个方法的代码如下:

private void processPendingInstall(final InstallArgs args, final int currentStatus) {
    // Queue up an async operation since the package installation may take a little while.
    mHandler.post(new Runnable() {
        public void run() {
            mHandler.removeCallbacks(this);// 防止重复调用
            PackageInstalledInfo res = new PackageInstalledInfo();
            res.setReturnCode(currentStatus);
            res.uid = -1;
            res.pkg = null;
            res.removedInfo = null;
            if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                args.doPreInstall(res.returnCode);
                synchronized (mInstallLock) {
                    installPackageTracedLI(args, res);
                }
                args.doPostInstall(res.returnCode, res.uid);
            }
            ......
            // 将相关数据记录到 mRunningInstalls 集合中
            PostInstallData data = new PostInstallData(args, res);
            mRunningInstalls.put(token, data);
            ...... // 省略备份部分
            if (!doRestore) {
                Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                mHandler.sendMessage(msg);
            }
        }
    });
}

processPendingInstall()方法中post了一个任务:

我们还是先看下核心方法installPackageTracedLI()的内容,代码如下:

private void installPackageTracedLI(InstallArgs args, PackageInstalledInfo res) {
    installPackageLI(args, res);
}
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
    ......
    // Result object to be returned
    res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
    res.installerPackageName = installerPackageName;
    ......
    // 创建 APK 解析对象 PackageParser
    PackageParser pp = new PackageParser();
    pp.setSeparateProcesses(mSeparateProcesses);
    ......
    final PackageParser.Package pkg;
    try {
        // 解析 APK
        pkg = pp.parsePackage(tmpPackageFile, parseFlags);
    } catch (PackageParserException e) {
        res.setError("Failed parse during installPackageLI", e);
        return;
    }
    ......
    // 收集应用的签名信息
    if (args.signingDetails != PackageParser.SigningDetails.UNKNOWN) {
        pkg.setSigningDetails(args.signingDetails);
    } else {
        PackageParser.collectCertificates(pkg, false /* skipVerify */);
    }
    ......
    pp = null;
    String oldCodePath = null;
    boolean systemApp = false;
    synchronized (mPackages) {
        // Check if installing already existing package
        if ((installFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) {
            // 如果是应用升级的情况,继续使用旧的应用包名
            String oldName = mSettings.getRenamedPackageLPr(pkgName);
            if (pkg.mOriginalPackages != null
                    && pkg.mOriginalPackages.contains(oldName)
                    && mPackages.containsKey(oldName)) {
                pkg.setPackageName(oldName);
                pkgName = pkg.packageName;
                replace = true;
            } 
            ......// 省略部分新旧应用的属性比对
        }
        // 检查已安装的应用集合中是否存在该应用的记录
        PackageSetting ps = mSettings.mPackages.get(pkgName);
        if (ps != null) {
            // 存在同名应用
            ......// 省略一些本地库的签名判断
            // 检查是否为系统应用
            oldCodePath = mSettings.mPackages.get(pkgName).codePathString;
            if (ps.pkg != null && ps.pkg.applicationInfo != null) {
                systemApp = (ps.pkg.applicationInfo.flags &
                        ApplicationInfo.FLAG_SYSTEM) != 0;
            }
            res.origUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
        }
        // 检查应用中定义的Permission是否被重复定义
        int N = pkg.permissions.size();
        for (int i = N-1; i >= 0; i--) {
            final PackageParser.Permission perm = pkg.permissions.get(i);
            final BasePermission bp =
                    (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
            ...... 
            // 省略部分只要是对系统定义权限和非系统定义权限的区分
            // 如果是系统应用定义的权限,则忽略本应用中的定义,然后继续
            // 如果是非系统应用定义的权限,则本次安装失败
            ......
        }
    }
    ......
    try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
            "installPackageLI")) {
        if (replace) {
            ......
            replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
                    installerPackageName, res, args.installReason);
        } else {
            installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
                    args.user, installerPackageName, volumeUuid, res, args.installReason);
        }
    }
    ...... // 省略 dexopt 相关操作
    }
}

这部分的代码逻辑和前面介绍的scanDirLI()很像,不详细介绍了,整体流程是:

这里不深入进行分析了哈,跟踪完调用过程后其实都会走到addForInitLI()

再次吐槽一下PMSpackage解析部分的调用是真滴复杂啊,各种条件判断

我们继续看下POST_INSTALL消息的处理过程:

case POST_INSTALL: {
    PostInstallData data = mRunningInstalls.get(msg.arg1);
    final boolean didRestore = (msg.arg2 != 0);
    mRunningInstalls.delete(msg.arg1);
    if (data != null) {
        InstallArgs args = data.args;
        PackageInstalledInfo parentRes = data.res;
        ......
        // Handle the parent package
        handlePackagePostInstall(parentRes, grantPermissions, killApp,
                virtualPreload, grantedPermissions, didRestore,
                args.installerPackageName, args.observer);
        // Handle the child packages
        final int childCount = (parentRes.addedChildPackages != null)
                ? parentRes.addedChildPackages.size() : 0;
        for (int i = 0; i < childCount; i++) {
            PackageInstalledInfo childRes = parentRes.addedChildPackages.valueAt(i);
            handlePackagePostInstall(childRes, grantPermissions, killApp,
                    virtualPreload, grantedPermissions, false /*didRestore*/,
                    args.installerPackageName, args.observer);
        }
        ......
    }
    ......
} break;

POST_INSTALL的消息处理基本上都是在执行handlePackagePostInstall()方法,handlePackagePostInstall()方法主要工作包括两个:

关于PMS的软件卸载流程,大家同样可以从PackageInstaller入手,关键类是UninstallUninstalling。这里就先跳过了,PMS篇幅占用时间太久了。。。。。

标签:PMS,res,......,应用,Android,null,安装,final
来源: https://blog.csdn.net/lijie2664989/article/details/113090596