日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

探索 Java 隐藏的开销

發布時間:2023/11/30 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 探索 Java 隐藏的开销 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

隨著 Android 引入 Java 8 的一些功能,請記住每一個標準庫的 API 和語言特性都會帶來一些相關的開銷,這很重要。雖然設備越來越快而且內存越來越多,代碼大小和性能優化之間仍然是有著緊密關聯的。這篇?360AnDev?的演講會探索一些 Java 功能的隱藏開銷。我們會關注對庫開發者和應用開發者都有關系的優化和能夠衡量它們影響的工具。

介紹?(0:00)

在這篇演講里面,我將討論我近六個月以來一直在探索的事情,而且我想披露一些信息。隨著你的深入了解,你可能得不到一些明確的能夠應用在你的應用程序上的東西。但是,到結束的時候,我會有一些具體的技巧來展示如何避免我今天講的這些開銷。我也會展示許多我使用的命令行工具,這些資源的鏈接都在文章結束的地方。

Dex 文件?(1:14)

我們將從一個多項選擇的問題開始。下面這段代碼有多少個方法?沒有,一個或者兩個?

class Example { }

你可能馬上就有直覺的反應了。也許沒有,也許一個,也許兩個。讓我們看看我們是不是能回答這個問題。首先,類里面沒有方法。我在源文件里面沒有任何方法,所以看起來可以這么說。當然,這樣的答案真的沒有什么意思。讓我們開始把我們的類在 Android 里編譯一下,看看會發生什么:

$ echo "class Example { }" > Example.java$ javac Example.java$ javap Example.classclass Example { Example(); }

我們把內容寫到一個文件里面,然后用 Java 編譯器編譯源代碼然后把它變成 class 文件。我們可以使用其他的非 Java 開發套件的工具,它叫做?javap。這使得我們能夠深入了解編譯出來的 class 文件。如果在我們編譯的 class 文件上運行它,我們能看到我們的例子的 class 里面有一個構造函數。我們沒有在源文件里面編寫它,但是 Java C 決定自動增加一個那樣的構造函數。這意味著源文件里面沒有方法,但是 class 文件里面有一個。但這不是 Android 編譯停止的地方:

$ dx --dex --output=example.dex Example.class$ dexdump -f example.dex

在 Android SDK 中,有一個工具叫做?dx,它完成?dexing,這使得 Java Class 文件變成 Android Dalvik 二進制碼。我們通過?dex?運行我們的例子,Android SDK 里面還有另外一個工具叫做?dexdump,這個工具會給我們一些關于 dex 文件內部的信息。你運行它,它會打印一串東西。它們是文件的偏移量和計數器還有各種表。如果我們詳細點看看,一個明顯的事情是,dex 文件里面有一個函數列表:

method_ids_size : 2

它說我們的 class 里面有兩個方法。這說不通。不幸的是,dexdump?并沒有給我一個簡單的方法來了解這兩個方法是什么。因為如此,我寫了一個小工具來輸出 dex 文件里面的方法:

$ dex-method-list example.dex Example <init>() java.lang.Object <init>()

如果我們這樣做,我就能看到它返回了兩個方法。它返回了我們的構造函數,我們知道它是 Java 編譯器創建的,雖然我們沒有去寫它。但是它還說有一個對象構造函數。當然,我們的代碼沒有四處調用 new 對象,所以這個方法是哪里產生的呢,然后又在 dex 文件里面引用的呢?如果我們回到能打印 class 文件信息的?javap?工具,你能通過一些額外的標志來找到 class 里面的深度信息。我將使用?-c,這會把二進制代碼反編譯成可讀的信息。

$ javap -c Example.classclass Example {Example();Code:0: aload_01: invokespecial #1 //java/lang/Object."<init>":()V4: return }

在索引 1 處,是我們的對象構造函數,它被父類的構造函數調用。這是因為,即使我們不聲明它,Example?也是繼承于?Object?的。每一個構造函數都會調用它的父類的構造函數。它是自動插入的。這意味著我們的 class 流中有兩個方法。

所有這些關于我的初始問題的答案都是對的。區別就是術語不同。這是真實的情況。我們沒有定義任何方法。但是只有人類關心它。作為人類,我們讀寫這些源文件。我們是唯一關心它們內部構造的人。另外兩個方法更重要,方法的個數實際上是編譯進 class 文件里面了。無論是否聲明,這些方法都在 class 的內部。

這兩個方法是引用方法的數目。它和我們自己編寫的方法的計數是類似的,和所有其他在函數里引用以及 Android logger 函數的調用也差不多。我這里引用的?Log.d?函數和這個引用方法計數不一樣,因為這是我們在 dex 文件里面的計數。這也是人們經常在 Android 里面討論方法計數時的常用方法,因為 dex 有著聲名狼藉的對于引用方法的個數的限制。

我們看到一個沒有聲明的構造函數被創建了,所以讓我們看看其他自動生成的,我們可能不知道的隱藏開銷。嵌套類是一個有用的例子:

// Outer.javapublic class Outer {private class Example {} }

Java 1.0 不支持這樣做。它們是在晚些的版本里才出現的。當你在一個視圖或者展示層里面定義適配器的時候,你能看到這樣的東西。

// ItemsView.javapublic class ItemsView {private class ItemsAdapter {} }$ javac ItemsView.java$ ls ItemsView.classItemsView.java ItemsView$ItemsAdapter.class

如果我們編譯這個 class,這是一個有兩個 class 的文件。一個嵌套在另一個里面。如果我們編譯它,我們能在文件系統中看到兩個獨立的 class 文件。如果 Java 真的有內嵌類,我們就應該只能看到一個 class 文件。我們會得到ItemsView.class。但是這里 Java 沒有真正的嵌套,那么這些類文件里面是什么呢?在這個?ItemsView?里面,外層類,我們有的只是構造函數。這里沒有引用,沒有內嵌類的任何跡象:

$ javap 'ItemsView$ItemsAdapter'class ItemsView$ItemsAdapter {ItemsView$ItemsAdapter(); }

如果我們看看嵌套類的內容,你可以看到它有隱式的構造函數,而且你知道它在外部類的里面,因為它的名字被擾亂了。另外一個重要的事情是如果我返回去,我能看到這個?ItemsView?類是公共的,這和我們在源文件里面定義的一樣。但是內部類,內嵌類,雖然它定義為私有的,在類文件里面它不是私有的。它是包作用范圍的。這是對的,因為我們在同一個包中有兩個生成的類文件。重申一次,這進一步證明了在 Java 里面沒有真正的內嵌類。

// ItemsView.javapublic class ItemsView { }// ItemsAdapter.javaclass ItemsAdapter { }

雖然你內嵌了兩個類的定義,你可以有效地創建兩個類文件,它們在同一個包里緊鄰著對方。如果你想這樣做的話,你可以實現。你可以作為兩個獨立的文件使用命名規則:

// ItemsView.javapublic class ItemsView { }// ItemsView$ItemsAdapter.javaclass ItemsView$ItemsAdapter { }

美元符在 Java 里面是名字的有效字符。對方法或者附加名字也有效:

// ItemsView.javapublic class ItemsView {private static String displayText(String item) {return ""; // TODO} private class ItemsAdapter {} }

然而,這是真正有意思的地方,因為我知道我能夠做一些事情在外部類里找到一個?private static?方法,而且我能在內部類里面引用那個私有的方法:

// ItemsView.javapublic class ItemsView {private static String displayText(String item) {return ""; // TODO} }// ItemsView$ItemsAdapter.javaclass ItemsView$ItemsAdapter {void bindItem(TextView tv, String item) {tv.setText(ItemsView.displayText(item));} }

現在我們知道沒有真正的內嵌,但是,這在我們假設的獨立系統里面是如何工作的呢,這里我們的?ItemsAdapter類需要引用?ItemsView?的私有方法?這沒有編譯,而且它們會被編譯:

// ItemsView.javapublic class ItemsView {private static String displayText(String item) {return ""; // TODO}private class ItemsAdapter {void bindItem(TextView tv, String item) {tv.setText(ItemsView.displayText(item));}} }

發生了什么?當你回到我們的工具的時候,我們能再次使用?javac?。

$ javac -bootclasspath android-sdk/platforms/android-24/android.jar \ItemsView.java$ javap -c 'ItemsView$ItemsAdapter'class ItemsView$ItemAdapter {void bindItem(android.widget.TextView, java.lang.String);Code:0: aload_11: aload_22: invokestatic #3 // Method ItemsView.access$000:…5: invokevirtual #4 // Method TextView.setText:…8: return }

我在引用?TextView,這樣我才能在 Java 里面增加 Android APIs。現在我將打印出內嵌類的內容,來看看哪個函數被調用了。如果你看看索引 2,它沒有調用?displayText?方法。它調用的是?access$000,我們沒有定義它。它在?ItemsView?類里面嗎?

$ javap -p ItemsView123class ItemsView {ItemsView();private static java.lang.String displayText(…);static java.lang.String access$000(…); }

如果我們仔細看看,是的,它在。我們看到我們的?private static?方法仍然在那,但是我們現在需要這個我們沒有編寫的額外方法自動加入。

$ javap -p -c ItemsView123class ItemsView {ItemsView();Code: <removed>private static java.lang.String displayText(…);Code: <removed>static java.lang.String access$000(…);Code:0: aload_01: invokestatic #1 // Method displayText:…4: areturn }

如果我們看看這個函數的內容,它做的事情就是調用我們原來的?displayText?方法。這有意義,因為我們需要一個從包的作用域到類里調用它的私有方法的途徑。Java 會合成一個包作用域的方法來幫助實現這個函數調用。

// ItemsView.java public class ItemsView {private static String displayText(String item) {return ""; // TODO}static String access$000(String item) {return displayText(item);} }// ItemsView$ItemsAdapter.javaclass ItemsView$ItemsAdapter {void bindItem(TextView tv, String item) {tv.setText(ItemsView.access$000(item));} }

如果我們回到我們兩個類文件的例子,我們手工的例子,我們能讓編譯器按照同樣的方法工作。我們能夠增加方法,我們能更新另一個類,然后引用它。dex 文件有方法的限制,所以當你有這些因為你編寫源文件的方式的不同,而導致的必須要添加新的的方法加的話,這些函數的個數都是計算在內的。理解這點是很重要的,因為我們嘗試在某處訪問一個私有成員是不可能的。

Dex 進階?(10:52)

所以你可能會說,”好吧,你只做了 Java C。也許 dex 工具能看到這些,而且自動地為我們去除這些函數。”

$ dx --dex --output=example.dex *.class$ dex-method-list example.dexItemsView <init>() ItemsView access$000(String) → String ItemsView displayText(String) → String ItemsView$ItemsAdapter <init>(ItemsView) ItemsView$ItemsAdapter bindItem(TextView, String) android.widget.TextView setText(CharSequence) java.lang.Object <init>()

如果我們編譯這兩個生成的類,然后顯示它們,你可以看到實際情況不是這樣。dex 工具編譯它就好像它是個任意的其他方法一樣。在你的 dex 文件里面就這樣結束了。

你會說,”好吧,我聽說過這個新的?Jack?編譯器。而且 Jack 編譯器直接編譯源文件,然后直接產生 dex 文件,所以也許它做了些什么事情使得它不需要產生額外的方法。” 這樣肯定沒有 access 方法。但是,有一個?-wrap0?方法,它實際上做的是同樣的事情:

$ java -jar android-sdk/build-tools/24.0.1/jack.jar \-cp android-sdk/platforms/android-24/android.jar \--output-dex . \ItemsView.java$ dex-method-list classes.dexItemsView -wrap0(String) → String ItemsView <init>() ItemsView displayText(String) → String ItemsView$ItemsAdapter <init>(ItemsView) ItemsView$ItemsAdapter bindItem(TextView, String) android.widget.TextView setText(CharSequence) java.lang.Object <init>()

還有一個工具叫做?ProGuard,許多人都用它。你可能會說,”好吧,ProGuard 應該會處理這些事情,對吧?” 我可以寫一個快速的 ProGuard key。我能在我的類文件上運行 ProGuard,然后打印這些方法。這里是我得到的東西:

$ echo "-dontobfuscate -keep class ItemsView$ItemsAdapter { void bindItem(...); } " > rules.txt$ java -jar proguard-base-5.2.1.jar \-include rules.txt \-injars . \-outjars example-proguard.jar \-libraryjars android-sdk/platforms/android-24/android.jar$ dex-method-list example-proguard.jarItemsView access$000(String) → String ItemsView$ItemsAdapter bindItem(TextView, String) android.widget.TextView setText(CharSequence)

構造函數被移出了,因為它們沒有被使用。我將把它們加回來因為正常情況下它們是在的:

$ dex-method-list example-proguard.jarItemsView <init>() ItemsView access$000(String) → String ItemsView$ItemsAdapter <init>(ItemsView) ItemsView$ItemsAdapter bindItem(TextView, String) android.widget.TextView setText(CharSequence) java.lang.Object <init>()

你能看到 access 函數還在那里。但是如果你仔細看看,你保持了 access 方法,但是?displayText?消失了。這里發生了什么呢?你可以解壓縮 ProGuard 產生的?jar?包,然后回到我們的?javap?工具,看看 ProGuarded 類文件里面到底有些什么:

$ unzip example-proguard.jar$ javap -c ItemsViewpublic final class ItemsView {static java.lang.String access$000(java.lang.String);Code:0: ldc #1 // String ""2: areturn }

如果我們看看 access 函數,它不再調用?displayText。ProGuard 把?displayText?的內容抽取出來,然后移到 access 函數里面并且刪除了?displayText?函數。這個 access 函數是我們唯一引用的私有函數,所以它會變成 inline 函數,因為沒有其他人使用它了。是的,ProGuard 在某種程度上能夠幫得上忙。但是它不保證能夠有用。我們很幸運,因為這是一個小例子,但是優化也是不能保證的。你可能會想,”好吧,我真的沒有使用那么多的內嵌類,也許有一組。如果我只是會得到一組額外的函數,這關系不大,對嗎?”

匿名類?(13:06)

讓我向你介紹一些我們的朋友,匿名類:

class MyActivity extends Activity {@Override protected void onCreate(Bundle state) {super.onCreate(state);setContentView(R.layout.whatever);findViewById(R.id.button).setOnClickListener(new OnClickListener() {@Override public void onClick(View view) {// Hello!}});} }

匿名類的行為幾乎和內嵌類完全一樣。它們本質上是一樣的東西。它是一個內嵌類,但是沒有名字。如果在這些監聽者里面,這是我們常用的方法,你引用一個類里面的私有方法,這樣就會產生一個合成的 access 函數。

class MyActivity extends Activity {@Override protected void onCreate(Bundle state) {super.onCreate(state);setContentView(R.layout.whatever);findViewById(R.id.button).setOnClickListener(new OnClickListener() {@Override public void onClick(View view) {doSomething();}});}private void doSomething() {// ...} }

對于成員來說,事實也是這樣:

class MyActivity extends Activity {private int count;@Override protected void onCreate(Bundle state) {super.onCreate(state);setContentView(R.layout.whatever);findViewById(R.id.button).setOnClickListener(new OnClickListener() {@Override public void onClick(View view) {count = 0;++count;--count;count++;count--;Log.d("Count", "= " + count);}});} }

我認為這是一個有許多共性的例子。我們在外部類里面有這些成員,我們修改狀態的這些 activity 的成員在這些監聽者里面。我們做了一個完整的美妙實現,但是我們做的是設置一個值。我使用了前置加加,前置減減,后置加加和后置減減,然而日志消息需要從成員中讀取數值。我們在這里的函數有多少個呢?也許只有兩個。也許一個讀,一個寫,然后加加和減減變成讀加和寫。如果這是事實的話:

$ javac -bootclasspath android-sdk/platforms/android-24/android.jar \ MyActivity.java$ javap MyActivity class MyActivity extends android.app.Activity {MyActivity();protected void onCreate(android.os.Bundle);static int access$002(MyActivity, int); // count = 0 writestatic int access$004(MyActivity); // ++count preincstatic int access$006(MyActivity); // --count predecstatic int access$008(MyActivity); // count++ postincstatic int access$010(MyActivity); // count-- postdecstatic int access$000(MyActivity); // count read }

我們編譯它,然后就為每一個類型都產生了一個函數。所以如果你覺得在一個 activity 或者 fragment 或者其它什么東西,你有四到五個監聽者,和大概 10 個在外部類里的私有成員。你就會有一個很棒的 access 方法爆炸。你也許還沒有被說服這是個問題。你會說,”好吧,也許是 50,也許是 100.這真的有關系嗎?” 我們下面來看看。事實證明一切。

現實情況?(15:03)

你可以看到在現實中這是多么的普遍。這些命令可以幫你拿出手機上所有的 APK。每一個你安裝的應用,都是一個第三方的應用:

$ adb shell mkdir /mnt/sdcard/apks$ adb shell cmd package list packages -3 -f \ | cut -c 9- \ | sed 's|=| /mnt/sdcard/apks/|' \ | xargs -t -L1 adb shell cp$ adb pull /mnt/sdkcard/apks

我們可以寫一個腳本來使用這些 dex 函數列表,然后 greps 所有的不同數值:

#!/bin/bash accessors.shset -eMETHODS=$(dex-method-list $1 | \grep 'access\$') ACCESSORS=$(echo "$METHODS" | wc -l | xargs) METHOD_AND_READ=$(echo "$METHODS" | egrep 'access\$\d+00\(' | wc -l | xargs) WRITE=$(echo "$METHODS" | egrep 'access\$\d+02\(' | wc -l | xargs) PREINC=$(echo "$METHODS" | egrep 'access\$\d+04\(' | wc -l | xargs) PREDEC=$(echo "$METHODS" | egrep 'access\$\d+06\(' | wc -l | xargs) POSTINC=$(echo "$METHODS" | egrep 'access\$\d+08\(' | wc -l | xargs) POSTDEC=$(echo "$METHODS" | egrep 'access\$\d+10\(' | wc -l | xargs) OTHER=$(($ACCESSORS - $METHOD_AND_READ - $WRITE - $PREINC - $PREDEC - $POSTINC - $POSTDEC))NAME=$(basename $1)echo -e "$NAME\t$ACCESSORS\t$READ\t$WRITE\t$PREINC\t$PREDEC\t$POSTINC\t$POSTDEC\t$OTHER"

然后我們運行這個瘋狂的命令,它會遍歷每一個從手機里面獲取的 APK,運行這個腳本,然后你得到一個漂亮的報表:

$ column -t -s $'\t' \ <(echo -e "NAME\tTOTAL\tMETHOD/READ\tWRITE\tPREINC\tPREDEC\tPOSTINC\tPOSTDEC\tOTHER" \ && find apks -type f | \ xargs -L1 ./accessors.sh | \ sort -k2,2nr)

你能在 77 頁上看到這個表,它把使用 accessor 函數的包排了個序。在我的手機里面,我有幾千個。Amazon 占據了前六名中的五個。前幾名有 5000 個合成的 accessor 函數。5000 個函數,那是一整個庫了。這好像一個 apk 的 pad。你有一整個 apk 的 pad, 里面都是無用的函數,它們的存在只是為了跳轉到其它的函數去。

同樣的,因為我們使用了 ProGuard,混淆會使得這些數值變得更難以確認。初始化會搞砸這些數據。不要認為它們是準確的數據。它只是給了你一個大約的數值,讓你明白你創造了多少個函數。你的應用里面會有多少函數是比這些無用的 access 函數有用的?順便說一句,Twitter 在列表的末尾?它們有 1000 個。他們 ProGuarding 了他們的應用,所以可能實際情況更糟。但是我想這很有趣,因為它們有最多的函數,但是報告了最少的 access 函數數量。他們有 171,000 個函數,但是只用了 1,000 個合成的 accessors。這很令人吃驚。

我們能改變這個情況。這是很容易的。我們不需要把一些東西作為私有成員。當我們跨邊界引用它們的時候,我們需要讓它們成為包作用域級別。 IntelliGate 提供了這樣的檢查。它默認不起作用,但是你可以進去然后搜索一個私有方法。搜索是一件有意思的事情,如果采用我們的例子,它會將結果標記為黃色高亮。你可以?選擇性進入,它會讓你跳轉到你訪問的私有成員那里,然后把它設為包作用域的。

當你考慮這些內嵌類的時候,試著把它們想象成為兄弟姐妹,而不是父子關系。你不能從外部類里面訪問一個私有成員。你需要讓它成為包級別的。這才不會出問題,因為即使你在編寫一個庫,這些人也不會在同樣的包里面放入類文件,然后訪問這些你設為更容易訪問的內容。我會在功能需求里面放上一個這方面的 link 檢查。希望在未來,你在構建的時候,如果你做了類似的事情,構建就會失敗。

我遍歷過許多開源庫,而且修改過這些可見性的問題,這樣這些庫本身就不需要在 impose 成百上千的外部函數了。這對于生成代碼的庫來說尤為重要。我們能在我們的應用里面減少 2700 個函數,只需要改變一個我們代碼生成的步驟。只需要把一些東西從私有作用域改到包作用域,就能很輕松地減少 2700 個函數了。

合成方法?(18:45)

這些合成方法之所以叫做合成方法是因為你沒有編寫它們。

// Callbacks.javainterface Callback<T> {void call(T value); }class StringCallback implements Callback<String> {@Override public void call(String value) {System.out.println(value);} }

它們是為你自動生成的。這些 accessor 函數是唯一自動生成的函數。Generics 是另一個在 Java 1.0 之后才出現的東西,而且它必須重新翻譯成 Java 的工作方式。我們在庫里面,甚至在我們的應用里面能看到許多這樣的東西。我們使用這些泛型接口,因為它們非常方便,而且它們讓我們能保持類型正確。

$ javac Callbacks.java$ javap StringCallback class StringCallback implements Callback<java.lang.String> {StringCallback();public void call(java.lang.String);public void call(java.lang.Object); }

如果我們有一個函數接收一個泛型值,當你編譯它的時候,你會發現所有接收泛型參數的函數最終會變成兩個。一個接收串,不論你的泛型是什么,另一個接收對象。這和 erasure 很像。你聽到許多人都談論 erasure,你也許不理解到底發生了些什么。這更像一個 erasure 的說明。我們必須產生使用對象的代碼,因為當你訪問這個泛型函數的時候,這就是你最終調用的函數。

$ javap -c StringCallback class StringCallback implements Callback<java.lang.String> {StringCallback();Code: <removed>public void call(java.lang.String);Code: <removed>public void call(java.lang.Object);Code:0: aload_01: aload_12: checkcast #4 // class java/lang/String5: invokevirtual #5 // Method call:(Ljava/lang/String;)V8: return }

如果我們看看那個額外的函數里面發生了什么,它只有一個轉換。它強轉成了 year 類型然后它調用了接收泛型的真實實現。任何調用這個函數的人都會使用這個對象函數。調用代碼會傳入它們有的任何對象,然后這部分代碼就完成強制轉換,調用真正的實現。每一個使用泛型的函數,最后都會成為兩個函數。

// Providers.javainterface Provider<T> {T get(Context context); }class ViewProvider implements Provider<View> {@Override public View get(Context context) {return new View(context);} }

返回值也是一樣。如果你有一個返回泛型的函數,你將會看到一樣的事情。

$ javac -bootclasspath android-sdk/platforms/android-24/android.jar \Example.java$ javap -c ViewProviderclass ViewProvider implements Provider<android.view.View> {ViewProvider();Code: <removed>public android.view.View get(android.content.Context);Code: <removed>public java.lang.Object get(android.content.Context);Code:0: aload_01: aload_12: invokevirtual #4 // Method get:(…)Landroid/view/View;5: areturn }

產生了兩個函數。一個作為返回值。在這種情況下,是我們的視圖。然后在底部,返回了對象。這個函數非常簡單因為它不用完成任何事情,它僅僅是接收 view 然后把它變成對象。

// Providers.javainterface Provider<T> {T get(Context context); }class ViewProvider implements Provider<View> {@Override public View get(Context context) {return new View(context);} }

另一個需要指出的并且許多人都沒有意識到的問題是,這是 Java 語言的功能。

class ViewProvider implements Provider<View> {@Override public View get(Context context) {return new View(context);} }class TextViewProvider extends ViewProvider {@Override public TextView get(Context context) {return new TextView(context);} }

如果你有需要重載的函數,你可以改變它的返回值成為更具體的類型。這叫做協變返回類型。不一定和我們這個例子里面的一樣,我們在實現接口。基類不需要是一個接口或者其它的東西。你可以從基類重載一個方法。你可以把它的返回類型變成其他更具體的類型。

你這樣做的原因是如果你在我們第二個類里面有其他方法,它又要調用這個?get?函數的話,你就會這樣做了,因為它們需要實現的類型。不需要更寬泛的類型了,在這個例子里面,是?View?類型。它們完全可以只做對于TextView?的定制化,因為他們已經在那個類里面了。

協變返回類型?(21:58)

協變返回類型。我肯定你們能猜到這里面發生了什么。

$ javap TextViewProviderclass TextViewProvider extends ViewProvider {TextViewProvider();public android.widget.TextView get(android.content.Context);public android.view.View get(android.content.Context);public java.lang.Object get(android.content.Context); }

我們有了另一個函數。在這個例子里面,它既是泛型而且還有協變返回類型。我們把一個函數變成了三個基本上不做任何事情的函數。這是一個深入內部的 python 腳本:

#!/usr/bin/pythonimport os import subprocess import syslist = subprocess.check_output(["dex-method-list", sys.argv[1]])class_info_by_name = {}for item in list.split('\n'):first_space = item.find(' ')open_paren = item.find('(')close_paren = item.find(')')last_space = item.rfind(' ')class_name = item[0:first_space]method_name = item[first_space + 1:open_paren]params = [param for param in item[open_paren + 1:close_paren].split(', ') if len(param) > 0]return_type = item[last_space + 1:]if last_space < close_paren:return_type = 'void'# print class_name, method_name, params, return_typeif class_name not in class_info_by_name:class_info_by_name[class_name] = {}class_info = class_info_by_name[class_name]if method_name not in class_info:class_info[method_name] = []method_info_by_name = class_info[method_name]method_info_by_name.append({'params': params,'return': return_type})count = 0 for class_name, class_info in class_info_by_name.items():for method_name, method_info_by_name in class_info.items():for method_info in method_info_by_name:for other_method_info in method_info_by_name:if method_info == other_method_info:continue # Do not compare against self.params = method_info['params']other_params = other_method_info['params']if len(params) != len(other_params):continue # Do not compare different numbered parameter lists.match = Trueerased = Falsefor idx, param in enumerate(params):other_param = other_params[idx]if param != 'Object' and not param[0].islower() and other_param == 'Object':erased = Trueelif param != other_param:match = Falsereturn_type = method_info['return']other_return_type = other_method_info['return']if return_type != 'Object' and other_return_type == 'Object':erased = Trueelif return_type != other_return_type:match = Falseif match and erased:count += 1# print "FOUND! %s %s %s %s" % (class_name, method_name, params, return_type)# print " %s %s %s %s" % (class_name, method_name, other_params, other_return_type)print os.path.basename(sys.argv[1]) + '\t' + str(count)

這得花了很長時間才能弄明白,但是我想知道這在應用里面有多流行。我可以采用同樣的流程,然后對我設備上安裝的所有應用都運行一遍。

$ column -t -s $'\t' \<(echo -e "NAME\tERASED" \&& find apks -type f | \xargs -L1 ./erased.py | \sort -k2,2nr)

有幾千個。你做的不全在這。我說過 ProGuard 在某種程度上能幫上忙。好處是如果 ProGuard 能夠發現沒有任何人引用這個接收一個對象然后返回對象的泛型函數的話。它就能消滅它,所以你能看到 ProGuard 消滅了成千上萬個函數。但是有的函數不能被移除因為你在接口那里用抽象的方式調用了這些方法。

最后一個我想討論的例子,對于 Android 來說是新的,并且即將出現。這就是 Java 8 語言特性。

class Greeter {void sayHi() {System.out.println("Hi!");} }class Example {public static void main(String... args) {Executor executor = Executors.newSingleThreadExecutor();final Greeter greeter = new Greeter();executor.execute(new Runnable() {@Override public void run() {greeter.sayHi();}});} }

我們有 retro-lamina 一段時間了。但是現在 Jack compiler 在同樣的精神指導下實現了這些功能,這樣會讓它們向后兼容。但是新語言也會有相關的開銷嗎?

我有一些簡單的類,它們在調用它的函數的時候會打印?Hi。我想做的事情是讓我的?Greeter?在?Executor?的時候打印 hello。Executor?有一個函數叫做?run,它接收?Runnable。在當前的情況下,我們設置創建者類型為final。然后我們就能創建一個新的 runnable 來直接調用函數了。

class Greeter {void sayHi() {System.out.println("Hi!");} }class Example {public static void main(String... args) {Executor executor = Executors.newSingleThreadExecutor();Greeter greeter = new Greeter();executor.execute(() -> greeter.sayHi());} }

在 Lambda 的世界里,這變得特別簡潔了。它也會創建一個?Runnable,但是它是隱式創建的。你不需要指定類型。你不需要真正的函數名和參數類型。

最后一個是函數引用。這有些意思,因為這是一個不返回任何東西而且不接受任何參數的函數,我可以把它自動地變成?Runnable,因為我知道我需要做的事情就是調用這個函數。

class Greeter {void sayHi() {System.out.println("Hi!");} }class Example {public static void main(String... args) {Executor executor = Executors.newSingleThreadExecutor();Greeter greeter = new Greeter();executor.execute(greeter::sayHi);} }

這些開銷有多大?(24:45)

這很有趣,但是這些開銷到底有多大?采用這些語言特性的開銷到底多大?我準備了一個?Retrolambda?工具鏈和一個使用 Jack 的工具鏈。

Retrolambda toolchain$ javac *.java$ java -Dretrolambda.inputDir=. -Dretrolambda.classpath=. \-jar retrolambda.jar$ dx --dex --output=example.dex *.class$ dex-method-list example.dexJack toolchain $ java -jar android-sdk/build-tools/24.0.1/jack.jar \-cp android-sdk/platforms/android-24/android.jar \--output-dex . *.java$ dex-method-list classes.dex

這兩個都沒有使用 ProGuard,而且最簡化了 Jack,因為它不影響結果。在匿名類的例子中,對于我們到目前為止常常使用的例子而言,它總共有兩個方法。

Example$1 <init>(Greeter) Example$1 run()$ javap -c 'Example$1' final class Example$1 implements java.lang.Runnable {Example$1(Greeter);Code: <removed>public void run();Code:0: aload_01: getfield #1 // Field val$greeter:LGreeter;4: invokevirtual #3 // Method Greeter.sayHi:()V7: return }

如果我們編譯我們的 example,我們可以看到這就是我們的匿名類,它給附上了簡單升序的數字。我們看看構造函數。構造函數為我們接收了?Greeter?類,然后它有一個 run 函數,如果我們反編譯它,它所做的事情就是調用函數。這就是我們期望的事情,非常直接。

當我們使用 lambda 的時候,如果你使用的是一個老版本的 retrolambda,開銷就很大了。簡單的一小行代碼能變成六個或者七個函數來完成功能。謝天謝地,現在最小的版本是 4。 而且 Jack 和版本 3 工作的很好,所以只會比匿名類多出一個函數。但是區別在哪里呢?為什么會有多余的函數呢?

我們知道如何弄清楚。這是 retrolambda,它有兩個額外的函數:

Example lambda$main$0(Greeter) Example$$Lambda$1 <init>(Greeter) Example$$Lambda$1 lambdaFactory$(Greeter) → Runnable Example$$Lambda$1 run()$ javap -c 'Example$$Lambda$1'final class Example$$Lambda$1 implements java.lang.Runnable {public void run();Code:0: aload_01: getfield #15 // Field arg$1:LGreeter;4: invokestatic #21 // Method Example.lambda$main$0:7: return }

開始的那個函數是新的。這里發生的事情是:你在 lambda 里面定義了一段代碼,這段代碼需要被封裝到某個地方。它不在定義這個 lamda 的函數里面,因為如果這樣就會很怪。它不屬于那個函數。它需要在其他的某個地方定義,然后在你需要的時候傳入。

這就是開始的那個函數。它只是拷貝粘貼了你在同一個類里面的 lambda 的內容。這里你可以看到實現方法。它做的所有事情就是代理?sayHi?函數。和我們的 runnable 實現非常類似。我們仍然有構造函數。除了 run 函數的修改有點不同外。它會回到原來的類然后調用 lambda 函數,而不是直接調用?Greeter。這就是那個額外的函數。接下來看看 retrolambda 是如何工作的。

Example lambda$main$0(Greeter) Example$$Lambda$1 <init>(Greeter) Example$$Lambda$1 lambdaFactory$(Greeter) → Runnable Example$$Lambda$1 run()

它生成了另外一個函數,這是一個靜態工廠方法,它調用了構造函數,而不是直接調用構造函數來創建生成類。Jack 做的事情也很類似,除了額外的靜態方法。

Example -Example_lambda$1(Greeter) Example <init>() Example main(String[]) Example run(Runnable) Example$-void_main_java_lang_String__args_LambdaImpl0 <init>(Greeter) Example$-void_main_java_lang_String__args_LambdaImpl0 run() Greeter <init>() Greeter sayHi() java.io.PrintStream println(String) java.lang.Object <init>() java.lang.Runnable run()

我們仍然有 lambda one,雖然它的名字很瘋狂,而且生成類的命名也很有創造性,它在名字里使用了整個類型簽名。如上所示。三個方法。Lambda 產生了開始的額外方法。這就是另外一個額外方法開銷的原因。

函數引用也很有趣。Retrolambda 和 Jack 本質上是綁定的。Retrolambda 有時候需要產生一個額外的方法,原因是你可能需要引用一個私有方法,所以這不是 Retrolambda 產生的。Java 產生了這些合成 accessor 函數中的一個,因為你給另外一個類傳遞了一個私有方法的引用,它做不到。這是第四個函數產生的原因。

Jack,非常有趣,為每一個單獨的函數引用產生了三個函數。除了私有類,它應該為每一個函數產生兩個。這種情況下,它應該產生三個函數。現在,它為每一個函數引用都產生了一個 accessor 函數。這看起來像一個 bug,所以希望我們能看到 Jack 減少到兩個函數。這很重要,因為在函數引用上,這和匿名類一樣了。那么從匿名類轉到函數引用的抽象就沒有開銷了,這會很棒。

Lambda 不幸的是,不太可能減少到同樣的數量。原因是你還是可能在 lambda 里面引用私有函數或者私有成員。它不可能被拷貝到生成的 runnable 里面。因為,它沒有任何訪問這些東西的方法。我們也只能這么指望了。

現實中的 Lambdas?(30:05)

讓我們看看現實中有多少 lambda 正被使用著。同樣的方法。順便說一句,這花費的時間很長。

#!/bin/bash lambdas.shset -eALL=$(dex-method-list $1)RL=$(echo "$ALL" | \grep ' lambda\$' | wc -l | xargs) JACK=$(echo "$ALL" | \grep '_lambda\$' | wc -l | xargs)NAME=$(basename $1)echo -e "$NAME\t$RL\t$JACK" $ column -t -s $'\t' \<(echo -e "NAME\tRETROLAMBDA\tJACK" \&& find apks -type f | \xargs -L1 ./lambdas.sh | \sort -k2,2nr)NAME RETROLAMBDA JACK com.squareup.cash 826 0 com.robinhood.android 680 0 com.imdb.mobile 306 0 com.stackexchange.marvin 174 0 com.eventbrite.attendee 53 0 com.untappdllc.app 53 0

差不多 10 分鐘。這也取決于你最后獲取的應用有多少。如果你需要這樣做,請耐心一點。它需要一些時間,但是最后會有結果的。

不幸的是,顯然沒有多少人使用 lambda,我很興奮,因為我們使用的最多。我們有 826 個 lambda。這是 lambda 的個數而不是函數的個數。我們的函數個數是 lambda 的個數 826 乘以三或者四。

沒有人使用 Jack,或者至少我安裝的應用沒有用,沒有人同時使用 Jack 和 Lambda。他們可能用了 Jack 而沒有使用 lambda,這很奇怪。或者,他們使用了 ProGuarding。

所以,再強調一遍,ProGuard 完全隱藏了 lambda 類和函數名。如果你是一個使用 lambda 的流行應用,然后你使用了 ProGuard,這可能就是你不在這個列表上的原因。或者是因為我不喜歡你的應用。這是關于函數的一切。

我研究這個的原因是為了突破 65K 的限制。但是這些函數還有運行時候的開銷。加載額外的字節流是有開銷的。運行的時候如果你需要遍歷它們,那么還有額外的開銷。私有變量是我最喜歡的部分,因為許多時候你都能看到這些匿名的監聽者里面發生了什么。主線程上的 UI 交互往往會導致這些結果。

你不想要的正是這個在主線程上運行的計算開銷昂貴的代碼,不論它是動畫,計算大小或者其它的任何事情。每一次你應用那些私有變量的時候,你都不想要這些開銷。你都不想跳轉到那個額外的函數去。查找一個變量是很快的。調用一個函數,然后查找一個變量也很快,但是肯定比查找一個變量要慢些。因為這些 accessor 函數的存在,你沒有直接立即作用,而是引入了這些間接的途徑。但是它們是些無用的方法,它們能做的事情就是增大你的 APK ,然后拖慢你的應用。

集合?(33:21)

我想對齒輪做些修改,然后討論些更加關注運行時情況的事情。這和集合有關。

HashMap<K, V> ArrayMap<K, V> HashSet<K, V> ArraySet<V> HashMap<Integer, V> SparseArray<V> HashMap<Integer, Boolean> SparseBooleanArray HashMap<Integer, Integer> SparseIntArray HashMap<Integer, Long> SparseLongArray HashMap<Long, V> LongSparseArray<V>

如果你的應用里面有類似的事情,你可能浪費了比你需要的要多的資源。

Android 有這些專門的集合,我想大多數人現在都很了解。實現各式各樣,但是它們是專門為非常普遍的情況定制的。例如,當你需要一個 map 中整型的索引來引用某些值的時候。就有一個專門的集合可以使用。

許多時候人們都談論著 autoboxing 的內容。Autoboxing 是什么?如果你不知道的話。當我們有一個 HashMap 的時候,這個 HasMap 接受整型的 key,這樣你有一個整型的值,然后你想把這個值放進 Map 里面。 或者相反的,你在遍歷整個條目,你想根據 key 得到一個值,這個轉換不是直接能實現的。它需要一個額外的步驟叫做 aotoboxing,那里它先獲得主要的值,然后轉換成它的類版本,這個例子里面是整型。

最后的動作開銷不是很大。它是封裝一個類型。開始的例子開銷就大了。小數字還好,因為有緩存,但是如果你有一個隨機的變化劇烈的整形,每一次你調用一個方法的時候都會分配空間。這很普遍,而且接受大的整型。這是大部分人認為是優點的地方。這是個很明顯的優點,但是它還有兩個我們從未談及的其他優點。

第一個是數據間接性。如果你看看 HashMap 是如何實現的,它有一個這些節點的數組,這個數組有自己的大小。當你插入一個數值或者查找一個數值的時候,它就會跳到這個數組去。這就是 hash 的步驟。找到這個 hash 值是有時空開銷的,然后它返回數組的偏移。然而這是一個節點數組。節點的類型有 key 和 value。它也有 hash,它有一個指向額外節點的指針。我們回到那個數組,找到節點的引用,然后我們需要跳轉到那個節點了。如果我們需要值,我們就需要訪問這個節點。我們獲得節點的引用,然后跳轉過去。我們需要遍歷這些間接引用。它們在內存里面是不同的大小。你需要跳過這些內存來得到 key 的值。或者在一個 key 上賦一個值。這會更糟。

這就是 hash 碰撞問題,當兩個 hash 碰到同一個 bucket 的時候,HashMap 在那個 bucket 里面會有一個鏈表。如果我們碰巧碰到這種情況,我們就需要遍歷鏈表來獲取合適的 hash 值。我將討論一下 sparse 數組。 sparse 數組就是替代解決方案。我們馬上就會討論它,但是在這之前,另外一個優點就是開銷。這些集合的內存開銷。這里我們有兩個類。

$ java -jar jol-cli-0.5-full.jar internals java.util.HashMap # Running 64-bit HotSpot VM.# Objects are 8 bytes aligned. java.util.HashMap object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 004 4 (object header) 00 00 00 008 4 (object header) 9f 37 00 f8 12 4 Set AbstractMap.keySet null16 4 Collection AbstractMap.values null20 4 int HashMap.size 024 4 int HashMap.modCount 028 4 int HashMap.threshold 032 4 float HashMap.loadFactor 0.7536 4 Node[] HashMap.table null40 4 Set HashMap.entrySet null44 4 (loss due to the next object alignment)Instance size: 48 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我們可以使用一個叫做 Java 對象布局的工具,它會告訴你創建一個這樣的對象在內存里面的開銷。對 HashMap 運行它,它就會打印一堆東西。它會顯示你的每一個字段的開銷。這里重要的數字在最底下,每一個 HashMap, 只是 HashMap, 不是節點,不是 key 和 value 或者其它什么東西。就是 HashMap 對象本身是 48 個字節。這不差,這很小。

$ java -jar jol-cli-0.5-full.jar internals 'java.util.HashMap$Node'# Running 64-bit HotSpot VM.# Objects are 8 bytes aligned.java.util.HashMap$Node object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Node.hash N/A 16 4 Object Node.key N/A 20 4 Object Node.value N/A 24 4 Node Node.next N/A 28 4 (loss due to the next object alignment)Instance size: 32 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

我們對節點對象運行它。一樣的東西。我們看到我們的四個字段,這個是 32 個字節。每一個 map 里面的節點都是 32 個字節。Map 里面的每一個成員,每一個鍵值對都會在這個節點里。它是 32 乘以條目的個數。

當我們開始插入值的時候,我們可以使用這樣類似的公式來計算運行時一個對象的大小是多少。這不全是正確的。你還需要算上數組的開銷。還有一個數組持有這些數值,所以我們需要加上這些數值占用的空間。這很復雜。它是 4,這是一個單獨整形的大小,它也是對每個數值的引用,乘以數組的大小。

問題是,HashMap 有一個叫做 load 因子的東西。末尾的 8 字節是每個數組都有的開銷。但是 load 因子從來都不是飽和的。它會維系某種程度的飽和,所以當它達到那個飽和因子的時候,它就會像一個數組列表一樣增長。HashMap 也會持續增長這樣它就可以維系一些空的空間。

這樣做的原因是,如果不這樣做的話,你會有許多的碰撞和性能損失。這就差不多是一個 HashMap 不論有多少個條目,也不論 load 因子是多大的開銷了。我們能算出來它需要占用多少內存。順便說一句,默認的 load 因子是 75%。HashMap 只會達到 3/4 的容量。

Sparse 數組是你替代這樣的 HashMap 的東西。讓我們看看 HashMap 的兩個例子,間接引用,間接引用的級別和內存的大小。Sparse 存儲了兩個差不多的數組。一個是 key, 另一個是 value。如果我們尋找 map 里面的某個值,或者插入某個值,我們要做的第一件事就是訪問這個整型數組,不像 HashMap,不是固定時間開銷。它需要對數組做二叉樹查找。然后我們能訪問到這個數值,在這個例子里,二叉樹查找給了我們查找值的特定單元。因為這個數組是值,我們能直接返回,直接跳到引用,然會返回。

關于內存,這里的間接引用就少了很多。整型數組是連續的,它不是鏈表。我們能直接地在數組內部跳轉。它沒有我們需要解析的節點對象,然后才能獲得值的過程。間接引用少了許多。然而,非固定時間有時會慢很多。這就是為什么你想盡可能的保證數組小的原因。說到小,意味著成百上千的條目。如果你有上萬個條目,性能就會很糟,相比較而言,這個時候 HashMap 的內存開銷反而更有吸引力一些。

$ javac SparseArray.java$ java -cp .:jol-cli-0.5-full.jar org.openjdk.jol.Main \ internals android.util.SparseArray# Running 64-bit HotSpot VM.# Objects are 8 bytes aligned.android.util.SparseArray object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 004 4 (object header) 00 00 00 008 4 (object header) 1a 69 01 f8 12 4 int SparseArray.mSize 016 1 boolean SparseArray.mGarbage false17 3 (alignment/padding gap) N/A 20 4 int[] SparseArray.mKeys [0, 0, 0, 0, 0, 0, …] 24 4 Object[] SparseArray.mValues [null, null, null, …] 28 4 (loss due to the next object alignment)Instance size: 32 bytes Space losses: 3 bytes internal + 4 bytes external = 7 bytes total

在這個例子里面,因為類都是差不多的,而且是 JVM 64 bit, Android 現在也是 64 bit 了,數字是一樣的。如果你不習慣,把它們看作是個近似值,然后增加 20% 的變量。這肯定比弄清楚在 Android 里對象的大小要容易得多。Android 里面幾乎不可能弄清楚,這個方法會容易許多。

Sparse 數組的對象本身稍微小一點,32 字節。這個大小沒有太大關系。真正有影響的是你知道它們不可能只有 32 字節。我們仍然有條目增長問題。我們需要計算整個數組的大小,那是所有整形 key,那是 4 乘以條目的個數然后加上 8. 同時還有 value 數組的大小,是同樣的計算方式,4 乘以條目的個數然后加上 8.

Sparse 數組這里有一點不同,就是它沒有 load 因子。但是它也隱含著因子的概念,因為這些數組是樹,它們是二叉樹。它們不是全部填滿的。也不是連續的。它們中間有些空間是沒有被占用的。我們需要用另外一個捏造的因子來說明這個問題,這個因子和 load 因子很像。在這個例子里面,這完全取決于你往 map 里面插入什么元素。我使用同樣的值。我假設這些數組會 75% 的滿載,這是一個非常安全的假設。

SparseArray<V> 32 + (4 * entries + 4 * entries) / 0.75HashMap<Integer, V> 48 + 32 * entries + 4 * (entries / loadFactor) + 8

現在我們能直接比較這些內容了。我們知道我們能計算間接跳轉。那很容易。現在我們能用這些公式來計算這些實例真正占用多少內存了。

SparseArray<V> 32 + (4 * 50 + 4 * 50) / 0.75 = 656HashMap<Integer, V> 48 + 32 * 50 + 4 * (50 / 0.75) + 8 = 1922

對于 HashMap 我將使用默認的 .75 因子。我們可以假設一個條目的個數。在這個例子里,我選擇了 50。也許這是一個美國各州的 Map。然后我們能進行這些計算。你能看到 Sparese 數組大概是 HashMap 的 1/3。你需要記住,這里會有一些性能的損失,因為每一個操作都不再是瞬時的時間了。這里是 50 個元素,所以二叉樹的搜索是非常快的。

結論?(44:18)

講了很多東西。都是些非常瑣碎的需要去做的事情,這樣做才能避免函數編譯時的開銷,運行時不同對象的開銷,還有間接引用的開銷。

第一個我已經展示了。打開私有成員檢查然后注意它。不要忽視它們。你不需要為每一個類型都這樣做。你不需要都看一遍,然后全部一次解決。這是你在開發應用時能同時完成的事情。相反的,對于庫的開發者來說,這會更重要一點。

作為一個庫,你希望最小化你對 APK 大小的影響和運行時性能的影響。或者 deck 大小和運行時的性能。如果你在開發一個庫,你可能需要遍歷一遍然后找出所有的問題。合成的生成函數在庫里面沒有任何意義。它只是浪費了 dex 文件的空間。也浪費了運行的時間,把它們當作 Bug 一樣看待。

如果你在使用 retrolambda,請千萬確認你已經升級到最新的版本了,因為你可能浪費了成千上萬個函數。如果你在編寫一個開源庫,請注意處理好你的匿名類。也許這沒有太大的影響。但是,在強調一遍,你是追求對使用你庫的應用開發者造成最小的影響的,這樣做你的庫就不會有問題了。

試試 Jack。它很好用。它還是快速的開發中。它現在還缺少許多東西來使得每一個人都能使用。但是肯定的是,總有些不太重要的應用是不希望在編譯的時候做些不一樣的事情的。

不要忽略 bug。不要回避,找到它,并且解決它。”哦,不管怎么樣,我都用了 2 年了。” 你可以這么說,在你轉回 Java C 索引的時候上報這些 bug。這是未來的趨勢,你不可能閉關鎖國,因為它在發生著。現在接受這些事情比未來要容易些。然后是打開 ProGuard。ProGuard 也有作用。無論你是不是在用 ProGuard 或者是其他增量縮減工具,而且 instant run 也在一起配合的很好,想必這也是 Jack 未來會使用的東西。

不要死板地套用規則。如果你在 ProGuard 的規則文件里面看見?* *,那肯定是 100% 錯誤的。這一定不是你希望使用的方式,因為這樣做你不會從 ProGuard 里面得到任何好處。你可能說 “是的,我在使用 OKHttp,但是我不想使用 HTTP/2 的所有方法,我也從來沒有用過。我不想它們被移除,我希望保持它們以備未來使用” 這并不聰明。如果你在開源庫里看到這些,這是一個 bug。上報這個 bug。如果你的應用里面有這些東西,嘗試著理解為什么它們會被加入。把它們拿出來,然后想想,看看 ProGuard 報的錯誤,然后看看你能對你的規則做些什么特別的修改。如果這些東西引起你的注意了,而且你感興趣,這里還有其他的一些演講你可以作為參考。

總結

以上是生活随笔為你收集整理的探索 Java 隐藏的开销的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

婷婷激情影院 | 国产精品久99 | 国产精国产精品 | 欧美性脚交 | 免费在线国产黄色 | 天天操天天色天天射 | 国产亲近乱来精品 | 91在线观看视频 | 国产精品短视频 | 一区三区视频 | 日本xxxxav| 日本久久久精品视频 | 99这里只有精品99 | 国产一区二区三区免费视频 | 日日夜夜天天 | 天天操天天色天天射 | 最新日韩视频 | 公与妇乱理三级xxx 在线观看视频在线观看 | 国产精品久久艹 | 天天天天天干 | a亚洲视频 | 天天人人 | 色姑娘综合网 | 免费看一级特黄a大片 | 亚洲美女视频网 | 色婷婷综合五月 | 综合婷婷丁香 | 日本精品久久久久中文字幕 | 成人九九视频 | 99久久国产免费,99久久国产免费大片 | 日本韩国精品一区二区在线观看 | 午夜精品一区二区三区四区 | 久久艹综合 | 97精品国产97久久久久久春色 | 国产在线高清 | 久久久久成 | 黄色中文字幕在线 | 西西大胆啪啪 | 婷婷天天色 | 欧美精品日韩 | 日韩精品一区二区在线观看 | 97色婷婷 | 99免费看片 | 国产精品99久久久久久有的能看 | 国产国语在线 | 在线 国产 亚洲 欧美 | 手机在线黄色网址 | 久久久久免费 | 久草在线免费资源站 | 天天操天天色天天射 | 91精品对白一区国产伦 | 天天射天天 | 国产视频一区在线免费观看 | 久热电影 | 日韩精品中文字幕在线不卡尤物 | 国产精品免费在线播放 | 亚洲电影免费 | 国产黄色片久久 | 国产精品久久免费看 | 亚洲va天堂va欧美ⅴa在线 | 久久女同性恋中文字幕 | 久久精品视频免费播放 | 国产破处在线视频 | 国产在线观看午夜 | 亚州精品国产 | 成人午夜毛片 | 中文字幕a在线 | bbb搡bbb爽爽爽| 国产精品美女毛片真酒店 | 国产免费不卡 | www.日本色 | 久久综合九色欧美综合狠狠 | 国偷自产中文字幕亚洲手机在线 | 国产精品中文字幕在线观看 | 国产精品第一视频 | av网站免费看 | 成人国产精品久久久春色 | 中日韩三级视频 | 色婷婷综合久久久中文字幕 | 亚洲一区二区三区miaa149 | 国产精品9999 | 黄色免费观看视频 | 日韩精品一区电影 | 精品国产电影 | 国产精品一区二区免费在线观看 | 国产成人久久精品77777 | 蜜臀av性久久久久av蜜臀妖精 | 中文字幕高清在线播放 | 国产精品久久久久久久久久久久午夜 | 色播五月激情综合网 | 成人黄色小说在线观看 | 欧美日韩在线播放一区 | 天天干天天操天天 | 91视频免费播放 | 黄色片毛片 | 狠狠操夜夜操 | 免费观看mv大片高清 | 午夜国产福利在线观看 | 亚洲区另类春色综合小说校园片 | 九九99 | 中文字幕乱码在线播放 | 91av99| 伊人久久一区 | 91chinesexxx | 亚洲一级片在线看 | 欧美性免费 | 日韩欧美久久 | 激情网站| 69av免费视频 | 免费观看www7722午夜电影 | 久久无码精品一区二区三区 | 久久久国产精品免费 | 欧美日韩高清一区 | 国产精品电影在线 | 久久精品国产一区 | 午夜色站 | 人人爽人人爽人人爽学生一级 | 天堂av高清 | 精品久久久久久久久久久院品网 | 五月婷网站 | 九九九在线观看视频 | 就要干b | 一级理论片在线观看 | 夜夜骑日日 | 亚洲 欧美变态 另类 综合 | 西西444www| 成人一级片在线观看 | 天天综合久久综合 | 久久久精品小视频 | 午夜精品久久久久久久99无限制 | 99国产一区| 久久综合狠狠综合 | 伊人色**天天综合婷婷 | 波多野结衣视频一区 | 九草视频在线 | 国产精品第7页 | 天天操天天操一操 | 毛片网在线观看 | 国产精品久久久久久五月尺 | 日韩高清在线不卡 | 久久有精品 | 精品久久中文 | 自拍超碰在线 | 国产视频观看 | 国产美女精彩久久 | 91黄色在线视频 | 国产精品精品视频 | 久久久久欧美精品999 | 亚洲a网| 色美女在线 | 国产一级高清视频 | 黄污视频大全 | 丁香电影小说免费视频观看 | 亚洲综合在线五月天 | 91亚洲免费 | 国产精品一区二区三区在线看 | 在线观看视频免费大全 | 欧美激情一区不卡 | 日韩黄色大片在线观看 | 欧美成人性战久久 | 国产高清免费视频 | 久久激情综合 | 免费在线国产视频 | 国产一级片播放 | 色网站在线观看 | 亚洲精品色 | 91久久国产综合精品女同国语 | 免费看色的网站 | 日韩av成人在线观看 | 国产探花在线看 | 激情网五月婷婷 | 亚洲天堂香蕉 | 欧美一区二区三区在线观看 | 亚洲高清色综合 | 国产精品午夜免费福利视频 | 亚洲一级片在线观看 | 樱空桃av | 999超碰| 亚洲精欧美一区二区精品 | 欧美另类高清 videos | 欧美日韩精品在线播放 | 欧美国产日韩一区二区三区 | 欧美日韩国产一区二 | 在线观看免费av片 | av日韩av| 色综合网在线 | 久久激情电影 | 欧美午夜寂寞影院 | 国产精品五月天 | www视频在线播放 | 亚洲精品小区久久久久久 | 国产九九九视频 | 久久精品九色 | 国产一级做a | 色婷婷狠狠五月综合天色拍 | 99国产精品视频免费观看一公开 | 国产精品久久久久久一区二区 | 精品久久一区二区 | 激情丁香在线 | 五月婷婷导航 | 国产激情小视频在线观看 | 夜夜夜夜猛噜噜噜噜噜初音未来 | 国产视频亚洲精品 | 中文在线天堂资源 | 国产一级二级三级在线观看 | 黄色.com| 中文免费| 欧美成人播放 | 丁香资源影视免费观看 | 成人午夜黄色 | 精品999久久久 | 国产麻豆精品95视频 | 国产一卡久久电影永久 | 五月综合在线观看 | 日韩欧美在线视频一区二区三区 | 成人av一二三区 | 成人在线网站观看 | 中文字幕一区二区三区四区久久 | 国产免费一区二区三区最新6 | 久久网站最新地址 | 国产a视频免费观看 | 亚洲天堂网视频在线观看 | 国产精品刺激对白麻豆99 | 中文字幕亚洲欧美日韩 | 韩日精品在线 | 国产91综合一区在线观看 | 一区二区三区在线观看免费视频 | 69视频国产 | 九九久久影院 | 日韩av在线资源 | 亚洲高清91 | 久久久久女教师免费一区 | 成人午夜电影网站 | 91精品视频免费看 | 久久久精品电影 | 久久久久亚洲精品男人的天堂 | 玖玖视频| 日韩国产欧美在线视频 | 在线91av | 人人网av | 在线精品视频在线观看高清 | 亚洲精品乱码白浆高清久久久久久 | 在线观看日韩av | 国产中文字幕av | 国产在线观看地址 | 97超碰在线播放 | 亚洲六月丁香色婷婷综合久久 | 久久99久久99精品免观看软件 | avove黑丝| 国产精品综合久久久久久 | 人人玩人人添人人澡超碰 | 精品二区久久 | 一区精品在线 | 日韩av免费在线电影 | 日本最新一区二区三区 | 中文字幕在线观看国产 | 亚洲综合射 | 国产精品久久久久久久久久东京 | 久久的色| 99久久精品国产一区二区三区 | 欧洲色吧 | 欧美在线99| 免费a级毛片在线看 | 综合伊人av| 日本久久久久久 | 91在线视频播放 | 免费观看mv大片高清 | 天天视频色版 | 亚洲成免费 | 久久久久国产一区二区三区 | 久久这里只有精品视频首页 | 国产一区欧美二区 | 久草精品电影 | 天堂av在线免费观看 | 91高清视频在线 | 国产精品视频不卡 | 18国产精品福利片久久婷 | 国产亚洲精品久久久久久 | 国产一级视频免费看 | av中文字幕在线免费观看 | 欧美精品一区二区三区四区在线 | 久久不卡视频 | 亚洲婷婷在线视频 | 精品国产乱码一区二区三区在线 | 亚洲综合色激情五月 | 欧美日韩国产一区二区三区 | 在线看片视频 | www色片| 天天干一干 | 国产精品免费视频一区二区 | 免费精品在线观看 | 色综合色综合色综合 | 日本三级中文字幕在线观看 | 草樱av| 黄色电影在线免费观看 | 国产免费不卡av | 欧美激情视频一区二区三区免费 | 国产成人免费在线观看 | 91社区国产高清 | 精品二区久久 | av在线播放中文字幕 | 色综合色综合久久综合频道88 | www天天干| 久久99国产综合精品免费 | 五月视频 | 久久久男人的天堂 | 亚洲女欲精品久久久久久久18 | 天无日天天操天天干 | 久久精品国产99国产 | 超碰在线免费福利 | 天天射成人 | 欧美午夜性 | 国内精品久久久久久久久久清纯 | 国产精品粉嫩 | 综合色站| 国产五月色婷婷六月丁香视频 | 在线亚洲精品 | 99久久精品免费看国产 | 最新中文字幕视频 | 首页国产精品 | 五月婷婷爱 | 蜜臀久久99精品久久久无需会员 | 亚洲国产欧美在线看片xxoo | 日日夜夜免费精品 | 久久精品国产一区二区 | 中文字幕日韩伦理 | 97香蕉超级碰碰久久免费软件 | av在线中文 | 一本大道久久精品懂色aⅴ 五月婷社区 | 激情开心色 | 欧美日韩国产在线一区 | 天堂av在线免费观看 | 在线免费观看亚洲视频 | 久久久久久久影视 | 97精品国产aⅴ | 色噜噜日韩精品一区二区三区视频 | 六月丁香色婷婷 | 日本特黄一级片 | 999精品视频| 国产一区在线免费观看视频 | 国产精品97 | 色多多视频在线观看 | 久久九九国产精品 | 一区二区三区四区久久 | 免费看污片 | 久久久久久久久久国产精品 | 亚洲女同videos | 国产精品久久久久久一区二区 | 亚洲资源在线观看 | 欧美日韩午夜 | www.av在线播放 | 亚洲麻豆精品 | 伊人开心激情 | 午夜久久久影院 | av一区二区三区在线播放 | 欧美在线视频第一页 | av中文天堂 | 日韩av中文在线观看 | 在线电影91| 午夜精品一区二区三区视频免费看 | 91视频在线自拍 | 伊人久久精品久久亚洲一区 | 婷婷激情5月天 | 日韩小视频 | 色综久久 | 亚洲精品国产品国语在线 | 九色porny真实丨国产18 | 成人黄色电影在线播放 | 国产婷婷一区二区 | 三三级黄色片之日韩 | 三级黄色免费片 | 免费男女羞羞的视频网站中文字幕 | 国产精品毛片网 | adc在线观看 | 久久国产精品久久久久 | 国产精品综合久久久久久 | 97国产小视频 | 欧美精品亚洲精品 | 99在线视频精品 | 中文字幕一区二区三区乱码在线 | 国产香蕉av| 久久久久久国产精品亚洲78 | www日日| 五月激情丁香婷婷 | 69国产精品成人在线播放 | 国产成人一区二区在线观看 | 91亚洲网 | 就要干b| 69av在线视频 | 成人a视频片观看免费 | 成人av av在线| 天天干天天插伊人网 | 亚洲观看黄色网 | 国产精品久久艹 | 日韩免费观看视频 | 六月久久婷婷 | 亚洲精品激情 | 丁香六月婷婷激情 | 久久久国产网站 | 99久久日韩精品免费热麻豆美女 | 黄色免费网 | 在线精品一区二区 | 一区二区三区四区五区在线视频 | 欧美激情第八页 | 18pao国产成视频永久免费 | 又爽又黄在线观看 | 国产高清视频免费最新在线 | 九九视频精品在线 | 蜜臀av在线一区二区三区 | 亚洲一级二级三级 | www黄色 | 天天射天天爽 | 亚洲精品九九 | 欧美激情在线网站 | 中文字幕亚洲欧美日韩2019 | 99se视频在线观看 | 91香蕉视频720p | 国内精品久久久久久久97牛牛 | 一级黄色在线视频 | 一区二区三区在线视频观看58 | 91手机电视 | 500部大龄熟乱视频 欧美日本三级 | 97超碰中文字幕 | 欧美黄色成人 | 欧美日韩一级在线 | 午夜12点| 99视频偷窥在线精品国自产拍 | 日韩婷婷| 人人爽人人爽人人爽学生一级 | 国产麻豆精品95视频 | 日韩精品一区二区三区视频播放 | 天天操天天干天天综合网 | 午夜精品影院 | 97国产精品视频 | 五月天婷亚洲天综合网鲁鲁鲁 | 婷婷国产视频 | 欧美 高跟鞋交 xxxxhd | 国产成年人av | 欧美久草网 | 中文字幕色网站 | 美女黄频在线观看 | 亚洲欧美乱综合图片区小说区 | 天天射天天干天天爽 | 国产成人精品午夜在线播放 | 成人久久精品视频 | 亚洲综合色网站 | 亚洲视频免费 | 日韩欧美视频免费观看 | 黄色特一级片 | 午夜精品一区二区三区在线播放 | 在线播放av网址 | 午夜av大片| 国产精品乱码久久久 | 最近高清中文在线字幕在线观看 | 成人9ⅰ免费影视网站 | 色吧久久 | 日本三级在线观看中文字 | 在线视频日韩一区 | 99在线免费视频观看 | 日韩中文字幕在线不卡 | 久久av高清 | 久久女同性恋中文字幕 | jizz欧美性9 国产一区高清在线观看 | 国产成视频在线观看 | 日韩av看片 | 国产中文字幕精品 | 九色自拍视频 | 51久久夜色精品国产麻豆 | 久久综合操| 在线高清av| av一区在线播放 | 欧美日韩一区二区在线观看 | 欧美成人影音 | 久久国产综合视频 | 99免费精品视频 | www黄免费 | 麻豆小视频在线观看 | 日韩在线中文字幕视频 | av色网站 | 国产精品日韩 | 免费成人在线网站 | 亚洲婷婷网 | 四虎永久国产精品 | 伊人狠狠色 | 久久免费视频观看 | 久久免费视频网站 | 国产精品一区欧美 | 精品国内自产拍在线观看视频 | 久草视频99| 国产精品一区二区三区观看 | 国产精品丝袜久久久久久久不卡 | 日本成人黄色片 | 国产免费一区二区三区最新 | 9999在线观看 | 97视频在线观看成人 | 国产成人一区在线 | 欧美性久久久 | 免费福利在线播放 | 久草电影网 | 热久久免费视频精品 | 国产在线观看你懂的 | 亚洲激情综合网 | 黄色一级动作片 | 日韩视频一区二区在线 | 午夜精品一区二区三区在线观看 | 天天婷婷 | 欧美日韩中文国产一区发布 | 成年人免费看的视频 | 色.www | 人人爽影院| 久久久国产精品人人片99精片欧美一 | 4hu视频| 成人av在线电影 | 亚洲dvd | 国产精品扒开做爽爽的视频 | 中国黄色一级大片 | 免费男女羞羞的视频网站中文字幕 | av丝袜在线 | 五月色丁香 | 久久免费福利视频 | 国产不卡一区二区视频 | 久久精品视频在线观看 | 亚洲欧洲中文日韩久久av乱码 | 免费在线观看污网站 | 国产在线91精品 | 久久97超碰 | 中文字幕在线观看网 | 欧美国产一区在线 | 黄色一级大片免费看 | 99欧美| 天天操天天是 | 曰韩精品 | 狠狠久久综合 | 五月天欧美精品 | 国产成人在线观看免费 | 日韩视频欧美视频 | 亚洲精品国偷自产在线99热 | 在线中文字幕播放 | 国产高清视频免费在线观看 | 午夜精品久久久99热福利 | 四虎成人免费观看 | 国产黄色看片 | 美女黄频在线观看 | 夜夜躁天天躁很躁波 | 91热在线| 国产99亚洲| 九九久久在线看 | 色999视频 | 超碰夜夜 | 在线看黄网站 | 国产成人久久精品77777综合 | 国产一在线精品一区在线观看 | 国产在线高清 | 国产精品久久久久免费观看 | 在线观看日韩视频 | 激情视频久久 | 久久综合久色欧美综合狠狠 | 91精彩在线视频 | 手机在线中文字幕 | 日韩一区二区三区免费电影 | 国产二区精品 | 亚洲波多野结衣 | 夜夜操天天操 | 久久久综合精品 | 日韩中文字幕视频在线观看 | 国产精品三级视频 | 国产午夜激情视频 | 欧美一区二区免费在线观看 | av电影在线不卡 | 精品毛片一区二区免费看 | 成人免费观看完整版电影 | 97成人啪啪网 | 日韩中文字幕在线不卡 | 国产主播大尺度精品福利免费 | 国产一区私人高清影院 | 日本精品久久久久中文字幕5 | 婷婷伊人五月天 | 日韩免费在线观看 | 国产精品视频地址 | 国产99久久久久 | 黄色精品网站 | av国产在线观看 | 国产精品久久久久久久久久久久久久 | 99久在线精品99re8热视频 | 在线免费观看亚洲视频 | 91在线文字幕 | 欧美一二三区在线观看 | 久久国产日韩 | 91片黄在线观看 | 99久久精品国产欧美主题曲 | 久热精品国产 | 在线观看一级 | 国产久草在线 | 久久夜夜爽 | 国产艹b视频 | 91久久精品一区二区三区 | 五月婷婷中文字幕 | 国产在线观看高清视频 | 人人射人人澡 | 国产成人av网址 | 国产在线观看免费 | 日韩欧美一区二区三区在线观看 | 国产手机视频在线 | 美女视频黄免费的久久 | 国产精品女同一区二区三区久久夜 | 五月综合激情 | 女女av在线| 欧美日韩综合在线 | 日韩性久久 | 黄色三级视频片 | 日本最新一区二区三区 | 欧美日韩在线观看视频 | 黄色大片av| 69亚洲精品 | 亚洲精品欧美精品 | 97超级碰碰 | 色综合久久久网 | 中文字幕第一页av | 人人艹视频 | 亚洲精品女 | 97天天干 | 91tv国产成人福利 | 天天草天天干天天射 | 黄色三级在线看 | 99色在线| 99re6热在线精品视频 | 丁香狠狠| 日韩一区二区三区免费电影 | 激情伊人五月天久久综合 | 久久午夜视频 | 九九色综合 | 亚洲精品乱码久久久久v最新版 | 国产精品地址 | 日本在线视频网址 | 国产一区视频导航 | 91网址在线看 | 久久人人爽人人片 | www国产在线| 97视频免费在线观看 | 日韩午夜精品福利 | 激情网在线视频 | wwwwww黄| 九九在线国产视频 | 亚洲精品视频在线观看免费视频 | 国产精品视频免费在线观看 | 狠狠操精品 | 中文字幕 国产精品 | 欧美精品一区二区性色 | 国产一区欧美日韩 | 国产拍揄自揄精品视频麻豆 | 久久久久草 | 国产精品美女久久 | 欧美九九九 | 国产亚洲欧美在线视频 | 99欧美| 亚洲日本国产 | 蜜桃视频日韩 | 亚洲精品美女在线 | 五月天综合网 | 午夜精品一区二区三区在线观看 | 国产一级淫片免费看 | 在线日韩av | 日韩高清网站 | 香蕉网在线 | 色偷偷人人澡久久超碰69 | 91免费观看视频网站 | 欧美黄在线| 久久人人97超碰精品888 | 成人丁香花 | 中文字幕色播 | www.91av在线| 久久69精品久久久久久久电影好 | 蜜桃av人人夜夜澡人人爽 | 国内精品久久久久国产 | 欧美日韩免费在线观看视频 | 免费不卡中文字幕视频 | 日b视频在线观看网址 | 国产成人在线观看 | 久99视频| 中文字幕欲求不满 | 日本在线精品视频 | 国精产品满18岁在线 | 女人18毛片a级毛片一区二区 | 久久久免费网站 | 亚洲精品高清视频 | 91成人在线观看高潮 | 欧美一区免费观看 | 成人在线播放av | 日日干天天射 | 一区 二区 精品 | 免费精品视频在线观看 | 天天天干天天天操 | 91重口视频| 亚洲精品美女在线 | 操综合| 又长又大又黑又粗欧美 | 欧美成人精品在线 | 亚洲国产一区在线观看 | 69国产成人综合久久精品欧美 | 国产精品久久麻豆 | 91精品视频在线免费观看 | 成人久久 | 欧美日韩国产一区二 | 免费视频色 | 中文字幕区 | 99久久久国产精品免费99 | 91精品小视频 | 国产精品粉嫩 | 五月天激情婷婷 | 操操操天天操 | 国产高清精品在线观看 | 欧美日韩伦理在线 | 在线中文字幕一区二区 | 911精品视频 | 亚洲精品国偷拍自产在线观看 | 992tv又爽又黄的免费视频 | 在线观看免费版高清版 | 久久精品视频在线免费观看 | 国产精品免费视频观看 | 久草在线观看资源 | 中文字幕亚洲情99在线 | 最近2019好看的中文字幕免费 | 国产欧美精品一区二区三区 | 国产69精品久久app免费版 | 成人毛片久久 | 午夜婷婷网| 亚州免费视频 | 久久久精品小视频 | 天天爱综合| 免费av在线网 | 精品国产一区二区三区四区在线观看 | 91av在线看 | 亚洲日本va午夜在线电影 | 狠狠狠色丁香婷婷综合久久88 | 国产精品av免费观看 | 久久五月婷婷丁香社区 | 玖玖在线观看视频 | 欧美-第1页-屁屁影院 | 欧美一区二区三区在线视频观看 | 日韩最新av | 国产91粉嫩白浆在线观看 | 国产在线美女 | 91精品专区| www.成人精品| 天天操操操操操操 | 亚洲黄色成人网 | 在线播放av网址 | 欧美亚洲三级 | 亚洲视频1 | 91综合视频在线观看 | 成人av影院在线观看 | 精品国产视频在线观看 | 丁香久久久 | 亚洲国产精品999 | 91在线视频| 99久久精品免费看国产 | 欧美成人猛片 | 亚洲精欧美一区二区精品 | 国产高清一级 | 久久婷婷五月综合色丁香 | 婷婷资源站 | www.超碰| 色噜噜日韩精品一区二区三区视频 | 狠狠色丁香九九婷婷综合五月 | 久久久久久中文字幕 | 天天色天天射天天综合网 | 国产精品一区二 | 99精品系列 | www.久久视频 | 色综合天天爱 | 国产成人精品一区二区三区网站观看 | 国产美女被啪进深处喷白浆视频 | 久久国产欧美日韩 | 欧美一级视频免费 | 亚洲一区日韩 | 久久国内精品99久久6app | 香蕉视频在线网站 | 五月婷婷丁香网 | 久久久福利 | 一区二区三区 亚洲 | 色在线免费观看 | 美女久久精品 | www.99在线观看 | 午夜少妇av| 亚a在线 | 国内精品一区二区 | 国产成人a v电影 | 又爽又黄又无遮挡网站动态图 | 久久久久久久国产精品视频 | 国产精品美女久久久久久久 | 欧美极度另类性三渗透 | 久久丁香 | 免费看片网址 | 久草免费色站 | 一区在线观看 | 日韩av免费在线看 | 99色视频| 欧美一区二区三区在线视频观看 | 亚洲精品成人av在线 | 亚洲午夜久久久久久久久久久 | 99久久国产免费,99久久国产免费大片 | 久久91久久久久麻豆精品 | 欧美一级免费 | 欧美日韩aa | 国产黄色资源 | 亚洲欧美日韩精品久久奇米一区 | 九九综合在线 | 精品亚洲一区二区三区 | 91精品国产乱码在线观看 | 中文字幕在线视频第一页 | 中文有码在线 | 国产精品久久电影网 | 伊人伊成久久人综合网站 | 天天草av | 日本中文字幕一二区观 | 天天干夜夜操视频 | 亚洲九九九在线观看 | 99九九视频 | 日韩av中文在线 | 日韩欧美不卡 | 玖玖视频免费在线 | 色久天 | av高清网站在线观看 | 国产在线精品福利 | 欧美精品中文在线免费观看 | 婷婷久久网 | 日韩免费在线播放 | 国产午夜精品一区二区三区在线观看 | 国产粉嫩在线观看 | 成人免费视频网站在线观看 | 国产麻豆精品95视频 | 三级av免费观看 | 国产精品电影一区二区 | 国产尤物视频在线 | 精品久久久久久亚洲综合网站 | 国产午夜精品久久久久久久久久 | 91探花在线 | 美女很黄免费网站 | 精品黄色在线 | 天天综合天天综合 | 波多野结衣日韩 | 九九在线国产视频 | 亚洲天天在线日亚洲洲精 | se视频网址 | 亚洲国产精品女人久久久 | 伊人黄| 国产精品久久久久久99 | 久久视频免费在线观看 | 国产美女视频网站 | 四虎国产永久在线精品 | 超碰97中文 | 欧美色图视频一区 | 亚洲黄色在线观看 | 亚洲高清久久久 | 国产一区在线精品 | 91精品久久久久久久久 | 99久久久久久久久久 | 在线观看国产一区二区 | 久久国内精品99久久6app | 日韩黄色影院 | 国产精品不卡在线播放 | 欧美亚洲免费在线一区 | 天天操天天拍 | 国产精品三级视频 | 天天色影院 | 亚洲dvd | 精品国产99国产精品 | 国产一区二区在线免费播放 | 手机色站 | 三级黄色大片在线观看 | 欧美激情在线看 | 久久r精品 | 99精品国产99久久久久久97 | 激情久久五月 | 成人黄色电影在线观看 | www.夜色.com | 在线看国产精品 | 国产98色在线 | 日韩 | 久久精品欧美一区 | 欧美精品视 | 日本三级人妇 | 最新日韩电影 | 欧美在线一级片 | 特片网久久 | 一级精品视频在线观看宜春院 | a级国产乱理论片在线观看 伊人宗合网 | 500部大龄熟乱视频 欧美日本三级 | 久久人人爽爽人人爽人人片av | 精品国产乱码久久久久久久 | 久久你懂的 | 99这里都是精品 | 久操久 | 精品一区二区日韩 | 欧美aaa大片 | 欧美日韩精品综合 | 又黄又爽又刺激视频 | 久久艹中文字幕 | www免费视频com| av 一区二区三区四区 | 久久久亚洲国产精品麻豆综合天堂 | 日韩欧美精选 | 精品久久久一区二区 | 国产 日韩 欧美 在线 | 久久国产精品小视频 | 99精品免费久久久久久日本 | 欧美日韩不卡一区二区 | 日韩大片在线播放 | 欧美日韩在线精品一区二区 | 亚洲综合网| 国产精品网红直播 | 色的网站在线观看 | 免费高清av在线看 | 91精品网站在线观看 | 国产在线91在线电影 | 久久久 精品| 欧美精品第一 | 日韩黄色免费在线观看 | 亚洲精品乱码久久久久久蜜桃欧美 | 麻豆免费精品视频 | 久草网视频 | av电影一区二区 | 麻豆播放| 九九热精品视频在线播放 | 久久精品牌麻豆国产大山 | 亚洲开心激情 | 国产精品久久视频 | 国产不卡精品 | 免费色视频网站 | 欧美激情在线看 | 91麻豆精品国产自产在线 | 8x成人在线 | 中文在线免费一区三区 | 粉嫩aⅴ一区二区三区 | 久久激情久久 | 久久婷婷精品视频 | 亚洲三级黄色 | 国产黄色电影 | 精产嫩模国品一二三区 | 97成人资源 | 免费av成人在线 | 亚洲视频每日更新 | 久久国产日韩 | 国产呻吟在线 | 五月天综合婷婷 | 香蕉视频久久久 | 99久精品 | 国产精品一区二区久久久 | 黄色av成人在线 | 日韩欧美在线影院 | 亚洲成av人片在线观看 | 久久久久久久久久网 | 国产欧美在线一区二区三区 | 精品国产乱码一区二区三区在线 | 狠狠狠色丁香婷婷综合久久88 | 有码中文在线 | 成人av在线亚洲 | 久草视频首页 | 友田真希av | 久久99热这里只有精品国产 | 在线观看成人毛片 | 精品国产乱码久久 | 在线视频 一区二区 | 成人av在线影视 | 免费在线观看视频一区 | 亚洲免费国产视频 | 久久在线免费视频 | 欧美坐爱视频 | 久草国产在线观看 | 在线观av| 中文字幕一区二区三区视频 | 久久高清免费视频 | 99久在线精品99re8热视频 | 成人a在线观看高清电影 | 亚洲最大av在线播放 | 中文字幕精品一区 | 久久免费播放视频 | 色综合天天射 | 免费看片成年人 | 欧美日韩免费观看一区=区三区 | 欧美激情综合五月色丁香小说 | 在线看片一区 | 日日干天夜夜 | 夜夜操狠狠操 | 在线观看www. | 日本激情动作片免费看 | 精品久久久久一区二区国产 | 亚洲视频精品 | 在线观看中文字幕视频 | 免费午夜视频在线观看 | 国产成人精品在线 | 国产日韩欧美视频 | 天天色宗合 | 在线观看中文字幕视频 | 成年人av在线播放 | av一区二区三区在线播放 | 国产最新视频在线观看 | 在线观看91久久久久久 | 四虎影视欧美 |