奇怪的知識點:用代碼run代碼

前言

人閑下來就會對各種各樣的東西感到好奇欢唾,好奇的東西多了就發(fā)現(xiàn)自己是真的菜鳄厌。

今天這篇文章寫出來的原因岂贩,源自一次非常非赤拖辏“詭異的”IDE的語法錯誤提示个扰。

文章是由android的知識引入,但真正想聊的東西是編譯原理葱色。所以:才有了標題《奇怪的知識》递宅。因此各位看官沒必要太糾結(jié)自己沒有學過android或者Java,不影響閱讀~

正文

復現(xiàn)一次語法錯誤的代碼:

image.png

android知識部分

IDE提示的也很明白:res的id不能在library級別的module中的switch語法中應(yīng)用苍狰。原因是res的id不是常量办龄。

注意:同樣的代碼在application級別的module中是沒有語法問題的。所以對于res的id來說舞痰,application中是常量土榴,library中不是常量诀姚。如果有同學看過R的內(nèi)容响牛,就會發(fā)現(xiàn)的確如此:

這個是application中的R文件:

image.png

這個是library中的R文件:

image.png

這個顯現(xiàn)引申出一個android打包的知識點:aapt過程中的資源合并

一句話描述這個知識點:不同module之間的重復的資源會按優(yōu)先級的進行合并覆蓋赫段。這個流程引發(fā)的問題呀打,很多老司機都遇到過,資源被覆蓋了糯笙,我們引用的資源永遠會被指向唯一的res贬丛。這肯定是不符合預(yù)期的。

因此諸如給資源名加前綴的方案便應(yīng)運而生给涕。

為什么不是final

這里咱們聊一個問題:常量有什么特別之處豺憔?下面的代碼,編譯之后就是能看到常量的特別之處:

class TestFinal {
    static final int sInt = 1;

    void testFinal(){
        int temp = sInt;
        System.out.println(temp);
    }
}

編譯后的代碼會是這樣:

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

會發(fā)現(xiàn)編譯器的優(yōu)化够庙,會把常量直接內(nèi)聯(lián)到代碼引用之處恭应。那么這邊咱們想想:如果library里的res也是常量會出現(xiàn)什么問題?

常量被內(nèi)聯(lián)耘眨,一旦發(fā)生項目中資源重復昼榛,打包過程中就出現(xiàn)覆蓋,那么內(nèi)聯(lián)的常量已經(jīng)不能映射到真正的資源上了剔难,因為資源已經(jīng)被覆蓋胆屿。

不是final引發(fā)的問題

library中的R引用不是常量,就意味著這種用法也是不能工作的:

image.png

可以看到偶宫,注解也是要常量的非迹,所以這個問題對我們印象還是挺大的...等等!Butterknife就是注解的這種用法為什么沒有問題纯趋?憎兽?

深入了解過Butterknife的同學應(yīng)該知道,Butterknife針對這種情況進行了特殊處理:

image.png

Butterknife的方案

Butterknife為了不讓注解處出現(xiàn)語法錯誤,自己創(chuàng)造了一個叫做R2的類唇兑。這個類其實就是原樣copy了R酒朵,唯一不同就是,R2都是常量扎附。

的確這樣不會有語法錯誤蔫耽,但是咱們剛才也分析了:常量內(nèi)聯(lián),資源覆蓋留夜。所以一旦滿足case匙铡,那就是crash。所以Butterknife有時如何規(guī)避這個問題的呢碍粥?

看過Butterknife中findViewById()源碼的同學應(yīng)該都是到鳖眼,此處Butterknife的實現(xiàn)大概是這樣:

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

我們能夠看到,Butterknife最終打進包里的代碼嚼摩,并沒有發(fā)生常量內(nèi)聯(lián)钦讳!所以它是怎么做的呢?

看到這里的同學枕面,不妨停下來愿卒。自己想想如果是你,你會怎么解決這個問題潮秘?這里我說說我能想到的方案

ASM階段琼开,把內(nèi)聯(lián)的代碼,再給它改寫成R的正常引用枕荞。問題就來了:ASM的輸入是class柜候,這個時機我沒辦法再拿到R的正常引用了。那如果繼續(xù)提前這個干預(yù)的過程躏精,放到APT階段呢渣刷?
試了一下,也沒有搞定玉控。APT階段拿到的注解value也已經(jīng)是被內(nèi)聯(lián)的常量了...

這就有點奇怪了飞主,Butterknife是如何做到通過內(nèi)聯(lián)的常量和R引用的映射呢?翻看了Butterknife的源碼高诺,發(fā)現(xiàn)Butterknife是在APT階段執(zhí)行的碌识,關(guān)鍵類在ButterKnifeProcessor

Butterknife通過JCTree這個api拿到了R的引用虱而,然后把內(nèi)聯(lián)的代碼又改回了R的引用筏餐。具體的api實現(xiàn)咱們就不看了,有興趣的同學可以自行g(shù)ithub牡拇。

咱們接下來聊一聊這個JCTree是干啥的魁瞪?

編譯原理

我們都知道:日常我們寫下的代碼穆律,最終想要運行在目標機器上都需要編譯成目標機器能夠識別的機器碼。而做這些工作的我們稱之為編譯器导俘。一般編譯器就是干了如下的事情:

圖片來自《編譯原理》第二版

image.png

在各種源碼編譯的實現(xiàn)中峦耘,基本都不約而同地抽象出一個概念個:抽象語法樹(AST),以求在整個編譯實現(xiàn)過程更加的方便旅薄。

一句話解釋抽象語法樹:源代碼語法結(jié)構(gòu)的一種抽象表示辅髓。它以樹狀的形式表現(xiàn)編程語言的語法結(jié)構(gòu),樹上的每個節(jié)點都表示源代碼中的一種結(jié)構(gòu)少梁。

咱們粗略了解了編譯器的的實現(xiàn)流程洛口,那么編譯器又是怎么實現(xiàn)的呢?當然是用代碼實現(xiàn)的咯凯沪,而且它們的實現(xiàn)往往離我們很近...以我們java編譯器為例第焰。

入坑Java時,我們應(yīng)該都試過javac妨马。而這個命令的實現(xiàn)在哪挺举?就在JDK里的tools.jar中的com.sun.tools.javac.Main包下。核心邏輯在于com.sun.tools.javac.main.JavaCompiler身笤。

image.png

這里邊就實現(xiàn)了如何分析我們的源碼豹悬,如何轉(zhuǎn)化成class。也就上那個圖中編譯器該干的事液荸。

那么JCTree在整個編譯過程中充當什么角色呢?一句話:JCTree是對源碼的一種api級別的描述脱篙〗壳或者說JCTree是java編譯流程中語法樹的實現(xiàn)。

也就是說通過JCTree相關(guān)api绊困,我們可以訪問到源碼結(jié)構(gòu)文搂。說起來似乎很抽象,我們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)
    }
}
image.png

基于這一套api我們是能夠獲取到源碼的任何信息的秤朗。而且這段demo代碼煤蹭,只需要導入tools.jar就可以快速運行,成本非常的低取视。

用代碼run代碼

上述我們同過JavaCompiler的實例硝皂,對java源碼進行了動態(tài)的編譯,拿到的結(jié)果就是這個java源碼的class文件作谭。有了class文件稽物,我們就可以通過ClassLoader去加載這個class。

有了上邊的基礎(chǔ)折欠,實現(xiàn)源碼已經(jīng)不重要贝或,這里貼一個鏈接大家自取吧:How do you dynamically compile and load external java classes?

尾聲

我個人沒有正經(jīng)的學過編譯原理吼过,所以了解這部分內(nèi)容時,覺得還是挺神奇的咪奖。也希望這篇文章能對同樣沒有學過編譯原理的同學帶來一些思考和啟發(fā)~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末盗忱,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子羊赵,更是在濱河造成了極大的恐慌售淡,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件慷垮,死亡現(xiàn)場離奇詭異揖闸,居然都是意外死亡,警方通過查閱死者的電腦和手機料身,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門汤纸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人芹血,你說我怎么就攤上這事贮泞。” “怎么了幔烛?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵啃擦,是天一觀的道長。 經(jīng)常有香客問我饿悬,道長令蛉,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任狡恬,我火速辦了婚禮珠叔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弟劲。我一直安慰自己祷安,他們只是感情好,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布兔乞。 她就那樣靜靜地躺著汇鞭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪庸追。 梳的紋絲不亂的頭發(fā)上霍骄,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音锚国,去河邊找鬼腕巡。 笑死,一個胖子當著我的面吹牛血筑,可吹牛的內(nèi)容都是我干的绘沉。 我是一名探鬼主播煎楣,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼车伞!你這毒婦竟也來了择懂?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤另玖,失蹤者是張志新(化名)和其女友劉穎困曙,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谦去,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡慷丽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了鳄哭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片要糊。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妆丘,靈堂內(nèi)的尸體忽然破棺而出锄俄,到底是詐尸還是另有隱情,我是刑警寧澤勺拣,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布奶赠,位于F島的核電站,受9級特大地震影響药有,放射性物質(zhì)發(fā)生泄漏毅戈。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一塑猖、第九天 我趴在偏房一處隱蔽的房頂上張望竹祷。 院中可真熱鬧,春花似錦羊苟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阻桅,卻和暖如春凉倚,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背嫂沉。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工稽寒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人趟章。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓杏糙,卻偏偏與公主長得像慎王,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宏侍,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容