深度揭秘,Android应用是如何安装到手机上的

b35be0b7423b7b5074409e6d25481844.jpeg

/   今日科技快讯   /

ChatGPT 的横空出世,在业界掀起了惊涛骇浪。专家表示,ChatGPT 和相关人工智能技术可能会威胁到一些工作岗位,尤其是白领工作。

自去年11月发布以来,新型聊天机器人模型 ChatGPT 已经被用于各种各样的工作:撰写求职信、编写儿童读物,甚至帮助学生在论文中作弊。谷歌公司发现,从理论上来讲,如果机器人参加谷歌的面试,该公司会雇佣它成为一名入门级程序员。

/   作者简介   /

本篇文章来自Pingred_hjh的投稿,文章主要对PackageManagerService的相关分析,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章。

Pingred_hjh的博客地址:

https://blog.csdn.net/qq_39867049?type=blog

/   开始   /

把一个安卓应用的apk文件下载下来之后,点击它进行安装,然后安装完成后,在桌面上点击它的图标进入app到使用,这个过程相信大家都很熟悉了,那么当点击这个apk进行安装的时候,PMS就开始进行工作,下面将详细讲解它是怎样工作的。

/   PackageManagerService   /

简称PMS,当安卓系统要安装某个应用时,apk文件会被加载到/data/app目录下,然后PMS就对该目录下的apk缓存文件进行扫描:

7131a958994b3ad18d0f1d319f284779.png

也就是说PMS就会在/data/app目录里对这个apk文件进行解析,apk文件里是有很多文件的,因此要让系统迅速定位到这么多类(四大组件等),就应该有个服务事先把它们的包名路径等信息加载好,这样就可以快速地定位到这些类(比如入口Activity等),而这个服务就是PMS,然后AMS即ActivityManagerService就能根据PMS里面的这些类信息来进行创建入口Activity等四大组件类并启动。

另外,在安卓手机开机时,PMS也会去扫描加载/data/app目录和system/app里每个apk里的所有类信息,然后缓存到PMS它自己里面定义好的集合里,供AMS使用:

a67bd5c543b0e5c439321b0be72cd696.png

PMS加载每个apk(包括系统apk)里的清单文件,然后把里面相应的类(节点)信息进行分类存储:

bb0c1204b90357ed8b58a86ffd4f6175.png

像上图AndroidManifest.xml清单文件里的节点信息,它是一个xml文件,PMS将这些节点信息缓存起来不用占太多容量,而且节点信息又清晰明了,有了这些信息足以让AMS快速去反射创建类对象,然后进行启动。

PMS使用的是一个工具类PackageParse去解析清单文件的,将里面的节点信息(Activity、Service、provider等)解析出来缓存到PMS里面的一个Package里的不同集合里:

074a21e0fcecec6fe18e8b6fba98dded.png

但这里要注意的是,收集Activity信息的集合里的这个Activity类它不是我们平常见到的活动类Activity类,它是Package类里的自己定义的特殊Activity,里面存储的只是清单文件里关于Activity类的一些路径、是否是启动活动等的一些配置信息,因此它不是正常的完整活动类Activity:

c52fc16f7498d0c6bd66dd7e439e6bac.png

上面这些集合就是Package类里对于清单文件里配置的四大组件而定义的缓存集合,这些集合就是用来缓存清单文件里配置的四大组件,但并不是真正的四大组件的类。

每个apk对应一个缓存对象Package,而PMS是一个单独进程,来管理这些Package对象。

之后当用户点击app图标的那一刻,AMS就根据PMS里Package里的这些集合里的类信息以及intent里设置的意图信息,然后定位到要显示的类,创建然后启动它:

a9a402a1ceb9ff18ff74ae90ee6000db.png

AMS从PMS里拿到这个启动类的信息数据之后,就缓存到它自己的ActivityThread里的ActivityClientRecord集合里,也就是跳转记录集合里:

d15634dffa669dc4f4f49cc034415fa1.png

/   SystemServer   /

SystemServer是管理和启动所有服务的类,PMS和AMS就是它负责管理和启动的。而它自己的启动方法run方法则是在手机开启时就会被调用:

/*** The main entry point from zygote.*/public static void main(String[] args) {new SystemServer().run();}

可以看到,它是在main里调用的,而main方法又是被zygote进程调用的,也就是说SystemServer进程是被zygote进程启动的。重新看回SystemServer的run方法,往下看:

// Start services.try {traceBeginAndSlog("StartServices");startBootstrapServices();startCoreServices();startOtherServices();SystemServerInitThreadPool.shutdown();} catch (Throwable ex) {Slog.e("System", "******************************************");Slog.e("System", "************ Failure starting system services", ex);throw ex;} finally {traceEnd();}

这里面启动了很多服务,看startBootstrapServices()方法:

// Activity manager runs the show.traceBeginAndSlog("StartActivityManager");// TODO: Might need to move after migration to WM.ActivityTaskManagerService atm = mSystemServiceManager.startService(ActivityTaskManagerService.Lifecycle.class).getService();mActivityManagerService = ActivityManagerService.Lifecycle.startService(mSystemServiceManager, atm);mActivityManagerService.setSystemServiceManager(mSystemServiceManager);mActivityManagerService.setInstaller(installer);mWindowManagerGlobalLock = atm.getGlobalLock();traceEnd();...traceBeginAndSlog("StartPackageManagerService");try {Watchdog.getInstance().pauseWatchingCurrentThread("packagemanagermain");mPackageManagerService = PackageManagerService.main(mSystemContext, installer,mFactoryTestMode != FactoryTest.FACTORY_TEST_OFF, mOnlyCore);} finally {Watchdog.getInstance().resumeWatchingCurrentThread("packagemanagermain");}mFirstBoot = mPackageManagerService.isFirstBoot();mPackageManager = mSystemContext.getPackageManager();traceEnd();

可以看到,构建了AMS对象也就是mActivityManagerService ,然后启动了AMS,继续往下看就是调用了PackageManagerService的main方法,即启动了PMS,所以我们也可以知道,当开机时,SystemServer会去启动AMS和PMS等这些服务:

5b5dbe56384d34dc244baa5eaf6cc2e8.png

而且从run方法来看,这个方法里这么多代码,相信里面也是不少耗时功能的,所以这也是手机开机时比较耗时的原因之一。

如果我们想拿到PMS对象,则可以通过context的getPackageManager()方法,但它返回的是PackageManager抽象对象,因此要调用它的实现对象ContextImpl的实现方法:

@Overridepublic 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;}

可以看到,通过ActivityThread的getPackageManager方法去获取,然后返回的是IPackageManager抽象对象,它是个Binder对象,因为此时我们是在app里使用context去获取PMS,这相当于app进程和SystemServer进程的通信(SystemServer拥有PMS),也就是跨进程间通信,因此要使用Binder机制来进行获取进程对象的:

@UnsupportedAppUsagepublic static IPackageManager getPackageManager() {if (sPackageManager != null) {//Slog.v("PackageManager", "returning cur default = " + sPackageManager);return sPackageManager;}IBinder b = ServiceManager.getService("package");//Slog.v("PackageManager", "default service binder = " + b);sPackageManager = IPackageManager.Stub.asInterface(b);//Slog.v("PackageManager", "default service = " + sPackageManager);return sPackageManager;}

既然PMS也是服务,那么我们来看它的main方法:

public static PackageManagerService main(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {// Self-check for initial settings.PackageManagerServiceCompilerMapping.checkProperties();PackageManagerService m = new PackageManagerService(context, installer,factoryTest, onlyCore);m.enableSystemUserPackages();ServiceManager.addService("package", m);final PackageManagerNative pmn = m.new PackageManagerNative();ServiceManager.addService("package_native", pmn);return m;}

先是构造了自己PackageManagerService对象m,然后按照key-value方式存放到ServiceManager里,其实就是缓存,下次再来取PMS对象时,就可以直接按照key方法来取:

06d02b2949e8a3bf73aec3088648fb5e.png

所以可以知道ServiceManager存储了很多服务的对象:

f7c01f3f3f99d299e8fb705cbc701b92.png

App应用就可以去调用ServiceManager去取这些服务对象了,当然取出来的是Binder对象,存的时候则是真实的服务对象。

我们继续看回PMS的main方法里,它构造了自己,因此来看它的构造方法:

public PackageManagerService(Context context, Installer installer,boolean factoryTest, boolean onlyCore) {LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES);Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager");EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START,SystemClock.uptimeMillis());if (mSdkVersion <= 0) {Slog.w(TAG, "**** ro.build.version.sdk not set!");}mContext = context;mFactoryTest = factoryTest;mOnlyCore = onlyCore;mMetrics = new DisplayMetrics();mInstaller = installer;...

代码很长,所以这也解释了为什么手机开机时要花费这么长时间了,我们往下看这段代码:

public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {...if (!mOnlyCore) {EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START,SystemClock.uptimeMillis());scanDirTracedLI(sAppInstallDir, 0, 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....}...}

这里调用了一个scanDirTracedLI()方法,里面有个变量sAppInstallDir,可以看看它是什么:

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

通过注释可以知道,这个sAppInstallDir就是缓存的要安装的应用的路径,也就是安装app时它的apk文件缓存的目录:

dc93721b8edfa1792bd4ed799cd9937f.png

DIR_ANDROID_ADTA的值就是data/app目录,刚刚可以看到sAppInstallDir被传到这个scanDirTracedLI方法里,这个方法就是去扫描data/app目录下每个apk方法,而系统app的apk缓存文件(system/app)也是在PMS构造方法里去扫描的:

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

而这里的systemAppDir跟踪一下可以知道就是system/app目录:

4e18d3184210a64c721dd36b487ff07b.png

接下来我们来看看scanDirTracedLI()这个扫描方法的详情:

private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");try {scanDirLI(scanDir, parseFlags, scanFlags, currentTime);} finally {Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);}}

它里面调用了scanDirLI(),那么继续跟踪此方法:

private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {final File[] files = scanDir.listFiles();if (ArrayUtils.isEmpty(files)) {Log.d(TAG, "No files in app dir " + scanDir);return;}if (DEBUG_PACKAGE_SCANNING) {Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags+ " flags=0x" + Integer.toHexString(parseFlags));}try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,mParallelPackageParserCallback)) {// Submit files for parsing in parallelint fileCount = 0;for (File file : files) {final boolean isPackage = (isApkFile(file) || file.isDirectory())&& !PackageInstallerService.isStageName(file.getName());if (!isPackage) {// Ignore entries which are not packagescontinue;}parallelPackageParser.submit(file, parseFlags);fileCount++;}...}}}

可以看到关键代码for循环里遍历每个apk文件,然后判断是否是apk文件才去解析,然后把apk文件传到了parallelPackageParser的submit方法里,来看看这个方法的详情:

public void submit(File scanFile, int parseFlags) {mService.submit(() -> {ParseResult pr = new ParseResult();Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parallel parsePackage [" + scanFile + "]");try {PackageParser pp = new PackageParser();pp.setSeparateProcesses(mSeparateProcesses);pp.setOnlyCoreApps(mOnlyCore);pp.setDisplayMetrics(mMetrics);pp.setCacheDir(mCacheDir);pp.setCallback(mPackageParserCallback);pr.scanFile = scanFile;pr.pkg = parsePackage(pp, 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 interruptionmInterruptedInThread = Thread.currentThread().getName();}});}

实质上是调用了mService的submit方法,这个mService是一个线程池:

private final ExecutorService mService = ConcurrentUtils.newFixedThreadPool(MAX_THREADS,"package-parsing-thread", Process.THREAD_PRIORITY_FOREGROUND);

1f662e804c13d129af6a3eeb2526be53.png

线程核心数为4,也就是说,解析apk其实是启动了一个线程池去解析的:

be8b2ea9369c42752070507cb699cc22.png

这是安卓29(10.0)的版本下的解析代码,而以前版本比如6.0-8.0的版本下是没有使用线程池去解析的,直接在扫描方法里解析的:

889b6a5e2cdb78c410905d092f8b05c2.png

可以看到,6.0版本的解析过程中就没有启动线程池,而是直接就使用PackageParse去解析了,所以对比10.0系统,谷歌很明显优化了PMS在解析apk文件时的操作,启动了线程池去解析,因此现在新版的安卓手机开机时间会比以前快很多。而9.0和11.0也是启动了线程池去解析的,这里就不展示源码,感兴趣可自行搜索查看。

/   解析   /

上文parallelPackageParser的submit()里已经知道,里面会使用线程池,然后调用parsePackage()方法,该方法就是进行解析,把清单文件里的类信息都解析成Package对象返回给PMS:

40d47b79d3becbce839958f7907a8a26.png

现在来看看parsePackage()方法里的详情:

@VisibleForTestingprotected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,int parseFlags) throws PackageParser.PackageParserException {return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);}

接着跟踪packageParser的parsePackge()方法:

/*** Equivalent to {@link #parsePackage(File, int, boolean)} with {@code useCaches == false}.*/@UnsupportedAppUsagepublic Package parsePackage(File packageFile, int flags) throws PackageParserException {return parsePackage(packageFile, flags, false /* useCaches */);}

返回的是Package的parsePackage()方法,所以继续跟踪:

public Package parsePackage(File packageFile, int flags, boolean useCaches)throws PackageParserException {Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;if (parsed != null) {return parsed;}long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;if (packageFile.isDirectory()) {parsed = parseClusterPackage(packageFile, flags);} else {parsed = parseMonolithicPackage(packageFile, flags);}long cacheTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;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;}

首先就是判断有无缓存useCaches,有则从缓存里直接调用getCachedResult()获取这个解析对象Package,没有缓存则重新解析,这种缓存机制也是9.0之后才有,9.0之前的版本是没有缓存,每次都需重新解析(所以这也是优化解析性能的一个点,感兴趣可自行查看源码对比):

继续往下看解析方法可以看到,里面有个判断packageFile.isDirectory(),如果不是目录,也就是有apk文件的,则调用parseMonolithicPackage(),因此继续看该方法的详情:

@Deprecated@UnsupportedAppUsagepublic Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {final PackageLite lite = parseMonolithicPackageLite(apkFile, flags);if (mOnlyCoreApps) {if (!lite.coreApp) {throw new PackageParserException(INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,"Not a coreApp: " + apkFile);}}final SplitAssetLoader assetLoader = new DefaultSplitAssetLoader(lite, flags);try {final Package pkg = parseBaseApk(apkFile, assetLoader.getBaseAssetManager(), flags);pkg.setCodePath(apkFile.getCanonicalPath());pkg.setUse32bitAbi(lite.use32bitAbi);return pkg;} catch (IOException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,"Failed to get path: " + apkFile, e);} finally {IoUtils.closeQuietly(assetLoader);}}

代码虽然有点长,但还是可以看到中间调用了parseBaseApk()方法:

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)throws PackageParserException {final String apkPath = apkFile.getAbsolutePath();String volumeUuid = null;if (apkPath.startsWith(MNT_EXPAND)) {final int end = apkPath.indexOf('/', MNT_EXPAND.length());volumeUuid = apkPath.substring(MNT_EXPAND.length(), end);}mParseError = PackageManager.INSTALL_SUCCEEDED;mArchiveSourcePath = apkFile.getAbsolutePath();if (DEBUG_JAR) Slog.d(TAG, "Scanning base APK: " + apkPath);XmlResourceParser parser = null;try {final int cookie = assets.findCookieForPath(apkPath);if (cookie == 0) {throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,"Failed adding asset path: " + apkPath);}parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);final Resources res = new Resources(assets, mMetrics, null);final String[] outError = new String[1];final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);if (pkg == null) {throw new PackageParserException(mParseError,apkPath + " (at " + parser.getPositionDescription() + "): " + outError[0]);}pkg.setVolumeUuid(volumeUuid);pkg.setApplicationVolumeUuid(volumeUuid);pkg.setBaseCodePath(apkPath);pkg.setSigningDetails(SigningDetails.UNKNOWN);return pkg;} catch (PackageParserException e) {throw e;} catch (Exception e) {throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,"Failed to read manifest from " + apkPath, e);} finally {IoUtils.closeQuietly(parser);}}

看到这里相信大家都能知道,parseBaseApk这个方法就是去解析清单文件AndroidManifest.xml,最终得把解析的节点信息都封装在Package对象,一个apk对应一个Package对象,然后返回给PMS,看看这个Package类:

public final static class Package implements Parcelable {@UnsupportedAppUsagepublic String packageName;// The package name declared in the manifest as the package can be// renamed, for example static shared libs use synthetic package names.public String manifestPackageName;...// For now we only support one application per package.@UnsupportedAppUsagepublic ApplicationInfo applicationInfo = new ApplicationInfo();@UnsupportedAppUsagepublic final ArrayList<Permission> permissions = new ArrayList<Permission>(0);@UnsupportedAppUsagepublic final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);@UnsupportedAppUsagepublic final ArrayList<Activity> activities = new ArrayList<Activity>(0);@UnsupportedAppUsagepublic final ArrayList<Activity> receivers = new ArrayList<Activity>(0);@UnsupportedAppUsagepublic final ArrayList<Provider> providers = new ArrayList<Provider>(0);@UnsupportedAppUsagepublic final ArrayList<Service> services = new ArrayList<Service>(0);@UnsupportedAppUsagepublic final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);@UnsupportedAppUsagepublic final ArrayList<String> requestedPermissions = new ArrayList<String>();...
}

可以看到,有包名信息,还有权限集合,以及四大组件对应的存储集合,每个集合存放对应的类型(Activity、Service、Provider和PermissionGroup等)。

我们继续看回parseBaseApk()方法:

f59d8ed9bf3e94d3a91e47c0f6bb78d9.png

openXmlResourceParser()方法打开的就是我们的清单文件,也就是解析xml文件的节点信息,最后返回解析对象,然后把解析对象又传到了下面调用的parseBaseApk()方法里:

7b1ac958b4ba46a35fe45e494adfe2b9.png

来看parseBaseApk方法的详情:

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,String[] outError) throws XmlPullParserException, IOException {final String splitName;final String pkgName;...final Package pkg = new Package(pkgName);TypedArray sa = res.obtainAttributes(parser,com.android.internal.R.styleable.AndroidManifest);pkg.mVersionCode = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_versionCode, 0);pkg.mVersionCodeMajor = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_versionCodeMajor, 0);pkg.applicationInfo.setVersionCode(pkg.getLongVersionCode());pkg.baseRevisionCode = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_revisionCode, 0);pkg.mVersionName = sa.getNonConfigurationString(com.android.internal.R.styleable.AndroidManifest_versionName, 0);if (pkg.mVersionName != null) {pkg.mVersionName = pkg.mVersionName.intern();}pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);pkg.mCompileSdkVersion = sa.getInteger(com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);if (pkg.mCompileSdkVersionCodename != null) {pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();}pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;sa.recycle();return parseBaseApkCommon(pkg, null, res, parser, flags, outError);}

该方法是为了获取到app的版本号等信息(清单文件里定义的sdkVersion等),最后返回parseBaseApkCommon()方法:

private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,IOException {...int outerDepth = parser.getDepth();while ((type = parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {continue;}String tagName = parser.getName();if (acceptedTags != null && !acceptedTags.contains(tagName)) {Slog.w(TAG, "Skipping unsupported element under <manifest>: "+ tagName + " at " + mArchiveSourcePath + " "+ parser.getPositionDescription());XmlUtils.skipCurrentTag(parser);continue;}if (tagName.equals(TAG_APPLICATION)) {if (foundApp) {if (RIGID_PARSER) {outError[0] = "<manifest> has more than one <application>";mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;return null;} else {Slog.w(TAG, "<manifest> has more than one <application>");XmlUtils.skipCurrentTag(parser);continue;}}foundApp = true;if (!parseBaseApplication(pkg, res, parser, flags, outError)) {return null;}}...else if (tagName.equals(TAG_PERMISSION_GROUP)) {if (!parsePermissionGroup(pkg, flags, res, parser, outError)) {return null;}} else if (tagName.equals(TAG_PERMISSION)) {if (!parsePermission(pkg, res, parser, outError)) {return null;}}...
}

可以看到关键的代码,while循环里获取清单文件AndroidManifest.xml里每个节点,对不同的节点进行相应的解析方法,我们可以来看看其中一个方法,比如parseBaseApplication():

private boolean parseBaseApplication(Package owner, Resources res,XmlResourceParser parser, int flags, String[] outError)throws XmlPullParserException, IOException {final ApplicationInfo ai = owner.applicationInfo;final String pkgName = owner.applicationInfo.packageName;TypedArray sa = res.obtainAttributes(parser,com.android.internal.R.styleable.AndroidManifestApplication);...String tagName = parser.getName();if (tagName.equals("activity")) {Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs, false,owner.baseHardwareAccelerated);if (a == null) {mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;return false;}hasActivityOrder |= (a.order != 0);owner.activities.add(a);} else if (tagName.equals("receiver")) {Activity a = parseActivity(owner, res, parser, flags, outError, cachedArgs,true, false);if (a == null) {mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;return false;}hasReceiverOrder |= (a.order != 0);owner.receivers.add(a);} else if (tagName.equals("service")) {Service s = parseService(owner, res, parser, flags, outError, cachedArgs);if (s == null) {mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;return false;}hasServiceOrder |= (s.order != 0);owner.services.add(s);} else if (tagName.equals("provider")) {Provider p = parseProvider(owner, res, parser, flags, outError, cachedArgs);if (p == null) {mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;return false;}owner.providers.add(p);}
...
}

可以看到,分别对清单文件里application节点里的元素activity节点和service节点等进行解析与创建,创建的是Package它自己的Activity类和Service类,这些类跟我们平时见到的Activity类和Service类是不同的,是PMS的自定义的内部类,用来存储清单文件里我们设置的类的信息,用相应的集合来存储,最后供AMS创建真正的类时用的:

public final static class Activity extends Component<ActivityIntentInfo> implements Parcelable {@UnsupportedAppUsagepublic final ActivityInfo info;private boolean mHasMaxAspectRatio;private boolean mHasMinAspectRatio;private boolean hasMaxAspectRatio() {return mHasMaxAspectRatio;}private boolean hasMinAspectRatio() {return mHasMinAspectRatio;}// To construct custom activity which does not exist in manifestActivity(final Package owner, final String className, final ActivityInfo info) {super(owner, new ArrayList<>(0), className);this.info = info;this.info.applicationInfo = owner.applicationInfo;}...
}

不过这里要注意的是,解析广播组件的时候,用的也是Activity来构造,那是因为广播和Activity在清单文件里设置的时候很像,因此这里也是处于方便的考虑,把广播也认作是Activity。

最后解析出来的Activity对象都被添加到Package里对应的集合里:

db8fe416380cbcf4a06a6fa8eabe2cec.png

也就是这些集合:

@UnsupportedAppUsage
public final ArrayList<Permission> permissions = new ArrayList<Permission>(0);
@UnsupportedAppUsage
public final ArrayList<PermissionGroup> permissionGroups = new ArrayList<PermissionGroup>(0);
@UnsupportedAppUsage
public final ArrayList<Activity> activities = new ArrayList<Activity>(0);
@UnsupportedAppUsage
public final ArrayList<Activity> receivers = new ArrayList<Activity>(0);
@UnsupportedAppUsage
public final ArrayList<Provider> providers = new ArrayList<Provider>(0);
@UnsupportedAppUsage
public final ArrayList<Service> services = new ArrayList<Service>(0);
@UnsupportedAppUsage
public final ArrayList<Instrumentation> instrumentation = new ArrayList<Instrumentation>(0);

我们再来看看parseActivity()方法:

private Activity parseActivity(Package owner, Resources res,XmlResourceParser parser, int flags, String[] outError, CachedComponentArgs cachedArgs,boolean receiver, boolean hardwareAccelerated)throws XmlPullParserException, IOException {...while ((type=parser.next()) != XmlPullParser.END_DOCUMENT&& (type != XmlPullParser.END_TAG|| parser.getDepth() > outerDepth)) {if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {continue;}if (parser.getName().equals("intent-filter")) {ActivityIntentInfo intent = new ActivityIntentInfo(a);if (!parseIntent(res, parser, true /*allowGlobs*/, true /*allowAutoVerify*/,intent, outError)) {return null;}if (intent.countActions() == 0) {Slog.w(TAG, "No actions in intent filter at "+ mArchiveSourcePath + " "+ parser.getPositionDescription());} else {a.order = Math.max(intent.getOrder(), a.order);a.intents.add(intent);}...}...    
}

可以该方法里解析activity节点时,也会遍历activity节点里的子节点,即解析清单文件里面的比如activity节点里的intent-filter信息,把ActivityIntentInfo对象intent解析出来然后放进Activity的intents集合里,这个intents集合是Component类里的II集合(Activity继承Component):

public static abstract class Component<II extends IntentInfo> {@UnsupportedAppUsagepublic final ArrayList<II> intents;@UnsupportedAppUsagepublic final String className;@UnsupportedAppUsagepublic Bundle metaData;@UnsupportedAppUsagepublic Package owner;/** The order of this component in relation to its peers */public int order;ComponentName componentName;String componentShortName;...
}

/   总结   /

相信经过上面的讲解之后,大家都会对PMS有了更深的理解了,而且在开发过程中,我们自己也可以通过PMS去做一些事情,既然通过源码知道了PMS是通过scanDirTracedLI()方法去扫描apk文件里的清单文件,然后把里面的类等信息解析成一个Package对象(通过PackageParser的parsePackage()方法所得),那我们也可以通过反射对应的方法去对一些特定的apk文件进行扫描和解析,然后根据Package对象获取到里面的类信息,去创建我们需要的四大组件类,从而达到我们的app和其他apk进行通信的目的了。还有很多这些类似的应用场景也是可以利用PMS去完成的。

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

LeakCanary是怎么检测到内存泄露的,看完这篇你就懂了

Kotlin Flow响应式编程,StateFlow和SharedFlow

欢迎关注我的公众号

学习技术或投稿

222032073d82647723ac0cdba46032f2.png

3679f3785ce21b7e80ce569674e371b9.jpeg

长按上图,识别图中二维码即可关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/53364.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

chatgpt赋能python:Python模拟点击安卓屏幕完整教程

Python模拟点击安卓屏幕完整教程 随着移动设备的日益普及&#xff0c;安卓系统成为了全球最受欢迎的操作系统之一。而在开发安卓应用时&#xff0c;我们经常需要模拟点击安卓屏幕进行测试和调试。本文将详细介绍如何使用Python模拟点击安卓屏幕。 1. 安装ADB工具 ADB&#x…

必看!TIKTOK TSP跨境服务商入驻详细流程

TIKTOK TSP跨境服务商如何赚钱&#xff1f;需要哪些资质和能力&#xff1f; 跨境电商领域近期最热门的话题&#xff0c;离不开在海外大火的TIKTOK。相较于国内抖音已经相当成熟的电商板块&#xff0c;海外TIKTOK电商直播才刚刚起步&#xff0c;所以&#xff0c;申请找专业团队&…

马来西亚美妆快速增长,卖家如何借力TikTok打造跨境电商爆品?

随着颜值经济的崛起&#xff0c;美妆个护产品正逐渐向“必需品”升级&#xff0c;其中值得注意的是&#xff0c;马来西亚的美妆个护市场正在不断增长。 根据EchoTik的统计&#xff0c;美妆个护是马来西亚站点所有 TikTok Shop 品类中最受欢迎的&#xff0c;平均价格为10.7 美元…

音视频出海,如何乘风破浪?

点击上方“LiveVideoStack”关注我们 策划&#xff1a;Alex、包研 编辑&#xff1a;Alex 出海 年终访谈 #003# 编者按&#xff1a;最近几年&#xff0c;以TikTok为首的泛娱乐出海企业的成功让大家看到了中国之外的巨大市场和机会。但面对紧张的国际形势&#xff0c;严格的监管措…

应用出海活跃,开发教程

移动应用行业在国内显现出用户增量放缓的趋势&#xff0c;多种类型的应用渗透率也渐趋饱和。随着政策支持力度的加大&#xff0c;越来越多移动应用走向了海外市场&#xff0c;拓宽用户群。 根据艾瑞咨询在《2022年移动应用出海趋势洞察白皮书》中指出&#xff0c;游戏类占出海…

简历还能这样写——程序员

求职简历不是一份记流水账的东西&#xff0c;是让面试官短时间内了解你的亮点的。 大部分求职简历都 有以下几个特征&#xff1a; 喜欢说自己的特长、优点&#xff0c;面试官真的不关注你的性格&#xff0c;特长什么的&#xff0c;总不能让你当面唱一个歌吧。 还有喜欢列举一…

简历应该这么写!

很多同学刚开始找工作时&#xff0c;投出去很多简历&#xff0c;但是都石沉大海了&#xff0c;没有后文。之所以简历不通过&#xff0c;往往都是简历不够“好看”。 很多大公司HR经常一天要看几百份&#xff0c;甚至上千份简历&#xff0c;基本都是10秒内看一份简历&#xff0…

chatgpt赋能python:用Python分析微信聊天记录,挖掘更多的信息

用Python分析微信聊天记录&#xff0c;挖掘更多的信息 微信是一款广受欢迎的社交软件&#xff0c;许多人在日常生活中都会使用它来和朋友、同事进行沟通。而微信聊天记录中潜藏着许多有趣的信息&#xff0c;比如谁是你最常联系的好友、聊天时间的分布、聊天内容的情感倾向等等…

中国安卓智能手机公司小米收集用户数据

根据F-Secure的研究&#xff0c;来自于中国的智能手机供应商小米收集并发送用户信息到后台服务器。 该安全公司发现了小米手机数据收集&#xff0c;在调查中这款手机引起了普遍的担忧。 小米手机已经在过去的几个月里为他们的便宜&#xff0c;物有所值的手机和公司动态相关新闻…

Android基础之intent传值方法和传递大量数据方案

https://blog.csdn.net/chenliguan/article/details/47188243 1 Android之intent传值的三种方法 &#xff08;1&#xff09; startActivity(); &#xff08;2&#xff09; startActivityForResult(); &#xff08;3&#xff09;3 调用在下个activity自定义的方法"actionS…

获取手机APP对网络访问数据的一个思路

工作中要用到一款android下的APP软件&#xff0c;其中有一个功能是查看课件的pdf资料&#xff0c;每个资料后面还有一个下载的按钮&#xff0c;但是都是不可用&#xff0c;注释写到不提供下载&#xff0c;估计是考虑到版权问题吧。但真的非常需要其中的资料&#xff0c;怎么办呢…

intent传递较大数据的解决和intent不能传递较大数据的原因

一、首先提供需要在activity之间用intent传递较大数据的方法。intent不能传递较大数据&#xff0c;需要传递数据的时候&#xff0c;写一个操作类&#xff1a; public class DataHelper {private static final DataHelper helper new DataHelper();public static DataHelper ge…

小米手机,程序在后台运行一段时间之后,访问不了网络

小米手机&#xff0c;程序后台运行一段时间后&#xff0c;程序访问不了网络&#xff0c;访问超时&#xff0c;锁屏不锁屏都是一样&#xff0c;&#xff08;不知道QQ为啥可以收到&#xff09; 暂时找到的解决方案&#xff1a; 小米手机--设置--其他高级设置--电源和性能--神隐…

OpenAI API最新速查表;轻松制作数字分身;8个ChatGPT「作弊」策略;微软提示工程官方教程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; The OpenAI API in Python 最新速查表 ShowMeAI知识星球资源编码&#xff1a;R102 大语言模型的发展&#xff0c;正在推动 OpenAI API…

国外电子测量技术投稿经验

国外电子测量技术和电子测量技术是同一系列&#xff0c;2020年入选北大中文核心期刊&#xff0c;相对来说比较好中&#xff0c;审稿专家非常亲和&#xff0c;提出的问题都挺好回复和解答。审稿周期不到两个月&#xff0c;编辑退修以后就特别快了。 以下是投稿过程&#xff1a…

小红书用户消费心理及种草价值:后疫情时代消费心理研究

省时查报告-专业、及时、全面的行研报告库 省时查方案-专业、及时、全面的营销策划方案库 【免费下载】2023年2月份热门报告盘点 ChatGPT种子用户招募&#xff08;无限制次数直接使用&#xff09; 小红书整体框架及玩法大全 小红书爆文笔记进阶指南 罗振宇2023年跨年演讲PPT原稿…

Linux的ip经常变动的解决方法

第一步&#xff1a;查看ip 通过ip addr命令查询本机IP地址&#xff0c;可以看出是ens33网卡   如果我们不设置固定的ip地址&#xff0c;那么IP地址就会自动变更&#xff0c;所以我们通过编辑配置文件将IP地址写死 第二步&#xff1a; 编辑 ens33 网卡的网络配置文件 在Linu…

如何修复和解决 IP 地址冲突

您的网络是否面临 IP 分配错误&#xff0c;或者网络中的某些用户抱怨网络连接丢失&#xff1f;好消息是您的主机或服务器没有运行病毒;相反&#xff0c;它遇到了导致网络连接问题的IP 地址冲突&#xff0c;您可以在几分钟内修复它。 Windows 在网络错误消息中抽象地将此错误的原…

虚拟机中ip地址总是自动变化解决办法

在一开始我创建的虚拟机去用XShell远程连接的时候&#xff0c;我的主机地址总是变化&#xff0c;上一次还是192.168.57.151&#xff0c;下一次就变成了192.168.57.153&#xff0c;再下一次就不知道变成了什么&#xff0c;就很麻烦&#xff0c;每次都要去修改会话属性&#xff0…

python如何解决爬虫ip被封- - -“您操作太频繁,请稍后再访问“

描述 python 3.9.6 pycharm 问题 当我想爬取某招聘网站的信息的时候出现如下信息 {"status":false,"msg":"您操作太频繁,请稍后再访问","clientIp":"113.92.xxx.xxx","state":2402} 原因 招聘网站的反爬机制…