博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Framework 核心服务之 PackageManagerService 钻研(2)- 构造函数
阅读量:6221 次
发布时间:2019-06-21

本文共 27930 字,大约阅读时间需要 93 分钟。

开篇

PackageManagerService 系列文章如下(基于 Android 9.0 源码)

         ?                             
         ?   
         ?   
         ?   
         ?   
         ?   
         ?   

核心源码

关键类 路径
SystemServer.java frameworks/base/services/java/com/android/server/SystemServer.java
PackageManagerService.java frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java
Process.java frameworks/base/core/java/android/os/Process.java
SystemConfig.java frameworks/base/core/java/com/android/server/SystemConfig.java
Settings.java frameworks/base/services/core/java/com/android/server/pm/Settings.java

简介

PackageManagerService(PMS)是 SystemServer 启动后的第一个核心服务,也是 Android 系统中最常用的服务之一。它负责系统中 Package 的管理,应用程序的安装、卸载、信息查询等。如果你是面向 Android 系统开发的工程师,基础概念我也不需要再多赘述,我们直接跟源码。

构造函数分析 - 扫描Package

PMS 构造函数第二阶段的工作就是扫描系统中的 APK 了。由于需要逐个扫描文件,因此手机上装的程序越多,PMS 的工作量就越大,系统启动速度也就越慢,这就是为什么你的手机启动速度有快慢的原因。

系统库的 dex 优化

接着上面的 PMS 构造函数继续分析源码:

public PackageManagerService(Context context, Installer installer,            boolean factoryTest, boolean onlyCore) {        ... ...        // DEX 优化        mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,                "*dexopt*");        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);                synchronized (mPackages) {            mHandlerThread = new ServiceThread(TAG,                    Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);            mHandlerThread.start();            mHandler = new PackageHandler(mHandlerThread.getLooper());            mProcessLoggingHandler = new ProcessLoggingHandler();            Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);            mInstantAppRegistry = new InstantAppRegistry(this);            // 为 data/ 目录下的某些子目录生成File实例            File dataDir = Environment.getDataDirectory();            // data/app 存放第三方应用            mAppInstallDir = new File(dataDir, "app");            mAppLib32InstallDir = new File(dataDir, "app-lib");            mAsecInternalPath = new File(dataDir, "app-asec").getPath();            // data/app-private 存放 drm 保护的应用            mDrmAppPrivateInstallDir = new File(dataDir, "app-private");            sUserManager = new UserManagerService(context, this,                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);            // 获取 SystemConfig 中解析到的 
标签标识的 permission 信息,保存到 Settings::mPermissions ArrayMap
permConfig = systemConfig.getPermissions(); for (int i=0; i
libConfig = systemConfig.getSharedLibraries(); final int builtInLibCount = libConfig.size(); for (int i = 0; i < builtInLibCount; i++) { String name = libConfig.keyAt(i); String path = libConfig.valueAt(i); addSharedLibraryLPw(path, null, name, SharedLibraryInfo.VERSION_UNDEFINED, SharedLibraryInfo.TYPE_BUILTIN, PLATFORM_PACKAGE_NAME, 0); } mFoundPolicyFile = SELinuxMMAC.readInstallPolicy(); // 读取packages.xml的内容,并对mSettings::mPackages等成员进行赋值;packages.xml文件中的内容是上一次扫描apk目录的结果; // 当前这一次扫描的结果是保存在PackageManagerService::mPackages列表中; // 对比上次扫描的结果来检查本次扫描到的应用中是否有被升级包覆盖的系统应用,如果有则从PackageManagerService::mPackages中移除; // 这样,PackageManagerService::mPackages的记录就和mSettings::mPackages的一致了; // 系统最终会将本次apk扫描的结果重新写入packages.xml中 mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false)); // Clean up orphaned packages for which the code path doesn't exist // and they are an update to a system app - caused by bug/32321269 final int packageSettingCount = mSettings.mPackages.size(); // 清理那些代码路径不存在的异常 package for (int i = packageSettingCount - 1; i >= 0; i--) { PackageSetting ps = mSettings.mPackages.valueAt(i); if (!isExternal(ps) && (ps.codePath == null || !ps.codePath.exists()) && mSettings.getDisabledSystemPkgLPr(ps.name) != null) { mSettings.mPackages.removeAt(i); mSettings.enableSystemPackageLPw(ps.name); } } if (mFirstBoot) { requestCopyPreoptedFiles(); } // 设置模块来代替 framework-res.apk 中缺省的 ResolverActivity String customResolverActivity = Resources.getSystem().getString( R.string.config_customResolverActivity); if (TextUtils.isEmpty(customResolverActivity)) { customResolverActivity = null; } else { mCustomResolverComponentName = ComponentName.unflattenFromString( customResolverActivity); } long startTime = SystemClock.uptimeMillis(); // 记录扫描开始的时间 // 需要系统提前加载的一些 jar final String bootClassPath = System.getenv("BOOTCLASSPATH"); final String systemServerClassPath = System.getenv("SYSTEMSERVERCLASSPATH"); if (bootClassPath == null) { Slog.w(TAG, "No BOOTCLASSPATH found!"); } if (systemServerClassPath == null) { Slog.w(TAG, "No SYSTEMSERVERCLASSPATH found!"); }

扫描系统 Package

清空 cache 文件后,PMS 终于进入重点段了。接下来看 PMS 第二阶段工作的核心内容,即扫描 Package,相关代码如下:

// PackageManagerService.java            // 定义 frameworkDir 指向 /system/frameworks 目录            File frameworkDir = new File(Environment.getRootDirectory(), "framework");            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;            // When upgrading from pre-N, we need to handle package extraction like first boot,            // as there is no profiling data available.            mIsPreNUpgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N;            mIsPreNMR1Upgrade = mIsUpgrade && ver.sdkVersion < Build.VERSION_CODES.N_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
pkgSettingIter = mSettings.mPackages.values().iterator(); while (pkgSettingIter.hasNext()) { PackageSetting ps = pkgSettingIter.next(); if (isSystemApp(ps)) { // 遍历Settings::mPackages集合,将系统APP加入到PackageManagerService::mExistingSystemPackages mExistingSystemPackages.add(ps.name); } } } mCacheDir = preparePackageParserCache(mIsUpgrade); // 定义扫描参数 int scanFlags = SCAN_BOOTING | SCAN_INITIAL; if (mIsUpgrade || mFirstBoot) { scanFlags = scanFlags | SCAN_FIRST_BOOT_OR_UPGRADE; } // 先扫描 /vendor/overlay 目录 scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0); // 调用 scanDirTracedLI 函数扫描 /system/frameworks 目录 scanDirTracedLI(frameworkDir, mDefParseFlags | PackageParser.PARSE_IS_SYSTEM | 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"); // 扫描 /system/priv-app 下的 package scanDirTracedLI(privilegedAppDir, mDefParseFlags | 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"); // 扫描 /system/app 下的 package scanDirTracedLI(systemAppDir, mDefParseFlags | 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 } // 扫描 /vendor/app 下的 package scanDirTracedLI(vendorAppDir, mDefParseFlags | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0); // Collect all OEM packages. final File oemAppDir = new File(Environment.getOemDirectory(), "app"); // 扫描 OEM 的 Package scanDirTracedLI(oemAppDir, mDefParseFlags | PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

由以上代码可知,PMS 将扫描以下几个目录(仅列出重点):

         ✨ /system/frameworks :该目录中的文件都是系统库,例如:framework.jar、services.jar、framework-res.apk。不过 scanDirTracedLI 只扫描 APK 文件,所以 framework-res.apk 是该目录中唯一“受宠”的文件。

         ✨ /system/app :该目录下全是默认的系统应用。例如:Browser.apk、SettingsProvider.apk 等。

         ✨ /vendor/app :该目录中的文件由厂商提供,即全是厂商特定的 APK 文件,目前市面上的厂商都把自己的应用放在 /system/app 目录下。

scanDirTracedLI

PMS 调用 scanDirTracedLI 函数进行扫描,下面分析此函数:

public void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {        try {            scanDirLI(dir, parseFlags, scanFlags, currentTime);  // 调用 scanDirLI 函数        } finally {            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);        }    }

我们可以看出,实际是调用了 scanDirLI 函数进行扫描工作!

scanDirLI

下面的重点就是来关注 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;        }        ParallelPackageParser parallelPackageParser = new ParallelPackageParser(                mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,                mParallelPackageParserCallback);        int fileCount = 0;        for (File file : files) {            final boolean isPackage = (isApkFile(file) || file.isDirectory())                    && !PackageInstallerService.isStageName(file.getName());            if (!isPackage) {                // Ignore entries which are not packages                continue;            }            parallelPackageParser.submit(file, parseFlags);            fileCount++;        }        // Process results one by one        for (; fileCount > 0; fileCount--) {            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();            Throwable throwable = parseResult.throwable;            int errorCode = PackageManager.INSTALL_SUCCEEDED;            if (throwable == null) {                // Static shared libraries have synthetic package names                if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {                    renameStaticSharedLibraryPackage(parseResult.pkg);                }                try {                    if (errorCode == PackageManager.INSTALL_SUCCEEDED) {                        // 调用 scanPackageLI 函数扫描一个特定的文件,返回值是 PackageParser 的内部类 Package,该类的实例代表一个 APK 文件,所以它就是和 APK 文件对应的数据结构                        scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,                                currentTime, null);                    }                ... ...            }            // Delete invalid userdata apps            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&                    errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {                // 非系统 Package 扫描失败,删除文件                removeCodePathLI(parseResult.scanFile);            }        }        parallelPackageParser.close();    }

scanPackageLI - 01

PMS 中有三处 scanPackageLI,我们后面会一一分析到,先来看第一个也是最先碰到的 sanPackageLI 函数。

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,            final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)            throws PackageManagerException {        if ((scanFlags & SCAN_CHECK_ONLY) == 0) {            if (pkg.childPackages != null && pkg.childPackages.size() > 0) {                scanFlags |= SCAN_CHECK_ONLY;            }        } else {            scanFlags &= ~SCAN_CHECK_ONLY;        }        // Scan the parent        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,                scanFlags, currentTime, user);        // Scan the children        final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;        for (int i = 0; i < childCount; i++) {            PackageParser.Package childPackage = pkg.childPackages.get(i);            scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,                    currentTime, user);        }        if ((scanFlags & SCAN_CHECK_ONLY) != 0) {            return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);        }        return scannedPkg;    }

scanPackageInternalLI

调用 scanPackageInternalLI():

private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,            int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)            throws PackageManagerException {        PackageSetting ps = null;        PackageSetting updatedPkg;        // 判断系统 APP 是否需要更新        synchronized (mPackages) {            // 查看是否已经有该安装包,通过 mSetting 查找            String oldName = mSettings.getRenamedPackageLPr(pkg.packageName);            if (pkg.mOriginalPackages != null && pkg.mOriginalPackages.contains(oldName)) {                // 如果存在同一个包名的老的安装包,且已经改回原始名称了                ps = mSettings.getPackageLPr(oldName);            }            // 如果没有原始包,则使用真实包名            if (ps == null) {                ps = mSettings.getPackageLPr(pkg.packageName);            }            // 查这个包是否是一个隐藏或者可以更新的系统包            updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);            // If this is a package we don't know about on the system partition, we            // may need to remove disabled child packages on the system partition            // or may need to not add child packages if the parent apk is updated            // on the data partition and no longer defines this child package.            if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {                // If this is a parent package for an updated system app and this system                // app got an OTA update which no longer defines some of the child packages                // we have to prune them from the disabled system packages.                PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);                if (disabledPs != null) {                    final int scannedChildCount = (pkg.childPackages != null)                            ? pkg.childPackages.size() : 0;                    final int disabledChildCount = disabledPs.childPackageNames != null                            ? disabledPs.childPackageNames.size() : 0;                    for (int i = 0; i < disabledChildCount; i++) {                        String disabledChildPackageName = disabledPs.childPackageNames.get(i);                        boolean disabledPackageAvailable = false;                        for (int j = 0; j < scannedChildCount; j++) {                            PackageParser.Package childPkg = pkg.childPackages.get(j);                            if (childPkg.packageName.equals(disabledChildPackageName)) {                                disabledPackageAvailable = true;                                break;                            }                         }                         if (!disabledPackageAvailable) {                             mSettings.removeDisabledSystemPackageLPw(disabledChildPackageName);                         }                    }                }            }        }        ... ...                // Note that we invoke the following method only if we are about to unpack an application        // 调用第二个 scanPackageLI 函数        PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags                | SCAN_UPDATE_SIGNATURE, currentTime, user);        ... ...    }

scanPackageLI - 02

private PackageParser.Package scanPackageLI(File scanFile, int parseFlags, int scanFlags,            long currentTime, UserHandle user) throws PackageManagerException {        // 创建一个 PackageParser 对象,用于解析包        PackageParser pp = new PackageParser();        // 设置 PackageParse 的三个属性        pp.setSeparateProcesses(mSeparateProcesses);        pp.setOnlyCoreApps(mOnlyCore);        pp.setDisplayMetrics(mMetrics);        pp.setCallback(mPackageParserCallback);        // 判断扫描模式        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;        }        // 解析APK获取对应PackageParser.Package对象 pkg        final PackageParser.Package pkg;        // 调用 PackageParser 的 parsePackage 函数解析 APK 文件        try {            // ? ? ? ? ? ? 真正的解析            pkg = pp.parsePackage(scanFile, parseFlags);        } catch (PackageParserException e) {            throw PackageManagerException.from(e);        } finally {            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);        }        // Static shared libraries have synthetic package names        if (pkg.applicationInfo.isStaticSharedLibrary()) {            renameStaticSharedLibraryPackage(pkg);        }        return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);    }

扫描非系统 Package

if (!mOnlyCore) {   // mOnlyCore 用于控制是否扫描非系统 Package                // do this first before mucking with mPackages for the "expecting better" case                final Iterator
pkgIterator = mPackages.values().iterator(); while (pkgIterator.hasNext()) { final PackageParser.Package pkg = pkgIterator.next(); if (pkg.isStub) { stubSystemApps.add(pkg.packageName); } } final Iterator
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) { /// M: Operator apps are belong to system domain, therefore, need prune. /// We should also consider OTA from old version without mtkFlag if (sPmsExt.isNotOperatorApp(ps)) continue; } /* * If the package is scanned, it's not erased. */ final PackageParser.Package scannedPkg = mPackages.get(ps.name); 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. */ 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(scannedPkg, true); mExpectingBetter.put(ps.name, ps.codePath); } continue; } if (!mSettings.isDisabledSystemPackageLPr(ps.name)) { psit.remove(); logCriticalInfo(Log.WARN, "System package " + ps.name + " no longer exists; it's data will be wiped"); // Actual deletion of code and data will be handled by later // reconciliation step } else { // we still have a disabled system package, but, it still might have // been removed. check the code path still exists and check there's // still a package. the latter can happen if an OTA keeps the same // code path, but, changes the package name. final PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(ps.name); if (disabledPs.codePath == null || !disabledPs.codePath.exists() || disabledPs.pkg == null) { possiblyDeletedUpdatedSystemApps.add(ps.name); } } } } if (!mOnlyCore) { scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0); scanDirTracedLI(mDrmAppPrivateInstallDir, mDefParseFlags | PackageParser.PARSE_FORWARD_LOCK, scanFlags | SCAN_REQUIRE_KNOWN, 0); // Remove disable package settings for updated system apps that were // removed via an OTA. If the update is no longer present, remove the // app completely. Otherwise, revoke their system privileges. for (String deletedAppName : possiblyDeletedUpdatedSystemApps) { PackageParser.Package deletedPkg = mPackages.get(deletedAppName); mSettings.removeDisabledSystemPackageLPw(deletedAppName); final String msg; if (deletedPkg == null) { // should have found an update, but, we didn't; remove everything msg = "Updated system package " + deletedAppName + " no longer exists; removing its data"; // Actual deletion of code and data will be handled by later // reconciliation step } else { // found an update; revoke system privileges msg = "Updated system package + " + deletedAppName + " no longer exists; revoking system privileges"; // Don't do anything if a stub is removed from the system image. If // we were to remove the uncompressed version from the /data partition, // this is where it'd be done. final PackageSetting deletedPs = mSettings.mPackages.get(deletedAppName); deletedPkg.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM; deletedPs.pkgFlags &= ~ApplicationInfo.FLAG_SYSTEM; /// M: [Operator] Revoke operator permissions for the original operator sPmsExt.clearExtFlags(deletedPkg, deletedPs); } 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++) { 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"); int reparseFlags = mDefParseFlags; 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; } mSettings.enableSystemPackageLPw(packageName); try { scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); } catch (PackageManagerException e) { Slog.e(TAG, "Failed to parse original system package: " + e.getMessage()); } } } // Uncompress and install any stubbed system applications. // This must be done last to ensure all stubs are replaced or disabled. decompressSystemApplications(stubSystemApps, scanFlags); final int cachedNonSystemApps = PackageParser.sCachedPackageReadCount.get() - cachedSystemApps; final long dataScanTime = SystemClock.uptimeMillis() - systemScanTime - startTime; final int dataPackagesCount = mPackages.size() - systemPackagesCount; Slog.i(TAG, "Finished scanning non-system apps. Time: " + dataScanTime + " ms, packageCount: " + dataPackagesCount + " , timePerPackage: " + (dataPackagesCount == 0 ? 0 : dataScanTime / dataPackagesCount) + " , cached: " + cachedNonSystemApps); if (mIsUpgrade && dataPackagesCount > 0) { MetricsLogger.histogram(null, "ota_package_manager_data_app_avg_scan_time", ((int) dataScanTime) / dataPackagesCount); } }

构造函数分析 - 扫尾工作

这部分任务比较简单,就是将第二阶段手机的信息再集中整理一次,可自行研究。

总结

第二阶段分析就此结束!

后作

转载地址:http://qgrja.baihongyu.com/

你可能感兴趣的文章
keepalived+双主架构
查看>>
robotframwork的WEB功能测试(二)—登录
查看>>
java_有秒计时的数字时钟
查看>>
最小生成树-Prim算法与Kruskal算法
查看>>
阅读笔记11
查看>>
Javascript中大括号“{}”的多义性
查看>>
Loadrunner Get&Post方法性能测试脚本解析
查看>>
Android manifest 获取源代码
查看>>
第三讲课后作业1
查看>>
Studio快捷键
查看>>
弹丸类以及魂类的构想
查看>>
Daily Srum 10.21
查看>>
11-13 js操作css样式
查看>>
html5 css练习 画廊 元素旋转
查看>>
关于JS
查看>>
你得学会并且学得会的Socket编程基础知识(转)
查看>>
[Python]安装完pip、pygame后,仍然import pygame报错
查看>>
吃鸡蛋引发的血案,详解内存中的字节序
查看>>
【1139】数据结构上机测试2-2:单链表操作B (逆序建表+重复元素删除)
查看>>
C++ 内存管理之三(栈和堆)
查看>>