精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

得物 Android 包體積資源優化實踐

移動開發 Android
本文主要介紹了得物APP資源優化做了的一些動作,其中對資源優化插件的工作模式進行了重點介紹。當然,對于資源依舊有不少手段可以完善,比如提供高效簡單的 9 圖下發方案,包體積平臺增加圖片相似度檢測能力、把一些次級的資源通過插件包下發都是之后可以嘗試的地方。?

包體積優化中,資源優化一般都是首要且容易有成效的優化方向。資源優化是通過優化APK中的資源項來優化包體積,本文我們會介紹得物App在資源優化上做的一些實踐。

1、插件優化

插件優化資源在得物App最新版本上收益12MB。插件優化的日志在包體積平臺有具體的展示,也是為了提供一個資源問題追溯的能力。

圖片圖片

1.1 插件環境配置

插件首先會初始化環境配置,如果機器上未安裝運行環境則會去oss下載對應的可執行文件。

圖片圖片

1.2 圖片壓縮

在開發階段,開發同學首先會通過TinyPNG等工具主動對圖片進行壓縮,而對于三方庫和一些業務遺漏處理的圖片則會在打包的時候通過gradle插件進行壓縮。

圖片壓縮插件使用 cwebp 對圖片進行webp轉換,使用 guetzli 對JPEG進行壓縮,使用pngquant對PNG 進行壓縮,使用 gifsicle 對gif進行壓縮。在實施對過程中,對于 res 目錄下的文件優先使用 webp 處理,對assets 目錄下的文件則進行同格式壓縮。下面先介紹下資源壓縮插件的工作模式和原理。

1.2.1 Res圖片壓縮

  • 第一步,找到并遍歷 ap_ 文件

圖片圖片

這里對 ap_ 文件進行一下簡單介紹,ap_ 文件是由 AAPT2 生成的,AAPT2(Android 資源打包工具)是一種構建工具,Android Studio 和 Android Gradle 插件使用它來編譯和打包應用的資源。AAPT2 會解析資源、為資源編制索引,并將資源編譯為針對 Android 平臺進行過優化的二進制格式。

AAPT2這個工具在打包過程中主要做了下列工作:

把"assets"和"res/raw"目錄下的所有資源進行打包(會根據不同的文件后綴選擇壓縮或不壓縮),而"res/"目錄下的其他資源進行編譯或者其他處理(具體處理方式視文件后綴不同而不同,例如:".xml"會編譯成二進制文件,".png"文件會進行優化等等)后才進行打包;

會對除了assets資源之外所有的資源賦予一個資源ID常量,并且會生成一個資源索引表resources.arsc;

編譯AndroidManifest.xml成二進制的XML文件;

把上面3個步驟中生成結果保存在一個*.ap_文件,并把各個資源ID常量定義在一個 R.java\ R.txt中;

  • 第二步,解壓 ap_ 文件,找到 res/drawable 、res/mipmap 、res/raw 目錄下的圖片進行壓縮
fun compressImg(imgFile: File): Long {
    if (ImageUtil.isJPG(imgFile) || ImageUtil.isGIF(imgFile) || ImageUtil.isPNG(imgFile)) {
        val lastIndexOf = imgFile.path.lastIndexOf(".")
        if (lastIndexOf < 0) {
            println("compressImg ignore ${imgFile.path}")
            return 0
        }
        val tempFilePath =
                "${imgFile.path.substring(0, lastIndexOf)}_temp${imgFile.path.substring(lastIndexOf)}"


        if (ImageUtil.isJPG(imgFile)) {
            Tools.cmd("guetzli", "--quality 85 ${imgFile.path} $tempFilePath")
        } else if (ImageUtil.isGIF(imgFile)) {
            Tools.cmd("gifsicle", "-O3 --lossy=25 ${imgFile.path} -o $tempFilePath")
        } else if (ImageUtil.isPNG(imgFile)) {
            Tools.cmd(
                    "pngquant",
                    "--skip-if-larger --speed 1 --nofs --strip --force  --quality=75  ${imgFile.path} --output $tempFilePath"
            )
        }
        val oldSize = imgFile.length()
        val tempFile = File(tempFilePath)
        val newSize = tempFile.length()
        return if (newSize in 1 until oldSize) {
            val imgFileName: String = imgFile.path
            if (imgFile.exists()) {
                imgFile.delete()
            }
            tempFile.renameTo(File(imgFileName))
            oldSize - newSize
        } else {
            if (tempFile.exists()) {
                tempFile.delete()
            }
            0L
        }
    }
    return 0
}

圖片的壓縮收益最大,且實施簡單,風險最低,是資源優化的首選。

1.2.2 Assets圖片壓縮

Assets 圖片壓縮的處理方式與 res 下差不多,區別僅僅在于掛載的 task 與 壓縮模式不同,Assets 下單資源由于是通過 AssetsManager 按照名稱獲取的,且使用場景不可控,無法明確感知業務使用對格式是否有要求的前提下,同格式壓縮是相對穩妥的方案。

val mergeAssets = project.tasks.getByName("merge${variantName}Assets")
mergeAssets.doLast { task ->
    (task as MergeSourceSetFolders).outputDir.asFileTree.files.filter {
        val originalPath = it.absolutePath.replace(task.outputDir.get().toString() + "/", "")
        val filter = context.compressAssetsExtension.whiteList.contains(originalPath)
        if (filter) {
            println("Assets compress ignore:$originalPath")
        }
        !filter
    }.forEach { file ->
        val originalPath = file.absolutePath.replace(task.outputDir.get().toString() + "/", "")
        val reduceSize = CompressUtil.compressImg(file)
        if (reduceSize > 0) {
            assetsShrinkLength += reduceSize
            assetsList.add("$originalPath => reduce[${byteToSize(reduceSize)}]")
        }
    }
    println("assets optimized:${byteToSize(assetsShrinkLength)}")
}

1.3 資源去重

相較于壓縮,資源的去重需要對arsc文件格式有一點了解。為了便于理解,這里先對arsc二進制文件進行一點簡單的介紹。

resource.arsc文件是Apk打包過程中的產生的一個資源索引文件,它是一個二進制文件,源碼ResourceTypes.h 定義了其數據結構。通過學習resource.arsc文件結構,可以幫助我們深入了解apk包體積優化中使用到的 重復資源刪除、資源文件名混淆 技術。關于 ARSC 文件的具體細節感興趣的可以參考:https://huanle19891345.github.io/en/android/%E7%83%AD%E4%BF%AE%E5%A4%8D%E5%AD%97%E8%8A%82%E7%A0%81/tinker/%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/resource.arsc%E7%94%9F%E6%88%90%E5%92%8C%E7%BB%93%E6%9E%84/

圖片圖片

將apk使用AS 打開也能看到resource.arsc中存儲的信息

圖片圖片

說回到資源去重,去重打原理很簡單,找到資源文件目錄下相同的文件,然后刪除掉重復的文件,最后到 arsc 中修改記錄,將刪除的文件索引名稱進行替換。

由于刪除重復資源在 arsc 中只是對常量池中路徑替換,并沒有刪除 arsc 中的記錄,也沒有修改PackageChunk 中的常量池內容,也就是對應上圖中的 Name  字段,故而重復資源的刪除安全性比較高。

下面介紹下具體實施方案:

  • 第一步遍歷ap文件,通過 crc32 算法找出相同文件。之所以選擇 crc32 是因為 gralde 的 entry file 自帶 crc32 值,不需要進行額外計算,但是 crc32 是有沖突風險的,故而又對 crc32 的重復結果進行 md5 二次校驗。
  • 第二步則是對原始重復文件的刪除
  • 第三步修改 ResourceTableChunk 常量池內容,進行資源重定向
// 查詢重復資源
val groupResources = ZipFile(apFile).groupsResources()
// 獲取
val resourcesFile = File(unZipDir, "resources.arsc")
val md5Map = HashMap<String, HashSet<ZipEntry>>()
val newResouce = FileInputStream(resourcesFile).use { stream ->
    val resouce = ResourceFile.fromInputStream(stream)
    groupResources.asSequence()
        .filter { it.value.size > 1 }
        .map { entry ->
            entry.value.forEach { zipEntry ->
                if (whiteList.isEmpty() || !whiteList.contains(zipEntry.name)) {
                    val file = File(unZipDir, zipEntry.name)
                    MD5Util.computeMD5(file).takeIf { it.isNotEmpty() }?.let {
                        val set = md5Map.getOrDefault(it, HashSet())
                        set.add(zipEntry)
                        md5Map[it] = set
                    }
                }
            }
            md5Map.values
        }
        .filter { it.size > 1 }
        .forEach { collection ->
            // 刪除多余資源
            collection.forEach { it ->
                val zips = it.toTypedArray()
                // 所有的重復資源都指定到這個第一個文件上
                val coreResources = zips[0]
                for (index in 1 until zips.size) {
                    // 重復的資源
                    val repeatZipFile = zips[index]
                    result?.add("${repeatZipFile.name} => ${coreResources.name}    reduce[${byteToSize(repeatZipFile.size)}]")
                    // 刪除解壓的路徑的重復文件
                    File(unZipDir, repeatZipFile.name).delete()
                    // 將這些重復的資源都重定向到同一個文件上
                    resouce
                        .chunks
                        .filterIsInstance<ResourceTableChunk>()
                        .forEach { chunk ->
                            val stringPoolChunk = chunk.stringPool
                            val index = stringPoolChunk.indexOf(repeatZipFile.name)
                            if (index != -1) {
                                // 進行剔除重復資源
                                stringPoolChunk.setString(index, coreResources.name)
                            }
                        }
                }
            }
        }


    resouce
}

1.4 資源混淆

資源混淆則是在資源去重打基礎上更進一步,與代碼混淆的思路一致,用長路徑替換短路徑,一來減小文件名大小,二來降低arsc中常量池中二進制文件大小。

長路徑替換短路徑修改 ResourceTableChunk 即可,與重復資源處理如出一轍。

同時我們發現 PackageChunk 中常量池中字段還是原來的內容,但是并不影響apk的運行。因為通過getDrawable(R.drawable.xxx)方式加載的資源在編譯后對應的是getDrawable(0x7f08xxxx)這種16進制的內容,其實就是與 arsc 中的 ID 對應,用不上 Name 字段。而通過getResources().getIdentifier()方式調用的我們通過白名單keep住了,Name 字段在這里也是可以移除的。

val resourcesFile = File(unZipDir, "resources.arsc")
        val newResouce = FileInputStream(resourcesFile).use { inputStream ->
            val resouce = ResourceFile.fromInputStream(inputStream)
            resouce
                .chunks
                .filterIsInstance<ResourceTableChunk>()
                .forEach { chunk ->
                    val stringPoolChunk = chunk.stringPool
                    // 獲取所有的路徑
                    val strings = stringPoolChunk.getStrings() ?: return@forEach


                    for (index in 0 until stringPoolChunk.stringCount) {
                        val v = strings[index]


                        if (v.startsWith("res")) {
                            if (ignore(v, context.proguardResourcesExtension.whiteList)) {
                                println("resProguard  ignore  $v ")
                                // 把文件移到新的目錄
                                val newPath = v.replaceFirst("res", whiteTempRes)
                                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                                if (!parent.exists()) {
                                    parent.mkdirs()
                                }
                                keeps.add(newPath)
                                // 移動文件
                                File("$unZipDir${File.separator}$v").renameTo(File("$unZipDir${File.separator}$newPath"))
                                continue
                            }
                            // 判斷是否有相同的
                            val newPath = if (mappings[v] == null) {
                                val newPath = createProcessPath(v, builder)
                                // 創建路徑
                                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                                if (!parent.exists()) {
                                    parent.mkdirs()
                                }
                                // 移動文件
                                val isOk =
                                    File("$unZipDir${File.separator}$v").renameTo(File("$unZipDir${File.separator}$newPath"))
                                if (isOk) {
                                    mappings[v] = newPath
                                    newPath
                                } else {
                                    mappings[v] = v
                                    v
                                }
                            } else {
                                mappings[v]
                            }
                            strings[index] = newPath!!
                        }
                    }


                    val str2 = mappings.map {
                        val startIndex = it.key.lastIndexOf("/") + 1
                        var endIndex = it.key.lastIndexOf(".")


                        if (endIndex < 0) {
                            endIndex = it.key.length
                        }
                        if (endIndex < startIndex) {
                            it.key to it.value
                        } else {
//                            val vStartIndex = it.value.lastIndexOf("/") + 1
//                            var vEndIndex = it.value.lastIndexOf(".")
//                            if (vEndIndex < 0) {
//                                vEndIndex = it.value.length
//                            }
//                            val result = it.value.substring(vStartIndex, vEndIndex)
                            // 使用相同的字符串,以減小體積
                            it.key.substring(startIndex, endIndex) to "du"
                        }
                    }.toMap()


                    // 修改 arsc PackageChunk 字段
                    chunk.chunks.values.filterIsInstance<PackageChunk>()
                        .flatMap { it.chunks.values }
                        .filterIsInstance<StringPoolChunk>()
                        .forEach {
                            for (index in 0 until it.stringCount) {
                                it.getStrings()?.forEachIndexed { index, s ->
                                    str2[s]?.let { result ->
                                        it.setString(index, result)
                                    }
                                }
                            }
                        }


                    // 將 mapping 映射成 指定格式文件,供給反混淆服務使用
                    val mMappingWriter: Writer = BufferedWriter(FileWriter(file, false))
                    val packageName = context.proguardResourcesExtension.packageName
                    val pathMappings = mutableMapOf<String, String>()
                    val idMappings = mutableMapOf<String, String>()
                    mappings.filter { (t, u) -> t != u }.forEach { (t, u) ->
                        result?.add(" $t => $u")
                        compress[t]?.let {
                            compress[u] = it
                            compress.remove(t)
                        }
                        val pathKey = t.substring(0, t.lastIndexOf("/"))
                        pathMappings[pathKey] = u.substring(0, u.lastIndexOf("/"))
                        val typename = t.split("/")[1].split("-")[0]
                        val path1 = t.substring(t.lastIndexOf("/") + 1, t.indexOf("."))
                        val path2 = u.substring(u.lastIndexOf("/") + 1, u.indexOf("."))
                        val path = "$packageName.R.$typename.$path1"
                        val pathV = "$packageName.R.$typename.$path2"
                        if (idMappings[path].isNullOrEmpty()) {
                            idMappings[path] = pathV
                        }
                    }
                    generalFileResMapping(mMappingWriter, pathMappings)
                    generalResIDMapping(mMappingWriter, idMappings)
                }


            // 刪除res下的文件
            FileOperation.deleteDir(File("$unZipDir${File.separator}res"))
            // 將白名單的文件移回res
            keeps.forEach {
                val newPath = it.replaceFirst(whiteTempRes, "res")
                val parent = File("$unZipDir${File.separator}$newPath").parentFile
                if (!parent.exists()) {
                    parent.mkdirs()
                }
                File("$unZipDir${File.separator}$it").renameTo(File("$unZipDir${File.separator}$newPath"))
            }
            // 收尾刪除 res2
            FileOperation.deleteDir(File("$unZipDir${File.separator}$whiteTempRes"))
            resouce
        }
  • 白名單配置必不可少,保證反射調用資源不參與混淆
  • createProcessPath 用于將長路徑修改為短路徑
  • 修改 PackageChunk 中的常量池,用于極致的包體裁剪,未壓縮前減小包體300kb,arsc壓縮后降低包體70kb

圖片圖片

  • 生成資源混淆mapping文件,提供給包體積服務進行資源名稱還原使用

資源混淆的落地過程必須要謹慎,對存量代碼,在得物app中我們先通過字節碼掃描找出所有反射調用資源的地方,配置keep文件。對于后續業務開發中新增的反射調用則通過測試流程及早發現問題。

1.5 ARSC壓縮

Arsc 壓縮降低的體積非常可觀,壓縮后的arsc 700kb,未壓縮的約 7MB。實施起來通過 7zip對 arsc文件壓縮即可。

圖片

但是 Target Sdk 在30以上 arsc 壓縮被禁了。壓縮 resources.arsc 雖然能帶來包體上的收益,但也有弊端,它將帶來內存和運行速度上的劣勢。不壓縮的resources.arsc系統可以使用mmap來節約內存的使用(一個app的資源至少被3個進程所持有:自己, launcher, system),而壓縮的resources.arsc會存在于每個進程中。

2、資源下發

Apk 中的存量大資源在打包后包體積平臺檢測出來,針對問題資源排期處理。動態下發和無用刪除則是處理存量資源的常用手段,同時通過 CI 前置管控新增資源過大的情況。

資源下發的主體主要是 so 文件和圖片,對下發的資源的管控則需可以通過平臺化管理。堵不如疏,能下發的資源就下發是包體優化的一大利器。

圖片圖片

下發的資源通過動態資源管理平臺進行處理

圖片圖片

3、無用資源刪除

無用資源的檢測結合bytex的 resCheck 編譯期 與 matrix-apk-canary smail 掃描的結果,將業務可以處理的部分在平臺上展示,版本迭代過程中邊迭代邊治理,能夠有效防止無用資源的持續惡化。

圖片圖片

4、總結

本文主要介紹了得物APP資源優化做了的一些動作,其中對資源優化插件的工作模式進行了重點介紹。當然,對于資源依舊有不少手段可以完善,比如提供高效簡單的 9 圖下發方案,包體積平臺增加圖片相似度檢測能力、把一些次級的資源通過插件包下發都是之后可以嘗試的地方。

責任編輯:武曉燕 來源: 得物技術
相關推薦

2024-06-06 10:39:32

2025-03-13 06:48:22

2022-06-01 09:18:37

抖音ReDex算法優化

2023-03-30 18:39:36

2025-11-11 01:55:00

2022-06-07 15:33:51

Android優化實踐

2022-10-28 13:41:51

字節SDK監控

2023-10-09 18:35:37

得物Redis架構

2022-12-12 18:56:04

2022-05-07 15:51:47

Android資源文件文件名

2022-12-14 18:40:04

得物染色環境

2023-02-08 18:33:49

SRE探索業務

2023-11-27 18:38:57

得物商家測試

2022-10-26 18:44:33

藍紙箱設計數據

2025-07-31 00:00:25

2023-08-09 20:43:32

2024-09-03 16:09:59

2023-05-10 18:34:49

推薦價格體驗優化UV

2025-04-02 02:10:00

2022-11-14 14:53:14

架構技術編輯工具
點贊
收藏

51CTO技術棧公眾號

最近看过的日韩成人| 国产日韩欧美夫妻视频在线观看| 黄色录像a级片| 欧美日韩女优| 亚洲午夜久久久久久久久电影网 | 91一区二区视频| 欧美视频久久| 伊人久久大香线蕉av一区二区| 午夜剧场高清版免费观看| 成年网站在线视频网站| 国产午夜精品一区二区三区视频| 97超碰人人看人人| 久久精品五月天| 亚洲午夜av| 在线观看日韩欧美| 巨胸大乳www视频免费观看| 久久久精品一区二区毛片免费看| 五月天国产精品| 99热都是精品| 福利片在线看| 91色九色蝌蚪| 北条麻妃高清一区| 91麻豆成人精品国产| 麻豆9191精品国产| 97国产成人精品视频| 全网免费在线播放视频入口| 欧美一区电影| 亚洲另类图片色| 中国特级黄色大片| 久久中文字幕一区二区| 欧美日韩在线免费视频| caopor在线视频| 成人观看网址| 五月婷婷综合激情| 黄色三级中文字幕| 宅男网站在线免费观看| 国产精品久久久久久妇女6080| 欧洲久久久久久| 日本一卡二卡四卡精品| 99re这里只有精品视频首页| 成人午夜影院在线观看| 精品国产无码一区二区| 韩国精品一区二区| 成人在线小视频| 一级特黄色大片| 卡一卡二国产精品| 国产日韩精品电影| 国产精品视频一区二区三区,| 精品在线一区二区| 成人在线播放av| 国产乱淫av片免费| 国产综合色产在线精品| 成人免费网站在线看| 一级黄色a毛片| 狠狠久久亚洲欧美| 亚洲999一在线观看www| 国内毛片毛片毛片毛片| 国产高清成人在线| 国产精品久久波多野结衣| 亚洲精品网站在线| av欧美精品.com| 免费久久99精品国产自| 精品视频三区| 国产精品美女久久久久久久久久久 | 天天爱天天操天天干| 91超碰碰碰碰久久久久久综合| 欧美性大战久久久久久久蜜臀| 冲田杏梨av在线| 亚洲男男av| 欧美r级电影在线观看| 99热超碰在线| 欧美猛男做受videos| 色黄久久久久久| 欧美日韩国产精品一区二区三区| 国内自拍视频一区二区三区| 992tv成人免费影院| 成人免费视频国产免费| 激情综合亚洲精品| 国产精品一区二| 国产原创av在线| 亚洲欧美怡红院| 福利视频免费在线观看| 中文日产幕无线码一区二区| 欧美日韩一级二级| 日本50路肥熟bbw| 制服丝袜日韩| 欧美成人午夜剧场免费观看| 91午夜视频在线观看| 奇米777欧美一区二区| 亚洲在线免费看| 亚洲色大成网站www| 国产精品三级久久久久三级| 日本免费a视频| 日韩一区二区三区在线免费观看| 日韩一级视频免费观看在线| 欧美亚一区二区三区| 国产精品久久久久无码av| 久久久久久久久久亚洲| 中文字幕一区二区三区四区视频| 粉嫩蜜臀av国产精品网站| 日韩一区不卡| 99re6在线精品视频免费播放| 欧美天堂亚洲电影院在线播放| 久久久久99人妻一区二区三区| 精品国产美女| 久久久久久久影院| 在线亚洲欧美日韩| 91浏览器在线视频| 成人小视频在线观看免费| 国产欧美在线观看免费| 日韩精品久久久久久福利| 国精品人伦一区二区三区蜜桃| 亚洲影视在线| 国产精品18毛片一区二区| 米奇精品一区二区三区| 91精品办公室少妇高潮对白| 制服丝袜在线第一页| 国产精品久久久久久久久久10秀| 国产成人涩涩涩视频在线观看| 国产成人手机在线| 亚洲男人的天堂av| 天天干天天操天天玩| 蜜桃一区二区三区| 国产69精品99久久久久久宅男| 国产又粗又猛又爽| 国产精品嫩草影院av蜜臀| 国产性xxxx18免费观看视频| 果冻天美麻豆一区二区国产| 久久国产精品久久国产精品| 亚洲自拍偷拍另类| 国产免费成人在线视频| 国产精品少妇在线视频| 日韩在线麻豆| 97**国产露脸精品国产| 色偷偷在线观看| 亚洲国产毛片aaaaa无费看| 6080国产精品| 婷婷久久一区| 91久久综合亚洲鲁鲁五月天| 日本网站在线免费观看视频| 欧美视频第二页| 阿v天堂2014| 日本一不卡视频| 亚洲国产欧美不卡在线观看| 成人看片在线观看| 色777狠狠综合秋免鲁丝| 久久精品国产亚洲av麻豆蜜芽| 国产午夜精品一区二区三区嫩草| av片中文字幕| 欧美视频免费| 国产玖玖精品视频| 嫩草在线视频| 欧美一区二区三区在线电影| 欧美日韩在线国产| 国产成人免费高清| 成年人网站免费视频| 五月国产精品| 国产精品va在线| 日韩子在线观看| 在线成人午夜影院| 青娱乐在线视频免费观看| 成人午夜短视频| 又粗又黑又大的吊av| 嫩草影视亚洲| 国产日本欧美在线观看| av超碰免费在线| 精品久久国产字幕高潮| 国产综合精品视频| 国产精品久久夜| 老司机av网站| 一本色道久久| 亚洲一区二区三区免费观看| 奇米一区二区| 青青草99啪国产免费| 2021av在线| 欧美成人a∨高清免费观看| www.国产成人| 中文字幕一区二区三区在线观看| 一区二区在线免费观看视频| 亚洲一区免费| 欧美一级黄色录像片| 欧美交a欧美精品喷水| 国产精品高清在线观看| 182tv在线播放| 日韩久久免费视频| 91资源在线视频| 图片区日韩欧美亚洲| 五月天婷婷丁香网| 成人免费视频视频在线观看免费| 国产一区二区视频免费在线观看| 亚洲第一天堂| 欧美大香线蕉线伊人久久| 亚洲色图综合| 奇米4444一区二区三区| 永久免费网站在线| 国产亚洲精品久久久久久牛牛| 精品久久久久久亚洲综合网站| 一本到不卡免费一区二区| 天天看片中文字幕| 国产精品美女一区二区在线观看| 偷偷色噜狠狠狠狠的777米奇| 久久精品av麻豆的观看方式| 国产免费黄视频| 7777久久香蕉成人影院| 欧美一进一出视频| 卡通动漫精品一区二区三区| 成人中文字幕+乱码+中文字幕| 亚洲黄色网址| 欧美激情在线狂野欧美精品| 欧美猛烈性xbxbxbxb| 国产婷婷97碰碰久久人人蜜臀| 国产国语亲子伦亲子| 欧美亚洲动漫另类| 国产精品第5页| 亚洲国产一区二区在线播放| 日韩影院一区二区| 国产精品久久久久久久久久免费看| 国产一级二级在线观看| 成人综合婷婷国产精品久久| 欧美成人乱码一二三四区免费| 天堂久久一区二区三区| 成人黄色av片| 国产在线不卡| 国产91在线亚洲| 亚洲91久久| 伊人av成人| 成人一二三区| 特级西西444www大精品视频| 亚洲人成伊人成综合图片| 国产九色精品| 波多野结衣欧美| 豆国产97在线| 哺乳一区二区三区中文视频| 91免费版黄色| 成人豆花视频| 亚洲一区二区在线播放| 国产精品视频首页| 91精品久久香蕉国产线看观看 | 国产乱码精品一区二区三区中文 | 亚洲啪啪av| 成人3d动漫在线观看| 神马影院午夜我不卡影院| 国产一区二区电影在线观看| 欧美日韩国产免费一区二区三区 | 在线观看日韩专区| 97视频精彩视频在线观看| 国产亚洲精品日韩| av小片在线| 按摩亚洲人久久| 麻豆传媒视频在线观看免费| 久久精品男人天堂| 久久免费电影| 91a在线视频| 美女福利一区二区| 国产精品综合不卡av| 亚洲免费资源| 国产精品一区而去| 久久99国产精品视频| 午夜精品美女久久久久av福利| 999精品一区| 国产一级大片免费看| 亚洲欧洲视频| 久久午夜夜伦鲁鲁一区二区| 精品一区二区三区视频| 国产a级片视频| 久久亚洲一级片| 天堂av网手机版| 一区二区在线观看不卡| 日本五十路女优| 欧美性色综合网| av中文字幕免费| 日韩毛片中文字幕| 欧美jizz18性欧美| 欧美激情一区二区久久久| 中文在线免费视频| 国产啪精品视频| 高清精品视频| 视频一区在线免费观看| 欧美激情日韩| 亚洲熟妇av一区二区三区| 久久国产精品露脸对白| 亚洲欧美日韩色| 国产精品入口麻豆九色| 国产一级免费av| 欧美性欧美巨大黑白大战| 国产 日韩 欧美 精品| 亚洲夜晚福利在线观看| 超碰中文在线| 国产精品专区一| 欧美一级全黄| 青少年xxxxx性开放hg| 国产精品亚洲产品| 香蕉视频色在线观看| 2020国产精品| 久草视频手机在线观看| 欧美亚洲一区二区三区四区| 欧美一级特黄aaaaaa大片在线观看| 丝袜美腿精品国产二区| 激情av在线播放| 成人a在线视频| 亚洲免费专区| 国产免费一区二区视频| 久国产精品韩国三级视频| 黄色工厂在线观看| 一区二区免费看| 亚洲无码精品在线播放| 亚洲人成五月天| 超碰在线视屏| 99久热re在线精品996热视频| 日韩高清欧美| 久久久久久久久久福利| 不卡在线观看av| 九九视频免费在线观看| 91精品欧美一区二区三区综合在 | 欧美区二区三区| 久久久久久久性潮| 日本一区二区在线视频| 国产一区二区三区的电影| 国产精品欧美性爱| 亚洲另类在线制服丝袜| 国产精品久久久久精| 最近更新的2019中文字幕| 成人教育av| 久久久久久久久一区| 亚洲黑丝一区二区| 久久久久亚洲av成人网人人软件| 青青草手机在线观看| 在线日韩一区| 中文字幕免费高| 蜜桃91丨九色丨蝌蚪91桃色| 女~淫辱の触手3d动漫| 精品高清美女精品国产区| 丰满人妻一区二区三区无码av| 久久婷婷国产麻豆91天堂| 日本成人一区二区| 亚洲一卡二卡三卡四卡无卡网站在线看| 老司机午夜精品视频| 三级网站在线免费观看| 日韩欧美aaa| 黄色在线视频观看网站| 国产精品99久久久久久白浆小说| 亚洲免费观看高清完整版在线观| 国产亚洲天堂网| 久久亚洲综合色一区二区三区 | 日本精品在线| 成人中心免费视频| 欧美777四色影| 在线观看一区二区三区四区| 亚洲福利视频一区二区| 无码精品视频一区二区三区| 2019中文字幕在线观看| 免费成人高清在线视频theav| 91av在线免费播放| 日本一区二区久久| 国产精品伦一区二区三区| 欧美成人精品一区| 国产美女撒尿一区二区| 国产黄视频在线| 国产人妖乱国产精品人妖| 国内av在线播放| 久久6免费高清热精品| 国产一区二区三区亚洲| 无码aⅴ精品一区二区三区浪潮 | wwwwww.欧美系列| 久久影视中文字幕| www.亚洲天堂| jizz18欧美18| 黑森林福利视频导航| 成人欧美一区二区三区视频网页| 开心激情综合网| 国产精品av电影| 欧美在线亚洲| 日本一区二区三区网站| 欧美精品一卡两卡| 丁香花在线电影小说观看| 欧美午夜视频在线| 精品亚洲国产成人av制服丝袜 | 性国产高清在线观看| 精品不卡一区二区三区| 日韩电影一二三区| 久久国产露脸精品国产| 亚洲日韩欧美视频| 国产亚洲精aa在线看| 色欲av无码一区二区人妻| 亚洲欧美综合色| 日本福利午夜视频在线| 91美女片黄在线观看游戏| 国产欧美在线| 手机在线免费看片| 亚洲美女av在线| 日本精品视频| 国产精品乱码久久久久| 一区二区三区四区激情 | 国内外成人免费激情在线视频 | 97超碰在线人人| 国产精品美女视频| 三级视频网站在线| 91在线在线观看| 欧美aaaaa成人免费观看视频| 欧美三级韩国三级日本三斤在线观看 |