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

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

概述研究应用的安装过程,老样子,我们还是先从使用入手。在Android中,通过发送Intent就可以启动应用的安装过程,比如:Uriuri=Uri.fromFile(newFile(apkFilePath));Intentintent=newIntent(Intent.ACTION_VIEW);intent.setDataAndType(uri,"application/vnd.and

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

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来说,它的主要作用是

解析Uri协议进行解析,包括filepackage两种 如果是file协议会解析APK文件得到包信息PackageInfo 对未知来源进行处理 如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源,就会初始化安装确认界面如果管理员限制来自未知源的安装, 就d出提示Dialog或者跳转到设置界面 提取并在界面展示应用对应的权限

当点击确认安装后,其实会执行到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();            }            ......        }    }

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

getPackageManager().installExistingPackage():主要是对已经并扫描到SettingsmPackages集合中的应用进行 *** 作 根据当前的userID从集合中查询应用状态,如果存在并且应用没有安装,就进行一些简单的配置工作最后通过prepareAppDataAfterInstallliF方法,创建特定userID下的应用数据 InstallingAsyncTask任务中的Session系列 *** 作:一个应用完整的安装过程从这里开始
private final class InstallingAsyncTask extends AsyncTask<VoID, VoID,        PackageInstaller.Session> {    volatile boolean isDone;    @OverrIDe    protected PackageInstaller.Session doInBackground(VoID... params) {        PackageInstaller.Session session;        ......        session = getPackageManager().getPackageInstaller().openSession(mSessionID);        try {            file file = new file(mPackageURI.getPath());            try (inputStream in = new fileinputStream(file)) {                long sizeBytes = file.length();                try (OutputStream out = session.openWrite("PackageInstaller", 0, sizeBytes)) {                    byte[] buffer = new byte[1024 * 1024];                    while (true) {                        ......                        out.write(buffer, 0, numRead);                        ......                    }                }            }            return session;        }        ......    }    @OverrIDe    protected voID onPostExecute(PackageInstaller.Session session) {        if (session != null) {            ......            session.commit(pendingIntent.getIntentSender());            ......        } else {            getPackageManager().getPackageInstaller().abandonSession(mSessionID);            ......    }}
任务中做的第一个事情是:根据APKUri,将APK的信息通过IO流的形式写入到PackageInstaller.Session中第二个事情就是PackageInstallerSession.commit()函数

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()方法开启了安装第一阶段,不过整个安装过程比较复杂,我们先看看这个过程的序列图:

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对象 InstallParams是安装过程中的主要数据结构一般情况,安装应用的时间通常比较长,因此AndroID把安装过程拆分,把调用过程的参数数据保存到InstallParams中 创建并发送了一个Message消息 msg.whatINIT_copYmsg.objInstallParams对象

先来简单了解下InstallParams的类:

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

InstallParams继承自HandlerParams,用来记录安装应用的参数。InstallParams中有一个成员变量mArgs,是一个抽象类型InstallArgs,主要是用来执行APK的复制,真正的实现类包括: fileInstallArgs:用来完成非ASEC应用的安装 asec全称是AndroID Secure External Cache MoveInstallArgs:用来完成已安装应用的移动安装

简单了解完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;}

整个过程比较简洁:

首先是connectToService()去绑定和启动DefaultContainerService服务。 服务绑定是一个异步的过程,需要等待绑定结果通过onServiceConnected()返回从DefaultContainerConnection中可以看到,当服务连接成功后,会发送MSC_BOUNF消息
class DefaultContainerConnection implements ServiceConnection {    public voID onServiceConnected(Componentname name, IBinder service) {        final IMediaContainerService imcs = IMediaContainerService.Stub                .asInterface(Binder.allowBlocking(service));        mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));    }}
然后将安装的参数信息放到了mPendingInstalls列表中。 当有多个安装请求到达时,可以通过mPendingInstalls进行排队 如果添加前的列表为空,说明没有其他的安装请求,此时直接发送MSC_BOUND消息

那么,重点又转移到了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消息的处理过程就是:

调用InstallParams类的startcopy()方法来执行安装 处理逻辑肯定在这个方法里面,下面细讲 只要mPendingInstalls中还有安装信息,就会重复发送MCS_BOUND消息直到所有应用都处理完毕,然后发送一个延时10sMCS_UNBIND消息 MCS_UNBIND消息的处理很简单,收到消息后如果发现mPendingInstalls中有数据了,则发送MCS_BOUND消息继续安装。否则断开DefaultContainerService连接,安装过程到此结束
case MCS_UNBIND: {    if (mPendingInstalls.size() == 0 && mPendingVerification.size() == 0) {        if (mBound) {            // 断开 DefaultContainerService 连接            disconnectService();        }    } else if (mPendingInstalls.size() > 0) {        // 继续发送执行安装 *** 作的消息        mHandler.sendEmptyMessage(MCS_BOUND);    }    break;}

针对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()主要是进行错误处理:

MCS_RECONNECT消息处理中,会重新绑定DefaultContainerService如果绑定成功,安装过程就会重新开始,startcopy()会被再次调用。重试的次数记录在变量mRetrIEs中,超过4次,安装失败 handleStartcopy()执行成功,startcopy()会调用handleReturnCode()来继续处理

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()方法的主要逻辑是:

首先通过getMinimalPackageInfo()方法确认是否还有足够的安装空间,如果安装空间不够,会通过mInstaller.freeCache()来释放一部分接下来通过createInstallArgs()创建InstallArgs对象,createInstallArgs()方法中会对InstallParamsmove成员变量进行判断 move不为空使用MoveInstallArgsmove为空使用fileInstallArgs,我们重点来看这个 再接下来对apk进行校验,这个校验过程是通过发送Intent.ACTION_PACKAGE_NEEDS_VERIFICATION)广播给系统中所有可以接收该Intent的应用来完成的无需校验,执行InstallArgscopyApk()方法

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()的内容,也就是第二阶段。

安装第二阶段:装载应用

第二阶段的工作主要是

把应用的格式转换成oat格式为应用创建数据目录把应用的信息装载进PMS的数据结构中

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()来装载应用接下来的部分是通过Ibackupmanager服务执行备份相关的 *** 作,这部分就不先深入了,篇幅已经过大。。。备份完成后发送POST_INSTALL消息继续处理

我们还是先看下核心方法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()很像,不详细介绍了,整体流程是:

先通过PackageParser解析了安装的应用文件 解析过程和scanDirli()中的parsePackage()一样 得到解析结果后,主要是判断新安装的应用是否和已安装的应用构成升级关系 如果是,则调用replacePackageliF()继续处理否则调用installNewPackageliF()处理

这里不深入进行分析了哈,跟踪完调用过程后其实都会走到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()方法主要工作包括两个:

发广播,广播包括: Intent.ACTION_PACKAGE_ADDED:新应用安装成功Intent.ACTION_PACKAGE_REPLACED:应用更新成功发广播的目的是通知系统中的其他应用APK安装完成。例如,Launcher中需要增加应用的图标等 调用IPackageInstallObserver2onPackageInstalled()方法通知观察者 我们最初介绍的PackageInstaller在安装应用时就是通过注册观察者来实现结果监听的

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

总结

以上是内存溢出为你收集整理的深入Android系统(十)PMS-3-应用安装过程全部内容,希望文章能够帮你解决深入Android系统(十)PMS-3-应用安装过程所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1049813.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-25
下一篇 2022-05-25

发表评论

登录后才能评论

评论列表(0条)

保存