亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中兼容Java 8語法

發布時間:2021-05-26 11:51:14 來源:億速云 閱讀:416 作者:Leah 欄目:編程語言

本篇文章為大家展示了怎么在Android中兼容Java 8語法,內容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

Java 8概述

Java 8是Java開發語言非常重要的一個版本。Oracle從2014年3月18日發布Java 8,從該版本起,Java開始支持函數式編程。特別是吸收了運行在JVM上的Scala、Groovy等動態腳本語言的特性之后,Java 8在語言的表達力、簡潔性兩個方面有了很大的提高。

Java 8的主要語言特性改進概括起來包括以下幾點:

  • Lambda表達 (函數閉包)

  • 函數式接口 (@FunctionalInterface)

  • Stream API (通過流式調用支持map、filter等高階函數)

  • 方法引用(使用::關鍵字將函數轉化為對象)

  • 默認方法(抽象接口中允許存在default修飾的非抽象方法)

  • 類型注解和重復注解

其中Lambda表達、函數式接口、方法引用三個特性為Java帶來了函數式編程的風格;而Stream實現了map、filter、reduce等常見的高階函數,數據源囊括了數組、集合、IO通道等,這些又為Java帶來了流式編程或者說鏈式編程的風格,以上這些風格讓Java變得越來越現代化和易用。

Android和Java關系

其實Java在Android的快速發展過程中扮演著非常重要的角色,無論是作為開發語言(Java)、開發Framework(Android-SDK引用了80%的JDK-API),還是開發工具(Eclipse or Android Studio)。這些都和Java有著千絲萬縷的關系。不過可能是受到與Oracle的法律訴訟的影響,Google在Android上針對Java的升級一直都不是很積極:

  • Android 從1.0 一直升級到4.4,迭代了將近19個Android版本,才在4.4版本中支持了Java 7。

  • 然后從Android 4.4版本開始算起,一直到Android N(7.0)共4個Android版本,才在Jack/Jill工具鏈勉強支持了Java 8。但由于Jack/Jill工具鏈在構建流程中舍棄了原有Java字節碼的體系,導致大量既有的技術沉淀無法應用,致使許多App工程放棄了接入。

  • 最后直到Android P(9.0)版本, Google 才在Android Studio 3.x中通過新增的D8 dex編譯器正式支持了Java 8,但部分API并不能全版本支持。

可謂“歷經坎坷”。特別是Rx大行其道的今天,Rx配合Java 8特性Lambda帶來簡潔、高效的開發體驗,更是讓Android Developer望眼欲穿。

接下來,本文將從技術原理層面,來分析一下Android是如何支持Java 8的。

Lambda 表達式

想要更好的理解Android對Java 8的支持過程,Lambda表達式這一代表性的“語法糖”是一個非常不錯的切入點。所以,我們首先需要搞清楚Lambda表達式到底是什么?其底層的實現原理又是什么?

Lambda表達式是Java支持函數式編程的基礎,也可以稱之為閉包。簡單來說,就是在Java語法層面允許將函數當作方法的參數,函數可以當做對象。任一Lambda表達式都有且只有一個函數式接口與之對應,從這個角度來看,也可以說是該函數式接口的實例化。

Lambda表達式

通用格式:

怎么在Android中兼容Java 8語法

簡單范例:

怎么在Android中兼容Java 8語法

怎么在Android中兼容Java 8語法

說明:

  • Lambda表達式中 () 對應的是函數式接口-run方法的參數列表。

  • Lambda表達式中 System.out.println("xixi") / System.out.println("haha"),在運行時會是具體的run方法的實現。

Lambda表達式原理

針對實例中的代碼,我們來看下編譯之后的字節碼:

javac J8Sample.java  ->  J8Sample.class

javap -c -p J8Sample.class 
怎么在Android中兼容Java 8語法

從字節碼中我們可以看到:

  • 實例中Lambda表達式1變成了字節碼代碼塊中 Line 11的 0: invokedynamic #2,  0   // InvokeDynamic #0:run:()Ljava/lang/Runnable。

  • 實例中Lambda表達式2變成了字節碼代碼塊中 Line 20的 21: invokedynamic #6,  0   // InvokeDynamic #1:run:()Ljava/lang/Runnable。

可見,Lambda表達式在虛擬機層面上,是通過一種名為invokedynamic字節碼指令來實現的。那么invokedynamic又是何方神圣呢?

invokedynamic 指令解讀

invokedynamic指令是Java 7中新增的字節碼調用指令,作為Java支持動態類型語言的改進之一,跟invokevirtual、invokestatic、invokeinterface、invokespecial四大指令一起構成了虛擬機層面各種Java方法的分配調用指令集。區別在于:

  • 后四種指令,在編譯期間生成的class文件中,通過常量池(Constant Pool)的MethodRef常量已經固定了目標方法的符號信息(方法所屬者及其類型,方法名字、參數順序和類型、返回值)。虛擬機使用符號信息能直接解釋出具體的方法,直接調用。

  • 而invokedynamic指令在編譯期間生成的class文件中,對應常量池(Constant Pool)的Invokedynamic_Info常量存儲的符號信息中并沒有方法所屬者及其類型 ,替代的是BootstapMethod信息。在運行時, 通過引導方法BootstrapMethod機制動態確定方法的所屬者和類型。這一特點也非常契合動態類型語言只有在運行期間才能確定類型的特征。

那么,invokedynamic如何通過引導方法找到所屬者及其類型?我們依然結合前面的J8Sample實例:

javap -v J8Sample.class
怎么在Android中兼容Java 8語法

結合J8Sample.class字節碼,并對invokedynamic指令調用過程進行跟蹤分析。總結如下:

怎么在Android中兼容Java 8語法

依據上圖invokedynamic調用步驟,我們一步一步做一個分析講解。

步驟1 選取J8Sample.java源碼中Lambda表達式1:

Runnable runnable = () -> System.out.println("xixi");    // lambda表達式1

步驟2 通過javac J8Sample.java編譯得到J8Sample.class之后,Lambda表達式1變成:0: invokedynamic #2,  0    // InvokeDynamic #0:run:()Ljava/lang/Runnable;對應在J8Sample.class中發現了新增的私有靜態方法:
怎么在Android中兼容Java 8語法

步驟3 針對表達式1的字節碼分析 #2 對應的是class文件中的常量池:

#2 = InvokeDynamic      #0:#35         // #0:run:()Ljava/lang/Runnable;   

注意,這里InvokeDynamic不是指令,代表的是Constant_InvokeDynamic_Info結構。

步驟4 結構后面緊跟的 #0 標識的是class文件中的BootstrapMethod區域中引導方法的索引:

怎么在Android中兼容Java 8語法

步驟5 引導方法中的java/lang/invoke/LambdaMetafactory.metafactory才是invokedynamic指令的關鍵:

怎么在Android中兼容Java 8語法

怎么在Android中兼容Java 8語法

該方法會在運行時,在內存中動態生成一個實現Lambda表達式對應函數式接口的實例類型,并在接口的實現方法中調用步驟2中新增的靜態私有方法。

步驟6 使用java -Djdk.internal.lambda.dumpProxyClasses J8Sample.class運行一下,可以內存中動態生成的類型輸出到本地:

怎么在Android中兼容Java 8語法

步驟7 通過javap -p -c J8Sample\$\$Lambda\$1.class反編譯一下,可以看到生成類的實現:

怎么在Android中兼容Java 8語法
在run方法中使用了invokestatic指令,直接調用了J8Sample.lambda$main$0這個在編譯期間生成的靜態私有方法。

至此,上面7個步驟就是Lambda表達式在Java的底層的實現原理。Android 針對這些實現會怎么處理呢?

Android不能直接支持

回到Android系統上,Java-Bytecode(JVM字節碼)是不能直接運行在Android系統上的,需要轉換成Android-Bytecode(Dalvik/ART 字節碼)。

如圖:

怎么在Android中兼容Java 8語法

通過Lambda這節,我們知道Java底層是通過invokedynamic指令來實現,由于Dalvik/ART并沒有支持invokedynamic指令或者對應的替代功能。簡單的來說,就是Android的dex編譯器不支持invokedynamic指令,導致Android不能直接支持Java 8。

Android間接支持

既然不能直接支持,那就只能在Java-Bytecode轉換到Android-Bytecode這一過程中想辦法,間接支持。這個間接支持的過程我們統稱為Desugar(脫糖)過程。

官方流程圖:

怎么在Android中兼容Java 8語法

當前,無論是RetroLambda,還是Google的Jack & Jill 工具,還是最新的D8 dex編譯器:

  • 流程方面:都是按照如上圖所示的官方流程進行Desugar的。

  • 原理方面:卻是參照Lambda在Java底層的實現,并將這些實現移至到RetroLambda插件或者Jack、D8編譯器工具中。

下面我們逐個分析解讀一下。

Android 間接支持之RetroLambda

怎么在Android中兼容Java 8語法

如圖所示,RetroLambda 的Desugar過程發生在javac將源碼編譯完成之后,dx工具進行dex編譯之前。

RetroLambda Desugar

參照invokedynamic指令解讀一節中的步驟5,根據java/lang/invoke/LambdaMetafactory.metafactory方法,直接將原本在運行時生成在內存中的J8Sample\$\$Lambda\$1.class,在javac編譯結束之后,dx編譯dex之前,直接生成到本地,并使用生成的J8Sample\$\$Lambda\$1類修改J8Sample.class字節碼文件,將J8Sample.class中的invokedynamic指令替換成invokestatic指令。

將實例中的J8Sample.java放到一個配置了Retrolambda的Android工程中:

怎么在Android中兼容Java 8語法

AndroidStudio -> Build -> make project 編譯之后:

怎么在Android中兼容Java 8語法

app:transformClassesWithRetrolambdaForDebug任務發生在app:compileDebugJavaWithJavac (javac)后,app:transformDexArchiveWithDexMergerForDebug (dx)之前,同時在build/intermediates/transforms/retrolambda下面生產如圖所示的class文件。

J8Sample.class和J8Sample$$Lambda$1.class反編譯之后的代碼如下:

怎么在Android中兼容Java 8語法

怎么在Android中兼容Java 8語法

通過反編譯代碼,可以看出J8Sample.class中Lambda表達式已經被我們熟悉的1.7or1.6的語句所替代。

注意:右圖中J8Sample.lambda$main$0()方法在左圖中沒有顯示出來,但是J8Sample.class字節碼確實是存在的。
Android間接支持之Jack&Jill工具
怎么在Android中兼容Java 8語法

Jack是基于Eclipse的ecj編譯開發的, Jill是基于ASM4開發的。Jack&Jill工具鏈是Google在Android N(7.0)發布的,用于替換javac&dx的工具鏈,并且在jack過程內置了Desugar過程。

但是在Android P(9.0) 的時候將Jack&Jill工具鏈廢棄了,被javac&D8工具鏈替代了。這里就不做Desugar具體分析了。

Android間接支持之D8

怎么在Android中兼容Java 8語法

D8是Android P(9.0)新增的dex編譯器。并在Android Studio 3.1版本中默認使用D8作為dex的默認編譯器。

D8 Desugar

如圖所示,Desugar過程放在了D8的內部,由Android Studio這個IDE來實現這個轉換,原理基本和RetroLambda是一樣。

本質上也是參照java/lang/invoke/LambdaMetafactory.metafactory方法直接將原本在運行時生成在內存中的J8Sample\$\$Lambda\$1.class,在D8的編譯dex期間,直接生成并寫入到dex文件中。

同樣,將實例中的J8Sample.java放到支持D8的Android工程中:

怎么在Android中兼容Java 8語法

同樣,AndroidStudio -> Build -> make project編譯之后:

怎么在Android中兼容Java 8語法

javac編譯之后的J8Sample.class還是使用invokedynamic指令,即這一步并沒有Desugar:

怎么在Android中兼容Java 8語法

app:transformDexArchiveWithDexMergerForDebug(對應dx)任務之后,再對應build/intermediates/transforms/dexMerger目錄找第0個classex.dex。

執行$ANDROID_HOME/build-tools/28.0.3/dexdump -d classes.dex >> dexInfo.txt拿到dex信息。

還是選取實例中Lambda表達式1 :Runnable runnable = () -> System.out.println("xixi");來進行分析。

這個dexIno.txt文件非常大,有1.4M,我們通過com.J8Smaple2.J8Sample找到我們J8Sample在dex中位置。
怎么在Android中兼容Java 8語法

新增方法:

怎么在Android中兼容Java 8語法

J8Sample.main方法:

怎么在Android中兼容Java 8語法

圖中選中部分,對應就是Lambda表達式1 desugar之后的內容。

翻譯成Java的話就變成了:new Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc這個生成類的一個對象。類Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc對應前面的生成的J8Sample$$Lambda$1類型,只不過數字1變成了Hash值。
怎么在Android中兼容Java 8語法

實現Interface Ljava/lang/Runnable。Lcom/j8sample2/-$$Lambda$J8Sample$jWmuYH0zEF070TKXrjBFgnnqOKc.run方法:

怎么在Android中兼容Java 8語法

到這里,是不是和前面RetroLambda就一樣了。

總結

至此,Lambda及其invokedynamic指令、RetroLambda插件、D8編譯器各自的原理分析都已經結束了。

相比較Lambda在Java8自己內部的實現:即運行時,在內存中動態生成關聯的函數式接口的實例類型,通過BSM-引導方法找到該內存類(字節碼層面的反射)。

在Android上的其他三種Desugar方式,原理都是一樣的,區別在于時機不同:

  1. RetroLambda將函數式接口對應的實例類型的生產過程,放在javac編譯之后,dx編譯之前,并動態修改了表達式所屬的字節碼文件。

  2. Jack&Jill是直接將接口對應的實例類型,直接jack過程中生成,并編譯進了dex文件。

  3. D8的過程是在dex編譯過程中,直接在內存生成接口對應的實例類型,并將生成的類型直接寫入生成的dex文件中。


上述內容就是怎么在Android中兼容Java 8語法,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

若羌县| 阳东县| 茌平县| 修武县| 哈尔滨市| 枣强县| 斗六市| 开封市| 霍城县| 丹巴县| 太仆寺旗| 泾源县| 兴化市| 枣阳市| 嘉峪关市| 扎赉特旗| 阜阳市| 祁阳县| 陆丰市| 钦州市| 马山县| 中牟县| 南城县| 无锡市| 宁城县| 平乡县| 资阳市| 增城市| 吉林市| 疏勒县| 昌乐县| 封丘县| 故城县| 宜城市| 司法| 平和县| 长阳| 嘉鱼县| 大宁县| 武定县| 闵行区|