android intent 源码,Android源码--Intent的查找和匹配

前言

在Android开发中,Intent 是极其重要的一个类,它是各个组件、进程之间通讯的纽带。那么系统是如何通过Intent 来查找到对应的组件呢?比如Activity 跳转,用户设置好了一个Intent之后 ,系统如何查找与匹配符合要求的Activity? 这就是本篇需要学习的内容。

App信息表的创建

在文章Android源码中单例模式 中,我们知道系统启动之后就会注册各种系统服务,如WindowManagerService 、AcitivityManagerService等,其中有一个就是PackageManagerService (后续简称PMS)。PMS 启动之后,会扫描系统中已经安装的apk 目录,例如系统App安装的目录为/system/app ,第三方应用的目录为/data/app,PMS 会解析apk包下的AndroidMainfest.xml文件得到App的相关信息。AndroidMainfest.xml又包含了Activity、Service 等组件的注册信息 ,当PMS 扫描并且解析完成这些信息之后就会构建好了整个apk 的信息树,大致流程如图:

420395facf88

解析apk文件.png

PMS解析已安装的apk

PMS是一个尽职尽责的类, 对于apk的解析工作它在构造函数时,就已经开始了!我们看下PMS 的部分构造函数代码:

synchronized (mInstallLock) {

// writer

synchronized (mPackages) {

...

//获取data目录

File dataDir = Environment.getDataDirectory();

// /data/app 目录也就是第三方app安装的目录

mAppInstallDir = new File(dataDir, "app");

...

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

...

// Find base frameworks (resource packages without code).

//加载frameworks资源

scanDirTracedLI(frameworkDir, mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR

| PackageParser.PARSE_IS_PRIVILEGED,

scanFlags | SCAN_NO_DEX, 0);

// Collect ordinary system packages.

//获取系统app安装路径

final File systemAppDir = new File(Environment.getRootDirectory(), "app");

//扫描系统app安装路径

scanDirTracedLI(systemAppDir, mDefParseFlags

| PackageParser.PARSE_IS_SYSTEM

| PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);

...

//扫描第三方app安装路径

scanDirTracedLI(mAppInstallDir, 0, scanFlags | SCAN_REQUIRE_KNOWN, 0);

...

//扫描已经安装完的apk包

scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);

从上述代码中可以看到,PMS 不仅需要加载系统已经安装的apk ,在此之前还有加载Framework资源,加载了Framework资源之后才开始对扫描的指定目录的apk文件进行解析,上述代码只给出了系统apk的安装目录和第三方应用安装目录,扫描函数为scanDirTracedLI 我们看看这里面的实现:

private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {

scanDirLI(dir, parseFlags, scanFlags, currentTime);

}

private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {

//获取该目录下的所有文件

final File[] files = dir.listFiles();

...

// Submit files for parsing in parallel

//解析目录下所有的apk文件

int fileCount = 0;

for (File file : files) {

final boolean isPackage = (isApkFile(file) || file.isDirectory())

&& !PackageInstallerService.isStageName(file.getName());

//如果不是apk文件 忽略

if (!isPackage) {

// Ignore entries which are not packages

continue;

}

//解析apk文件

parallelPackageParser.submit(file, parseFlags);

fileCount++;

}

}

public static final boolean isApkFile(File file) {

return isApkPath(file.getName());

}

//是否已.apk结尾

public static boolean isApkPath(String path) {

return path.endsWith(".apk");

}

scanDirLI 就是扫描指定目录下的所有apk文件,然后通过调用submit 进行解析,重点应该在submit 方法中,我们继续往下看:

/**

* 提交文件解析

* Submits the file for parsing

* @param scanFile file to scan

* @param parseFlags parse falgs

*/

public void submit(File scanFile, int parseFlags) {

mService.submit(() -> {

//解析结果

ParseResult pr = new ParseResult();

try {

//创建一个包解析器

PackageParser pp = new PackageParser();

pp.setSeparateProcesses(mSeparateProcesses);

pp.setOnlyCoreApps(mOnlyCore);

pp.setDisplayMetrics(mMetrics);

pp.setCacheDir(mCacheDir);

pp.setCallback(mPackageParserCallback);

pr.scanFile = scanFile;

//解析apk包

pr.pkg = parsePackage(pp, scanFile, parseFlags);

} catch (Throwable e) {

pr.throwable = e;

} finally {

Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);

}

//将解析结果放入队列

mQueue.put(pr);

});

}

//调用PackageParser中的parsePackage方法解析

@VisibleForTesting

protected PackageParser.Package parsePackage(PackageParser packageParser, File scanFile,

int parseFlags) throws PackageParser.PackageParserException {

return packageParser.parsePackage(scanFile, parseFlags, true /* useCaches */);

}

在scanDirLI 方法中首先构造了一个PackageParser, 也就是一个apk解析器,然后调用PackageParser中的parsePackage方法解析,具体代码:

public Package parsePackage(File packageFile, int flags, boolean useCaches)

throws PackageParserException {

Package parsed = useCaches ? getCachedResult(packageFile, flags) : null;

if (packageFile.isDirectory()) {

//是文件夹类型 解析整个文件夹

parsed = parseClusterPackage(packageFile, flags);

} else {

//解析单个apk

parsed = parseMonolithicPackage(packageFile, flags);

}

//缓存结果

cacheResult(packageFile, flags, parsed);

return parsed;

}

在parsePackage 方法中会根据packageFile 的类型来选择不同的解析方法,我们直接看解析单个apk的parseMonolithicPackage 方法;

@Deprecated

public Package parseMonolithicPackage(File apkFile, int flags) throws PackageParserException {

...

//构造应用资源

final AssetManager assets = newConfiguredAssetManager();

try {

//会调用parseBaseApk(apkPath, res, parser, flags, outError)

final Package pkg = parseBaseApk(apkFile, assets, flags);

pkg.setCodePath(apkFile.getAbsolutePath());

pkg.setUse32bitAbi(lite.use32bitAbi);

return pkg;

} finally {

IoUtils.closeQuietly(assets);

}

}

在parseMonolithicPackage 方法中我们看到会调用有三个参数的parseBaseApk(apkFile, assets, flags);这三个参数的parseBaseApk 会调用五个参数的parseBaseApk ,我们先看下三个参数的parseBaseApk :

//三个参数的parseBaseApk

private Package parseBaseApk(File apkFile, AssetManager assets, int flags)

throws PackageParserException {

//获取apk绝对路径

final String apkPath = apkFile.getAbsolutePath();

...

Resources res = null;

XmlResourceParser parser = null;

try {

//获取资源

res = new Resources(assets, mMetrics, null);

//获取解析ANDROID_MANIFEST_FILENAME清单文件的解析器

parser = assets.openXmlResourceParser(cookie, ANDROID_MANIFEST_FILENAME);

final String[] outError = new String[1];

//调用五个参数的parseBaseApk解析清单文件

final Package pkg = parseBaseApk(apkPath, res, parser, flags, outError);

...

}

}

//五个参数的parseBaseApk

private Package parseBaseApk(String apkPath, Resources res, XmlResourceParser parser, int flags,

String[] outError) throws XmlPullParserException, IOException {

...

//创建一个包实例

final Package pkg = new Package(pkgName);

//获取清单文件中属性

TypedArray sa = res.obtainAttributes(parser,

com.android.internal.R.styleable.AndroidManifest);

//获取清单文件中版本号

pkg.mVersionCode = pkg.applicationInfo.versionCode = sa.getInteger(

com.android.internal.R.styleable.AndroidManifest_versionCode, 0);

//获取清单文件中版本名

pkg.mVersionName = sa.getNonConfigurationString(

com.android.internal.R.styleable.AndroidManifest_versionName, 0);

//回收

sa.recycle();

//真正解析清单文件

return parseBaseApkCommon(pkg, null, res, parser, flags, outError);

}

解析清单文件

上面代码主要是获取清单文件的信息,真正解析清单文件的各个节点的方法是parseBaseApkCommon,我们看下如何实现的:

private Package parseBaseApkCommon(Package pkg, Set acceptedTags, Resources res,

XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,

IOException {

...

//解析AndroidMainfest中的元素

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();

//解析application标签 四大组件都在这里解析

if (tagName.equals(TAG_APPLICATION)) {

...

if (!parseBaseApplication(pkg, res, parser, flags, outError)) {

return null;

}

//解析权限标签

} else if (tagName.equals(TAG_PERMISSION)) {

if (!parsePermission(pkg, res, parser, outError)) {

return null;

}

}

return pkg;

}

这个parseBaseApkCommon 方法才是真正解析AndroidMainfest.xml的方法,这里给出了两个标签,即Application 和permission 。Application 中包含Activity、``Service等标签,也就是Intent 所需要的标签。我们看下Application 标签的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;

//获取Application标签下的TypedArray

TypedArray sa = res.obtainAttributes(parser,

com.android.internal.R.styleable.AndroidManifestApplication);

//解析applicatinon的属性 包括name lable icon logo roundIcon

if (!parsePackageItemInfo(owner, ai, outError,

"", sa, false /*nameRequired*/,

com.android.internal.R.styleable.AndroidManifestApplication_name,

com.android.internal.R.styleable.AndroidManifestApplication_label,

com.android.internal.R.styleable.AndroidManifestApplication_icon,

com.android.internal.R.styleable.AndroidManifestApplication_roundIcon,

com.android.internal.R.styleable.AndroidManifestApplication_logo,

com.android.internal.R.styleable.AndroidManifestApplication_banner)) {

sa.recycle();

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

...

//解析Application标签下的Activity等标签

final int innerDepth = parser.getDepth();

int type;

while ((type = parser.next()) != XmlPullParser.END_DOCUMENT

&& (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {

if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {

continue;

}

//获取标签名

String tagName = parser.getName();

//解析Activity

if (tagName.equals("activity")) {

Activity a = parseActivity(owner, res, parser, flags, outError, false,

owner.baseHardwareAccelerated);

owner.activities.add(a);

//解析receiver

} else if (tagName.equals("receiver")) {

Activity a = parseActivity(owner, res, parser, flags, outError, true, false);

owner.receivers.add(a);

//解析service

} else if (tagName.equals("service")) {

Service s = parseService(owner, res, parser, flags, outError);

owner.services.add(s);

//解析provider

} else if (tagName.equals("provider")) {

Provider p = parseProvider(owner, res, parser, flags, outError);

owner.providers.add(p);

//解析meta-data

} else if (parser.getName().equals("meta-data")) {

// note: application meta-data is stored off to the side, so it can

// remain null in the primary copy (we like to avoid extra copies because

// it can be large)

if ((owner.mAppMetaData = parseMetaData(res, parser, owner.mAppMetaData,

outError)) == null) {

mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;

return false;

}

}

return true;

}

看到Activity、Service、Provider、Received等标签时这个过程我们有了深刻的了解,从parseBaseApplication 中我们看到这个过程就是普通的xml 解析,根据不同的标签调用不同的解析方法,例如,解析Activity 会调用parseActivity 方法,然后返回一个Activity的实例, 并将这个实例添加到Package对象的activitys的列表中。

PackageManagerService的scanPackageTracedLI

此时,我们需要回到上述构造方法scanDirTracedLI 解析完成之后的scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null); 方法,该函数的具体实现如下:

private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,

final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user) {

final PackageParser.Package scannedPkg;

// 调用scanPackageLI进行扫描apk包

scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);

return scannedPkg;

}

//scanPackageLI方法

private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user){

...

//调用scanPackageDirtyLI进行扫描apk包

final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,

currentTime, user);

return res;

}

private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,

final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user){

...

//提交包信息设置

commitPackageSettings(pkg, pkgSetting, user, scanFlags,

(policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);

...

return pkg;

}

以上函数依次调用,最后scanPackageDirtyLI 函数会调用commitPackageSettings 对所解析完成的包提交信息,我们看下具体实现:

*/

private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,

UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {

...

synchronized (mPackages) {

int N = pkg.providers.size();

int i;

for (i=0; i

PackageParser.Provider p = pkg.providers.get(i);

p.info.processName = fixProcessName(pkg.applicationInfo.processName,

p.info.processName);

mProviders.addProvider(p);

...

}

}

N = pkg.services.size();

for (i=0; i

PackageParser.Service s = pkg.services.get(i);

s.info.processName = fixProcessName(pkg.applicationInfo.processName,

s.info.processName);

mServices.addService(s);

}

N = pkg.receivers.size();

for (i=0; i

PackageParser.Activity a = pkg.receivers.get(i);

a.info.processName = fixProcessName(pkg.applicationInfo.processName,

a.info.processName);

mReceivers.addActivity(a, "receiver");

}

N = pkg.activities.size();

for (i=0; i

PackageParser.Activity a = pkg.activities.get(i);

a.info.processName = fixProcessName(pkg.applicationInfo.processName,

a.info.processName);

mActivities.addActivity(a, "activity");

}

}

}

我们看到,这里将上一步解析的Activity、 Service添加到mActivities 、mServices 中,这些类型定义是PMS 的字段,我们看看下面的程序。

public class PackageManagerService extends IPackageManager.Stub

implements PackageSender {

...

// All available activities, for your resolving pleasure.

final ActivityIntentResolver mActivities =

new ActivityIntentResolver();

// All available receivers, for your resolving pleasure.

final ActivityIntentResolver mReceivers =

new ActivityIntentResolver();

// All available services, for your resolving pleasure.

final ServiceIntentResolver mServices = new ServiceIntentResolver();

// All available providers, for your resolving pleasure.

final ProviderIntentResolver mProviders = new ProviderIntentResolver();

...

}

到这一步,整个已安装的apk的信息树就建立起来了,每个apk的应用名、包名、图标、Activity、Service等信息都存储在系统中,当用户使用Intent跳转到某个Activity或者启动某个Service时,系统则会到这个信息表中进行查找,符合要求的组建就会被启动。这样就通过Intent将整个系统的组件连接在一起,使得Android系统成为一个组件可复用、灵活的系统。

Intent的精确匹配

上面分析了apk 信息表的构建过程,下面我们分析一下Intent 的查找与匹配的过程。在开发中,我们需要启动每个具体的Activity ,代码大致是这样的:

Intent intent = new Intent(this, MyActivity.class);

startActivity(intent);

这种情况下指定了具体的组件,也就是MyActivty,此时在系统查找组件时会使用精确匹配,我们称为显示Intent ,还有一种情况是不指定具体的组件,而是给出一些模糊的查询属性,例如:

Intent intent = new Intent(Intent.ACTION_SEND);

startActivity(intent);

这类我们称为隐式Intent。

下面我们一起看看这些Intent的查找与匹配过程,startActivity这个函数经过几个函数的转发,最终会调用startActivityForResult,我们看看这个函数。

*/

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,

@Nullable Bundle options) {

Instrumentation.ActivityResult ar =

//启动Activity

mInstrumentation.execStartActivity(

this, mMainThread.getApplicationThread(), mToken, this,

intent, requestCode, options);

...

//发送启动请求

mMainThread.sendActivityResult(mToken, mEmbeddedID, requestCode, ar.getResultCode(),ar.getResultData());

}

Activity中的startActivityForResult 函数直接调用了Instrumentation 的execStartActivity 方法,具体代码如下:

public ActivityResult execStartActivity(

Context who, IBinder contextThread, IBinder token, Activity target,

Intent intent, int requestCode, Bundle options) {

...

try {

//将Intent中的数据迁移到粘贴板中

intent.migrateExtraStreamToClipData();

//准备离开当前进程

intent.prepareToLeaveProcess(who);

//调用AMS的startActivity

int result = ActivityManager.getService()

.startActivity(whoThread, who.getBasePackageName(), intent,

intent.resolveTypeIfNeeded(who.getContentResolver()),

token, target != null ? target.mEmbeddedID : null,

requestCode, 0, null, options);

checkStartActivityResult(result, intent);

}

return null;

}

execStartActivity 里面其实就是调用ActivityManagerService 的startActivity 方法,这个方法里面调用了

ActivityStarter对象的startActivityMayWait 方法,这个方法里面调用了ActivityStackSupervisor的resolveIntent 方法,这个方法里面最后调用了PMS 的resolveIntent 方法,PMS 又出现在了我们的视野中,在resolveIntent 方法中就调用了自身的queryIntentActivitiesInternal 方法,这个方法返回一个ResolveInfo的列表,

private @NonNull List queryIntentActivitiesInternal(Intent intent,

String resolvedType, int flags, int userId) {

//获取Intent的ComponentName对象

ComponentName comp = intent.getComponent();

//精确跳转时这个对象不为空

if (comp != null) {

final List list = new ArrayList(1);

//通过Intent直接获取ActivityInfo对象

final ActivityInfo ai = getActivityInfo(comp, flags, userId);

if (ai != null) {

final ResolveInfo ri = new ResolveInfo();

ri.activityInfo = ai;

list.add(ri);

}

return list;

}

...

//Component为空则为隐式Intent

synchronized (mPackages) {

...

if (pkgName == null) {

//包名为空就模糊匹配

result = filterIfNotSystemUser(mActivities.queryIntent(

intent, resolvedType, flags, userId), userId);

...

}else{

//通过包名获取到Package对象

final PackageParser.Package pkg = mPackages.get(pkgName);

if (pkg != null) {

result = filterIfNotSystemUser(

//通过获取Package对象获取到ResoloveInfo

mActivities.queryIntentForPackage(intent, resolvedType, flags, pkg.activities, userId),userId);

...

}

}

return result;

}

上述函数大致过程:如果Intent 指明了Comonet ,那么直接通过Componet 就可以找到ActivityInfo 列表,这个列表的数量只有一个,这个ActivityInfo 就是指定的那个组件,如果没有指定具体的组件,那么Component

为空,此时先看Intent 是否指定了要调转到的目标组件所在的包名,如果没有包名,调用queryIntent 模糊匹配,例如Action、Category等,如果有包名,就会调用queryIntentForPackage 通过包名获取到对应的ActivityInfo。这里需要注意mActivities 是就是上一节说到的存储了从AndroidMainfest.xml中解析到的Activity 既然已经找到了对应的Activity的信息,那么最好就是启动对应的Activity 。对于显示Intent来说,就是直接跳转到具体的Activity 中,对于隐式Intent 来说,可能会弹出系统的列表框让用户选择相应的应用。

总结

在系统启动时PackageManagerService 会启动,此时PMS 将解析所有已安装的应用的信息,构建一个信息表,当用户通过Intent 来跳转到某个组件时,会根据Intent 中包含的信息到PMS 中查找对应的组件列表,最后跳转到目标组件中。

参考

《Android源码设计模式》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值