android多用户下应用安装详解一(新应用安装情况)

来源:互联网 发布:nike beacon 知乎 编辑:程序博客网 时间:2024/06/01 18:44

  android4.2引入了多用户的机制,方便于多个用户共用同一台android设备,这样就可以在不同的用户下安装不同应用满足不同的场景需求。那多用户下到底是怎样实现应用安装隔离的呢,下面我们对此做出详细分析。

   首先我们想到的就是正常的应用安装流程中的多用户隔离:

     1.应用商店下载安装应用最终调到 :

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

            这是一个隐式意图,最终调到com.android.packageinstaller.PackageInstallerActivity,他继续传递给com.android.packageinstaller.InstallAppProgress,这是一个系统应用,专门用于处理安装请求。

    2,接着应用安装包会通过PackageManager,installPackageWithVerificationAndEncryption()安装应用,而熟悉android源码设计结果的很明显可以看到,这十一个典型的binder跨进程调用,最终实际上会掉到PackageManagerService的同名方法,而这个service是系统中专门复杂应用包管理的系统service,运行于system-server中,当然具体的应用安装详细流程肯定是在这个类里面处理的,下面我们开始详细分析它。

   3.       public void installPackageWithVerificationAndEncryption(Uri packageURI,
            IPackageInstallObserver observer, int flags, String installerPackageName,
            VerificationParams verificationParams, ContainerEncryptionParams encryptionParams) {
        ..............................
        ..............................
        UserHandle user;
        if ((flags&PackageManager.INSTALL_ALL_USERS) != 0) {
            user = UserHandle.ALL;
            /*ThunderSoft add for container  only let install in current user*/
        } else {
            user = new UserHandle(UserHandle.getUserId(uid));
        }

        ..............................
        ..............................
        final Message msg = mHandler.obtainMessage(INIT_COPY);
        msg.obj = new InstallParams(packageURI, observer, filteredFlags, installerPackageName,
                verificationParams, encryptionParams, user);
        mHandler.sendMessage(msg);
    }

   加色部分可以看到,我们会指定应用安装的用户user,而UserHandle.getUserId(uid)就是获取调用者所在的用户,从而实现安装到同一用户。


4. 中间handler,message的处理环节,我们省略,因为这不是我们今天的重点,并且网上有很多分析文章,我们直接跳到

   private void installNewPackageLI(PackageParser.Package pkg,

            int parseFlags, int scanMode, UserHandle user,
            String installerPackageName, PackageInstalledInfo res) {
        // Remember this for later, in case we need to rollback this install
        ................................
        String pkgName = pkg.packageName;
        mLastScanError = PackageManager.INSTALL_SUCCEEDED;
        PackageParser.Package newPackage = scanPackageLI(pkg, parseFlags, scanMode,
                System.currentTimeMillis(), user);

        ................................
   }

5.  接着:

        private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
            int parseFlags, int scanMode, long currentTime, UserHandle user) {
         ................................省略包解析的过程.....................
         pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
                    pkg.applicationInfo.flags, user, false);

         ................................
    }


6. 好了,来到了一个新类:com.android.server.pm.Settings  它负责处理设置和读取包的各种状态:

      PackageSetting getPackageLPw(PackageParser.Package pkg, PackageSetting origPackage,
            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
            String nativeLibraryPathString, int pkgFlags, UserHandle user, boolean add) {
        final String name = pkg.packageName;
        PackageSetting p = getPackageLPw(name, origPackage, realName, sharedUser, codePath,
                resourcePath, nativeLibraryPathString, pkg.mVersionCode, pkgFlags,
                user, add, true /* allowInstall */);
        return p;
    }

     

7.     private PackageSetting getPackageLPw(String name, PackageSetting origPackage,

            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
            String nativeLibraryPathString, int vc, int pkgFlags,
            UserHandle installUser, boolean add, boolean allowInstall) {
          ................................
          PackageSetting p = mPackages.get(name);
          ................................
         if (p == null) {
         ................................
           p = new PackageSetting(name, realName, codePath, resourcePath,
                        nativeLibraryPathString, vc, pkgFlags);
           List<UserInfo> users = getAllUsers();
                    if (users != null && allowInstall) {
                        for (UserInfo user : users) {
                            final boolean installed = installUser == null
                                    || installUser.getIdentifier() == UserHandle.USER_ALL
                                    || installUser.getIdentifier() == user.id;
                            p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
                                    installed,
                                    true, // stopped,
                                    true, // notLaunched
                                    false, // blocked
                                    null, null, null);
                            writePackageRestrictionsLPr(user.id);
                        }
                    }
            }

      这个方法我们分两步分析,先分析p == null的情况。这里mPackages是在开机的时候packagemanagerservice初始化的时候,会从/data/system/packages.xml中读取系统应用信息初始化出来的,这个稍后分析,而现在对于一个新安装应用的情况,显然p==null. 所以我们会new 一个PackageSetting实力出来,那么一个关键的类来了,PackageSetting就是系统中负责记录每一个应用各种状态的model类,下面我们来分析一下这个类

        i. final class PackageSetting extends PackageSettingBase   继承于PackageSettingBase

        ii.   class PackageSettingBase extends GrantedPermissions {

                  final String name;
               final String realName;
            File codePath;
             String codePathString;
            File resourcePath;
              String resourcePathString;
              String nativeLibraryPathString;

              private final SparseArray<PackageUserState> userState = new SparseArray<PackageUserState>();

           可以看到这里面维护了应用的名字,code路径,资源路径,动态库路径等,当然还有我们本文最关心的应用在各用户的安装状态,userState。这里面可能有些读者会感到奇怪,为什么是一个SparseArray这样的散列数组,而不是直接的PackageUserState。其实这也非常简单,这和android对于多用户下应用管理机制。首先,多用户下的同一个应用实际上运行的是同一套代码,即共享/data/app/xxx.apk资源,只是在不同用户下孵化出不同的进程从而实现用户隔离。比如两个用户下同时都安装了qq,实际上系统中只会在/data/app目录下存在一个qq.apk,包括apk里面的资源,dex, .so都是只有一套。这样的设计最大的好处就是节省系统存储资源,在packagemanagerservice管理的时候也只需要加载一套资源。但是共用同一套资源就带来一个问题,到底怎样区分应用是安装在那个用户下呢?这时,SparseArray<PackageUserState> userState这样一个散列数组就上场了,实际上它是维护了该应用在每个用户下的安装情况,我们看一下PackageUserState:

      public class PackageUserState {
          public boolean stopped;
          public boolean notLaunched;
           public boolean installed;
        public boolean blocked; // Is the app restricted by owner / admin
            public int enabled;

      。。。。。。。。。。。。。。。。

     }   这里面关键的的是installed,enabled属性,分别表示是否安装,及是否enable.  这样对于对于一个应用来说,如果系统中存在三个用户,那么userState数组中就维护了三个对象分别代表该应用在这三个用户中的安装情况。

  iii. 好了,我们继续回到getPackageLPw分析,

         List<UserInfo> users = getAllUsers();
                    if (users != null && allowInstall) {
                        for (UserInfo user : users) {
                            final boolean installed = installUser == null
                                    || installUser.getIdentifier() == UserHandle.USER_ALL
                                    || installUser.getIdentifier() == user.id;
                            p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
                                    installed,
                                    true, // stopped,
                                    true, // notLaunched
                                    false, // blocked
                                    null, null, null);
                            writePackageRestrictionsLPr(user.id);
                        }
                    }
            }

      这段实际上就是对一个新安装的应用,设置其在各用户下的安装状态,如果 installUser.getIdentifier() == UserHandle.USER_ALL ||  installUser == null,我们发现就是全用户安装,如果不是就是指定用户安装。


8.  我们再分析一下p!=null的情况:

      else {
            if (installUser != null && allowInstall) {
                List<UserInfo> users = getAllUsers();
                if (users != null) {
                    for (UserInfo user : users) {
                        if (installUser.getIdentifier() == UserHandle.USER_ALL
                                || installUser.getIdentifier() == user.id) {
                            boolean installed = p.getInstalled(user.id);
                            if (!installed) {
                                p.setInstalled(true, user.id);
                                writePackageRestrictionsLPr(user.id);
                            }
                        }
                    }
                }
            }
        }

      这里可以看成是覆盖安装的情况,也很好理解, mPackages.get(name)!=null说明系统中已经加载过这个应用。

     这块在分析最后一个writePackageRestrictionsLPr(user.id);  从直观上我们可以猜测这应该是对于应用安装状态的本地化处理。因为当前只是在内存中维护了安装状态,再次开机之后肯定就没有了,所以必然涉及到本地化的问题。我们来大致分析一下这个方法:

      void writePackageRestrictionsLPr(int userId) {
          File userPackagesStateFile = getUserPackagesStateFile(userId);
           final FileOutputStream fstr = new FileOutputStream(userPackagesStateFile);
            final BufferedOutputStream str = new BufferedOutputStream(fstr);

            final XmlSerializer serializer = new FastXmlSerializer();
            for (final PackageSetting pkg : mPackages.values()) {
                PackageUserState ustate = pkg.readUserState(userId);
                if (ustate.stopped || ustate.notLaunched || !ustate.installed
                        || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT
                        || ustate.blocked
                        || (ustate.enabledComponents != null
                                && ustate.enabledComponents.size() > 0)
                        || (ustate.disabledComponents != null
                                && ustate.disabledComponents.size() > 0)) {
                    serializer.startTag(null, TAG_PACKAGE);
                    serializer.attribute(null, ATTR_NAME, pkg.name);
                    if (DEBUG_MU) Log.i(TAG, "  pkg=" + pkg.name + ", state=" + ustate.enabled);


                    if (!ustate.installed) {
                        serializer.attribute(null, ATTR_INSTALLED, "false");
                    }
            ..........................xml写入过程...................................

    }  

    实际上被写入的文件是 /data/system/users/userId/package-restrictions.xml,这个文件每一个用户都对应一个,放在相应的userId目录下,大致看一下这个文件:

            <pkg name="com.android.soundrecorder" inst="false" />
    <pkg name="com.android.inputmethod.latin">
        <disabled-components>
            <item name="com.android.inputmethod.latin.setup.SetupActivity" />
        </disabled-components>
    </pkg>
    <pkg name="com.qualcomm.atuner" inst="false" />
    <pkg name="com.qualcomm.secureservices.otpgenerator" inst="false" />

    其中inst="false"即表明该应用在该用户下没有安装,所以我们发现实际上多用户下应用是否安装和我们想像的可能不太一样,它只是一个标识,并不是完全意义上的该用户下该应用什么都没有,实际上在/data/users/userId/下是有改应用的包名文件夹的,即使它没有在该用户下安装。而package-restrictions.xml这个文件会在开机的时候被packagemanagerservice读取到mPackages中,从而获取各应用的安装状态。

  好了,新应用多用户安装流程分析到这里,下篇再分析开机启动过程对应用安装状态加载的流程,以及其他一些特殊情况。

           


0 0
原创粉丝点击