如何使用run代码

发布时间:2021-10-15 16:21:24 作者:iii
来源:亿速云 阅读:175

这篇文章主要介绍“如何使用run代码”,在日常操作中,相信很多人在如何使用run代码问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”如何使用run代码”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

复现一次语法错误的代码:

如何使用run代码

正文

一、android知识部分

IDE提示的也很明白:res的id不能在library级别的module中的switch语法中应用。原因是res的id不是常量。

注意:同样的代码在application级别的module中是没有语法问题的。所以对于res的id来说,application中是常量,library中不是常量。如果有同学看过R的内容,就会发现的确如此:

这个是application中的R文件:

如何使用run代码

这个是library中的R文件:

如何使用run代码

这个显现引申出一个android打包的知识点:aapt[1]过程中的资源合并[2]。

一句话描述这个知识点:不同module之间的重复的资源会按优先级的进行合并覆盖。这个流程引发的问题,很多老司机都遇到过,资源被覆盖了,我们引用的资源永远会被指向唯一的res。这肯定是不符合预期的。

因此诸如给资源名加前缀的方案便应运而生。

为什么不是final

这里咱们聊一个问题:常量有什么特别之处?下面的代码,编译之后就是能看到常量的特别之处:

class TestFinal {     static final int sInt = 1;      void testFinal(){         int temp = sInt;         System.out.println(temp);     } }

编译后的代码会是这样:

public void testFinal(){     System.out.println(1); }

会发现编译器的优化,会把常量直接内联到代码引用之处。那么咱们想想:如果library里的res也是常量会出现什么问题?

常量被内联,一旦发生项目中资源重复,打包过程中就出现覆盖,那么内联的常量已经不能映射到真正的资源上了,毕竟资源已经被覆盖。

也就是会出现:资源找不到的crash

不是final引发的问题

library中的R引用不是常量,就意味着这种用法是不能工作的:

如何使用run代码

可以看到,注解也是要常量的,所以这个问题对我们日常影响还是挺大的...等等!Butterknife就是注解的这种用法,为什么没有问题??

深入了解过Butterknife的同学应该知道,Butterknife针对这种情况进行了特殊处理:

如何使用run代码

Butterknife的方案

Butterknife为了不让注解处出现语法错误,自己创造了一个叫做R2的类。这个类其实就是原样copy了R,唯一不同就是R2都是常量。

的确这样不会有语法错误,但是咱们刚才也分析了:常量内联,资源覆盖。所以一旦满足case,那就是crash。所以Butterknife又是如何规避这个问题的呢?

看过Butterknife中findViewById()源码的同学应该都知道,此处Butterknife的实现大概是这样:

public TestActivity_ViewBinding(T target, View source) {     this.target = target;     target.parentLayout = Utils.findRequiredViewAsType(source, R.id.test, "field 'parentLayout'", ViewGroup.class);

我们能够看到,Butterknife最终打进包里的代码,并没有发生常量内联!所以它是怎么做的呢?

看到这里的同学,不妨停下来想想,如果是你会怎么解决这个问题?这里我说说我能想到的方案:

ASM阶段,把内联的代码,再给它改写成R的正常引用。问题就来了:ASM的输入是class,这个时机我没办法再拿到R的正常引用了。

那如果继续提前这个干预的过程,放到APT阶段呢?试了一下,也没有搞定。APT阶段拿到的注解value也已经是被内联的常量了...

这就有点奇怪了,Butterknife是如何做到通过内联的常量和R引用的映射呢?翻看了Butterknife的源码,发现Butterknife是在APT阶段执行的,关键类在ButterKnifeProcessor[3]。

Butterknife通过JCTree这个api拿到了R的引用,然后把内联的代码又改回了R的引用。具体的api实现咱们就不看了,有兴趣的同学可以自行github。

咱们接下来聊一聊这个JCTree是干啥的?

二、编译原理

我们都知道:日常我们写下的代码,最终想要运行在目标机器上都需要编译成目标机器能够识别的机器码。而做这些工作的我们称之为编译器。一般编译器就是干了如下的事情:

图片来自《编译原理》第二版

如何使用run代码

在各种源码编译的实现中,基本都不约而同地抽象出一个概念:抽象语法树(AST),以求在整个编译实现过程更加的方便。

一句话解释抽象语法树:源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。

咱们粗略了解了编译器的的实现流程,那么编译器又是怎么实现的呢?当然是用代码实现的咯,而且它们的实现往往离我们很近...以我们java编译器为例。

入坑Java时,我们应该都试过javac。而这个命令的实现在哪?就在JDK里的tools.jar中的com.sun.tools.javac.Main包下。核心逻辑在于com.sun.tools.javac.main.JavaCompiler。

如何使用run代码

这里边就实现了如何分析我们的源码,如何转化成class。也就上那个图中编译器该干的事。

那么JCTree在整个编译过程中充当什么角色呢?一句话:JCTree是对源码的一种api级别的描述。或者说JCTree是java编译流程中语法树的实现。

也就是说通过JCTree相关api,我们可以访问到源码结构。说起来似乎很抽象,我们debug个一段代码就能get到它存在的意义了:

fun main() {     val context = Context()     val scanner = RScanner()     val javaCompiler = JavaCompiler.instance(context)     val testJavaCodeFile = File("/Users/x/xx/xxx/TestAutoCode.java")      ToolProvider         .getSystemJavaCompiler()         .getStandardFileManager(DiagnosticCollector(), null, null)         .getJavaFileObjectsFromFiles(listOf(testJavaCodeFile))         .forEach {             javaCompiler.parse(it).defs.forEach {                 scanner.scan(it)             }          } }  class RScanner : TreeScanner() {     override fun visitMethodDef(tree: JCTree.JCMethodDecl?) {         super.visitMethodDef(tree)     } }

如何使用run代码

基于这一套api我们是能够获取到源码的任何信息的。而且这段demo代码,只需要导入tools.jar就可以快速运行,成本非常的低。

三、用代码run代码

上述我们通过JavaCompiler的实例,对java源码进行了动态的编译,拿到的结果就是这个java源码的class文件。有了class文件,我们就可以通过ClassLoader去加载这个class。

有了上边的基础,实现源码已经不重要,这里贴一个链接大家自取吧:How do you dynamically compile and load  external java classes?[4]

到此,关于“如何使用run代码”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. docker run 命令
  2. UNEXPECTED INCONSISTENCY: RUN fsck MANUALLY

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:go-zero实践中的缓存设计之如何使用biz cache

下一篇:如何理解C++11标准中<future>头文件里面的类和相关函数

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》