Android启动过程中应用的安装过程分析 (一)

前言

Android系统在启动过程中,会扫描特定目录,完成对apk的安装.PMS在这个过程中主要完成俩件事情: 1.解析apk的配置文件AndroidManifest.xml,获得其安装信息. 2.为apk分配其Linux用户id和Linux用户组id,以便其在系统中获得合适的运行权限.

APK的Linux用户id只可能有1个,但是可以拥有若干个Linux用户组id,以便于在系统中获得更多的资源访问权限,例如,读取联系人信息,使用摄像头,拨打电话等等权限.PMS在安装一个APK时,如果发现它申请了一个特定的用户组id,就会为其分配一个相应的用户组id.

相关代码路径

frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
frameworks/base/core/java/android/content/pm/PackageParser.java
frameworks/base/services/core/java/com/android/server/pm/Settings.java
  • PackageManagerService.java是用于管理应用程序的服务
  • PackageParser.java主要包含解析apk的代码
  • Settings.java用于存储应用程序的信息

安装过程分析

从PMS的静态main方法说起:

1.PackageManagerService.main

    public static PackageManagerService main(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
        // Self-check for initial settings.
        PackageManagerServiceCompilerMapping.checkProperties();

        PackageManagerService m = new PackageManagerService(context, installer,
                factoryTest, onlyCore);
        m.enableSystemUserPackages();
        ServiceManager.addService("package", m);
        return m;
    }

PackageManagerService m = new PackageManagerService()启动了管理服务,ServiceManager.addService注册该服务到Service Manager,这样其他组件就可以通过Service Manager获得其访问接口.

2.PackageManagerService构造方法

 public PackageManagerService(Context context, Installer installer,
            boolean factoryTest, boolean onlyCore) {
  • PMS类中有个成员变量mSettings,用于管理应用程序安装信息,例如应用程序的Linux的用户id和用户组id,以及manifest解析得到的应用程序信息.
  • 由于Android系统每次重新启动,都会重新安装一遍系统的应用程序,像Linux的用户id这类的信息每次启动都需要保持一致,这就是上一条mSettings的作用.
  • mSettings.readLPw方法用于恢复上一次应用程序的安装信息.
  • 接着会调用scanDirTracedLI方法,会走到scanDirLI方法里,扫描安装apk,主要扫描的5个目录:/system/framework,/system/app,/vendor/app,/data/app和/data/app-private.
  • 然后updatePermissionsLPw为申请了特定安装资源访问权限的应用程序分配相应的Linux用户组ID.
  • 接着调用writeLPr将前面所获得的应用程序安装信息保存在本地的配置文件.
2.1 Settings.readLPw
    Settings(File dataDir, Object lock) {
        mSystemDir = new File(dataDir, "system");
        mSystemDir.mkdirs();
        .....
        mSettingsFilename = new File(mSystemDir, "packages.xml");
        mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml");
        mPackageListFilename = new File(mSystemDir, "packages.list");
       }

    boolean readLPw(@NonNull List<UserInfo> users) {
        FileInputStream str = null;
        if (mBackupSettingsFilename.exists()) {
            try {
                str = new FileInputStream(mBackupSettingsFilename);
                mReadMessages.append("Reading from backup settings file\n");
                PackageManagerService.reportSettingsProblem(Log.INFO,
                        "Need to read from backup settings file");
                if (mSettingsFilename.exists()) {
                    // If both the backup and settings file exist, we
                    // ignore the settings since it might have been
                    // corrupted.
                    Slog.w(PackageManagerService.TAG, "Cleaning up settings file "
                            + mSettingsFilename);
                    mSettingsFilename.delete();
                }
            } catch (java.io.IOException e) {
                // We'll try for the normal settings file.
            }
        }
    ......
      
        try {
            if (str == null) {
                if (!mSettingsFilename.exists()) {
                    mReadMessages.append("No settings file found\n");
                    PackageManagerService.reportSettingsProblem(Log.INFO,
                            "No settings file; creating initial state");
                    // It's enough to just touch version details to create them
                    // with default values
                    findOrCreateVersion(StorageManager.UUID_PRIVATE_INTERNAL);
                    findOrCreateVersion(StorageManager.UUID_PRIMARY_PHYSICAL);
                    return false;
                }
                str = new FileInputStream(mSettingsFilename);
            }
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(str, StandardCharsets.UTF_8.name());

            int type;
            ......
            int outerDepth = parser.getDepth();
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                String tagName = parser.getName();
                if (tagName.equals("package")) {
                    readPackageLPw(parser);
                } else if (tagName.equals("shared-user")) {
                    readSharedUserLPw(parser);
                } 
                ......
                str.close();
                ......
       }
  • Settings类的成员变量mSettingsFilename和mBackupSettingsFilename分别指向系统中的文件/data/system/packages.xml和/data/system/packages-backup.xml.这两个文件用于保存上次的应用程序安装信息,其中backup是备份.
  • if (mBackupSettingsFilename.exists())判断备份文件是否存在,如果存在,那么就会以它的内容作为上一次应用程序的安装信息.如果不存在则用/data/system/packages.xml作为上次应用程序的安装信息.
  • 接着调用XmlPullParser解析xml文件,解析标签为package的信息,用到方法readPackageLPw,解析标签为shared-user的标签获得用于描述应用的共享Linux用户id,用到的方法是readSharedUserLPw.
2.2 Settings.readPackageLPw
 private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
            name = parser.getAttributeValue(null, ATTR_NAME);
            realName = parser.getAttributeValue(null, "realName");
            idStr = parser.getAttributeValue(null, "userId");
            uidError = parser.getAttributeValue(null, "uidError");
            sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
            ......
            if (name == null) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: <package> has no name at "
                                + parser.getPositionDescription());
            } else if (codePathStr == null) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: <package> has no codePath at "
                                + parser.getPositionDescription());
            } else if (userId > 0) {
                packageSetting = addPackageLPw(name.intern(), realName, new File(codePathStr),
                        new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiString,
                        secondaryCpuAbiString, cpuAbiOverrideString, userId, versionCode, pkgFlags,
                        pkgPrivateFlags, parentPackageName, null);
                if (PackageManagerService.DEBUG_SETTINGS)
                    Log.i(PackageManagerService.TAG, "Reading package " + name + ": userId="
                            + userId + " pkg=" + packageSetting);
                if (packageSetting == null) {
                    PackageManagerService.reportSettingsProblem(Log.ERROR, "Failure adding uid "
                            + userId + " while parsing settings at "
                            + parser.getPositionDescription());
                } else {
                    packageSetting.setTimeStamp(timeStamp);
                    packageSetting.firstInstallTime = firstInstallTime;
                    packageSetting.lastUpdateTime = lastUpdateTime;
                }
            } else if (sharedIdStr != null) {
                userId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
                if (userId > 0) {
                    packageSetting = new PendingPackage(name.intern(), realName, new File(
                            codePathStr), new File(resourcePathStr), legacyNativeLibraryPathStr,
                            primaryCpuAbiString, secondaryCpuAbiString, cpuAbiOverrideString,
                            userId, versionCode, pkgFlags, pkgPrivateFlags, parentPackageName,
                            null);
                    packageSetting.setTimeStamp(timeStamp);
                    packageSetting.firstInstallTime = firstInstallTime;
                    packageSetting.lastUpdateTime = lastUpdateTime;
                    mPendingPackages.add((PendingPackage) packageSetting);
                    if (PackageManagerService.DEBUG_SETTINGS)
                        Log.i(PackageManagerService.TAG, "Reading package " + name
                                + ": sharedUserId=" + userId + " pkg=" + packageSetting);
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Error in package manager settings: package " + name
                                    + " has bad sharedId " + sharedIdStr + " at "
                                    + parser.getPositionDescription());
                }
            } else {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: package " + name + " has bad userId "
                                + idStr + " at " + parser.getPositionDescription());
            }
        } catch (NumberFormatException e) {
          ...... }
          ......
       }
  • 从标签”package”的xml元素解析属性name,userId和sharedUserId,其中,属性name代表上一次安装该应用程序的包名,而userId和sharedUserId代表其独立Linux用户ID和共享Linux用户ID.
  • if (userId > 0),检查是否应用程序上次安装被分配到一个独立的Linux用户id,如果是则调用addPackageLPw方法将其保存下来.
  • else if (sharedIdStr != null),如果sharedIdStr不等于null,那说明并没有给应用程序分配独立的Linux用户id,而是让它与其他的应用程序共享一个Linux用户ID.PMS会创建一个PendingPackage对象,用于描述一个Linux用户id不确定的应用程序.者需要等到readSharedUserLPw解析共享userid完成后,才能确认该应用程序上一次使用的共享userid是否还有效.
2.3 Settings.addPackageLPw

下面看addPackageLPw方法如何保存上一次安装应用程序的Linux用户ID

    PackageSetting addPackageLPw(String name, String realName, File codePath, File resourcePath,
            String legacyNativeLibraryPathString, String primaryCpuAbiString,
            String secondaryCpuAbiString, String cpuAbiOverrideString, int uid, int vc, int
            pkgFlags, int pkgPrivateFlags, String parentPackageName,
            List<String> childPackageNames) {
        PackageSetting p = mPackages.get(name);
        if (p != null) {
            if (p.appId == uid) {
                return p;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate package, keeping first: " + name);
            return null;
        }
        p = new PackageSetting(name, realName, codePath, resourcePath,
                legacyNativeLibraryPathString, primaryCpuAbiString, secondaryCpuAbiString,
                cpuAbiOverrideString, vc, pkgFlags, pkgPrivateFlags, parentPackageName,
                childPackageNames);
        p.appId = uid;
        if (addUserIdLPw(uid, p, name)) {
            mPackages.put(name, p);
            return p;
        }
        return null;
    }
  • 在PMS中,每一个应用程序的安装信息都是使用一个PackageSetting对象来描述的.这些PackageSetting对象被保存在Settings类的成员变量mPackages所代表的一个ArrayMap中.
final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();

String类型的Key值是应用程序的包名name.

  • if (p != null) 首先在Settings类的成员变量mPackages中检查是否存在一个与键值name所对应的PackageSetting对象.如果存在,if (p.appId == uid),判断PackageSetting对象的成员变量userId是否等于参数uid的值,如果相等,那么就说明PMS已经为应用程序分配过一个Linux用户ID,直接返回p.
  • 如果上述条件不存在,会创建一个PackageSetting对象p,用来描述Package名称等于name的应用程序的安装信息,p = new PackageSetting(…).之后将这个对象p的成员变量userId值设置为参数uid,最后调用addUserIdLPw方法在系统中保留值为uid的Linux用户ID.如果保留成功,那么会将对象p保存到Settings类的成员变量mPackages中.
2.4 Settings.addUserIdLPw
    private boolean addUserIdLPw(int uid, Object obj, Object name) {
        if (uid > Process.LAST_APPLICATION_UID) {
            return false;
        }

        if (uid >= Process.FIRST_APPLICATION_UID) {
            int N = mUserIds.size();
            final int index = uid - Process.FIRST_APPLICATION_UID;
            while (index >= N) {
                mUserIds.add(null);
                N++;
            }
            if (mUserIds.get(index) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate user id: " + uid
                        + " name=" + name);
                return false;
            }
            mUserIds.set(index, obj);
        } else {
            if (mOtherUserIds.get(uid) != null) {
                PackageManagerService.reportSettingsProblem(Log.ERROR,
                        "Adding duplicate shared id: " + uid
                                + " name=" + name);
                return false;
            }
            mOtherUserIds.put(uid, obj);
        }
        return true;
    }
  • Android系统中,uid大于FIRST_APPLICATION_UID(10000),且小于LAST_APPLICATION_UID(19999)的Linux用户ID是保留给应用程序使用的,而小于10000的ID用于保留给特权用户使用.
  • Settings类的成员变量mUserIds是一个类型为Arraylist的列表,用来维护那些已经分配给应用程序使用的Linux用户ID.如果保存在这个列表中的第index个位置的object对象不等于null,那么说明(FIRST_APPLICATION_UID+index)所对应的Linux用户ID已经被分配了.
  • Settings类中的另一个成员变量mOtherUserIds是一个类型为SparseArray的稀疏矩阵,用于保存uid小于FIRST_APPLICATION_UID(10000)的应用程序.
3.1 Settings.readSharedUserLPw
    private void readSharedUserLPw(XmlPullParser parser) throws XmlPullParserException,IOException {
        String name = null;
        String idStr = null;
        int pkgFlags = 0;
        int pkgPrivateFlags = 0;
        SharedUserSetting su = null;
        try {
            name = parser.getAttributeValue(null, ATTR_NAME);
            idStr = parser.getAttributeValue(null, "userId");
            int userId = idStr != null ? Integer.parseInt(idStr) : 0;
            if ("true".equals(parser.getAttributeValue(null, "system"))) {
                pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
            }
            if (name == null) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: <shared-user> has no name at "
                                + parser.getPositionDescription());
            } else if (userId == 0) {
                PackageManagerService.reportSettingsProblem(Log.WARN,
                        "Error in package manager settings: shared-user " + name
                                + " has bad userId " + idStr + " at "
                                + parser.getPositionDescription());
            } else {
                if ((su = addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags))
                        == null) {
                    PackageManagerService
                            .reportSettingsProblem(Log.ERROR, "Occurred while parsing settings at "
                                    + parser.getPositionDescription());
                }
            }
        } catch (NumberFormatException e) {
            PackageManagerService.reportSettingsProblem(Log.WARN,
                    "Error in package manager settings: package " + name + " has bad userId "
                            + idStr + " at " + parser.getPositionDescription());
        }

        if (su != null) {
            int outerDepth = parser.getDepth();
            int type;
            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                    continue;
                }

                String tagName = parser.getName();
                if (tagName.equals("sigs")) {
                    su.signatures.readXml(parser, mPastSignatures);
                } else if (tagName.equals("perms")) {
                    readInstallPermissionsLPr(parser, su.getPermissionsState());
                } else {
                    PackageManagerService.reportSettingsProblem(Log.WARN,
                            "Unknown element under <shared-user>: " + parser.getName());
                    XmlUtils.skipCurrentTag(parser);
                }
            }
        } else {
            XmlUtils.skipCurrentTag(parser);
        }
    }
  • 标签值等于”shared-user”的xml元素用于描述上一次安装应用程序时所分配的一个共享Linux一年用户的.其中属性name和userId描述一个共享Linux用户的名称和ID值,而属性system用来描述这个共享Linux用户ID是分配给一个系统类型的应用程序使用的,还是分配给一个用户类型的应用程序使用的.
  • addSharedUserLPw(name.intern(), userId, pkgFlags, pkgPrivateFlags)方法为名称等于name的共享Linux用户保留一个值为userId的Linux用户ID.
3.2 Settings.addSharedUserLPw
    SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        SharedUserSetting s = mSharedUsers.get(name);
        if (s != null) {
            if (s.userId == uid) {
                return s;
            }
            PackageManagerService.reportSettingsProblem(Log.ERROR,
                    "Adding duplicate shared user, keeping first: " + name);
            return null;
        }
        s = new SharedUserSetting(name, pkgFlags, pkgPrivateFlags);
        s.userId = uid;
        if (addUserIdLPw(uid, s, name)) {
            mSharedUsers.put(name, s);
            return s;
        }
        return null;
    }
  • 同2.2类似的,每一个共享Linux用户使用一个SharedUserSetting对象来描述.这些SharedUserSetting对象被保存在Settings类的成员变量mSharedUsers所描述的一个ArrayMap中,并且是以它们所描述的共享Linux用户的名称为关键字.
  • if (addUserIdLPw(uid, s, name)),如果uid保留成功,那么将SharedUserSetting对象s保存到mSharedUsers,这就表示PMS已经为名称等于name的共享Linux对象分配过Linux用户ID了.
    原文作者:飞飞飞_Android
    原文地址: https://www.jianshu.com/p/2c77264eb400
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞