PackageManagerService学习--上

  • 原文发布与个人博客KuTear,转载请注明*

pm命令的使用

pm 基本使用

pm工具在Android系统System/bin下,由于该路径已经在环境变量PATH下,所以我们可以在Android终端下运行pm命令

pm <command>

通过键入pm我们可以看见他的用法,主要包括以下几个方面

  • list => 列出安装的APK/权限组/机器硬件功能(NFC/蓝牙/WIFI)/设备支持库
  • path => 查询指定apk的路径
  • install => APK安装
  • dump => 获取系统中的信息(电量/activity/server)
  • clear => 清除APK数据
  • hide/unhide => 隐藏与恢复应用(被隐藏应用在应用管理中变得不可见,桌面图标也会消失)
  • enable/disable => 禁用和启用应用
  • grant/revoke => 赋予/撤销应用权限

参考:初探Android的PMS服务

pm 命令源码分析

Pm命令的源码在framework/base/cmds/pm下(本文基于5.1.1 r1 代码地址),只有一个类Pm.java,在其静态入口函数main(args)中执行函数run(args),这里我们可以看看其中一部分Pm list package

    public int run(String[] args) throws IOException, RemoteException {
        boolean validCommand = false;
        if (args.length < 1) {
            return showUsage();
        }

        mUm = IUserManager.Stub.asInterface(ServiceManager.getService("user"));
        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
        if (mPm == null) {
            System.err.println(PM_NOT_RUNNING_ERR);
            return 1;
        }
        mInstaller = mPm.getPackageInstaller();

        mArgs = args;
        String op = args[0];
        mNextArg = 1;

        if ("list".equals(op)) {
            return runList();
        }
            //....其他命令
    }

上面的代码通过进程间通信获取的PackageManagaerServer,然后运行具体的指令,下面看看list指令是怎么获取到数据的。

    /**
     * Execute the list sub-command.
     *
     * pm list [package | packages]
     * pm list permission-groups
     * pm list permissions
     * pm list features
     * pm list libraries
     * pm list instrumentation
     */
    private int runList() {
        String type = nextArg();
        if (type == null) {
            System.err.println("Error: didn't specify type of data to list");
            return 1;
        }
        if ("package".equals(type) || "packages".equals(type)) {
            return runListPackages(false);
        } else if ("permission-groups".equals(type)) {
            return runListPermissionGroups();
        } else if ("permissions".equals(type)) {
            return runListPermissions();
        } else if ("features".equals(type)) {
            return runListFeatures();
        } else if ("libraries".equals(type)) {
            return runListLibraries();
        } else if ("instrumentation".equals(type)) {
            return runListInstrumentation();
        } else if ("users".equals(type)) {
            return runListUsers();
        } else {
            System.err.println("Error: unknown list type '" + type + "'");
            return 1;
        }
    }

这里没有实质性的操作,只是分发list的子指令,下面我们看package指令的实现逻辑。

    /* 
     * Lists all the installed packages.
     */
    private int runListPackages(boolean showApplicationPackage) {
        int getFlags = 0;
        boolean listDisabled = false, listEnabled = false;
        boolean listSystem = false, listThirdParty = false;
        boolean listInstaller = false;
        //A user id constant to indicate the "owner" user of the device
        int userId = UserHandle.USER_OWNER;
        try {
            String opt;
            while ((opt=nextOption()) != null) {
                if (opt.equals("-l")) {
                    // old compat
                } else if (opt.equals("-lf")) {
                    showApplicationPackage = true;
                } else if (opt.equals("-f")) {
                    showApplicationPackage = true;
                } else if (opt.equals("-d")) {
                    listDisabled = true;
                } else if (opt.equals("-e")) {
                    listEnabled = true;
                } else if (opt.equals("-s")) {
                    listSystem = true;
                } else if (opt.equals("-3")) {
                    listThirdParty = true;
                } else if (opt.equals("-i")) {
                    listInstaller = true;
                } else if (opt.equals("--user")) {
                    userId = Integer.parseInt(nextArg());
                } else if (opt.equals("-u")) {
                    getFlags |= PackageManager.GET_UNINSTALLED_PACKAGES;
                } else {
                    System.err.println("Error: Unknown option: " + opt);
                    return 1;
                }
            }
        } catch (RuntimeException ex) {
            System.err.println("Error: " + ex.toString());
            return 1;
        }

        String filter = nextArg();

        try {
            //重点关注
            final List<PackageInfo> packages = getInstalledPackages(mPm, getFlags, userId);

            int count = packages.size();
            for (int p = 0 ; p < count ; p++) {
                PackageInfo info = packages.get(p);
                if (filter != null && !info.packageName.contains(filter)) {
                    continue;
                }
                final boolean isSystem =
                        (info.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0;
                if ((!listDisabled || !info.applicationInfo.enabled) &&
                        (!listEnabled || info.applicationInfo.enabled) &&
                        (!listSystem || isSystem) &&
                        (!listThirdParty || !isSystem)) {
                    System.out.print("package:");
                    if (showApplicationPackage) {
                        System.out.print(info.applicationInfo.sourceDir);
                        System.out.print("=");
                    }
                    System.out.print(info.packageName);
                    if (listInstaller) {
                        System.out.print("  installer=");
                        System.out.print(mPm.getInstallerPackageName(info.packageName));
                    }
                    System.out.println();
                }
            }
            return 0;
        } catch (RemoteException e) {
            System.err.println(e.toString());
            System.err.println(PM_NOT_RUNNING_ERR);
            return 1;
        }
    }

上面的代码一方面是添加过滤参数,然后获取所有安装的APK,在根据过滤参数展示对应的APK,而我们最为关心的就是获取所有APK的过程.到底是怎么获取的呢, 我们接着往下看。

    @SuppressWarnings("unchecked")
    private List<PackageInfo> getInstalledPackages(IPackageManager pm, int flags, int userId)
            throws RemoteException {
        ParceledListSlice<PackageInfo> slice = pm.getInstalledPackages(flags, userId);
        return slice.getList();
    }

到此,我们发现其实获取APK列表的过程是由IPackageManager来实现的,同样的方式分析其他命令的操作,其最后都是归集到IPackageManager上,而IPackageManager就是我们今天的主角,下面我们会重点分析PM启动的过程

PackageManagerServer的启动位置

SystemServer启动后,会实例化PackageManagerServer, 具体位置在PackageManagerServer#startBootstrapServices()

mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
                mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);

在这个main()函数中实现很简单,只是实例化一个PMS对象,然后将其加入SystemServer中,而最为重要的过程就是PackageManagerServer的实例化的过程,下面将会重点分析这个过程。

PackageManagerServer 的实例过程

PM相关的目录

  • 系统APK

    • /system/priv-app

    • /system/app

    • /vendor/app

    • /oem/app

  • 用户APK

    • /data/app

PMS配置文件

  • /data/system/packages.xml

    记录系统中所有已经安装的应用信息,包括基本信息,签名和权限。

  • /system/etc/permissions/

    读取当前Android设备的硬件特性和设定的相关权限。

参考:PMS运行时的一些规则

PM初始化过程

//PackageManagerServer.java构造函数
    //build类型,可以在shell下通过getprop来查看全部 
    //使用getprop ro.build.type获取到该build type
    //build type有user,userdebug,eng
    //user:性能好,Log/Debug信息少,相当于是正式版
    //eng:Log/Debug信息相对多
    //userdebug:debug强
    //根据这里的返回,我们猜测返回值和dexopt相关
    mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
    mMetrics = new DisplayMetrics();
    mSettings = new Settings(mPackages);
    mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.log", LOG_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
    mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID,
            ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

上面主要就是新建一个Setting对象,然后调用函数addSharedUserLPw(...),在Setting的构造函数中主要就是为上文说的/data/system/packages.xml等文件的创建和赋权限.现在看看函数addSharedUserLPw()的实现。

   SharedUserSetting addSharedUserLPw(String name, int uid, int pkgFlags, int pkgPrivateFlags) {
        //ArrayMap<String, SharedUserSetting> mSharedUsers
        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;
    }

该函数主要就是共享UID,例如有的系统应用会有

android:sharedUserId="android.uis.system"

而根据上面的设置就是该APK的UID为Process.SYSTEM_UID,从而达到共享系统UID的目的.而下面调用的函数addUserIdLPw(...)就是保存该UID和对应的ShareUserSetting 。接着看PackageMangerService的构造函数的下一部分。

    SystemConfig systemConfig = SystemConfig.getInstance();
    mGlobalGids = systemConfig.getGlobalGids();
    mSystemPermissions = systemConfig.getSystemPermissions();
    mAvailableFeatures = systemConfig.getAvailableFeatures();

SystemConfig在构造函数里对一些目录进行读取,这些目录包括

/system/etc/sysconfig
/system/etc/permissions
/oem/etc/sysconfig
/oem/etc/permissions

然后解析这些目录下面的文件,这些文件的作用是声明当前设备的功能(NFC/WIFI)等.下面我们看看解析函数

    private void readPermissionsFromXml(File permFile, boolean onlyFeatures) {
        FileReader permReader = null;
        try {
            permReader = new FileReader(permFile);
        } catch (FileNotFoundException e) {
            Slog.w(TAG, "Couldn't find or open permissions file " + permFile);
            return;
        }

        final boolean lowRam = ActivityManager.isLowRamDeviceStatic();

        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setInput(permReader);

            int type;
            while ((type=parser.next()) != parser.START_TAG
                       && type != parser.END_DOCUMENT) {
                ;
            }

            if (type != parser.START_TAG) {
                throw new XmlPullParserException("No start tag found");
            }
            //要求根结点为permissions OR config
            if (!parser.getName().equals("permissions") && !parser.getName().equals("config")) {
                throw new XmlPullParserException("Unexpected start tag in " + permFile
                        + ": found " + parser.getName() + ", expected 'permissions' or 'config'");
            }
            //遍历XML,并将不同的功能保存到不同的ArraySet<>中
            while (true) {
                XmlUtils.nextElement(parser);
                if (parser.getEventType() == XmlPullParser.END_DOCUMENT) {
                    break;
                }

                String name = parser.getName();
                if ("group".equals(name) && !onlyFeatures) {
                    String gidStr = parser.getAttributeValue(null, "gid");
                    if (gidStr != null) {
                        int gid = android.os.Process.getGidForName(gidStr);
                        mGlobalGids = appendInt(mGlobalGids, gid);
                    } else {
                        Slog.w(TAG, "<group> without gid in " + permFile + " at "
                                + parser.getPositionDescription());
                    }

                    XmlUtils.skipCurrentTag(parser);
                    continue;
                } else if ("permission".equals(name) && !onlyFeatures) {
                    String perm = parser.getAttributeValue(null, "name");
                    if (perm == null) {
                        Slog.w(TAG, "<permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
                    readPermission(parser, perm);

                } else if ("assign-permission".equals(name) && !onlyFeatures) {
                    String perm = parser.getAttributeValue(null, "name");
                    if (perm == null) {
                        Slog.w(TAG, "<assign-permission> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    String uidStr = parser.getAttributeValue(null, "uid");
                    if (uidStr == null) {
                        Slog.w(TAG, "<assign-permission> without uid in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    int uid = Process.getUidForName(uidStr);
                    if (uid < 0) {
                        Slog.w(TAG, "<assign-permission> with unknown uid \""
                                + uidStr + "  in " + permFile + " at "
                                + parser.getPositionDescription());
                        XmlUtils.skipCurrentTag(parser);
                        continue;
                    }
                    perm = perm.intern();
                    ArraySet<String> perms = mSystemPermissions.get(uid);
                    if (perms == null) {
                        perms = new ArraySet<String>();
                        mSystemPermissions.put(uid, perms);
                    }
                    perms.add(perm);
                    XmlUtils.skipCurrentTag(parser);

                } else if ("library".equals(name) && !onlyFeatures) {
                    String lname = parser.getAttributeValue(null, "name");
                    String lfile = parser.getAttributeValue(null, "file");
                    if (lname == null) {
                        Slog.w(TAG, "<library> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (lfile == null) {
                        Slog.w(TAG, "<library> without file in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        //Log.i(TAG, "Got library " + lname + " in " + lfile);
                        mSharedLibraries.put(lname, lfile);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("feature".equals(name)) {
                    String fname = parser.getAttributeValue(null, "name");
                    boolean allowed;
                    if (!lowRam) {
                        allowed = true;
                    } else {
                        String notLowRam = parser.getAttributeValue(null, "notLowRam");
                        allowed = !"true".equals(notLowRam);
                    }
                    if (fname == null) {
                        Slog.w(TAG, "<feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else if (allowed) {
                        //Log.i(TAG, "Got feature " + fname);
                        FeatureInfo fi = new FeatureInfo();
                        fi.name = fname;
                        mAvailableFeatures.put(fname, fi);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("unavailable-feature".equals(name)) {
                    String fname = parser.getAttributeValue(null, "name");
                    if (fname == null) {
                        Slog.w(TAG, "<unavailable-feature> without name in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mUnavailableFeatures.add(fname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("allow-in-power-save-except-idle".equals(name) && !onlyFeatures) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save-except-idle> without package in "
                                + permFile + " at " + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSaveExceptIdle.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("allow-in-power-save".equals(name) && !onlyFeatures) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<allow-in-power-save> without package in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mAllowInPowerSave.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("fixed-ime-app".equals(name) && !onlyFeatures) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<fixed-ime-app> without package in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mFixedImeApps.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);
                    continue;

                } else if ("app-link".equals(name)) {
                    String pkgname = parser.getAttributeValue(null, "package");
                    if (pkgname == null) {
                        Slog.w(TAG, "<app-link> without package in " + permFile + " at "
                                + parser.getPositionDescription());
                    } else {
                        mLinkedApps.add(pkgname);
                    }
                    XmlUtils.skipCurrentTag(parser);

                } else {
                    XmlUtils.skipCurrentTag(parser);
                    continue;
                }
            }
        } catch (XmlPullParserException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } catch (IOException e) {
            Slog.w(TAG, "Got exception parsing permissions.", e);
        } finally {
            IoUtils.closeQuietly(permReader);
        }

        for (String fname : mUnavailableFeatures) {
            if (mAvailableFeatures.remove(fname) != null) {
                Slog.d(TAG, "Removed unavailable feature " + fname);
            }
        }
    }

上面代码很容易理解,解析xml节点的数据到具体的List或Set中,在PackageManagerService的构造函数中就取出了mGlobalGids/mAvailableFeatures/mSystemPermissions出来,分别对应的TAG节点为

<group gid="" ></group>
<feature></feature>
<assign-permission></assign-permission>

下面接着分析PackageManagerService的构造函数

        mHandlerThread = new ServiceThread(TAG,
                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);  //HandlerThread
        mHandlerThread.start();
        mHandler = new PackageHandler(mHandlerThread.getLooper());
        Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);

        File dataDir = Environment.getDataDirectory(); //dataDir = "/data/"
        mAppDataDir = new File(dataDir, "data");       //  = "/data/data"
        mAppInstallDir = new File(dataDir, "app");     //  = "/data/app"
        mAppLib32InstallDir = new File(dataDir, "app-lib"); // = "/data/app-lib"
        mAsecInternalPath = new File(dataDir, "app-asec").getPath();  // = "data/app-asec"
        mUserAppDataDir = new File(dataDir, "user");   //=  "/data/user"
        mDrmAppPrivateInstallDir = new File(dataDir, "app-private"); // = "/data/app-private"
        sUserManager = new UserManagerService(context, this,
                mInstallLock, mPackages);

        // Propagate permission configuration in to package manager.
        //合并SystemConfig读取的permission到Settings下
        ArrayMap<String, SystemConfig.PermissionEntry> permConfig
                = systemConfig.getPermissions(); //permission标签下的
        for (int i=0; i<permConfig.size(); i++) {
            SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
            BasePermission bp = mSettings.mPermissions.get(perm.name);
            if (bp == null) {
                bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
                mSettings.mPermissions.put(perm.name, bp);
            }
            if (perm.gids != null) {
                bp.setGids(perm.gids, perm.perUser);
            }
        }
        //从SystemConfig中读取的libs存入共享库
        ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
        for (int i=0; i<libConfig.size(); i++) {
            mSharedLibraries.put(libConfig.keyAt(i),
                    new SharedLibraryEntry(libConfig.valueAt(i), null));
        }

        mFoundPolicyFile = SELinuxMMAC.readInstallPolicy();

上面的代码主要就是通过上面的SystemConfig获取到的信息存在在SettingsPMS内部。

       //这里会读取前面说的/data/system/packages.xml文件以及他的备份文件
       //这里特别说明下,会把解析的application存放到mSetting的mPackages中,后面会用到
        mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false),
                mSdkVersion, mOnlyCore);

        String customResolverActivity = Resources.getSystem().getString(
                R.string.config_customResolverActivity);
        if (TextUtils.isEmpty(customResolverActivity)) {
            customResolverActivity = null;
        } else {
            mCustomResolverComponentName = ComponentName.unflattenFromString(
                    customResolverActivity);
        }

        long startTime = SystemClock.uptimeMillis();

        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START,
                startTime);

        // Set flag to monitor and not change apk file paths when
        // scanning install directories.
        final int scanFlags = SCAN_NO_PATHS | SCAN_DEFER_DEX | SCAN_BOOTING | SCAN_INITIAL;
        //已经dexopt的apk存放位置
        final ArraySet<String> alreadyDexOpted = new ArraySet<String>();

        /**
         * Add everything in the in the boot class path to the
         * list of process files because dexopt will have been run
         * if necessary during zygote startup.
         */
        final String bootClassPath = System.getenv("BOOTCLASSPATH");
        final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH");
        //系统库类不要优化  可以通过echo $BOOTCLASSPATH查看
        if (bootClassPath != null) {
            String[] bootClassPathElements = splitString(bootClassPath, ':');
            for (String element : bootClassPathElements) {
                alreadyDexOpted.add(element);
            }
        } else {
            Slog.w(TAG, "No BOOTCLASSPATH found!");
        }

        if (systemServerClassPath != null) {
            String[] systemServerClassPathElements = splitString(systemServerClassPath, ':');
            for (String element : systemServerClassPathElements) {
                alreadyDexOpted.add(element);
            }
        } else {
            Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!");
        }

上面代码开始时解析packages.xml,该文件保存了系统内安装了的APK的信息,然后就是添加一些库类到alreadyDexOpted这个List里面,目的是以后做dexopt的时候跳过这些不必要的优化。这里说明一下packages.xml中的字段的保存位置(以下是在Settings.java中)。

package  -> mPackages(readPackageLPw()-->addPackageLPw()) //重点

permissions ->  mPermissions(readPermissionsLPw())

permission-trees -> mPermissionTrees(readPermissionsLPw())

shared-user -> mSharedUsers(readSharedUserLPw()-->addSharedUserLPw())

updated-package -> mDisabledSysPackages(readDisabledSysPackageLPw()) //这个标签是在OTA中添加的?删除也会有这个标记?

renamed-package -> mRenamedPackages

这里强调一下,会把package的信息存在Settings.mPackages中,并根据package标签下的installStatus字段判断app安装的状态,这在后面有用到。下面继续看PMS的构造函数。

        //通过命令getprop ro.product.cpu.abilist查看设备支持的指令集
        final List<String> allInstructionSets = InstructionSets.getAllInstructionSets();
        final String[] dexCodeInstructionSets =
                getDexCodeInstructionSets(
                        allInstructionSets.toArray(new String[allInstructionSets.size()]));

        /**
         * Ensure all external libraries have had dexopt run on them.
         */
        if (mSharedLibraries.size() > 0) {
            // NOTE: For now, we're compiling these system "shared libraries"
            // (and framework jars) into all available architectures. It's possible
            // to compile them only when we come across an app that uses them (there's
            // already logic for that in scanPackageLI) but that adds some complexity.
            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                for (SharedLibraryEntry libEntry : mSharedLibraries.values()) {
                    final String lib = libEntry.path;
                    if (lib == null) {
                        continue;
                    }

                    try {
                        int dexoptNeeded = DexFile.getDexOptNeeded(lib, null, dexCodeInstructionSet, false);
                        if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                            alreadyDexOpted.add(lib);
                            mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
                        }
                    } catch (FileNotFoundException e) {
                        Slog.w(TAG, "Library not found: " + lib);
                    } catch (IOException e) {
                        Slog.w(TAG, "Cannot dexopt " + lib + "; is it an APK or JAR? "
                                + e.getMessage());
                    }
                }
            }
        }

这段代码主要就是执行dexopt的过程,并将优化过的apk/jar放入alreadyDexOpted,这里mInstaller内部通过LocalStock发送一条指令dexopt apkpath ....,让LocalStock的服务端处理这个请求,我没有找到服务端的实现位置....但不影响我们继续阅读。

        File frameworkDir = new File(Environment.getRootDirectory(), "framework");

        // Gross hack for now: we know this file doesn't contain any
        // code, so don't dexopt it to avoid the resulting log spew.
        alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk");

        // Gross hack for now: we know this file is only part of
        // the boot class path for art, so don't dexopt it to
        // avoid the resulting log spew.
        alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar");

        /**
         * There are a number of commands implemented in Java, which
         * we currently need to do the dexopt on so that they can be
         * run from a non-root shell.
         */
        String[] frameworkFiles = frameworkDir.list();
        if (frameworkFiles != null) {
            // TODO: We could compile these only for the most preferred ABI. We should
            // first double check that the dex files for these commands are not referenced
            // by other system apps.
            for (String dexCodeInstructionSet : dexCodeInstructionSets) {
                for (int i=0; i<frameworkFiles.length; i++) {
                    File libPath = new File(frameworkDir, frameworkFiles[i]);
                    String path = libPath.getPath();
                    // Skip the file if we already did it.
                    if (alreadyDexOpted.contains(path)) {
                        continue;
                    }
                    // Skip the file if it is not a type we want to dexopt.
                    if (!path.endsWith(".apk") && !path.endsWith(".jar")) {
                        continue;
                    }
                    try {
                        int dexoptNeeded = DexFile.getDexOptNeeded(path, null, dexCodeInstructionSet, false);
                        if (dexoptNeeded != DexFile.NO_DEXOPT_NEEDED) {
                            mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded);
                        }
                    } catch (FileNotFoundException e) {
                        Slog.w(TAG, "Jar not found: " + path);
                    } catch (IOException e) {
                        Slog.w(TAG, "Exception reading jar: " + path, e);
                    }
                }
            }
        }

这段代码和上面的实现几乎一样,读取/system/framework/下的几个目录的jar包和apk,判断是否需要进行优化,值得注意的是并没有加入列表alreadyDexOpted

        final VersionInfo ver = mSettings.getInternalVersion();
        mIsUpgrade = !Build.FINGERPRINT.equals(ver.fingerprint);
        // when upgrading from pre-M, promote system app permissions from install to runtime  运行时权限
        mPromoteSystemApps =
                mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;

        // save off the names of pre-existing system packages prior to scanning; we don't
        // want to automatically grant runtime permissions for new system apps
        if (mPromoteSystemApps) {
            Iterator<PackageSetting> pkgSettingIter = mSettings.mPackages.values().iterator();
            while (pkgSettingIter.hasNext()) {
                PackageSetting ps = pkgSettingIter.next();
                if (isSystemApp(ps)) {
                    mExistingSystemPackages.add(ps.name);
                }
            }
        }

当系统有LOLLIPOP_MR1一下升级时到LOLLIPOP_MR1以上时,android在6.0引入动态权限机制,在这里也就是mPromoteSystemApps=true,会执行下面一段代码。

        // Collect vendor overlay packages.
        // (Do this before scanning any apps.)
        // For security and version matching reason, only consider
        // overlay packages if they reside in VENDOR_OVERLAY_DIR.
        File vendorOverlayDir = new File(VENDOR_OVERLAY_DIR);// = "/vendor/overlay"
        scanDirLI(vendorOverlayDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags | SCAN_TRUSTED_OVERLAY, 0);

        // Find base frameworks (resource packages without code).
        scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM   //system/framework
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED,
                scanFlags | SCAN_NO_DEX, 0);

        // Collected privileged system packages.
        final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
        scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR
                | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);

        // Collect ordinary system packages.
        final File systemAppDir = new File(Environment.getRootDirectory(), "app");
        scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

        // Collect all vendor packages.
        File vendorAppDir = new File("/vendor/app");
        try {
            vendorAppDir = vendorAppDir.getCanonicalFile();
        } catch (IOException e) {
            // failed to look up canonical path, continue with original one
        }
        scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

        // Collect all OEM packages.
        final File oemAppDir = new File(Environment.getOemDirectory(), "app");
        scanDirLI(oemAppDir, PackageParser.PARSE_IS_SYSTEM
                | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

        if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands");
        mInstaller.moveFiles(); //执行LocalStock发送movefiles命令

上面的代码的逻辑就是扫描指定的目录,这里的目录包括下面这些

/vendor/overlay
/system/framework
/system/priv-app
/system/app
/vendor/app
/oem/app

也就是我们上文提到的系统APK的存放目录,扫描结束之后会把apk信息存放在mPackages(这里是PMS,区别于SettingsmPackages)。下面我们来分析scanDirLI()的过程。

    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
        final File[] files = dir.listFiles();
        if (ArrayUtils.isEmpty(files)) {
            Log.d(TAG, "No files in app dir " + dir);
            return;
        }

        if (DEBUG_PACKAGE_SCANNING) {
            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
                    + " flags=0x" + Integer.toHexString(parseFlags));
        }
        //遍历该目录下的APK
        for (File file : files) {
            final boolean isPackage = (isApkFile(file) || file.isDirectory())
                    && !PackageInstallerService.isStageName(file.getName());
            if (!isPackage) {
                // Ignore entries which are not packages
                continue;
            }
            try {
                scanPackageLI(file, parseFlags | PackageParser.PARSE_MUST_BE_APK,
                        scanFlags, currentTime, null);
            } catch (PackageManagerException e) {
                Slog.w(TAG, "Failed to parse " + file + ": " + e.getMessage());

                // Delete invalid userdata apps
                // 删除无效的用户APK
                if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
                        e.error == PackageManager.INSTALL_FAILED_INVALID_APK) {
                    logCriticalInfo(Log.WARN, "Deleting invalid package at " + file);
                    if (file.isDirectory()) {
                        mInstaller.rmPackageDir(file.getAbsolutePath());
                    } else {
                        file.delete();
                    }
                }
            }
        }
    }

上面代码核心就是遍历指定的文件夹,对文件夹内部的文件执行函数scanPackageLI(File,...) ,通过其注释我们了解到他是扫描包的。它的代码也比较长,下面选择其中一部分说明。

    private PackageParser.Package scanPackageLI(File scanFile,  
            int parseFlags, int scanMode, long currentTime) {  
        ......  
  
        String scanPath = scanFile.getPath();  
        parseFlags |= mDefParseFlags;  
        PackageParser pp = new PackageParser();  
          
        ......  
  
        final PackageParser.Package pkg = pp.parsePackage(scanFile,  
            scanPath, mMetrics, parseFlags);  
  
        ......  
  
        return scanPackageLI(pkg, parseFlags, scanMode | SCAN_UPDATE_SIGNATURE, currentTime);  
    }  

为指定的文件创建PackageParser,将解析结果存入Package ,最后在调用函数scanPackageLI(Package,...)。而在函数PackageParser.Package.parsePackage(...)中会判断scanFile是文件还是目录(Android分包),会对他们做不同的处理,我们这里简单点,就看是文件的分支,当时文件时,会调用函数parseMonolithicPackage(packageFile, flags) ,下面分析这个函数。

   public Package parseMonolithicPackage(File apkFile, int flags){ 
      final AssetManager assets = new AssetManager();
      final Package pkg = parseBaseApk(apkFile, assets, flags);
      pkg.codePath = apkFile.getAbsolutePath();
      return pkg;
   }  

这里的核心就是函数parseBaseApk(File,...),根据名称感觉有些明朗了,不就解析APK嘛,看看到底是怎么实现的吧。

   private Package parseBaseApk(File apkFile, AssetManager assets, int flags){
        ....
        Resources res = null;
        XmlResourceParser parser = null;
        res = new Resources(assets, mMetrics, null);
        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                    Build.VERSION.RESOURCES_SDK_INT);
        parser = assets.openXmlResourceParser(cookie, "AndroidManifest.xml");
        final String[] outError = new String[1];
        final Package pkg = parseBaseApk(res, parser, flags, outError);   
        ...
        return pkg;  
   }

卧槽,又是圈套,有调函数parseBaseApk(Resources,...)来解析,不过上面我们已经看见关键的AndroidManifest.xml已经出现了。通过阅读parseBaseApk(Resources,...),我们发现他会解析AndroidManifest.xml中的一部分文件,这里大体包括以下标签

- application
- overlay
- key-sets
- permission-group
- permission-tree
- uses-permission
- uses-permission-sdk-m | uses-permission-sdk-23
- uses-configuration
- uses-feature
- feature-group
- uses-sdk
- supports-screens
- protected-broadcast
- instrumentation
- original-package
- adopt-permissions
- uses-gl-texture
- compatible-screens
- supports-input
- eat-comment

惭愧,好多标签没见过,查看官网发现官网并没有列举以上全部AndroidManifest 。我们这里继续跟进application标签,发现他调用函数parseBaseApplication()。这个函数就是对Application内部四大组件进行解析。我们这里选取activity的部分来看看。

            // 函数参数 Package owner
            if (tagName.equals("activity")) {
                //class Activity extends Component<ActivityIntentInfo>
                Activity a = parseActivity(owner, res, parser, attrs, flags, outError, false,
                        owner.baseHardwareAccelerated);
                if (a == null) {
                    mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
                    return false;
                }

                owner.activities.add(a);
            }

parseActivity()就是解析activity标签下的内容,比如ActivityTheme什么的,解析的过程主要是利用TypedArray,具体的属性可以看看Activity,很多属性都是存储在一个flags标记了,这样减少了类中成员字段过多,这在Android中使用的比较多,比如View中很多属性也是存在一个flags中。到此一个解析好了的Package就好了,不知不觉,已经偏了十万八千里,不要急,拉回来,上面我们讲到scanPackageLI(File,...)的最后调用了scanPackageLI(Package,...),那么这个函数有是做什么的呢?这个函数调用了scanPackageDirtyLI(),这个函数的代码量也是相当吓人,这里不打算具体分析,主要工作就是为app创建目录,也就是/data/data/apk.name/这个目录,还有就是APK对应的libs的存放位置,App签名验证,收集APK要的权限,最重要的就是把解析信息存放到了PMSmPackages变量中,意味着App安装成功了,后面会用到这个变量。回到PackageManagerService的构造函数中来。

        // Prune any system packages that no longer exist.
        final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<String>();
        if (!mOnlyCore) {
            //mSettings.mPackages来自与package.xml的package标签
            Iterator<PackageSetting> psit = mSettings.mPackages.values().iterator();
            while (psit.hasNext()) {
                PackageSetting ps = psit.next();

                /*
                 * If this is not a system app, it can't be a
                 * disable system app.
                 */
                if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
                    continue;
                }

                /*
                 * If the package is scanned, it's not erased.
                 */
                //PMS的mPackages存放扫描过的APK
                final PackageParser.Package scannedPkg = mPackages.get(ps.name);
                //扫描到了 && packages.xml中存在
                if (scannedPkg != null) {
                    /*
                     * If the system app is both scanned and in the
                     * disabled packages list, then it must have been
                     * added via OTA. Remove it from the currently
                     * scanned package so the previously user-installed
                     * application can be scanned.
                     */
                    //package.xml的package标签和updated-package标签都包含这个pkg
                    //根据上面注释,意味着这个APK是通过OTA添加的,暂时移除
                    if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                        logCriticalInfo(Log.WARN, "Expecting better updated system app for "
                                + ps.name + "; removing system app.  Last known codePath="
                                + ps.codePathString + ", installStatus=" + ps.installStatus
                                + ", versionCode=" + ps.versionCode + "; scanned versionCode="
                                + scannedPkg.mVersionCode);
                        removePackageLI(ps, true);//mPackages.remove(ps.name);
                        mExpectingBetter.put(ps.name, ps.codePath);
                    }

                    continue;
                }
                //没有扫描到,在package标签下,但不在updated-package标签下,说明该APP已经不存在了
                //因此要删掉他的目录
                //直接从mSettings.mPackages中移除
                if (!mSettings.isDisabledSystemPackageLPr(ps.name)) {
                    psit.remove(); 
                    logCriticalInfo(Log.WARN, "System package " + ps.name
                            + " no longer exists; wiping its data");
                    removeDataDirsLI(null, ps.name);
                } else {
                    //没有扫描到,在package标签下,也在updated-package标签下
                    //可能由OTA引入(或删除?)
                    final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name);
                    if (disabledPs.codePath == null || !disabledPs.codePath.exists()) {
                        possiblyDeletedUpdatedSystemApps.add(ps.name);
                    }
                }
            }
        }

处理被用户隐藏的APP(前面讲的pm hide package),因为被隐藏的APP在package.xml还存在,这里就是把这些APP从保存他们的列表中移除。另外就是在package.xml中还有该APK,但是扫描系统目录发现这个APK已经不存在了的处理方式。执行完之后mSetting.mPackages剩下的就是无效的APK,我们需要将这些清除,于是就有了下面的几行代码

        //look for any incomplete package installations
        ArrayList<PackageSetting> deletePkgsList = mSettings.getListOfIncompleteInstallPackagesLPr();//获取mSettings.mPackages中installStatus为未成功安装的App(前文有讲,package中installStatus=false的apk)
        //clean up list
        for(int i = 0; i < deletePkgsList.size(); i++) {
            //clean up here
            cleanupInstallFailedPackage(deletePkgsList.get(i));
        }
        //delete tmp files
        deleteTempPackageFiles();

        // Remove any shared userIDs that have no associated packages
        mSettings.pruneSharedUsersLPw();//移除没有被关联的mSharedUsers

上面的作用就是清除无效APK引入的文件夹等。系统APK装载完了,下面就开始装载用户APK,

            scanDirLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);
            scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK,
                    scanFlags | SCAN_REQUIRE_KNOWN, 0);

上面的代码和前面扫描系统APK是一样的,这是目录和flags变了,逻辑是一样的。这里的目录包括

/data/app
/data/app-private

继续看构造函数。

            /**
             * Remove disable package settings for any updated system
             * apps that were removed via an OTA. If they're not a
             * previously-updated app, remove them completely.
             * Otherwise, just revoke their system-level permissions.
             */
            //在引进了用户app之后mPackages内容增加了,再看看是否有这些app
            for (String deletedAppName : possiblyDeletedUpdatedSystemApps) {
                PackageParser.Package deletedPkg = mPackages.get(deletedAppName);
                mSettings.removeDisabledSystemPackageLPw(deletedAppName);

                String msg;
                if (deletedPkg == null) {  //OTA删除
                    msg = "Updated system package " + deletedAppName
                            + " no longer exists; wiping its data";
                    removeDataDirsLI(null, deletedAppName);
                } else {    //在用户app中找到了,当然会移除系统包标识
                    msg = "Updated system app + " + deletedAppName
                            + " no longer present; removing system privileges for "
                            + deletedAppName;

                    deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;

                    PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName);
                    deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
                }
                logCriticalInfo(Log.WARN, msg);
            }

            /**
             * Make sure all system apps that we expected to appear on
             * the userdata partition actually showed up. If they never
             * appeared, crawl back and revive the system version.
             */
            for (int i = 0; i < mExpectingBetter.size(); i++) {  //有新包,更新APK
                final String packageName = mExpectingBetter.keyAt(i);
                if (!mPackages.containsKey(packageName)) {
                    final File scanFile = mExpectingBetter.valueAt(i);

                    logCriticalInfo(Log.WARN, "Expected better " + packageName
                            + " but never showed up; reverting to system");

                    final int reparseFlags;
                    //不同目录flags不一样
                    if (FileUtils.contains(privilegedAppDir, scanFile)) {
                        reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                | PackageParser.PARSE_IS_SYSTEM_DIR
                                | PackageParser.PARSE_IS_PRIVILEGED;
                    } else if (FileUtils.contains(systemAppDir, scanFile)) {
                        reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                | PackageParser.PARSE_IS_SYSTEM_DIR;
                    } else if (FileUtils.contains(vendorAppDir, scanFile)) {
                        reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                | PackageParser.PARSE_IS_SYSTEM_DIR;
                    } else if (FileUtils.contains(oemAppDir, scanFile)) {
                        reparseFlags = PackageParser.PARSE_IS_SYSTEM
                                | PackageParser.PARSE_IS_SYSTEM_DIR;
                    } else {
                        Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                        continue;
                    }
                    //加入mStting.mPackages   
                    mSettings.enableSystemPackageLPw(packageName);

                    try {
                        scanPackageLI(scanFile, reparseFlags, scanFlags, 0, null);
                    } catch (PackageManagerException e) {
                        Slog.e(TAG, "Failed to parse original system package: "
                                + e.getMessage());
                    }
                }
            }
            mExpectingBetter.clear();

上面这段代码就是删除被OTA移除app的目录,更新新引入的App的目录。

        // Now that we know all of the shared libraries, update all clients to have
        // the correct library paths.
        updateAllSharedLibrariesLPw(); //为需要sharelibs的apk关联libs,放在pkg.usesLibraryFiles

        for (SharedUserSetting setting : mSettings.getAllSharedUsersLPw()) {
            // NOTE: We ignore potential failures here during a system scan (like
            // the rest of the commands above) because there's precious little we
            // can do about it. A settings error is reported, though.
            adjustCpuAbisForSharedUserLPw(setting.packages, null /* scanned package */,
                    false /* force dexopt */, false /* defer dexopt */);
        }

        // Now that we know all the packages we are keeping,
        // read and update their last usage times.
        mPackageUsage.readLP();//读/data/system/package-usage.list

        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END,
                SystemClock.uptimeMillis());
        Slog.i(TAG, "Time to scan packages: "
                + ((SystemClock.uptimeMillis()-startTime)/1000f)
                + " seconds");

        // If the platform SDK has changed since the last time we booted,
        // we need to re-grant app permission to catch any new ones that
        // appear.  This is really a hack, and means that apps can in some
        // cases get permissions that the user didn't initially explicitly
        // allow...  it would be nice to have some better way to handle
        // this situation.
        int updateFlags = UPDATE_PERMISSIONS_ALL;
        if (ver.sdkVersion != mSdkVersion) {
            Slog.i(TAG, "Platform changed from " + ver.sdkVersion + " to "
                    + mSdkVersion + "; regranting permissions for internal storage");
            updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
        }
        updatePermissionsLPw(null, null, updateFlags);//Apk分配权限
        ver.sdkVersion = mSdkVersion;
        // clear only after permissions have been updated
        mExistingSystemPackages.clear();
        mPromoteSystemApps = false;

        // If this is the first boot, and it is a normal boot, then
        // we need to initialize the default preferred apps.
        //第一次启动,初始化默认程序,如浏览器,email程序
        if (!mRestoredSettings && !onlyCore) {
            mSettings.applyDefaultPreferredAppsLPw(this, UserHandle.USER_OWNER);
            applyFactoryDefaultBrowserLPw(UserHandle.USER_OWNER);
            primeDomainVerificationsLPw(UserHandle.USER_OWNER);
        }

        // If this is first boot after an OTA, and a normal boot, then
        // we need to clear code cache directories.
        if (mIsUpgrade && !onlyCore) {
            Slog.i(TAG, "Build fingerprint changed; clearing code caches");
            for (int i = 0; i < mSettings.mPackages.size(); i++) {
                final PackageSetting ps = mSettings.mPackages.valueAt(i);
                if (Objects.equals(StorageManager.UUID_PRIVATE_INTERNAL, ps.volumeUuid)) {
                    deleteCodeCacheDirsLI(ps.volumeUuid, ps.name);
                }
            }
            ver.fingerprint = Build.FINGERPRINT;
        }

        checkDefaultBrowser();

        // All the changes are done during package scanning.
        ver.databaseVersion = Settings.CURRENT_DATABASE_VERSION;

        // can downgrade to reader
        mSettings.writeLPr(); //写package.xml

        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_READY,
                SystemClock.uptimeMillis());

        mRequiredVerifierPackage = getRequiredVerifierLPr(); //string
        mRequiredInstallerPackage = getRequiredInstallerLPr(); //string

        mInstallerService = new PackageInstallerService(context, this); //根据名字知道大概是app安装相关服务

        mIntentFilterVerifierComponent = getIntentFilterVerifierComponentNameLPr();
        mIntentFilterVerifier = new IntentVerifierProxy(mContext,
                mIntentFilterVerifierComponent);

    } // synchronized (mPackages)
    } // synchronized (mInstallLock)

    // Now after opening every single application zip, make sure they
    // are all flushed.  Not really needed, but keeps things nice and
    // tidy.
    Runtime.getRuntime().gc();

    // Expose private service for system components to use.
    LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
}

到此,PMS的构造函数就阅读完毕了。

总结

本文由pm命令引入,讲解PMS的构造过程,这里在梳理一下这个过程。

  1. 通过packages.xml(backup)读取系统已经安装了的app,保存到mSettings的相关字段中去。
  2. 通过扫描系统目录/用户app目录,将扫描的app保存到PMSmPackages中去。
  3. 根据上两步的结果判断哪些app无效(删除),哪些被OTA的方式更新,删除或重建对应app的目录
  4. app扫描结束,app权限分配,app引用的库的关联
  5. 重新保存这些新信息到packages.xml供下次开机使用

参考

广告

关注我的微信公众号,及时获得博文推送
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容