美文网首页
Android启动过程中应用的安装过程分析 (一)

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

作者: 飞飞飞_Android | 来源:发表于2017-08-15 16:03 被阅读0次

    前言

    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.haomeiwen.com/subject/qutrrxtx.html