热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

奇怪的知识点:用代码run代码

前言人闲下来就会对各种各样的东西感到好奇,好奇的东西多了就发现自己是真的菜。今天这篇文章写出来的原因,源自一次非常非常“诡异的”IDE的语法错误提示。
前言

人闲下来就会对各种各样的东西感到好奇,好奇的东西多了就发现自己是真的菜。

今天这篇文章写出来的原因,源自一次非常非常“诡异的”IDE的语法错误提示。

文章是由android的知识引入,但真正想聊的东西是编译原理。所以:才有了标题《奇怪的知识》。因此各位看官没必要太纠结自己没有学过android或者Java,不影响阅读~

正文

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

奇怪的知识点:用代码run代码
image.png

android知识部分

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

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

这个是application中的R文件:

奇怪的知识点:用代码run代码
image.png

这个是library中的R文件:

奇怪的知识点:用代码run代码
image.png

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

一句话描述这个知识点:不同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也是常量会出现什么问题?

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

不是final引发的问题

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

奇怪的知识点:用代码run代码
image.png

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

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

奇怪的知识点:用代码run代码
image.png

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。

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

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

编译原理

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

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

奇怪的知识点:用代码run代码
image.png

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

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

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

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

奇怪的知识点:用代码run代码
image.png

这里边就实现了如何分析我们的源码,如何转化成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代码
image.png

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

用代码run代码

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

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

尾声

我个人没有正经的学过编译原理,所以了解这部分内容时,觉得还是挺神奇的。也希望这篇文章能对同样没有学过编译原理的同学带来一些思考和启发~


推荐阅读
  • 涉及的知识点-ViewGroup的测量与布局-View的测量与布局-滑动冲突的处理-VelocityTracker滑动速率跟踪-Scroller实现弹性滑动-屏幕宽高的获取等实现步 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • Week04面向对象设计与继承学习总结及作业要求
    本文总结了Week04面向对象设计与继承的重要知识点,包括对象、类、封装性、静态属性、静态方法、重载、继承和多态等。同时,还介绍了私有构造函数在类外部无法被调用、static不能访问非静态属性以及该类实例可以共享类里的static属性等内容。此外,还提到了作业要求,包括讲述一个在网上商城购物或在班级博客进行学习的故事,并使用Markdown的加粗标记和语句块标记标注关键名词和动词。最后,还提到了参考资料中关于UML类图如何绘制的范例。 ... [详细]
  • 本文详细介绍了Android中的坐标系以及与View相关的方法。首先介绍了Android坐标系和视图坐标系的概念,并通过图示进行了解释。接着提到了View的大小可以超过手机屏幕,并且只有在手机屏幕内才能看到。最后,作者表示将在后续文章中继续探讨与View相关的内容。 ... [详细]
  • SmartRefreshLayout自定义头部刷新和底部加载
    1.添加依赖implementation‘com.scwang.smartrefresh:SmartRefreshLayout:1.0.3’implementation‘com.s ... [详细]
  • 开发笔记:图像识别基于主成分分析算法实现人脸二维码识别
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了图像识别基于主成分分析算法实现人脸二维码识别相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • IOS开发之短信发送与拨打电话的方法详解
    本文详细介绍了在IOS开发中实现短信发送和拨打电话的两种方式,一种是使用系统底层发送,虽然无法自定义短信内容和返回原应用,但是简单方便;另一种是使用第三方框架发送,需要导入MessageUI头文件,并遵守MFMessageComposeViewControllerDelegate协议,可以实现自定义短信内容和返回原应用的功能。 ... [详细]
  • 今天就跟大家聊聊有关怎么在Android应用中实现一个换肤功能,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根 ... [详细]
  • android 触屏处理流程,android触摸事件处理流程 ? FOOKWOOD「建议收藏」
    android触屏处理流程,android触摸事件处理流程?FOOKWOOD「建议收藏」最近在工作中,经常需要处理触摸事件,但是有时候会出现一些奇怪的bug,比如有时候会检测不到A ... [详细]
  • 详解Android  自定义UI模板设计_由浅入深
    学习安卓已有一些日子,前段时间整理了不少笔记,但是发现笔记不变分享与携带。今天开始整理博客,全当是与大家分享交流与自身学习理解的过程吧。结合最近在做的一个新闻类app及学习中的问题,一点一点整理一下, ... [详细]
author-avatar
fvcvb_974
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有