
上一次我们说AndroidManifest.xml是一个Android应用的身份证。系统通过它认识一个应用知道它叫什么能做什么需要什么。但有一个问题我们没有深入说系统是怎么读这份身份证的读的过程发生了什么读完之后这些信息去了哪里又是怎么被用起来的这些问题藏着Android系统运作的一些核心逻辑。今天我们把它挖出来看看。一、先说一个让人意外的事实你以为系统读的是那个xml文件。但实际上不是。当你构建一个APKAndroidManifest.xml会经历一次变身。原始的AndroidManifest.xml是标准的文本格式XML人可以直接读用任何文本编辑器打开都能看懂。但打进APK包里的是一个二进制格式的XML文件。它叫做AXMLAndroid Binary XML。为什么要转成二进制原因很实际。文本XML解析慢占空间大。二进制XML解析快体积小而且格式固定解析器可以做得非常简单高效。手机的资源有限能省则省。所以系统真正读的是这个二进制版本。但对我们开发者来说这个转换是透明的我们写的是文本XML构建工具自动帮我们转换我们不需要关心。二、AXML长什么样理解解析过程先要知道被解析的东西长什么样。AXML文件是一个有严格结构的二进制文件由几个块组成每个块有固定的格式。大致结构是这样┌─────────────────────────────┐ │ 文件头Header │ │ 魔数 文件大小 │ ├─────────────────────────────┤ │ 字符串池String Pool │ │ 所有字符串集中存储在这里 │ ├─────────────────────────────┤ │ 资源ID表Resource IDs │ │ 属性名对应的资源ID │ ├─────────────────────────────┤ │ XML内容块XML Chunk │ │ 标签的开始、结束、属性值 │ └─────────────────────────────┘最关键的设计是字符串池。在文本XML里字符串是分散的每个地方用到一个字符串就在那个地方写一次。比如android:name这个属性名可能在文件里出现几十次每次都要写一遍。在AXML里所有字符串只存一份放在字符串池里。其他地方用到这个字符串不存字符串本身只存一个整数表示这个字符串在字符串池里的索引。这样重复的字符串只占一份空间文件体积大幅减小。解析的时候遇到一个索引去字符串池里查拿到对应的字符串。这个设计在很多地方都能看到是一种经典的空间优化手段。三、解析的入口在哪里Android系统里负责解析AndroidManifest.xml的主要是两个地方。一个是安装时由PackageManagerService负责。另一个是运行时由ActivityManagerService在需要的时候查询。我们先说安装时的解析。当你安装一个APK系统的PackageManagerService简称PMS被触发。PMS是Android系统里的一个核心服务专门负责管理应用的安装、卸载、查询。它的解析入口在PackageParser这个类里。整个解析流程从这里开始// 简化的伪代码展示流程publicPackageparsePackage(FilepackageFile){// 第一步打开APK文件ApkAssetsapkAssetsApkAssets.loadFromPath(packageFile.getPath());// 第二步找到并读取AndroidManifest.xmlXmlResourceParserparserapkAssets.openXml(AndroidManifest.xml);// 第三步解析XML构建Package对象PackagepkgparseBaseApk(parser);returnpkg;}这里的XmlResourceParser就是处理AXML格式的解析器。它把二进制格式的XML转换成一个可以逐标签读取的流式接口。四、解析器是怎么工作的AXML的解析用的是流式解析的方式。流式解析就像你在读一本书从第一页翻到最后一页一页一页地读不会跳来跳去。解析器维护一个当前位置的指针每次调用next()指针向前移动返回下一个事件。事件有几种类型START_DOCUMENT → 文档开始 START_TAG → 遇到一个开始标签比如 activity END_TAG → 遇到一个结束标签比如 /activity TEXT → 遇到文本内容 END_DOCUMENT → 文档结束解析代码的基本结构是一个循环inttype;while((typeparser.next())!XmlPullParser.END_DOCUMENT){if(typeXmlPullParser.START_TAG){StringtagNameparser.getName();switch(tagName){casemanifest:parseManifest(parser,pkg);break;caseapplication:parseApplication(parser,pkg);break;caseactivity:parseActivity(parser,pkg);break;// ... 其他标签}}}遇到不同的标签调用不同的解析函数。每个解析函数负责读取这个标签的所有属性构建对应的数据对象。五、解析manifest标签拿到应用的基本信息遇到manifest标签解析器做的第一件事是读取package属性。privatevoidparseManifest(XmlPullParserparser,Packagepkg){// 读取包名StringpackageNameparser.getAttributeValue(null,package);pkg.packageNamepackageName;// 读取版本信息intversionCodeparser.getAttributeIntValue(ANDROID_RESOURCES,versionCode,0);StringversionNameparser.getAttributeValue(ANDROID_RESOURCES,versionName);pkg.mVersionCodeversionCode;pkg.mVersionNameversionName;// 继续解析子标签parseManifestChildren(parser,pkg);}包名是整个解析过程里最先确定的信息。因为后续所有的组件注册都需要以包名为前缀。版本信息用于后续的版本比较判断是否需要更新。六、解析application标签构建应用的骨架遇到application标签解析器开始构建应用的核心数据结构。privatevoidparseApplication(XmlPullParserparser,Packagepkg){ApplicationInfoaipkg.applicationInfo;// 读取应用名称ai.nonLocalizedLabelparser.getAttributeValue(ANDROID_RESOURCES,label);// 读取应用图标资源IDai.iconparser.getAttributeResourceValue(ANDROID_RESOURCES,icon,0);// 读取主题资源IDai.themeparser.getAttributeResourceValue(ANDROID_RESOURCES,theme,0);// 读取自定义Application类名Stringnameparser.getAttributeValue(ANDROID_RESOURCES,name);if(name!null){ai.classNamebuildClassName(pkg.packageName,name);}// 读取allowBackupai.flags|parser.getAttributeBooleanValue(ANDROID_RESOURCES,allowBackup,true)?ApplicationInfo.FLAG_ALLOW_BACKUP:0;// 继续解析子标签activity、service、receiver、providerparseApplicationChildren(parser,pkg,ai);}注意这里的ai.flags。Android用一个整数的不同位来存储多个布尔类型的标志。FLAG_ALLOW_BACKUP是其中一位FLAG_DEBUGGABLE是另一位FLAG_SYSTEM是另一位。用位运算来存储多个布尔值是一种节省内存的经典技巧。七、解析Activity注册每一个界面遇到activity标签解析器构建一个ActivityInfo对象。privateActivityparseActivity(XmlPullParserparser,Packagepkg,ApplicationInfoai){ActivityInfoanewActivityInfo();// 读取类名处理相对路径Stringnameparser.getAttributeValue(ANDROID_RESOURCES,name);a.namebuildClassName(pkg.packageName,name);// 读取exported属性booleanexportedparser.getAttributeBooleanValue(ANDROID_RESOURCES,exported,false);// 读取屏幕方向a.screenOrientationparser.getAttributeIntValue(ANDROID_RESOURCES,screenOrientation,ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);// 读取软键盘模式a.softInputModeparser.getAttributeIntValue(ANDROID_RESOURCES,windowSoftInputMode,0);// 解析intent-filter子标签intouterDepthparser.getDepth();inttype;while((typeparser.next())!END_DOCUMENT(type!END_TAG||parser.getDepth()outerDepth)){if(typeSTART_TAGparser.getName().equals(intent-filter)){ActivityIntentInfointentnewActivityIntentInfo(a);parseIntentFilter(parser,intent);a.intents.add(intent);}}pkg.activities.add(a);returna;}这里有一个细节值得注意。解析intent-filter的时候用了parser.getDepth()来控制循环边界。深度是当前标签的嵌套层级。manifest的深度是1application的深度是2activity的深度是3intent-filter的深度是4。当解析器遇到END_TAG且深度回到了activity的深度说明/activity结束标签出现了这个Activity的所有子标签都解析完了退出循环。这种用深度控制递归解析的方式是XML流式解析里的标准做法。八、解析intent-filter建立意图匹配规则intent-filter的解析是整个Manifest解析里逻辑最复杂的部分之一。privatevoidparseIntentFilter(XmlPullParserparser,IntentInfooutInfo){intouterDepthparser.getDepth();inttype;while((typeparser.next())!END_DOCUMENT(type!END_TAG||parser.getDepth()outerDepth)){if(type!START_TAG)continue;StringnodeNameparser.getName();if(nodeName.equals(action)){// 读取actionStringvalueparser.getAttributeValue(ANDROID_RESOURCES,name);outInfo.addAction(value);}elseif(nodeName.equals(category)){// 读取categoryStringvalueparser.getAttributeValue(ANDROID_RESOURCES,name);outInfo.addCategory(value);}elseif(nodeName.equals(data)){// 读取data包括scheme、host、path等Stringschemeparser.getAttributeValue(ANDROID_RESOURCES,scheme);Stringhostparser.getAttributeValue(ANDROID_RESOURCES,host);StringpathPrefixparser.getAttributeValue(ANDROID_RESOURCES,pathPrefix);if(scheme!null)outInfo.addDataScheme(scheme);if(host!null)outInfo.addDataAuthority(host,null);if(pathPrefix!null)outInfo.addDataPath(pathPrefix,PatternMatcher.PATTERN_PREFIX);}}}解析完成后每个intent-filter变成了一个包含actions、categories、data规则的对象。这个对象后来会被用于Intent匹配。当系统收到一个隐式Intent需要找到能处理它的组件就会拿这个Intent去和所有已注册的intent-filter逐一比对。比对规则Intent的action必须在intent-filter的action列表里。Intent的category必须全部包含在intent-filter的category列表里。Intent的data必须符合intent-filter的data规则。三个条件都满足这个组件才能响应这个Intent。九、解析完成之后数据去了哪里解析完成PackageParser返回一个Package对象。这个对象包含了从Manifest里提取的所有信息publicclassPackage{publicStringpackageName;// 包名publicApplicationInfoapplicationInfo;// 应用信息publicArrayListActivityactivities;// 所有ActivitypublicArrayListServiceservices;// 所有ServicepublicArrayListProviderproviders;// 所有ProviderpublicArrayListReceiverreceivers;// 所有BroadcastReceiverpublicArrayListPermissionpermissions;// 声明的权限publicArrayListStringrequestedPermissions;// 申请的权限// ... 更多字段}这个Package对象被传给PackageManagerServicePMS把它存进自己的内存数据结构里。同时PMS还会把关键信息写入磁盘存在/data/system/packages.xml文件里。这个文件是系统级别的应用注册表。手机重启之后PMS不需要重新解析所有APK直接读这个文件就能恢复所有已安装应用的信息。十、运行时这些信息是怎么被用的安装时解析是把信息存起来。运行时这些信息被频繁查询和使用。场景一启动Activity。你调用startActivity(intent)ActivityManagerService收到请求。AMS拿着这个Intent去问PMS谁能处理这个IntentPMS遍历所有已注册的intent-filter找到匹配的Activity返回给AMS。AMS启动那个Activity。场景二权限检查。你的应用调用了一个需要权限的API系统检查你的应用是否有这个权限。系统去查PMS里存的权限信息看你的Manifest里有没有声明这个权限用户有没有授权。没有抛出SecurityException。场景三隐式广播。系统发出一个广播比如开机完成的广播。PMS找出所有注册了BOOT_COMPLETED这个action的BroadcastReceiver。系统逐一通知它们。场景四应用列表。你打开设置看已安装的应用列表。系统从PMS里查询所有已安装的Package读取每个Package的applicationInfo拿到应用名称和图标显示出来。所有这些场景背后都是同一套数据安装时从AndroidManifest.xml解析出来的那些信息。它们被解析被存储被查询被用于系统的每一个角落。你每次点击一个应用图标每次收到一条推送每次系统弹出权限请求背后都有AndroidManifest.xml的影子。它不是一个静态的配置文件它是应用和系统之间持续对话的基础。系统读懂了它才能知道如何对待你的应用。应用写好了它才能告诉系统自己是谁能做什么想要什么。这场对话从安装的那一刻开始贯穿应用的整个生命周期。