您好,登錄后才能下訂單哦!
這篇文章主要介紹“如何使用run代碼”,在日常操作中,相信很多人在如何使用run代碼問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”如何使用run代碼”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
復現一次語法錯誤的代碼:
正文
一、android知識部分
IDE提示的也很明白:res的id不能在library級別的module中的switch語法中應用。原因是res的id不是常量。
注意:同樣的代碼在application級別的module中是沒有語法問題的。所以對于res的id來說,application中是常量,library中不是常量。如果有同學看過R的內容,就會發現的確如此:
這個是application中的R文件:
這個是library中的R文件:
這個顯現引申出一個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引用不是常量,就意味著這種用法是不能工作的:
可以看到,注解也是要常量的,所以這個問題對我們日常影響還是挺大的...等等!Butterknife就是注解的這種用法,為什么沒有問題??
深入了解過Butterknife的同學應該知道,Butterknife針對這種情況進行了特殊處理:
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是干啥的?
二、編譯原理
我們都知道:日常我們寫下的代碼,最終想要運行在目標機器上都需要編譯成目標機器能夠識別的機器碼。而做這些工作的我們稱之為編譯器。一般編譯器就是干了如下的事情:
圖片來自《編譯原理》第二版
在各種源碼編譯的實現中,基本都不約而同地抽象出一個概念:抽象語法樹(AST),以求在整個編譯實現過程更加的方便。
一句話解釋抽象語法樹:源代碼語法結構的一種抽象表示。它以樹狀的形式表現編程語言的語法結構,樹上的每個節點都表示源代碼中的一種結構。
咱們粗略了解了編譯器的的實現流程,那么編譯器又是怎么實現的呢?當然是用代碼實現的咯,而且它們的實現往往離我們很近...以我們java編譯器為例。
入坑Java時,我們應該都試過javac。而這個命令的實現在哪?就在JDK里的tools.jar中的com.sun.tools.javac.Main包下。核心邏輯在于com.sun.tools.javac.main.JavaCompiler。
這里邊就實現了如何分析我們的源碼,如何轉化成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) } }
基于這一套api我們是能夠獲取到源碼的任何信息的。而且這段demo代碼,只需要導入tools.jar就可以快速運行,成本非常的低。
三、用代碼run代碼
上述我們通過JavaCompiler的實例,對java源碼進行了動態的編譯,拿到的結果就是這個java源碼的class文件。有了class文件,我們就可以通過ClassLoader去加載這個class。
有了上邊的基礎,實現源碼已經不重要,這里貼一個鏈接大家自取吧:How do you dynamically compile and load external java classes?[4]
到此,關于“如何使用run代碼”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。