安卓游戏发行-控件注解框架-关键点1

avatar

作者

大家好,我叫祥子😊; 

本人15年毕业于广东药科大学,于2018年8月加入37手游安卓团队,曾经就职于网易担任安卓开发工程师; 

目前是37手游安卓团队负责人,除日常团队相关管理外,空闲喜欢专研安卓相关技术,因为始终坚信 “技术管理" 是一定要持续关注技术,保持对技术的热情,这样才不会是空中楼阁...

背景

(1)正常App开发中,在写Activity或者Fragment时,无法避免的会用到findViewById这类的代码,然后强制类型转换出我们所需要的控件类型,说实话,对于追求代码简洁,高可读,并且想偷懒的程序员来说,写这样的重复代码,简直就是灾难;

所以我们会用到控件注解框架(如:butterknife),来解决上面的困扰,具体细节网上很多博客可以查到(如:butterknife系列),这里就不展开讲。

(2)游戏发行SDK开发中,我们并不能愉快的直接使用网上的框架,为什么?接下来我们来看看;

上面图是发行行业的常规流程,其中可以看到:

  • 游戏研发商接完我们的SDK,给到我们的是一个apk(这里我们叫它母包);
  • 然后我们发行方需要进行重新(二次)打包,也就是《反编译母包-准备渠道和SDK材料-融合-回编译》,重新输出apk上架(这里我们叫它渠道包);
  • 最后是把渠道包上架,用户下载使用

如果我们游戏发行商SDK中,用通过ID找控件(findViewById方式),走上面的流程之后,输出的渠道包,会有找不到控件崩溃的异常,具体原因如下:

  • 接入我们发行商的SDK输出母包,这个SDK中已经有findViewById(R.id.sqBtn),这次编译假设ID的值为0x7f070001,并且对应的资源类为R1类

  • 重新(二次)打包-反编译母包,此时根据resource.arsc文件产生public.xml文件,这个public.xml会固定住ID的值,也就是R.id.sqBtn的值始终为0x7f070001(resource.arsc文件和public.xml文件不了解的同学可以看以前的博客

  • 重新(二次)打包-准备渠道和SDK材料,关于《渠道和SDK材料》这里不展开,相信发行的同学是知道的,这里在生成《渠道和SDK材料》的时候,其实是经过了编译和重新生成了R类的,假设这次生成的R.id.sqBtn的值为0x7f070002,并且对应的资源类为R2类

  • 重新(二次)打包-融合/回编译,融合过程中,会把R2类覆盖R1类

  • 重新(二次)打包-渠道包,由于包以前的R1类变为了R2类,R.id.sqBtn的值变为了0x7f070002;然后app运行,到resource.arsc中查找资源的时候,因为resource.arsc中的值为以前的0x7f070001(因public.xml固定作用),值不一样导致程序异常。

行业普遍的方案

因为不能使用findViewById去找控件,所以大部分情况下,发行行业安卓找控件一般采取的是getIdentifier的方式;例如:getIdentifier(“sqBtn”, ”id” ,pkgName),这样做的弊端是:

  • 效率方面,程序员编码的效率有提高空间(没有编码的自动提示等)

  • 隐患方面,编译的时候不像id写法那样,会检查资源是否存在,如果不小心单词写错,然后又没有测试出来,会出现毁灭性的崩溃,导致线上事故

要解决的2个关键点

(1)关键点一:既然系统的R.id在发行流程中不能用,只能用getIdentifier(“sqBtn”, ”id” ,pkgName)的方式,那么我们就要看看怎么利用资源名 + getIdentifier, 通过某种方式转换为类似ID那样可以提示编程,怎么办呢?

37手游有这么一句话 “方法总比困难多”,通过gradle的插件能力可以制造出自己的SqR,这里我们叫它《自制资源SqR技术》

(2)关键点二:有了符合我们发行领域的SqR资源了,那么接下来就是类似 butterknife 那样利用注解技术实现控件注入框架,这里我们叫它**《自定义注解技术》**

备注:由于涉及的知识点比较多,所以会分开来讲,本章主要讲关键点1

关键点1-自制资源SqR技术(这是个Gradle插件)

使用效果

1)编译时候校验:

2)编码时候提示:

使用简介

1)新增加资源,如:图片/字符串等(一般情况是批量先弄好,再走到第2步)

2)生成SqR资源(除了这样点击之外,也可以弄成AS的快捷键更加方便使用)

3)愉快的使用

实现流程

1)首先需要先构建一个gradle工程,由于这个不是本章重点,故不在这里展开;不了解的同学可以去查相关资料,这里推荐一个浅显易懂的例子,戳这里>>>

2)然后是开始写我们的生成SqR代码了,写之前先看下我们要构造的文件最终模样:

写的核心思想是通过对R.java(工程为app模块)文件或者R.txt(工程为lib模块)文件进行改造

  • 假如工程为app模块,则通过R.java文件改,关键点主要3个,具体看下图:

关键代码简析:

//rPackage:获得包名,以便获取路径 
//例子:com.sq.mobile.sqinject_gradle_plugin 
def rPackage = this.getPackageName(variant)println(rPackage)rPackage = rPackage.replace(".", "/")


//获得资源任务(the Android Resources processing task)
def variantOutput = variant.outputs.first()
def processResources = variantOutput.processResourcesProvider.get()
def rFiles = project.files(processResources.textSymbolOutputFile).builtBy(processResources)

//获得R.txt路径
//R.txt路径:sqinjectgradleplugin/app/build/intermediates/symbols/debug/R.txt
def RFilePath = rFiles.singleFile.absolutePath


//获得输出路径
//sqinjectgradleplugin/app/build/generated/not_namespaced_r_class_sources/debug/r
def outputDir = processResources.getSourceOutputDir()

//将原先的R文件的int换成String,并将其值使用变量名赋值
//tempFile = sqinjectgradleplugin/app/build/generated/not_namespaced_r_class_sources/debug/r/com/sq/mobile/sqinject_gradle_plugin/R.javaFile 
tempFile = new File(outputDir.absolutePath + File.separator + rPackage + File.separator + "R.java");
println("R file path: " + tempFile.absolutePath)
rFileContent = tempFile.textPattern 
pattern = Pattern.compile("public static(.*?) int (.*?)=(.*?);")
Matcher matcher = pattern.matcher(rFileContent);
while (matcher.find()) {    
  String replace = "public static final String " + matcher.group(2) + " = \"" + matcher.group(2) + "\";";    
  rFileContent = rFileContent.replaceAll(matcher.group(), replace)
}
rFileContent = rFileContent.replaceAll("class R", "class SqR")
  • 假如工程为lib模块,则通过R.txt文件改,关键点在于把下图进行规整,构建我们要的文件格式

关键代码简析:

源码

请戳这里>>>

总结

文章主要介绍了控件注入框架的关键点1,利用插件方式实现getIdentifier的ID化;

下一篇将介绍关键点2,利用注解技术实现SqR的注解使用,最后实现类似 butterknife 框架

;这个框架目前在我们内部已经广泛使用,它命名为《SqInject》框架,目前申请专利中;

后续我们除了会讲解关键点2之外,会一并把框架开源出来,敬请期待>>>

欢迎交流

过程中有问题或者需要交流的同学,可以扫描二维码加好友,然后进群进行问题和技术的交流等;