其实说到自晋级,信任大多数的同伴们更多想到的是经过热修正的办法,从dex文件入手利用类加载机制完结bugfix。当然这也是运用自晋级的一种计划,像Tinker、Robust这些成熟的框架现已帮咱们做了很多根底的作业,可是假如咱们是在做ROM,或许智能硬件(车载座舱、电视、汽车等)方面的作业,咱们维护的或许就不仅仅一个app,从体系运用到自研运用少则10几个,多则20+,30+,假如对每个app都做热修正承继,显然是有点儿浪费资源了。

由于事务晋级迭代的速度是很快的,可是ROM现已是比较稳定的,假如每次事务晋级都需求配合ROM显然是比较重的一次操作,因此在事务快速迭代的阶段,咱们对某些app做统一的自晋级,

Android进阶宝典 -- 自升级方案基础之PKMS源码分析

不同的版本晋级新的功用,就类似于咱们在研发阶段不断地install本地debug运用测验,其实就相当于将v1.0版本的事务app卸载掉,然后重新装置v1.1版本的事务app,那么体系哪个服务可以完结这一系列的操作呢,便是PKMS。

1 PKMS介绍

什么是PKMS,全称为PackageManagerService,从字面意思上来看,便是包办理服务,或许是apk的办理服务,可以完结apk的信息查询、装置以及卸载。当咱们打开手机之后进入到Launcher页面之后,咱们看到一切的运用展现都是经过PKMS查询到的,以ListView的形式展现出来,包含咱们长按运用卸载,也是调用了PKMS的才能。

1.1 PKMS进程间通讯

经过前面咱们关于体系发动流程的剖析,咱们知道当SystemServer进程发动之后,就会发动像AMS、PKMS等中心服务,所以PKMS是归于SystemServer进程的,此刻客户端想要调用PKMS服务的才能,必然涉及到了跨进程通讯,咱们先看下跨进程通讯是怎么完结的。

一般在客户端想要调用PKMS的才能,首要需求获取到PackageManager目标,这是一个抽象类,

packageManager.getPackageInfo("com.lay.pkms",0)

详细完结类为ApplicationPackageManager,咱们看下它的源码,这儿就会有进程间通讯的完结,由于肯定是调用到了PKMS的才能,咱们拿getPackageInfo这个办法来看。

留意这个类是一个隐藏类,因此想要查看详细的完结需求从源码中查找,途径如下:

frameworks/base/core/java/android/app/ApplicationPackageManager.java

public class ApplicationPackageManager extends PackageManager {
    private static final String TAG = "ApplicationPackageManager";
    private final static boolean DEBUG_ICONS = false;
    private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB
    // Default flags to use with PackageManager when no flags are given.
    private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
    private final Object mLock = new Object();
    @GuardedBy("mLock")
    private UserManager mUserManager;
    @GuardedBy("mLock")
    private PackageInstaller mInstaller;
    @GuardedBy("mLock")
    private ArtManager mArtManager;
    @GuardedBy("mDelegates")
    private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
    @GuardedBy("mLock")
    private String mPermissionsControllerPackageName;
    UserManager getUserManager() {
        synchronized (mLock) {
            if (mUserManager == null) {
                mUserManager = UserManager.get(mContext);
            }
            return mUserManager;
        }
    }
    @Override
    public int getUserId() {
        return mContext.getUserId();
    }
    @Override
    public PackageInfo getPackageInfo(String packageName, int flags)
    throws NameNotFoundException {
        return getPackageInfoAsUser(packageName, flags, mContext.getUserId());
    }
    @Override
    public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
    throws NameNotFoundException {
        try {
            PackageInfo pi = mPM.getPackageInfo(packageName, flags, userId);
            if (pi != null) {
                return pi;
            }
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
        throw new NameNotFoundException(packageName);
    }
}

咱们看到在调用getPackageInfo办法时,终究调用了getPackageInfoAsUser办法,在这个办法中,咱们看到有一个变量mPM,看样子便是PKMS的一个署理目标,所以咱们只需求确认mPM是哪个类的目标即可。

private final IPackageManager mPM;

从源码中可以看到,mPM是IPackageManager的一个实例目标,而IPackageManager便是 SystemServer和客户端进程间通讯的aidl,其途径在下面:

frameworks/base/core/java/android/content/pm/IPackageManager.aidl

由于这个文件里的办法太多了,我就不贴出来了,有爱好的同伴可以自行查看。

那么mPM是在什么时分初始化的呢?

protected ApplicationPackageManager(ContextImpl context,
    IPackageManager pm) {
    mContext = context;
    mPM = pm;
}

经过源码咱们发现,在ApplicationPackageManager的结构办法中,咱们看到会对其进行赋值,因此咱们前面看到getPackageManager办法调用时,应该便是创立了ApplicationPackageManager目标。

@Override
public PackageManager getPackageManager() {
    if (mPackageManager != null) {
        return mPackageManager;
    }
    IPackageManager pm = ActivityThread.getPackageManager();
    if (pm != null) {
        // Doesn't matter if we make more than one instance.
        return (mPackageManager = new ApplicationPackageManager(this, pm));
    }
    return null;
}

所以在getPackageInfoAsUser办法中,便是调用了PKMS的getPackageInfo办法,所以C/S之间的通讯办法如下:

Android进阶宝典 -- 自升级方案基础之PKMS源码分析

1.2 PKMS发动流程剖析

前面咱们提了一嘴,在SystemServer进程发动之后,就会发动PKMS,现在咱们详细剖析一下,PKMS的发动流程到底干了什么事。

1.2.1 SystemServer引导服务发动流程

详细的SystemServer发动流程,感爱好的同伴可以去前面的文章中看,咱们直接进入到SystemServer的startBootstrapServices办法中。

try {
    Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");
    mPackageManagerService = PackageManagerService.main(mSystemContext, installer,
            mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);
} finally {
    Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");
}

越过其他服务的发动,咱们看到在发动PackageManagerService服务的时分,是直接调用了其mian办法。

// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions.  We need this to complete before we initialize other services.
t.traceBegin("StartInstaller");
Installer installer = mSystemServiceManager.startService(Installer.class);
t.traceEnd();

在此之前,咱们先重视一个Installer服务,经过注释咱们发现这个服务还是挺重要的,在初始化其他服务之前,首要要确保Installer服务的发动现已完结。

Only run "core" apps if we're encrypting the device.
String cryptState = VoldProperties.decrypt().orElse("");
if (ENCRYPTING_STATE.equals(cryptState)) {
    Slog.w(TAG, "Detected encryption in progress - only parsing core apps");
    mOnlyCore = true;
} else if (ENCRYPTED_STATE.equals(cryptState)) {
    Slog.w(TAG, "Device encrypted - only parsing core apps");
    mOnlyCore = true;
}

然后会判别手机是否加密了,例如设置了手机暗码,那么就只会解析core app,像做车载运用,一般都会将这个变量写死为false,由于没有隐私相关的app可以运用,一般车载也不会有设置暗码这个选项。

然后就会调用PackageManagerService的main办法,这儿咱们看到是把installer服务、手机是否加密作为变量参数传进去了。同伴们留意一下,这个办法是一个耗时办法,并且在手机敞开发动的时分会慢,也是这个办法在捣鬼。

在发动PKMS完结之后,会依据手机设备是否加密,来决议是否发动OtaDexoptService服务。OtaDexoptService主要用来办理A/B晋级和OTA晋级以及dex文件优化,关于这两个晋级名词假如做过体系晋级的同伴或许会了解,当体系晋级时,端上会调用OS与A/B晋级和OTA晋级相关的接口,完结体系晋级。

// Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
// A/B artifacts after boot, before anything else might touch/need them.
// Note: this isn't needed during decryption (we don't have /data anyways).
if (!mOnlyCore) {
    boolean disableOtaDexopt = SystemProperties.getBoolean("config.disable_otadexopt",
            false);
    if (!disableOtaDexopt) {
        t.traceBegin("StartOtaDexOptService");
        try {
            Watchdog.getInstance().pauseWatchingCurrentThread("moveab");
            OtaDexoptService.main(mSystemContext, mPackageManagerService);
        } catch (Throwable e) {
            reportWtf("starting OtaDexOptService", e);
        } finally {
            Watchdog.getInstance().resumeWatchingCurrentThread("moveab");
            t.traceEnd();
        }
    }
}

当然更新策略也是依据app等级划分,core app > system app > other app,所以硬件设备是否支持加密(设置暗码),对OTA晋级有着直接影响。

关于dex的优化,便是在引导服务流程完毕之后,在startOtherServices办法中进行的。

1.2.2 其他服务发动startOtherServices

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
    // ......
    if (!mOnlyCore) {
        t.traceBegin("UpdatePackagesIfNeeded");
        try {
            Watchdog.getInstance().pauseWatchingCurrentThread("dexopt");
            mPackageManagerService.updatePackagesIfNeeded();
        } catch (Throwable e) {
            reportWtf("update packages", e);
        } finally {
            Watchdog.getInstance().resumeWatchingCurrentThread("dexopt");
        }
        t.traceEnd();
    }
    // ......
    try {
        //完结磁盘清理维护
        mPackageManagerService.performFstrimIfNeeded();
    } catch (Throwable e) {
        reportWtf("performing fstrim", e);
    }
    // ......
    mPackageManagerService.systemReady();
}

这儿只看与PKMS相关的处理;首要会判别手机是否加密,假如没有加密,那么就会判别是否有package需求更新,dex是否需求优化。

然后会调用performFstrimIfNeeded进行磁盘清理,终究重要的一步便是会调用systemReady办法,表明PKMS准备就绪了,假如这个时分服务出现异常,那么后续服务就不会再初始化,就会导致手机敞开死机。

那么这儿咱们总结一下,PKMS准备就绪需求的环节:

  • 发动Installer服务,是堵塞的,后续服务初始化的前提;
  • 判别手机是否加密,假如加密,则mOnlyCore = true,否则为false;
  • 履行PKMS的main办法,耗时堵塞的;
  • 依据mOnlyCore决议发动晋级服务OtaDexoptService;
  • 依据mOnlyCore判别是否需求查看包更新,即调用PKMS的updatePackagesIfNeeded;
  • 调用PKMS的performFstrimIfNeeded办法进行磁盘清理;
  • 履行PKMS的systemReady办法,表明PKMS现已准备就绪了。

所以当手机发动比较慢的时分,剖析原因就可以从这几个方向入手。

2 PKMS main办法剖析

前面主要介绍了PKMS在发动时到准备就绪的7个步骤,其间在第3步中调用了PKMS的main办法,咱们看下在main办法中的处理逻辑。

public static PackageManagerService main(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    // Self-check for initial settings.
    PackageManagerServiceCompilerMapping.checkProperties();
    final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
            Trace.TRACE_TAG_PACKAGE_MANAGER);
    t.traceBegin("create package manager");
    final Object lock = new Object();
    final Object installLock = new Object();
    Injector injector = new Injector(
            context, lock, installer, installLock, new PackageAbiHelperImpl(),
            (i, pm) ->
                    new ComponentResolver(i.getUserManagerService(), pm.mPmInternal, lock),
            (i, pm) ->
                    PermissionManagerService.create(context, lock),
            (i, pm) ->
                    new UserManagerService(context, pm,
                            new UserDataPreparer(installer, installLock, context, onlyCore),
                            lock),
            (i, pm) ->
                    new Settings(Environment.getDataDirectory(),
                            i.getPermissionManagerServiceInternal().getPermissionSettings(),
                            lock),
            new Injector.LocalServicesProducer<>(ActivityTaskManagerInternal.class),
            new Injector.LocalServicesProducer<>(ActivityManagerInternal.class),
            new Injector.LocalServicesProducer<>(DeviceIdleInternal.class),
            new Injector.LocalServicesProducer<>(StorageManagerInternal.class),
            new Injector.LocalServicesProducer<>(NetworkPolicyManagerInternal.class),
            new Injector.LocalServicesProducer<>(PermissionPolicyInternal.class),
            new Injector.LocalServicesProducer<>(DeviceStorageMonitorInternal.class),
            new Injector.SystemServiceProducer<>(DisplayManager.class),
            new Injector.SystemServiceProducer<>(StorageManager.class),
            new Injector.SystemServiceProducer<>(AppOpsManager.class),
            (i, pm) -> AppsFilter.create(pm.mPmInternal, i),
            (i, pm) -> (PlatformCompat) ServiceManager.getService("platform_compat"));
    PackageManagerService m = new PackageManagerService(injector, onlyCore, factoryTest);
    t.traceEnd(); // "create package manager"
    injector.getCompatibility().registerListener(SELinuxMMAC.SELINUX_LATEST_CHANGES,
            packageName -> {
                synchronized (m.mInstallLock) {
                    final AndroidPackage pkg;
                    final PackageSetting ps;
                    final SharedUserSetting sharedUser;
                    final String oldSeInfo;
                    synchronized (m.mLock) {
                        ps = m.mSettings.getPackageLPr(packageName);
                        if (ps == null) {
                            Slog.e(TAG, "Failed to find package setting " + packageName);
                            return;
                        }
                        pkg = ps.pkg;
                        sharedUser = ps.getSharedUser();
                        oldSeInfo = AndroidPackageUtils.getSeInfo(pkg, ps);
                    }
                    if (pkg == null) {
                        Slog.e(TAG, "Failed to find package " + packageName);
                        return;
                    }
                    final String newSeInfo = SELinuxMMAC.getSeInfo(pkg, sharedUser,
                            m.mInjector.getCompatibility());
                    if (!newSeInfo.equals(oldSeInfo)) {
                        Slog.i(TAG, "Updating seInfo for package " + packageName + " from: "
                                + oldSeInfo + " to: " + newSeInfo);
                        ps.getPkgState().setOverrideSeInfo(newSeInfo);
                        m.prepareAppDataAfterInstallLIF(pkg);
                    }
                }
            });
    m.installWhitelistedSystemPackages();
    ServiceManager.addService("package", m);
    final PackageManagerNative pmn = m.new PackageManagerNative();
    ServiceManager.addService("package_native", pmn);
    return m;
}

在main办法中是存在回来值的,回来的便是PKMS实例目标,在main办法中,也是经过new出来了一个PackageManagerService目标,咱们看下PKMS的结构办法。

假如看过源码的同伴会发现,PKMS的结构办法有上千行代码,这儿只看中心办法即可,经过结构办法打印的日志,可以分为5个阶段进行剖析。

2.1 阶段1- BOOT_PROGRESS_PMS_START

这个阶段可以认为是发动阶段,主要做一些初始化的操作。

2.2 阶段2- BOOT_PROGRESS_PMS_SYSTEM_SCAN_START

在这个阶段开端,进入到体系扫描阶段,在这个阶段里会扫描/system/app,system/priv-app等体系目录下的app,例如蓝牙apk、相机apk,以及各个事务存储的/system/app(假如需求预置权限)事务apk,统统都会扫描到,存储在PKMS傍边,此刻在敞开发动的时分,就现已拿到了手机傍边存在的一切apk,那么在进入到Launcher之后,就会拿到这些apk的信息,展现在ListView列表傍边。

2.3 阶段3- BOOT_PROGRESS_PMS_DATA_SCAN_START

第三个阶段为扫描普通运用阶段,前面主要是从体系目录中扫描体系apk,那么用户从运用商铺下载的运用会存储在user分区,那么此刻就会从sAppInstallDir目录中去查找。

if (!mOnlyCore) {
    EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,
            SystemClock.uptimeMillis());
    scanDirTracedLI(sAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0,
            packageParser, executorService);
}

咱们先看下sAppInstallDir详细是哪个目录,便是存储在/data/app目录下的悉数运用;

/** Directory where installed applications are stored */
private static final File sAppInstallDir =
        new File(Environment.getDataDirectory(), "app");

所以普通运用的扫描便是从scanDirTracedLI办法开端。

  • 第一步,调用scanDirLI办法
private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime,
        PackageParser2 packageParser, ExecutorService executorService) {
    final File[] files = scanDir.listFiles();
    //......
    ParallelPackageParser parallelPackageParser =
            new ParallelPackageParser(packageParser, executorService);
    // Submit files for parsing in parallel
    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) {
            // TODO(toddke): move lower in the scan chain
            // Static shared libraries have synthetic package names
            if (parseResult.parsedPackage.isStaticSharedLibrary()) {
                renameStaticSharedLibraryPackage(parseResult.parsedPackage);
            }
            try {
                addForInitLI(parseResult.parsedPackage, parseFlags, scanFlags,
                        currentTime, null);
            } catch (PackageManagerException e) {
                errorCode = e.error;
                Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
            }
        } else if (throwable instanceof PackageParserException) {
            PackageParserException e = (PackageParserException)
                    throwable;
            errorCode = e.error;
            Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
        } else {
            throw new IllegalStateException("Unexpected exception occurred while parsing "
                    + parseResult.scanFile, throwable);
        }
        if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0 && errorCode != INSTALL_SUCCEEDED) {
            mApexManager.reportErrorWithApkInApex(scanDir.getAbsolutePath());
        }
        // Delete invalid userdata apps
        if ((scanFlags & SCAN_AS_SYSTEM) == 0
                && errorCode != PackageManager.INSTALL_SUCCEEDED) {
            logCriticalInfo(Log.WARN,
                    "Deleting invalid package at " + parseResult.scanFile);
            removeCodePathLI(parseResult.scanFile);
        }
    }
}

首要创立一个ParallelPackageParser目标,这个类的主要作用便是内部维护了线程池和堵塞行列。

class ParallelPackageParser {
    private static final int QUEUE_CAPACITY = 30;
    private static final int MAX_THREADS = 4;
    private volatile String mInterruptedInThread;
    private final BlockingQueue<ParseResult> mQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY);
    static ExecutorService makeExecutorService() {
        return ConcurrentUtils.newFixedThreadPool(MAX_THREADS, "package-parsing-thread",
                Process.THREAD_PRIORITY_FOREGROUND);
    }
}

然后会遍历/data/app目录下悉数的文件,找到apk文件,调用ParallelPackageParser的subimt办法,将其入队。

  • 第二步:调用ParallelPackageParser的submit办法
/**
 * Submits the file for parsing
 * @param scanFile file to scan
 * @param parseFlags parse flags
 */
public void submit(File scanFile, int parseFlags) {
    mExecutorService.submit(() -> {
        ParseResult pr = new ParseResult();
        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");
        try {
            pr.scanFile = scanFile;
            pr.parsedPackage = parsePackage(scanFile, parseFlags);
        } catch (Throwable e) {
            pr.throwable = e;
        } finally {
            Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
        }
        try {
            mQueue.put(pr);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            // Propagate result to callers of take().
            // This is helpful to prevent main thread from getting stuck waiting on
            // ParallelPackageParser to finish in case of interruption
            mInterruptedInThread = Thread.currentThread().getName();
        }
    });
}

这个办法比较简单,便是经过线程池,在内部封装了一个ParseResult目标,将apk文件作为其目标参数,终究将ParseResult目标参加BlockQueue行列中。

  • 第三步:PackageParser2.parsePackage

这儿为啥要开线程池,一个便是考虑并发的问题,再一个便是咱们看到会调用parsePackage办法解析,经过PackageParser2进行解析。

protected ParsedPackage parsePackage(File scanFile, int parseFlags)
        throws PackageParser.PackageParserException {
    return mPackageParser.parsePackage(scanFile, parseFlags, true);
}

咱们看下PackageParser2中的parsePackage办法,这个办法会回来一个解析过的package目标ParsedPackage。

@AnyThread
public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
        throws PackageParserException {
    // ......
    ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
    if (result.isError()) {
        throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                result.getException());
    }
    ParsedPackage parsed = (ParsedPackage) result.getResult().hideAsParsed();
    long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
    if (mCacher != null) {
        mCacher.cacheResult(packageFile, flags, parsed);
    }
    if (LOG_PARSE_TIMINGS) {
        parseTime = cacheTime - parseTime;
        cacheTime = SystemClock.uptimeMillis() - cacheTime;
        if (parseTime + cacheTime > LOG_PARSE_TIMINGS_THRESHOLD_MS) {
            Slog.i(TAG, "Parse times for '" + packageFile + "': parse=" + parseTime
                    + "ms, update_cache=" + cacheTime + " ms");
        }
    }
    return parsed;
}

在这个办法内部,会调用ParsingPackageUtils的parsePackage办法,终究会回来解析的结果。

public ParseResult<ParsingPackage> parsePackage(ParseInput input, File packageFile,
        int flags)
        throws PackageParserException {
    if (packageFile.isDirectory()) {
        return parseClusterPackage(input, packageFile, flags);
    } else {
        return parseMonolithicPackage(input, packageFile, flags);
    }
}

在parsePackage办法中,首要会判别传进来的文件是文件夹还是文件;假如是文件夹,那么就会调用parseClusterPackage办法,假如是文件,那么就会调用parseMonolithicPackage办法。

由于咱们传进来的是apk文件,咱们重视下parseMonolithicPackage这个办法。

/**
 * Parse the given APK file, treating it as as a single monolithic package.
 * <p>
 * Note that this <em>does not</em> perform signature verification; that
 * must be done separately in {@link #getSigningDetails(ParsingPackageRead, boolean)}.
 */
private ParseResult<ParsingPackage> parseMonolithicPackage(ParseInput input, File apkFile,
        int flags) throws PackageParserException {
    ParseResult<PackageParser.PackageLite> liteResult =
            ApkLiteParseUtils.parseMonolithicPackageLite(input, apkFile, flags);
    if (liteResult.isError()) {
        return input.error(liteResult);
    }
    final PackageParser.PackageLite lite = liteResult.getResult();
    if (mOnlyCoreApps && !lite.coreApp) {
        return input.error(INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED,
                "Not a coreApp: " + apkFile);
    }
    final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);
    try {
        //中心函数
        ParseResult<ParsingPackage> result = parseBaseApk(input,
                apkFile,
                apkFile.getCanonicalPath(),
                assetLoader.getBaseAssetManager(), flags);
        if (result.isError()) {
            return input.error(result);
        }
        return input.success(result.getResult()
                .setUse32BitAbi(lite.use32bitAbi));
    } catch (IOException e) {
        return input.error(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,
                "Failed to get path: " + apkFile, e);
    } finally {
        IoUtils.closeQuietly(assetLoader);
    }
}

在这个办法中,中心代码为parseBaseApk办法的调用,这个办法用于对单体apk进行解析,终究回来解析之后的结果。

  • 第四步:parseBaseApk办法调用

在parseBaseApk办法中,会调用其重载办法,咱们重视下这个重载办法,看代码注释是用于解析apk的manifest清单文件。

/**
 * Parse the manifest of a <em>base APK</em>. When adding new features you
 * need to consider whether they should be supported by split APKs and child
 * packages.
 *
 * @param apkPath The package apk file path
 * @param res     The resources from which to resolve values
 * @param parser  The manifest parser
 * @param flags   Flags how to parse
 * @return Parsed package or null on error.
 */
private ParseResult<ParsingPackage> parseBaseApk(ParseInput input, String apkPath,
        String codePath, Resources res, XmlResourceParser parser, int flags)
        throws XmlPullParserException, IOException, PackageParserException {
    final String splitName;
    final String pkgName;
    ParseResult<Pair<String, String>> packageSplitResult =
            ApkLiteParseUtils.parsePackageSplitNames(input, parser, parser);
    if (packageSplitResult.isError()) {
        return input.error(packageSplitResult);
    }
    Pair<String, String> packageSplit = packageSplitResult.getResult();
    pkgName = packageSplit.first;
    splitName = packageSplit.second;
    if (!TextUtils.isEmpty(splitName)) {
        return input.error(
                PackageManager.INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME,
                "Expected base APK, but found split " + splitName
        );
    }
    //解析Manifest
    final TypedArray manifestArray = res.obtainAttributes(parser, R.styleable.AndroidManifest);
    try {
        final boolean isCoreApp =
                parser.getAttributeBooleanValue(null, "coreApp", false);
        final ParsingPackage pkg = mCallback.startParsingPackage(
                pkgName, apkPath, codePath, manifestArray, isCoreApp);
        final ParseResult<ParsingPackage> result =
                parseBaseApkTags(input, pkg, manifestArray, res, parser, flags);
        if (result.isError()) {
            return result;
        }
        return input.success(pkg);
    } finally {
        manifestArray.recycle();
    }
}

在这个办法中,首要会解析manifest清单文件拿到一个TypedArray,其间记录了当前apk清单文件中每个节点的信息,调用parseBaseApkTags办法。

private ParseResult<ParsingPackage> parseBaseApkTags(ParseInput input, ParsingPackage pkg,
        TypedArray sa, Resources res, XmlResourceParser parser, int flags)
        throws XmlPullParserException, IOException {
    ParseResult<ParsingPackage> sharedUserResult = parseSharedUser(input, pkg, sa);
    if (sharedUserResult.isError()) {
        return sharedUserResult;
    }
   //设置apk存储位置 pkg.setInstallLocation(anInteger(PackageParser.PARSE_DEFAULT_INSTALL_LOCATION,
            R.styleable.AndroidManifest_installLocation, sa))
            .setTargetSandboxVersion(anInteger(PackageParser.PARSE_DEFAULT_TARGET_SANDBOX,
                    R.styleable.AndroidManifest_targetSandboxVersion, sa))
            /* Set the global "on SD card" flag */
            .setExternalStorage((flags & PackageParser.PARSE_EXTERNAL_STORAGE) != 0);
    boolean foundApp = false;
    final int depth = parser.getDepth();
    int type;
    //解析清单文件
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG
            || parser.getDepth() > depth)) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        String tagName = parser.getName();
        final ParseResult result;
        // TODO(b/135203078): Convert to instance methods to share variables
        // <application> has special logic, so it's handled outside the general method
        // 中心代码
        if (PackageParser.TAG_APPLICATION.equals(tagName)) {
            if (foundApp) {
                if (PackageParser.RIGID_PARSER) {
                    result = input.error("<manifest> has more than one <application>");
                } else {
                    Slog.w(TAG, "<manifest> has more than one <application>");
                    result = input.success(null);
                }
            } else {
                foundApp = true;
                result = parseBaseApplication(input, pkg, res, parser, flags);
            }
        } else {
            result = parseBaseApkTag(tagName, input, pkg, res, parser, flags);
        }
        if (result.isError()) {
            return input.error(result);
        }
    }
    if (!foundApp && ArrayUtils.size(pkg.getInstrumentations()) == 0) {
        ParseResult<?> deferResult = input.deferError(
                "<manifest> does not contain an <application> or <instrumentation>",
                DeferredError.MISSING_APP_TAG);
        if (deferResult.isError()) {
            return input.error(deferResult);
        }
    }
    if (!ParsedAttribution.isCombinationValid(pkg.getAttributions())) {
        return input.error(
                INSTALL_PARSE_FAILED_BAD_MANIFEST,
                "Combination <feature> tags are not valid"
        );
    }
    convertNewPermissions(pkg);
    convertSplitPermissions(pkg);
    // At this point we can check if an application is not supporting densities and hence
    // cannot be windowed / resized. Note that an SDK version of 0 is common for
    // pre-Doughnut applications.
    if (pkg.getTargetSdkVersion() < DONUT
            || (!pkg.isSupportsSmallScreens()
            && !pkg.isSupportsNormalScreens()
            && !pkg.isSupportsLargeScreens()
            && !pkg.isSupportsExtraLargeScreens()
            && !pkg.isResizeable()
            && !pkg.isAnyDensity())) {
        adjustPackageToBeUnresizeableAndUnpipable(pkg);
    }
    return input.success(pkg);
}

parseBaseApkTags从办法名中就可以猜到,其实便是解析apk的参数,也便是Manifest清单文件的解析。

  • 中心代码

在解析清单文件时,判别是否为application标签。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.appbuilder">
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

假如是application标签,由于foundApp默认为false,就会将其置为true,代表找到了application,此刻会调用parseBaseApplication办法。

后续这个foundApp标志位判别,假如为false,那么就会报错,由于没有application标签,详细异常可以看代码,咱们接下来看parseBaseApplication办法。

  • 第五步:parseBaseApplication解析application标签
private ParseResult<ParsingPackage> parseBaseApplication(ParseInput input,
        ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags)
        throws XmlPullParserException, IOException {
    final String pkgName = pkg.getPackageName();
    int targetSdk = pkg.getTargetSdkVersion();
    TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestApplication);
    try {
        // TODO(b/135203078): Remove this and force unit tests to mock an empty manifest
        // This case can only happen in unit tests where we sometimes need to create fakes
        // of various package parser data structures.
        if (sa == null) {
            return input.error("<application> does not contain any attributes");
        }
        String name = sa.getNonConfigurationString(R.styleable.AndroidManifestApplication_name,
                0);
        if (name != null) {
            String packageName = pkg.getPackageName();
            String outInfoName = ParsingUtils.buildClassName(packageName, name);
            if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(outInfoName)) {
                return input.error("<application> invalid android:name");
            } else if (outInfoName == null) {
                return input.error("Empty class name in package " + packageName);
            }
            pkg.setClassName(outInfoName);
        }
        TypedValue labelValue = sa.peekValue(R.styleable.AndroidManifestApplication_label);
        if (labelValue != null) {
            pkg.setLabelRes(labelValue.resourceId);
            if (labelValue.resourceId == 0) {
                pkg.setNonLocalizedLabel(labelValue.coerceToString());
            }
        }
        parseBaseAppBasicFlags(pkg, sa);
        String manageSpaceActivity = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
                R.styleable.AndroidManifestApplication_manageSpaceActivity, sa);
        if (manageSpaceActivity != null) {
            String manageSpaceActivityName = ParsingUtils.buildClassName(pkgName,
                    manageSpaceActivity);
            if (manageSpaceActivityName == null) {
                return input.error("Empty class name in package " + pkgName);
            }
            pkg.setManageSpaceActivityName(manageSpaceActivityName);
        }
        if (pkg.isAllowBackup()) {
            // backupAgent, killAfterRestore, fullBackupContent, backupInForeground,
            // and restoreAnyVersion are only relevant if backup is possible for the
            // given application.
            String backupAgent = nonConfigString(Configuration.NATIVE_CONFIG_VERSION,
                    R.styleable.AndroidManifestApplication_backupAgent, sa);
            if (backupAgent != null) {
                String backupAgentName = ParsingUtils.buildClassName(pkgName, backupAgent);
                if (backupAgentName == null) {
                    return input.error("Empty class name in package " + pkgName);
                }
                if (PackageParser.DEBUG_BACKUP) {
                    Slog.v(TAG, "android:backupAgent = " + backupAgentName
                            + " from " + pkgName + "+" + backupAgent);
                }
                pkg.setBackupAgentName(backupAgentName)
                        .setKillAfterRestore(bool(true,
                                R.styleable.AndroidManifestApplication_killAfterRestore, sa))
                        .setRestoreAnyVersion(bool(false,
                                R.styleable.AndroidManifestApplication_restoreAnyVersion, sa))
                        .setFullBackupOnly(bool(false,
                                R.styleable.AndroidManifestApplication_fullBackupOnly, sa))
                        .setBackupInForeground(bool(false,
                                R.styleable.AndroidManifestApplication_backupInForeground, sa));
            }
            TypedValue v = sa.peekValue(
                    R.styleable.AndroidManifestApplication_fullBackupContent);
            int fullBackupContent = 0;
            if (v != null) {
                fullBackupContent = v.resourceId;
                if (v.resourceId == 0) {
                    if (PackageParser.DEBUG_BACKUP) {
                        Slog.v(TAG, "fullBackupContent specified as boolean=" +
                                (v.data == 0 ? "false" : "true"));
                    }
                    // "false" => -1, "true" => 0
                    fullBackupContent = v.data == 0 ? -1 : 0;
                }
                pkg.setFullBackupContent(fullBackupContent);
            }
            if (PackageParser.DEBUG_BACKUP) {
                Slog.v(TAG, "fullBackupContent=" + fullBackupContent + " for " + pkgName);
            }
        }
        if (sa.getBoolean(R.styleable.AndroidManifestApplication_persistent, false)) {
            // Check if persistence is based on a feature being present
            final String requiredFeature = sa.getNonResourceString(R.styleable
                    .AndroidManifestApplication_persistentWhenFeatureAvailable);
            pkg.setPersistent(requiredFeature == null || mCallback.hasFeature(requiredFeature));
        }
        // TODO(b/135203078): Should parsing code be responsible for this? Maybe move to a
        //  util or just have PackageImpl return true if either flag is set
        // Debuggable implies profileable
        pkg.setProfileableByShell(pkg.isProfileableByShell() || pkg.isDebuggable());
        if (sa.hasValueOrEmpty(R.styleable.AndroidManifestApplication_resizeableActivity)) {
            pkg.setResizeableActivity(sa.getBoolean(
                    R.styleable.AndroidManifestApplication_resizeableActivity, true));
        } else {
            pkg.setResizeableActivityViaSdkVersion(
                    targetSdk >= Build.VERSION_CODES.N);
        }
        String taskAffinity;
        if (targetSdk >= Build.VERSION_CODES.FROYO) {
            taskAffinity = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestApplication_taskAffinity,
                    Configuration.NATIVE_CONFIG_VERSION);
        } else {
            // Some older apps have been seen to use a resource reference
            // here that on older builds was ignored (with a warning).  We
            // need to continue to do this for them so they don't break.
            taskAffinity = sa.getNonResourceString(
                    R.styleable.AndroidManifestApplication_taskAffinity);
        }
        ParseResult<String> taskAffinityResult = ComponentParseUtils.buildTaskAffinityName(
                pkgName, pkgName, taskAffinity, input);
        if (taskAffinityResult.isError()) {
            return input.error(taskAffinityResult);
        }
        pkg.setTaskAffinity(taskAffinityResult.getResult());
        String factory = sa.getNonResourceString(
                R.styleable.AndroidManifestApplication_appComponentFactory);
        if (factory != null) {
            String appComponentFactory = ParsingUtils.buildClassName(pkgName, factory);
            if (appComponentFactory == null) {
                return input.error("Empty class name in package " + pkgName);
            }
            pkg.setAppComponentFactory(appComponentFactory);
        }
        CharSequence pname;
        if (targetSdk >= Build.VERSION_CODES.FROYO) {
            pname = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestApplication_process,
                    Configuration.NATIVE_CONFIG_VERSION);
        } else {
            // Some older apps have been seen to use a resource reference
            // here that on older builds was ignored (with a warning).  We
            // need to continue to do this for them so they don't break.
            pname = sa.getNonResourceString(
                    R.styleable.AndroidManifestApplication_process);
        }
        ParseResult<String> processNameResult = ComponentParseUtils.buildProcessName(
                pkgName, null, pname, flags, mSeparateProcesses, input);
        if (processNameResult.isError()) {
            return input.error(processNameResult);
        }
        String processName = processNameResult.getResult();
        pkg.setProcessName(processName);
        if (pkg.isCantSaveState()) {
            // A heavy-weight application can not be in a custom process.
            // We can do direct compare because we intern all strings.
            if (processName != null && !processName.equals(pkgName)) {
                return input.error(
                        "cantSaveState applications can not use custom processes");
            }
        }
        String classLoaderName = pkg.getClassLoaderName();
        if (classLoaderName != null
                && !ClassLoaderFactory.isValidClassLoaderName(classLoaderName)) {
            return input.error("Invalid class loader name: " + classLoaderName);
        }
        pkg.setGwpAsanMode(sa.getInt(R.styleable.AndroidManifestApplication_gwpAsanMode, -1));
    } finally {
        sa.recycle();
    }
    boolean hasActivityOrder = false;
    boolean hasReceiverOrder = false;
    boolean hasServiceOrder = false;
    final int depth = parser.getDepth();
    int type;
    while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
            && (type != XmlPullParser.END_TAG
            || parser.getDepth() > depth)) {
        if (type != XmlPullParser.START_TAG) {
            continue;
        }
        final ParseResult result;
        String tagName = parser.getName();
        boolean isActivity = false;
        switch (tagName) {
            case "activity":
                isActivity = true;
                // fall-through
            case "receiver":
                ParseResult<ParsedActivity> activityResult =
                        ParsedActivityUtils.parseActivityOrReceiver(mSeparateProcesses, pkg,
                                res, parser, flags, PackageParser.sUseRoundIcon, input);
                if (activityResult.isSuccess()) {
                    ParsedActivity activity = activityResult.getResult();
                    if (isActivity) {
                        hasActivityOrder |= (activity.getOrder() != 0);
                        pkg.addActivity(activity);
                    } else {
                        hasReceiverOrder |= (activity.getOrder() != 0);
                        pkg.addReceiver(activity);
                    }
                }
                result = activityResult;
                break;
            case "service":
                ParseResult<ParsedService> serviceResult =
                        ParsedServiceUtils.parseService(mSeparateProcesses, pkg, res, parser,
                                flags, PackageParser.sUseRoundIcon, input);
                if (serviceResult.isSuccess()) {
                    ParsedService service = serviceResult.getResult();
                    hasServiceOrder |= (service.getOrder() != 0);
                    pkg.addService(service);
                }
                result = serviceResult;
                break;
            case "provider":
                ParseResult<ParsedProvider> providerResult =
                        ParsedProviderUtils.parseProvider(mSeparateProcesses, pkg, res, parser,
                                flags, PackageParser.sUseRoundIcon, input);
                if (providerResult.isSuccess()) {
                    pkg.addProvider(providerResult.getResult());
                }
                result = providerResult;
                break;
            case "activity-alias":
                activityResult = ParsedActivityUtils.parseActivityAlias(pkg, res,
                        parser, PackageParser.sUseRoundIcon, input);
                if (activityResult.isSuccess()) {
                    ParsedActivity activity = activityResult.getResult();
                    hasActivityOrder |= (activity.getOrder() != 0);
                    pkg.addActivity(activity);
                }
                result = activityResult;
                break;
            default:
                result = parseBaseAppChildTag(input, tagName, pkg, res, parser, flags);
                break;
        }
        if (result.isError()) {
            return input.error(result);
        }
    }
    if (TextUtils.isEmpty(pkg.getStaticSharedLibName())) {
        // Add a hidden app detail activity to normal apps which forwards user to App Details
        // page.
        ParseResult<ParsedActivity> a = generateAppDetailsHiddenActivity(input, pkg);
        if (a.isError()) {
            // Error should be impossible here, as the only failure case as of SDK R is a
            // string validation error on a constant ":app_details" string passed in by the
            // parsing code itself. For this reason, this is just a hard failure instead of
            // deferred.
            return input.error(a);
        }
        pkg.addActivity(a.getResult());
    }
    if (hasActivityOrder) {
        pkg.sortActivities();
    }
    if (hasReceiverOrder) {
        pkg.sortReceivers();
    }
    if (hasServiceOrder) {
        pkg.sortServices();
    }
    // Must be run after the entire {@link ApplicationInfo} has been fully processed and after
    // every activity info has had a chance to set it from its attributes.
    setMaxAspectRatio(pkg);
    setMinAspectRatio(pkg);
    setSupportsSizeChanges(pkg);
    pkg.setHasDomainUrls(hasDomainURLs(pkg));
    return input.success(pkg);
}

这个办法中,其实就会解析application标签中一切的信息,包含内部的四大组件标签activity/service/receiver/contentprovider,每解析一个,都会存储在ParsingPackage傍边。

ParsingPackage addActivity(ParsedActivity parsedActivity);
ParsingPackage addService(ParsedService parsedService);

那么这么做的好处便是,当咱们每次发动一个Activity的时分,不需求每次都去解析Activity相关信息,例如launchMode,直接去ParsingPackage中获取即可。

至此,PKMS的main办法基本算是履行完毕了,当然还有剩余的2个阶段,这儿不做过多赘述,所以假如咱们的手机装置了很多运用,那么敞开速度上或许会受到影响,可是这个是用户可以接受的,这也为咱们后续发动运用或许发动Activity提供了强力的数据支撑,不需求在发动的时分解析Activity的相关参数信息。

所以这儿或许会有一些问题,需求同伴们留意一下:

  • 问题1:当Activity A跳转到Activity B的时分,Activity B的launchMode是什么时分解析的?

由于在手机发动的时分,会扫描一切apk的清单文件,关于每个Activity的launchMode也是写在清单文件傍边,所以在第三阶段就拿到了launchMode信息,存储在了PKMS内存傍边。

  • 问题2:一切静态播送是什么时分注册的?

经过上一个问题,咱们也能猜到,由于在PKMS扫描阶段会扫描一切apk的清单文件,关于静态播送也是放在清单文件傍边的,所以也是在第三阶段进行注册的。

  • 问题3:体系是怎么装置apk的?

当体系装置apk时,会将apk拷贝到/data/app目录下,类似于adb push的操作,然后重启PKMS会扫描到新装置的运用,并存储信息到PKMS中。

经过这一节咱们大概可以了解PKMS在发动时到底干了什么事,以及客户端在调用PKMS接口时内部进程间通讯的详细逻辑,这也对咱们后续的自晋级计划有必定的理论根底。