使用PackageInstaller安装app流程学习小结

本文详细解析了Android应用从点击安装到安装完成的全过程,包括验证未知来源、确定安装位置、复制临时文件、解析AndroidManifest.xml、重命名文件、分配UID、创建数据目录、执行dexopt操作以及更新配置文件等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

  • 首先本文不是做PackageManagerService学习总结,PackageManagerService这货有1万2千多行代码,学习起来颇费劲,并且这货功能强大,本文只会总结其中一个小小的功能
  • 为何要做这个总结呢?说来话长,鄙人菜鸟一枚,接到一个安装应用过程中重启的问题,原因找到,但不知如何解决,无奈,只有硬着头皮学习了下这部分内容
  • OK,废话不多说,接下来直接上干货,如果文中有问题或有质疑的地方可以直接修改,不胜感激。


PackageInstaller介绍

  • PackageInstaller是个神马东西呢?
我们知道安装app有很多中方式,诸如adb install,应用助手(豌豆荚),开机安装(开机启动时),下载到手机存储后点击安装。PackageInstaller这哥就是给手动安装app提供一个界面的apk。
当我们点安装应用时会启动这个应用来显示安装过程,安装的事情并不是他在做,正在安装是由PackageManagerService来完成,当然幕后英雄确是Installer。
  • 代码位置
packages/apps/PackageInstaller
  • 疑问【以下有几个问题,如果亲都知道的话,那么可以不用再看本文啦】
如何使用PackageInstaller来安装应用?
应用首选安装位置是在何时确定的?是在设置里设置的,还是在app中定义的,还是PackageManagerService这货说了算?
安装应用过程中,哪些服务和类会插手这件事?
安装过程中首先会生成一个.tmp临时文件,这个文件在何时被rename为apk的?
应用都有uid,这个uid是在什么时候被赋值的?
packages.xml and packages.list有什么用?
安装app主要做了哪些事?


App安装过程中涉及类

  • 先来盘类图看看,如对PackageManagerService不熟悉的话,先看后面的流程,看完再来看这个类图
App Install ClassDiagram.jpg
  • 安装过程时会插手的主要类如下
packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java
frameworks/base /core/java/android/app/ApplicationPackageManager.java
frameworks/base /core/java/android/content/pm/PackageParser.java
frameworks/base /core/java/android/content/res/AssetManager.java
frameworks/base /packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
frameworks/base /services/java/com/android/server/pm/Installer.java
frameworks/base /services/java/com/android/server/pm/PackageManagerService.java
frameworks/base /services/java/com/android/server/pm/Settings.java
  • 简单说明这些类作用
PackageInstallerActivity.java:在文件管理器里点击apk后就会调用该类,主要用于显示要安装的apk的一些权限信息
InstallAppProgress.java:当看完所有权限后,点安装后就会调用该类,用于显示安装进度,这时候PackageManagerService就在默默的安装应用
ApplicationPackageManager.java:这是类是PackageManager的儿子,我们使用mContext.getPackageManager得到的其实就是ApplicationPackageManager的对象,它爹PackageManager是个抽象类,对外的方法都定义在里面
PackageParser.java:解析app,主要解析apk中的AndroidManifest.xml,解析里面的四大组件以及权限信息放入内存里,最后写到packages.xml和package.list(/data/system下)中
AssetManager.java:把AndroidManifest.xml从app中拿出来给PackageParser.java去解析
DefaultContainerService.java:这个服务用于检查存储状态,得到合适的安装位置
Installer.java:PackageManagerService调用它去执行安装,他会把PackageManagerService传过来的数据封装成命令,然后让底层的Installer去执行
PackageManagerService.java:管理app的大神,安装、移动、卸载、查询等都由他管


App安装流程分析

  • 先来个时序图--安装成功的时序图。点击两次可看大图



  • 流程分析,当然对着代码看上面的时序图也很明了

1.当点击文件管理器中的apk时,会调用FolderFragment的openFile方法,该方法里会将应用信息传给PackageInstallerActivity,并启动PackageInstaller

代码位置:vendor/qcom/proprietary/qrdplus/FileExplorer/src/com/android/qrdfileexplorer/FolderFragment.java

private void openFile(File f) {
        final Uri fileUri = Uri.fromFile(f);
        final Intent intent = new Intent();
        intent.setAction(android.content.Intent.ACTION_VIEW);
        intent.putExtra(Intent.EXTRA_TITLE, f.getName());
        intent.putExtra(EXTRA_ALL_VIDEO_FOLDER, true);
        Uri contentUri = null;
        String type = getMIMEType(f);
        ......
            if (contentUri != null) {
                intent.setDataAndType(contentUri, type);
            } else {
                intent.setDataAndType(fileUri, type);
            }
            try {
                startActivitySafely(intent);
            } 
        ......
    }

2.PackageInstaller启动过后会检查是否开启未知来源,未开启就需要先进入设置设置后,方可继续安装,之后会依次调用initiateInstall()->startInstallConfirm();
在initiateInstall中会检查是否已经安装过,是否是系统应用等,调用startInstallConfirm去初始化界面,显示权限信息,当点击安装按钮时,启动安装,切换界面到InstallAppProgress

代码位置:packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

 @Override
    protected void onCreate(Bundle icicle) {
        ......
        mPm = getPackageManager();
        boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
        ......
        initiateInstall();
    }
    private void initiateInstall() {
        String pkgName = mPkgInfo.packageName;
        String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
        if (oldName != null && oldName.length > 0 && oldName[0] != null) {
            pkgName = oldName[0];
            mPkgInfo.packageName = pkgName;
            mPkgInfo.applicationInfo.packageName = pkgName;
        }
        // Check if package is already installed. display confirmation dialog if replacing pkg
        try {
            // This is a little convoluted because we want to get all uninstalled
            // apps, but this may include apps with just data, and if it is just
            // data we still want to count it as "installed".
            mAppInfo = mPm.getApplicationInfo(pkgName,
                    PackageManager.GET_UNINSTALLED_PACKAGES);
            if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
                mAppInfo = null;
            }
        } catch (NameNotFoundException e) {
            mAppInfo = null;
        }
        mInstallFlowAnalytics.setReplace(mAppInfo != null);
        mInstallFlowAnalytics.setSystemApp(
                (mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
        startInstallConfirm();
    }

3.在InstallAppProgress中会调用initView去初始化界面并调用ApplicationPackageManager的installPackageWithVerificationAndEncryption方法来安装

代码位置:packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallAppProgress.java

  @Override
    public void onCreate(Bundle icicle) {
        ......
        initView();
    }
    public void initView() {
        ......
        if ("package".equals(mPackageURI.getScheme())) {
            try {
                pm.installExistingPackage(mAppInfo.packageName);
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_SUCCEEDED);
            } catch (PackageManager.NameNotFoundException e) {
                observer.packageInstalled(mAppInfo.packageName,
                        PackageManager.INSTALL_FAILED_INVALID_APK);
            }
        } else {
            pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
                    installerPackageName, verificationParams, null);
        }
    }

4.ApplicationPackageManager的installPackageWithVerificationAndEncryption里也是调用PMS的installPackageWithVerificationAndEncryption方法

代码位置:frameworks/base/core/java/android/app/ApplicationPackageManager.java

    @Override
    public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        try {
            mPM.installPackageWithVerificationAndEncryption(packageURI, observer, flags,
                    installerPackageName, verificationParams, encryptionParams);
        } catch (RemoteException e) {
            // Should never happen!
        }
    }

5.installPackageWithVerificationAndEncryption方法里,首先会获取设置中的用户安装位置,并且会把InstallParams对象和安装位置flag封装到Message里,然后发出一个消息后就撒手不管了。

代码位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES,
                null);
        final int uid = Binder.getCallingUid();
        if(getInstallLocation() == PackageHelper.APP_INSTALL_INTERNAL){
            userFilteredFlags = flags |PackageManager.INSTALL_INTERNAL;
        } else if(getInstallLocation() == PackageHelper.APP_INSTALL_EXTERNAL){
            userFilteredFlags = flags |PackageManager.INSTALL_EXTERNAL;
        } else{
            userFilteredFlags = filteredFlags;
        }
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, userFilteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);
    }

6.接下来就该PackageHandler上场了,会依次处理INIT_COPY、MCS_BOUN消息,这里面会去连接DefaultContainerService服务,接着会InstallParams的startCopy方法

代码位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->内部类:PackageHandler

 public void handleMessage(Message msg) {
        try {
            doHandleMessage(msg);
        } finally {
            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        }
    }
    void doHandleMessage(Message msg) {
        switch (msg.what) {
            case INIT_COPY: {
                HandlerParams params = (HandlerParams) msg.obj;
                if (!mBound) {
                    if (!connectToService()) {
                        params.serviceError();
                        return;
                    } else {
                        mPendingInstalls.add(idx, params);
                    }
                } else {
                    mPendingInstalls.add(idx, params);
                    if (idx == 0) {
                        mHandler.sendEmptyMessage(MCS_BOUND);
                    }
                }
                break;
            }
            case MCS_BOUN: {
                if (msg.obj != null) {
                    mContainerService = (IMediaContainerService) msg.obj;
                }
                if (mContainerService == null) {
                    for (HandlerParams params : mPendingInstalls) {
                        params.serviceError();
                    }
                    mPendingInstalls.clear();
                } else if (mPendingInstalls.size() > 0) {
                    HandlerParams params = mPendingInstalls.get(0);
                    if (params != null) {
                        if (params.startCopy()) {
                            ......
                        }
                    }
                } else {
                    Slog.w(TAG, "Empty queue");
                }
                break;
            }
            ......
            case POST_INSTALL: {
                ...
                if (data != null) {
                    InstallArgs args = data.args;
                    PackageInstalledInfo res = data.res;
                    if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                        ......
                        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                res.pkg.applicationInfo.packageName, extras, null, null, firstUsers);
                        final boolean update = res.removedInfo.removedPackage != null;
                        if (update) {
                            extras.putBoolean(Intent.EXTRA_REPLACING, true);
                        }
                        sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
                                res.pkg.applicationInfo.packageName, extras, null, null, updateUsers);
                        if (update) {
                            sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
                                    res.pkg.applicationInfo.packageName, extras, null, null, updateUsers);
                            sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
                                    null, null, res.pkg.applicationInfo.packageName, null, updateUsers);
                            if (isForwardLocked(res.pkg) || isExternal(res.pkg)) {
                                ......
                                sendResourcesChangedBroadcast(true, true, pkgList,uidArray, null);
                            }
                        }
                        if (res.removedInfo.args != null) {
                            deleteOld = true;
                        }
                    }
                    ......
                    if (args.observer != null) {
                        try {
                            args.observer.packageInstalled(res.name, res.returnCode);
                        } catch (RemoteException e) {
                            Slog.i(TAG, "Observer no longer exists.");
                        }
                    }
                } else {
                    Slog.e(TAG, "Bogus post-install token " + msg.arg1);
                }
                break;
            }
        }
    }

7.InstallParams的startCopy方法里,会调用handleStartCopy方法

代码位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->内部类:InstallParams 继承于HandlerParams

    final boolean startCopy() {
        boolean res;
        try {
            if (++mRetries > MAX_RETRIES) {
                Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up");
                mHandler.sendEmptyMessage(MCS_GIVE_UP);
                handleServiceError();
                return false;
            } else {
                handleStartCopy();
                res = true;
            }
        } catch (RemoteException e) {
            mHandler.sendEmptyMessage(MCS_RECONNECT);
            res = false;
        }
        handleReturnCode();
        return res;
    }

8.handleStartCopy方法中会检查应用是否能安装,如不合法则返回FAILED的CODE,接着会调用DefaultContainerService的getMinimalPackageInfo方法,该方法用于获取存储状态,返回合适的安装位置
如果返回码是INSTALL_SUCCEEDED,那接下来就会调用InstallParams的copyApk,如果安装到内置,调用的就是FileInstallArgs的copyApk方法,如安装到外置就调用AsecInstallArgs的copyApk方法
AsecInstallArgs和FileInstallArgs都是InstallParams的子类

代码位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->内部类:FileInstallArgs 继承于InstallParams

public void handleStartCopy() throws RemoteException {
        ......
        if (onInt && onSd) {
            ret = PackageManager.INSTALL_FAILED_INVALID_INSTALL_LOCATION;
        } else {
            ......
            try {
                mContext.grantUriPermission(DEFAULT_CONTAINER_PACKAGE, mPackageURI,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION);
                ........
                if (packageFile != null) {
                    ......
                    pkgLite = mContainerService.getMinimalPackageInfo(packageFilePath, flags, lowThreshold);
                }
            }
        }
        final InstallArgs args = createInstallArgs(this);
        mArgs = args;
        ......
        if (ret == PackageManager.INSTALL_SUCCEEDED) {
            ......
                ret = args.copyApk(mContainerService, true);
            ......
        }
        mRet = ret;
    }

9.copyApk方法中会依次调用FileInstallArgs 的createCopyFile->PackageManagerService的createTempPackageFile方法去创建临时文件。

代码位置:frameworks/base/services/java/com/android/server/pm/PackageManagerService.java ->内部类:FileInstallArgs 继承于InstallParams
          frameworks/base/services/java/com/android/server/pm/PackageManagerService.java
vmdl*.tmp就是copy成的临时文件
App Install pic 2.jpg

    void createCopyFile() {
        installDir = isFwdLocked() ? mDrmAppPrivateInstallDir : mAppInstallDir;
        codeFileName = createTempPackageFile(installDir).getPath();
        resourceFileName = getResourcePathFromCodePath();
        libraryPath = getLibraryPathFromCodePath();
        created = true;
    }
    private File createTempPackageFile(File installDir) {
        File tmpPackageFile;
        try {
            tmpPackageFile = File.createTempFile("vmdl", ".tmp", installDir);
        } catch (IOException e) {
            Slog.e(TAG, "Couldn't create temp file for downloaded package file.");
            return null;
        }
        try {
            FileUtils.setPermissions(
                    tmpPackageFile.getCanonicalPath(), FileUtils.S_IRUSR|FileUtils.S_IWUSR,
                    -1, -1);
            if (!SELinux.restorecon(tmpPackageFile)) {
                return null;
            }
        } catch (IOException e) {
            Slog.e(TAG, "Trouble getting the canoncical path for a temp file.");
            return null;
        }
        return tmpPackageFile;
    }

10.临时文件已经有了,handleStartCopy方法走完,接着回到步骤7,调用InstallParams的handleReturnCode方法,handleReturnCode中会执行processPendingInstall,在该方法中做了大量工作

    @Override
    void handleReturnCode() {
        if (mArgs != null) {
            processPendingInstall(mArgs, mRet);
            if (mTempPackage != null) {
                if (!mTempPackage.delete()) {
                    Slog.w(TAG, "Couldn't delete temporary file: " +
                            mTempPackage.getAbsolutePath());
                }
            }
        }
    }

11.来看看processPendingInstall到底做了什么?processPendingInstall中最关键方法--installPackageLI,主要的操作(验证签名,创建/data/data,分配UID,dexopt)都在这个方法中完成。

    private void processPendingInstall(final InstallArgs args, final int currentStatus) {
        mHandler.post(new Runnable() {
            public void run() {
                mHandler.removeCallbacks(this);
                PackageInstalledInfo res = new PackageInstalledInfo();
                res.returnCode = currentStatus;
                res.uid = -1;
                res.pkg = null;
                res.removedInfo = new PackageRemovedInfo();
                if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
                    args.doPreInstall(res.returnCode);
                    synchronized (mInstallLock) {
                        installPackageLI(args, true, res);
                    }
                    args.doPostInstall(res.returnCode, res.uid);
                }
                ......
                if (!doRestore) {
                    Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
                    mHandler.sendMessage(msg);
                }
            }
        });
    }

12.我们来看一下installPackageLI方法,首选会让parsePackage去解析apk里的AndroidManifest.xml,使用的是parsePackage方法,把解析出来的内容放到Package对象中
接着调用doRename去将之前的tmp文件重命名为apk。apk已经在/data/app下了,apk的属性也被解析出来放在内存(Package对象)中了
那么现在还需要做什么呢?apk有了,数据目录(/data/data)还没有,所以后面会进行uid赋值,验证签名,创建相应的/data/data目录,dexopt操作,这些工作是由installNewPackageLI来完成

    private void installPackageLI(InstallArgs args,
            boolean newInstall, PackageInstalledInfo res) {
        ......
        int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0);
        PackageParser pp = new PackageParser(tmpPackageFile.getPath());
        pp.setSeparateProcesses(mSeparateProcesses);
        final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile,
                null, mMetrics, parseFlags);
        ......
        if (!args.doRename(res.returnCode, pkgName, oldCodePath)) {
            res.returnCode = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
            return;
        }
        ......
        if (replace) {
            replacePackageLI(pkg, parseFlags, scanMode, args.user,
                    installerPackageName, res);
        } else {
            installNewPackageLI(pkg, parseFlags, scanMode | SCAN_DELETE_DATA_ON_FAILURES, args.user,
                    installerPackageName, res);
        }
        synchronized (mPackages) {
            final PackageSetting ps = mSettings.mPackages.get(pkgName);
            if (ps != null) {
                res.newUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
            }
        }
    }

13.让我们来看看installNewPackageLI具体怎么完成这些工作的吧。

private void installNewPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {
        ......
        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode, System.currentTimeMillis(), user);
        if (newPackage == null) {
            ......
        } else {
            updateSettingsLI(newPackage, installerPackageName, null, null, res);
            ......
            if (res.returnCode != PackageManager.INSTALL_SUCCEEDED) {
                deletePackageLI(pkgName, UserHandle.ALL, false, null, null, dataDirExists ? PackageManager.DELETE_KEEP_DATA : 0,
                        res.removedInfo, true);
            }
        }
    }

14.scanPackageLI方法中,先调用getPackageLPw->newUserIdLPw(Settings类方法)去设置uid,在调用verifySignaturesLP验证签名
然后调用createDataDirsLI创建/data/data数据目录,最后调用performDexOptLI进行dexopt操作
createDataDirsLI是靠调用mInstaller.install方法来完成目录创建,framework中的Installer会和底层幕后Installer勾兑,完成目录创建工作
performDexOptLI操作最后也是通过mInstaller.dexopt来完成的

    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
        ......
        synchronized (mPackages) {
            ......
            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                    pkg.applicationInfo.flags, user, false);
            if (pkgSetting == null) {
                mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                return null;
            }
            ......
            if (!verifySignaturesLP(pkgSetting, pkg)) {
                if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                    return null;
                }
                // The signature has changed, but this package is in the system
                // image...  let's recover!
                pkgSetting.signatures.mSignatures = pkg.mSignatures;
                // However...  if this package is part of a shared user, but it
                // doesn't match the signature of the shared user, let's fail.
                // What this means is that you can't change the signatures
                // associated with an overall shared user, which doesn't seem all
                // that unreasonable.
                if (pkgSetting.sharedUser != null) {
                    if (compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                            pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) {
                        Log.w(TAG, "Signature mismatch for shared user : " + pkgSetting.sharedUser);
                        mLastScanError = PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
                        return null;
                    }
                }
                // File a report about this.
                String msg = "System package " + pkg.packageName
                        + " signature changed; retaining data.";
                reportSettingsProblem(Log.WARN, msg);
            }
            ......
        }
        ......
        if (mPlatformPackage == pkg) {
            ......
        } else {
            ......
            if (dataPath.exists()) {
               ......
            } else {
                ......
                int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
                                           pkg.applicationInfo.seinfo);
                if (ret < 0) {
                    // Error from installer
                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
                    return null;
                }
                if (dataPath.exists()) {
                    pkg.applicationInfo.dataDir = dataPath.getPath();
                } else {
                    Slog.w(TAG, "Unable to create data directory: " + dataPath);
                    pkg.applicationInfo.dataDir = null;
                }
            }
            /*
             * Set the data dir to the default "/data/data/<package name>/lib"
             * if we got here without anyone telling us different (e.g., apps
             * stored on SD card have their native libraries stored in the ASEC
             * container with the APK).
             *
             * This happens during an upgrade from a package settings file that
             * doesn't have a native library path attribute at all.
             */
            if (pkg.applicationInfo.nativeLibraryDir == null && pkg.applicationInfo.dataDir != null) {
                if (pkgSetting.nativeLibraryPathString == null) {
                    setInternalAppNativeLibraryPath(pkg, pkgSetting);
                } else {
                    pkg.applicationInfo.nativeLibraryDir = pkgSetting.nativeLibraryPathString;
                }
            }
            pkgSetting.uidError = uidError;
        }
        ......
        // We also need to dexopt any apps that are dependent on this library.  Note that
        // if these fail, we should abort the install since installing the library will
        // result in some apps being broken.
        if (clientLibPkgs != null) {
            if ((scanMode&SCAN_NO_DEX) == 0) {
                for (int i=0; i<clientLibPkgs.size(); i++) {
                    PackageParser.Package clientPkg = clientLibPkgs.get(i);
                    if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                            == DEX_OPT_FAILED) {
                        if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
                            removeDataDirsLI(pkg.packageName);
                        }
 
                        mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                        return null;
                    }
                }
            }
        }
        // Request the ActivityManager to kill the process(only for existing packages)
        // so that we do not end up in a confused state while the user is still using the older
        // version of the application while the new one gets installed.
        ......
        // Also need to kill any apps that are dependent on the library.
        ......
        return pkg;
    }
    private int createDataDirsLI(String packageName, int uid, String seinfo) {
        int[] users = sUserManager.getUserIds();
        int res = mInstaller.install(packageName, uid, uid, seinfo);
        if (res < 0) {
            return res;
        }
        for (int user : users) {
            if (user != 0) {
                res = mInstaller.createUserData(packageName,
                        UserHandle.getUid(user, uid), user);
                if (res < 0) {
                    return res;
                }
            }
        }
        return res;
    }
    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
            boolean inclDependencies) {
        ......
        return performDexOptLI(pkg, forceDex, defer, done);
    }
    private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer,
            HashSet<String> done) {
        ......
        if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) {
            ......
            try {
                if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) {
                    if (!forceDex && defer) {
                        if (mDeferredDexOpt == null) {
                            mDeferredDexOpt = new HashSet<PackageParser.Package>();
                        }
                        mDeferredDexOpt.add(pkg);
                        return DEX_OPT_DEFERRED;
                    } else {
                        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
                        ret = mInstaller.dexopt(path, sharedGid, !isForwardLocked(pkg));
                        pkg.mDidDexOpt = true;
                        performed = true;
                    }
                }
            } catch (FileNotFoundException e) {
                ......
            } catch (IOException e) {
                ......
            } catch (dalvik.system.StaleDexCacheError e) {
                ......
            } catch (Exception e) {
                ......
            }
            if (ret < 0) {
                //error from installer
                return DEX_OPT_FAILED;
            }
        }
        return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED;
    }

performDexOptLI后生成的文件
App Install pic3.jpg

15.到目前为止,scanPackageLI已经走完了,接下来就该更新packages.list,packages.xml了
系统中所有app的信息都保存在这两个文件中,当有app安装、卸载、更新时都会更新这两个文件
回到步骤13,当installNewPackageLI中的scanPackageLI走完后,后面会调用updateSettingsLI去更新文件
mSettings.writeLPr()来完成往packages.list,packages.xml中更新数据

    private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
            int[] allUsers, boolean[] perUserInstalled,
            PackageInstalledInfo res) {
        String pkgName = newPackage.packageName;
        synchronized (mPackages) {
            ......
            mSettings.setInstallStatus(pkgName, PackageSettingBase.PKG_INSTALL_INCOMPLETE);
            mSettings.writeLPr();
        }
        ......
        synchronized (mPackages) {
            updatePermissionsLPw(newPackage.packageName, newPackage,
                    UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0
                            ? UPDATE_PERMISSIONS_ALL : 0));
            ......
            mSettings.writeLPr();
        }
    }

packages.list文件部分截取
App Install pic 5.jpg
packages.xml部分截取,包含包名,安装时间,签名,权限,文件安装路径等信息
App Install pic 6.jpg

16.installPackageLI到这里已经执行完了,现在回到步骤11,后续会执行到Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0); mHandler.sendMessage(msg);发送消息
消息发出后,回到步骤POST_INSTALL,这里面主要做了两件事,发生一条ACTION_PACKAGE_ADDED广播告诉大家,又有新包了,这是launcher什么的赶紧把图标加上
然后回调args.observer.packageInstalled(res.name, res.returnCode);告诉PackageInstaller安装结果
然后就显示安装完成界面。欧拉,应用安装结束。


总结

小结一下安装app到底主要做了哪些事情?

1.验证是否允许安装未知来源应用
2.得到用户设置的首选安装位置
3.检验app有效性,检查存储状态,得到最佳安装位置
4.拷贝app到安装位置,此时为.tmp临时文件
5.解析AndroidManifest.xml
6.重命名tmp为apk
7.赋值UID,验证权限
8.创建/data/data/下数据目录
9.执行dexopt操作
10.更新packages.xml,packages.list
11.发送广播,回调安装状态


<think>我们正在讨论Android U(API级别33)及以上版本使用PackageInstaller安装APK的方法。 根据引用[2]和引用[3]的内容,在Android 7.0(API 24)及以上版本,由于文件权限的更改,我们不能再使用`file://`URI来传递APK文件,而应该使用FileProvider来生成`content://`URI。 但是,在Android U(API 33)及以上版本,Google进一步加强了隐私和安全,对于安装APK的方式也有新的要求。特别是,从Android 11(API 30)开始,我们可能需要处理包可见性(package visibility)的问题,而在Android U中,可能还有新的变化。 然而,引用[2]中提到了使用PackageInstallerActivity的方法,但在较新的Android版本中,直接启动系统安装界面可能不再推荐,因为系统组件可能会变化,而且从Android 10开始,Google限制了应用启动后台活动。 实际上,在Android U及以上,我们更推荐使用PackageInstaller API来安装APK,这是一个更系统、更安全的方式。PackageInstaller允许我们以静默方式安装应用(需要系统权限)或者创建一个安装会话,然后通过一个意图让用户确认安装。 但是请注意,普通应用无法使用PackageInstaller进行静默安装(需要系统签名或root权限)。普通应用通常只能启动系统安装界面(使用Intent.ACTION_INSTALL_PACKAGE或Intent.ACTION_VIEW),或者使用PackageInstaller创建一个安装会话并启动确认界面。 下面我将介绍在Android U及以上版本使用PackageInstaller安装APK的步骤: 1. 创建安装会话:使用PackageInstaller创建一个新的安装会话。 2. 将APK文件写入会话。 3. 提交会话,并获取一个PendingIntent用于启动安装确认界面。 4. 启动安装确认界面,用户确认后安装。 注意:在Android U(API 33)及以上,我们还需要注意以下几点: - 需要申请`REQUEST_INSTALL_PACKAGES`权限(在Android 8.0及以上需要)。 - 如果目标SDK版本为Android 11(API 30)或更高,需要在清单文件中声明需要交互的包(使用<queries>元素)或者使用package visibility API。 - 使用FileProvider提供文件内容URI,确保文件共享安全。 但是,使用PackageInstaller API进行安装(非静默,即需要用户确认)并不需要特殊权限(除了`REQUEST_INSTALL_PACKAGES`),因为最终用户会看到安装界面并确认。 步骤详解: 步骤1:在AndroidManifest.xml中添加必要的权限和FileProvider配置。 添加权限: ```xml <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> ``` 配置FileProvider(如果还没有): ```xml <application ...> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileProvider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ... </application> ``` 在res/xml目录下创建file_paths.xml(例如): ```xml <?xml version="1.0" encoding="utf-8"?> <paths> <!-- 这里配置你存放APK的路径 --> <external-path name="external_files" path="." /> </paths> ``` 步骤2:使用PackageInstaller进行安装。 以下是一个示例方法,用于在Android U及以上版本安装APK: ```java private void installApk(Context context, File apkFile) { // 检查是否具有安装权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!context.getPackageManager().canRequestPackageInstalls()) { // 如果没有权限,则跳转到设置界面申请权限 Uri packageURI = Uri.parse("package:" + context.getPackageName()); Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); return; } } PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL); params.setSize(apkFile.length()); int sessionId = -1; try { // 创建会话 sessionId = packageInstaller.createSession(params); PackageInstaller.Session session = packageInstaller.openSession(sessionId); // 将APK文件写入会话 InputStream in = new FileInputStream(apkFile); OutputStream out = session.openWrite("base.apk", 0, apkFile.length()); byte[] buffer = new byte[65536]; int length; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } session.fsync(out); out.close(); in.close(); // 创建用于启动安装确认界面的PendingIntent Intent intent = new Intent(context, InstallReceiver.class); // 需要一个广播接收器来接收安装结果 intent.setAction("com.example.INSTALL_COMPLETE"); // 自定义action PendingIntent pendingIntent = PendingIntent.getBroadcast(context, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); // 提交会话,准备安装 session.commit(pendingIntent.getIntentSender()); session.close(); } catch (IOException | SecurityException e) { e.printStackTrace(); if (sessionId != -1) { packageInstaller.abandonSession(sessionId); } } } ``` 步骤3:创建一个广播接收器(InstallReceiver)来接收安装结果。 ```java public class InstallReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent != null) { int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); String message = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); switch (status) { case PackageInstaller.STATUS_PENDING_USER_ACTION: // 需要用户确认,这里会启动一个Activity Intent activityIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); break; case PackageInstaller.STATUS_SUCCESS: // 安装成功 Toast.makeText(context, "安装成功", Toast.LENGTH_SHORT).show(); break; case PackageInstaller.STATUS_FAILURE: case PackageInstaller.STATUS_FAILURE_ABORTED: case PackageInstaller.STATUS_FAILURE_BLOCKED: case PackageInstaller.STATUS_FAILURE_CONFLICT: case PackageInstaller.STATUS_FAILURE_INCOMPATIBLE: case PackageInstaller.STATUS_FAILURE_INVALID: case PackageInstaller.STATUS_FAILURE_STORAGE: // 安装失败 Toast.makeText(context, "安装失败: " + message, Toast.LENGTH_SHORT).show(); break; } } } } ``` 注意:在清单文件中注册广播接收器: ```xml <receiver android:name=".InstallReceiver" android:exported="true"> <intent-filter> <action android:name="com.example.INSTALL_COMPLETE" /> </intent-filter> </receiver> ``` 但是请注意,以上代码中,我们创建了一个广播接收器来接收安装结果。当安装需要用户确认时,我们会收到`STATUS_PENDING_USER_ACTION`状态,然后启动一个Activity(即系统安装界面)让用户确认。 然而,在Android U及以上版本,Google可能对安装流程有进一步调整,因此建议测试不同版本。 另外,上述方法在写入APK文件时使用了流操作,需要注意文件读取权限和存储权限(在Android 10及以上,可能需要使用MediaStore或SAF)。 总结:在Android U及以上,使用PackageInstaller API进行安装是一个更现代的方式,它避免了直接调用系统组件的风险(因为系统组件可能变化)。同时,我们还需要处理权限请求和文件访问问题。 注意:以上代码仅为示例,实际应用中需要处理更多的异常和兼容性问题。 引用[1]提到,这个过程虽然涉及到一些权限和API的调用,但只要按照步骤来,实现起来并不复杂。希望这篇文章能帮助你顺利地完成这个任务。 如果你在实现过程中遇到任何问题,欢迎随时向我咨询。祝你编程愉快![^1] 另外,根据引用[3],我们还需要注意在AndroidManifest.xml中添加`REQUEST_INSTALL_PACKAGES`权限,并且确保文件存在。 最后,关于在Android U及以上版本,可能还需要考虑其他新的安全限制,比如存储访问的变化(例如,Scoped Storage)等。因此,确保你的应用正确处理了APK文件的存储位置和访问方式。 如果您的应用需要支持更广泛的Android版本,可能需要同时处理旧版本(使用Intent安装)和新版本(使用PackageInstaller)的安装方式。但根据问题,我们只关注Android U及以上版本。 希望这个教程对您有帮助!</think>### Android U及以上版本使用PackageInstaller安装APK教程 在Android U(API 33)及以上版本中,由于系统安全策略升级,安装APK需要采用新的PackageInstaller API。以下是完整实现步骤: #### 1. 添加权限和配置 ```xml <!-- AndroidManifest.xml --> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application> <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> </application> ``` #### 2. 创建文件路径配置 `res/xml/file_paths.xml`: ```xml <paths> <external-path name="external_storage" path="." /> </paths> ``` #### 3. 核心安装代码(支持Android U+) ```java public void installApk(Context context, File apkFile) { // 检查安装权限 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.getPackageManager().canRequestPackageInstalls()) { Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES) .setData(Uri.parse("package:" + context.getPackageName())); context.startActivity(intent); return; } // 创建安装会话 PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); PackageInstaller.SessionParams params = new PackageInstaller.SessionParams( PackageInstaller.SessionParams.MODE_FULL_INSTALL ); try { int sessionId = packageInstaller.createSession(params); PackageInstaller.Session session = packageInstaller.openSession(sessionId); // 写入APK数据 try (InputStream in = new FileInputStream(apkFile); OutputStream out = session.openWrite("base.apk", 0, -1)) { byte[] buffer = new byte[16384]; int len; while ((len = in.read(buffer)) > 0) { out.write(buffer, 0, len); } session.fsync(out); } // 创建安装确认Intent Intent intent = new Intent(context, InstallResultReceiver.class); intent.setAction("INSTALL_COMPLETE_ACTION"); PendingIntent pendingIntent = PendingIntent.getBroadcast( context, sessionId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE ); // 提交安装会话 session.commit(pendingIntent.getIntentSender()); session.close(); } catch (IOException e) { e.printStackTrace(); } } ``` #### 4. 安装结果接收器 ```java public class InstallResultReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1); switch (status) { case PackageInstaller.STATUS_PENDING_USER_ACTION: // 触发用户确认界面 Intent confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT); context.startActivity(confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); break; case PackageInstaller.STATUS_SUCCESS: Toast.makeText(context, "安装成功", Toast.LENGTH_SHORT).show(); break; default: String errorMsg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE); Toast.makeText(context, "安装失败: " + errorMsg, Toast.LENGTH_LONG).show(); } } } ``` #### 5. 注册广播接收器 ```xml <receiver android:name=".InstallResultReceiver" android:exported="true"> <intent-filter> <action android:name="INSTALL_COMPLETE_ACTION" /> </intent-filter> </receiver> ``` ### 关键注意事项 1. **文件访问权限**:Android U+要求使用FileProvider访问APK文件 2. **动态权限**:必须动态申请`REQUEST_INSTALL_PACKAGES`权限 3. **会话管理**:每个安装都需要独立会话ID 4. **结果处理**:必须通过广播接收器处理安装结果 5. **Android 14+适配**:需添加`<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>`[^3] 此方法符合Android U+的安全规范,避免了直接调用系统组件的兼容性问题,能稳定触发系统原生安装界面[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值