if (rType.equals(RType.STYLEABLE)) {
int count = 0;
for (Node attrNode = node.getFirstChild(); attrNode != null; attrNode = attrNode.getNextSibling()) {
if (attrNode.getNodeType() != Node.ELEMENT_NODE || !attrNode.getNodeName().equals(“attr”)) {
continue;
}
String rawAttrName = extractNameAttribute(attrNode);
String attrName = sanitizeName(rType, resourceCollector, rawAttrName);
if (!rawAttrName.startsWith(“android:”)) {
resourceCollector.addIntResourceIfNotPresent(RType.ATTR, attrName);
}
}
} else {
resourceCollector.addIntResourceIfNotPresent(rType, resourceName);
}
}
如果不是styleable的资源,则直接获取resourceName,然后调用resourceCollector.addIntResourceIfNotPresent(rType, resourceName)。
如果是styleable类型的资源,则会遍历找到其内部的attr节点,找出非android:
开头的(因为android:开头的attr的id不需要我们去确定),设置rType为ATTR,value为attr属性的name,调用addIntResourceIfNotPresent。
public void addIntResourceIfNotPresent(RType rType, String name) { //, ResourceDirectory resourceDirectory) {
if (!rTypeEnumeratorMap.containsKey(rType)) {
if (rType.equals(RType.ATTR)) {
rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(1));
} else {
rTypeEnumeratorMap.put(rType, new ResourceIdEnumerator(currentTypeId++));
}
}
RDotTxtEntry entry = new FakeRDotTxtEntry(IdType.INT, rType, name);
Set resourceSet = null;
if (this.rTypeResourceMap.containsKey(rType)) {
resourceSet = this.rTypeResourceMap.get(rType);
} else {
resourceSet = new HashSet();
this.rTypeResourceMap.put(rType, resourceSet);
}
if (!resourceSet.contains(entry)) {
String idValue = String.format(“0x%08x”, rTypeEnumeratorMap.get(rType).next());
addResource(rType, IdType.INT, name, idValue); //, resourceDirectory);
}
}
首先构建一个entry,然后判断当前的rTypeResourceMap中是否存在该资源实体,如果存在,则什么都不用做。
如果不存在,则需要构建一个entry,那么主要是id的构建。
关于id的构建:
还记得rTypeEnumeratorMap么,其内部包含了我们设置的”res mapping”文件,存储了每一类资源(rType)的资源的最大resourceId值。
那么首先判断就是是否已经有这种类型了,如果有的话,获取出该类型当前最大的resourceId,然后+1,最为传入资源的resourceId.
如果不存在当前这种类型,那么如果类型为ATTR则固定type为1;否则的话,新增一个typeId,为当前最大的type+1(currentTypeId中也是记录了目前最大的type值),有了类型就可以通过ResourceIdEnumerator.next()来获取id。
经过上述就可以构造出一个idValue了。
最后调用:
addResource(rType, IdType.INT, name, idValue);
查看代码:
public void addResource(RType rType, IdType idType, String name, String idValue) {
Set resourceSet = null;
if (this.rTypeResourceMap.containsKey(rType)) {
resourceSet = this.rTypeResourceMap.get(rType);
} else {
resourceSet = new HashSet();
this.rTypeResourceMap.put(rType, resourceSet);
}
RDotTxtEntry rDotTxtEntry = new RDotTxtEntry(idType, rType, name, idValue);
if (!resourceSet.contains(rDotTxtEntry)) {
if (this.originalResourceMap.containsKey(rDotTxtEntry)) {
this.rTypeEnumeratorMap.get(rType).previous();
rDotTxtEntry = this.originalResourceMap.get(rDotTxtEntry);
}
resourceSet.add(rDotTxtEntry);
}
}
大体意思就是如果该资源不存在就添加到rTypeResourceMap。
首先构建出该资源实体,判断该类型对应的资源集合是否包含该资源实体(这里contains只比对name和type),如果不包含,判断是否在originalResourceMap中,如果存在(这里做了一个previous操作,其实与上面的代码的next操作对应,主要是针对资源存在我们的res map中这种情况)则取出该资源实体,最终将该资源实体加入到rTypeResourceMap中。
ok,到这里需要小节一下,我们刚才对所有values相关文件夹下的文件已经处理完毕,大致的处理为:遍历文件中的节点,大致有item,dimen,color,drawable,bool,integer,array,style,declare-styleable,attr,fraction这些节点,将所有的节点按类型分类存储到rTypeResourceMap中(如果和设置的”res map”文件中相同name和type的节点不需要特殊处理,直接复用即可;如果不存在则需要生成新的typeId、resourceId等信息)。
其中declare-styleable
这个标签,主要读取其内部的attr标签,对attr标签对应的资源按上述处理。
处理完成values相关文件夹之后,还需要处理一些res下的其他文件,比如layout、layout、anim等文件夹,该类资源也需要在R中生成对应的id值,这类值也需要固化。
processFileNamesInDirectory
public static void processFileNamesInDirectory(String resourceDirectory,
AaptResourceCollector resourceCollector) throws IOException {
File resourceDirectoryFile = new File(resourceDirectory);
String directoryName = resourceDirectoryFile.getName();
int dashIndex = directoryName.indexOf(‘-’);
if (dashIndex != -1) {
directoryName = directoryName.substring(0, dashIndex);
}
if (!RESOURCE_TYPES.containsKey(directoryName)) {
throw new AaptUtilException(resourceDirectoryFile.getAbsolutePath() + " is not a valid resource sub-directory.");
}
File[] fileArray = resourceDirectoryFile.listFiles();
if (fileArray != null) {
for (File file : fileArray) {
if (file.isHidden()) {
continue;
}
String filename = file.getName();
int dotIndex = filename.indexOf(‘.’);
String resourceName = dotIndex != -1 ? filename.substring(0, dotIndex) : filename;
RType rType = RESOURCE_TYPES.get(directoryName);
resourceCollector.addIntResourceIfNotPresent(rType, resourceName);
System.out.println("rType = " + rType + " , resName = " + resourceName);
ResourceDirectory resourceDirectoryBean = new ResourceDirectory(file.getParentFile().getName(), file.getAbsolutePath());
resourceCollector.addRTypeResourceName(rType, resourceName, null, resourceDirectoryBean);
}
}
}
遍历res下所有文件夹,根据文件夹名称确定其对应的资源类型(例如:drawable-xhpi,则认为其内部的文件类型为drawable类型),然后遍历该文件夹下所有的文件,最终以文件名为资源的name,文件夹确定资源的type,最终调用:
resourceCollector
.addIntResourceIfNotPresent(rType, resourceName);
processXmlFilesForIds
public static void processXmlFilesForIds(String resourceDirectory,
List references, AaptResourceCollector resourceCollector) throws Exception {
List xmlFullFilenameList = FileUtil
.findMatchFile(resourceDirectory, Constant.Symbol.DOT + Constant.File.XML);
if (xmlFullFilenameList != null) {
for (String xmlFullFilename : xmlFullFilenameList) {
File xmlFile = new File(xmlFullFilename);
String parentFullFilename = xmlFile.getParent();
File parentFile = new File(parentFullFilename);
if (isAValuesDirectory(parentFile.getName()) || parentFile.getName().startsWith(“raw”)) {
// Ignore files under values* directories and raw*.
continue;
}
processXmlFile(xmlFullFilename, references, resourceCollector);
}
}
}
遍历除了raw*
以及values*
相关文件夹下的xml文件,执行processXmlFile。
public static void processXmlFile(String xmlFullFilename, List references, AaptResourceCollector resourceCollector)
throws IOException, XPathExpressionException {
Document document = JavaXmlUtil.parse(xmlFullFilename);
NodeList nodesWithIds = (NodeList) ANDROID_ID_DEFINITION.evaluate(document, XPathConstants.NODESET);
for (int i = 0; i < nodesWithIds.getLength(); i++) {
String resourceName = nodesWithIds.item(i).getNodeValue();
if (!resourceName.startsWith(ID_DEFINITION_PREFIX)) {
throw new AaptUtilException(“Invalid definition of a resource: '” + resourceName + “'”);
}
resourceCollector.addIntResourceIfNotPresent(RType.ID, resourceName.substring(ID_DEFINITION_PREFIX.length()));
}
// 省略了无关代码
}
主要找xml文档中以@+
(去除@+android:id
),其实就是找到我们自定义id节点,然后截取该节点的id值部分作为属性的名称(例如:@+id/tv
,tv即为属性的名称),最终调用:
resourceCollector
.addIntResourceIfNotPresent(RType.ID,
resourceName.substring(ID_DEFINITION_PREFIX.length()));
上述就完成了所有的资源的收集,那么剩下的就是写文件了:
public static void generatePublicResourceXml(AaptResourceCollector aaptResourceCollector,
String outputIdsXmlFullFilename,
String outputPublicXmlFullFilename) {
if (aaptResourceCollector == null) {
return;
}
FileUtil.createFile(outputIdsXmlFullFilename);
FileUtil.createFile(outputPublicXmlFullFilename);
PrintWriter idsWriter = null;
PrintWriter publicWriter = null;
try {
FileUtil.createFile(outputIdsXmlFullFilename);
FileUtil.createFile(outputPublicXmlFullFilename);
idsWriter = new PrintWriter(new File(outputIdsXmlFullFilename), “UTF-8”);
publicWriter = new PrintWriter(new File(outputPublicXmlFullFilename), “UTF-8”);
idsWriter.println(“<?xml version=\"1.0\" encoding=\"utf-8\"?>”);
publicWriter.println(“<?xml version=\"1.0\" encoding=\"utf-8\"?>”);
idsWriter.println(“”);
publicWriter.println(“”);
Map<RType, Set> map = aaptResourceCollector.getRTypeResourceMap();
Iterator<Entry<RType, Set>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Entry<RType, Set> entry = iterator.next();
RType rType = entry.getKey();
if (!rType.equals(RType.STYLEABLE)) {
Set set = entry.getValue();
for (RDotTxtEntry rDotTxtEntry : set) {
String rawName = aaptResourceCollector.getRawName(rType, rDotTxtEntry.name);
if (StringUtil.isBlank(rawName)) {
rawName = rDotTxtEntry.name;
}
publicWriter.println(“<public type=”" + rType + “” name=“” + rawName + “” id=“” + rDotTxtEntry.idValue.trim() + “” />");
}
Set ignoreIdSet = aaptResourceCollector.getIgnoreIdSet();
for (RDotTxtEntry rDotTxtEntry : set) {
if (rType.equals(RType.ID) && !ignoreIdSet.contains(rDotTxtEntry.name)) {
idsWriter.println(“<item type=”" + rType + “” name=“” + rDotTxtEntry.name + “”/>");
}
}
}
idsWriter.flush();
publicWriter.flush();
}
idsWriter.println(“”);
publicWriter.println(“”);
} catch (Exception e) {
throw new PatchUtilException(e);
} finally {
if (idsWriter != null) {
idsWriter.flush();
idsWriter.close();
}
if (publicWriter != null) {
publicWriter.flush();
publicWriter.close();
}
}
}
主要就是遍历rTypeResourceMap,然后每个资源实体对应一条public
标签记录写到public.xml
中。
此外,如果发现该元素节点的type为Id,并且不在ignoreSet中,会写到ids.xml这个文件中。(这里有个ignoreSet,这里ignoreSet中记录了values下所有的<item type=id
的资源,是直接在项目中已经声明过的,所以去除)。
六、TinkerProguardConfigTask
还记得文初说:
- 我们在上线app的时候,会做代码混淆,如果没有做特殊的设置,每次混淆后的代码差别应该非常巨大;所以,build过程中理论上需要设置混淆的mapping文件。
- 在接入一些库的时候,往往还需要配置混淆,比如第三方库中哪些东西不能被混淆等(当然强制某些类在主dex中,也可能需要配置相对应的混淆规则)。
这个task的作用很明显了。有时候为了确保一些类在main dex中,简单的做法也会对其在混淆配置中进行keep(避免由于混淆造成类名更改,而使main dex的keep失效)。
如果开启了proguard会执行该task。
这个就是主要去设置混淆的mapping文件,和keep一些必要的类了。
@TaskAction
def updateTinkerProguardConfig() {
def file = project.file(PROGUARD_CONFIG_PATH)
project.logger.error(“try update tinker proguard file with ${file}”)
// Create the directory if it doesnt exist already
file.getParentFile().mkdirs()
// Write our recommended proguard settings to this file
FileWriter fr = new FileWriter(file.path)
String applyMappingFile = project.extensions.tinkerPatch.buildConfig.applyMapping
//write applymapping
if (shouldApplyMapping && FileOperation.isLegalFile(applyMappingFile)) {
project.logger.error(“try add applymapping ${applyMappingFile} to build the package”)
fr.write("-applymapping " + applyMappingFile)
fr.write(“\n”)
} else {
project.logger.error(“applymapping file ${applyMappingFile} is illegal, just ignore”)
}
fr.write(PROGUARD_CONFIG_SETTINGS)
fr.write(“#your dex.loader patterns here\n”)
//they will removed when apply
Iterable loader = project.extensions.tinkerPatch.dex.loader
for (String pattern : loader) {
if (pattern.endsWith(“*”) && !pattern.endsWith(“**”)) {
pattern += “*”
}
fr.write("-keep class " + pattern)
fr.write(“\n”)
}
fr.close()
// Add this proguard settings file to the list
applicationVariant.getBuildType().buildType.proguardFiles(file)
def files = applicationVariant.getBuildType().buildType.getProguardFiles()
project.logger.error(“now proguard files is ${files}”)
}
读取我们设置的mappingFile,设置
-applymapping applyMappingFile
然后设置一些默认需要keep的规则:
PROGUARD_CONFIG_SETTINGS =
“-keepattributes Annotation \n” +
“-dontwarn com.tencent.tinker.anno.AnnotationProcessor \n” +
“-keep @com.tencent.tinker.anno.DefaultLifeCycle public class *\n” +
“-keep public class * extends android.app.Application {\n” +
" *;\n" +
“}\n” +
“\n” +
“-keep public class com.tencent.tinker.loader.app.ApplicationLifeCycle {\n” +
" *;\n" +
“}\n” +
“-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n” +
" *;\n" +
“}\n” +
“\n” +
“-keep public class com.tencent.tinker.loader.TinkerLoader {\n” +
" *;\n" +
“}\n” +
“-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n” +
" *;\n" +
“}\n” +
“-keep public class com.tencent.tinker.loader.TinkerTestDexLoad {\n” +
" *;\n" +
“}\n” +
“\n”
最后是keep住我们的application、com.tencent.tinker.loader.**
以及我们设置的相关类。
TinkerManifestTask中:addApplicationToLoaderPattern主要是记录自己的application类名和tinker相关的一些load class
com.tencent.tinker.loader.*
,记录在project.extensions.tinkerPatch.dex.loader
。
七、TinkerMultidexConfigTask
对应文初:
当项目比较大的时候,我们可能会遇到方法数超过65535的问题,我们很多时候会通过分包解决,这样就有主dex和其他dex的概念。集成了tinker之后,在应用的Application启动时会非常早的就去做tinker的load操作,所以就决定了load相关的类必须在主dex中。
如果multiDexEnabled开启。
主要是让相关类必须在main dex。
“-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {\n” +
" *;\n" +
“}\n” +
“\n” +
“-keep public class * extends com.tencent.tinker.loader.TinkerLoader {\n” +
" *;\n" +
“}\n” +
“\n” +
“-keep public class * extends android.app.Application {\n” +
" *;\n" +
“}\n”
Iterable loader = project.extensions.tinkerPatch.dex.loader
for (String pattern : loader) {
if (pattern.endsWith(“*”)) {
if (!pattern.endsWith(“**”)) {
pattern += “*”
}
}
lines.append(“-keep class " + pattern + " {\n” +
" *;\n" +
“}\n”)
.append(“\n”)
}
相关类都在loader这个集合中,在TinkerManifestTask中设置的。
八、TinkerPatchSchemaTask
主要执行Runner.tinkerPatch
protected void tinkerPatch() {
try {
//gen patch
ApkDecoder decoder = new ApkDecoder(config);
decoder.onAllPatchesStart();
decoder.patch(config.mOldApkFile, config.mNewApkFile);
decoder.onAllPatchesEnd();
//gen meta file and version file
PatchInfo info = new PatchInfo(config);
info.gen();
//build patch
PatchBuilder builder = new PatchBuilder(config);
builder.buildPatch();
} catch (Throwable e) {
e.printStackTrace();
goToError();
}
}
主要分为以下环节:
-
生成patch
-
生成meta-file和version-file,这里主要就是在assets目录下写一些键值对。(包含tinkerId以及配置中configField相关信息)
-
build patch
(1)生成pacth
顾名思义就是两个apk比较去生成各类patch文件,那么从一个apk的组成来看,大致可以分为:
-
dex文件比对的patch文件
-
res文件比对的patch res文件
-
so文件比对生成的so patch文件
看下代码:
public boolean patch(File oldFile, File newFile) throws Exception {
//check manifest change first
manifestDecoder.patch(oldFile, newFile);
unzipApkFiles(oldFile, newFile);
Files.walkFileTree(mNewApkDir.toPath(), new ApkFilesVisitor(config, mNewApkDir.toPath(),
mOldApkDir.toPath(), dexPatchDecoder, soPatchDecoder, resPatchDecoder));
soPatchDecoder.onAllPatchesEnd();
dexPatchDecoder.onAllPatchesEnd();
manifestDecoder.onAllPatchesEnd();
resPatchDecoder.onAllPatchesEnd();
//clean resources
dexPatchDecoder.clean();
soPatchDecoder.clean();
resPatchDecoder.clean();
return true;
}
代码内部包含四个Decoder:
- manifestDecoder
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
Android高级架构师
由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。
- 330页PDF Android学习核心笔记(内含上面8大板块)
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
- Android BAT部分大厂面试题(有解析)
好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算
部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新**
如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
[外链图片转存中…(img-TnGlQgn5-1712317973182)]
Android高级架构师
由于篇幅问题,我呢也将自己当前所在技术领域的各项知识点、工具、框架等汇总成一份技术路线图,还有一些架构进阶视频、全套学习PDF文件、面试文档、源码笔记。
- 330页PDF Android学习核心笔记(内含上面8大板块)
[外链图片转存中…(img-M5ubh9ZQ-1712317973183)]
[外链图片转存中…(img-zPFrZTnq-1712317973183)]
-
Android学习的系统对应视频
-
Android进阶的系统对应学习资料
[外链图片转存中…(img-ybnnbXcr-1712317973183)]
- Android BAT部分大厂面试题(有解析)
[外链图片转存中…(img-Uf5hYxhu-1712317973184)]
好了,以上便是今天的分享,希望为各位朋友后续的学习提供方便。觉得内容不错,也欢迎多多分享给身边的朋友哈。
一个人可以走的很快,但一群人才能走的更远。如果你从事以下工作或对以下感兴趣,欢迎戳这里加入程序员的圈子,让我们一起学习成长!
AI人工智能、Android移动开发、AIGC大模型、C C#、Go语言、Java、Linux运维、云计算、MySQL、PMP、网络安全、Python爬虫、UE5、UI设计、Unity3D、Web前端开发、产品经理、车载开发、大数据、鸿蒙、计算机网络、嵌入式物联网、软件测试、数据结构与算法、音视频开发、Flutter、IOS开发、PHP开发、.NET、安卓逆向、云计算