亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

Tinker接入及源碼分析(三)

2019-11-09 15:01:40
字體:
來源:轉載
供稿:網友

該系列文章分析基于 Tinker1.7.6 版本

Tinker項目地址:https://github.com/Tencent/tinker

Tinker接入及源碼分析(一):簡單介紹以及如何接入

Tinker接入及源碼分析(二):加載補丁源碼分析

Tinker接入及源碼分析(三):合成補丁源碼分析

上篇文章分析了加載補丁的源碼,本篇文章會繼續分析tinker初始化過程以及合成補丁的過程。

之前也說過,使用Tinker之前必須通過如下代碼初始化Tinker:

public static class Builder { PRivate final Context context; private final boolean mainProcess; private final boolean patchProcess; private int status = -1; private LoadReporter loadReporter; private PatchReporter patchReporter; private PatchListener listener; private File patchDirectory; private File patchInfoFile; private File patchInfoLockFile; private Boolean tinkerLoadVerifyFlag; /** * Start building a new {@link Tinker} instance. */ public Builder(Context context) { if (context == null) { throw new TinkerRuntimeException("Context must not be null."); } this.context = context; this.mainProcess = TinkerServiceInternals.isInMainProcess(context); this.patchProcess = TinkerServiceInternals.isInTinkerPatchServiceProcess(context); this.patchDirectory = SharePatchFileUtil.getPatchDirectory(context); if (this.patchDirectory == null) { TinkerLog.e(TAG, "patchDirectory is null!"); return; } this.patchInfoFile = SharePatchFileUtil.getPatchInfoFile(patchDirectory.getAbsolutePath()); this.patchInfoLockFile = SharePatchFileUtil.getPatchInfoLockFile(patchDirectory.getAbsolutePath()); TinkerLog.w(TAG, "tinker patch directory: %s", patchDirectory); } //省略了set方法 public Tinker build() { if (status == -1) { status = ShareConstants.TINKER_ENABLE_ALL; } if (loadReporter == null) { loadReporter = new DefaultLoadReporter(context); } if (patchReporter == null) { patchReporter = new DefaultPatchReporter(context); } if (listener == null) { listener = new DefaultPatchListener(context); } if (tinkerLoadVerifyFlag == null) { tinkerLoadVerifyFlag = false; } return new Tinker(context, status, loadReporter, patchReporter, listener, patchDirectory, patchInfoFile, patchInfoLockFile, mainProcess, patchProcess, tinkerLoadVerifyFlag); } }

上面代碼省略了set方法,我們只關注默認設置。其中mainProcess,patchProcess判斷當前是否是應用進程和補丁合成進程。loadReporter,patchReporter 顧名思義是一些過程的回調。PatchListener 是我們關注的重點,也是補丁合成的入口,它的默認實現是DefaultPatchListener,下面分析會用到。

patchDirectory,patchInfoFile,patchInfoLockFile分別是:

/data/data/package_name/tinker/ /data/data/package_name/tinker/patch.info/data/data/package_name/tinker/info.lock

tinkerLoadVerifyFlag是新建application時傳進去的參數,用于判斷是否每次加載都做md5校驗。

初始化好Tinker之后再調用Tinker.create(tinker);

/** * create custom tinker by {@link Tinker.Builder} * please do it when very first your app start. * * @param tinker */ public static void create(Tinker tinker) { if (sInstance != null) { throw new TinkerRuntimeException("Tinker instance is already set."); } sInstance = tinker; }

sInstance是靜態變量,保證Tinker是單例的,并且只初始化一次。

最后調用tinker.install(applicationLike.getTinkerResultIntent());

public void install(Intent intentResult) { install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());} /** * you must install tinker first!! * * @param intentResult * @param serviceClass * @param upgradePatch */ public void install(Intent intentResult, Class<? extends AbstractResultService> serviceClass, AbstractPatch upgradePatch) { sInstalled = true; TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass); if (!isTinkerEnabled()) { TinkerLog.e(TAG, "tinker is disabled"); return; } if (intentResult == null) { throw new TinkerRuntimeException("intentResult must not be null."); } tinkerLoadResult = new TinkerLoadResult(); tinkerLoadResult.parseTinkerResult(getContext(), intentResult); //after load code set loadReporter.onLoadResult(patchDirectory, tinkerLoadResult.loadCode, tinkerLoadResult.costTime); if (!loaded) { TinkerLog.w(TAG, "tinker load fail!"); }}

這里值得注意的是install方法的后面兩個參數,serviceClass 是用于補丁合成成功后啟動的Service來處理合成結果,upgradePatch 是真正合成補丁的類,分別提供了默認實現DefaultTinkerResultService和UpgradePatch,這兩個參數也支持自定義。在install方法中會調用TinkerPatchService.setPatchProcessor(upgradePatch, serviceClass); 將這兩個參數通過靜態方法設置給TinkerPatchService類,TinkerPatchService類是合成補丁的Service,并且運行在新的進程中。

這樣就完成了Tinker的初始化。

第一篇文章介紹過使用以下方法來加載補?。?/p>TinkerInstaller.onReceiveUpgradePatch(context, patchLocation)

看一下具體實現:

/** * new patch file to install, try install them with :patch process * * @param context * @param patchLocation */ public static void onReceiveUpgradePatch(Context context, String patchLocation) { Tinker.with(context).getPatchListener().onPatchReceived(patchLocation); }

這里會調用PatchListener,還記得之前這個參數的默認實現嗎?

我們來看一下DefaultPatchListener的onPatchReceived方法:

public int onPatchReceived(String path) { int returnCode = patchCheck(path); if (returnCode == ShareConstants.ERROR_PATCH_OK) { TinkerPatchService.runPatchService(context, path); } else { Tinker.with(context).getLoadReporter().onLoadPatchListenerReceiveFail(new File(path), returnCode); } return returnCode;}

patchCheck(patch)方法會判斷是否開啟了Tinker,以及補丁文件是否存在。然后會啟動TinkerPatchService:TinkerPatchService.runPatchService(context, path);

TinkerPatchService是繼承于IntentService,IntentService與普通Service的區別這里就不說了,看它的onHandleIntent方法,繼承IntentService必須實現該方法,并且可以進行耗時操作:

@Override protected void onHandleIntent(Intent intent) { final Context context = getApplicationContext(); Tinker tinker = Tinker.with(context); tinker.getPatchReporter().onPatchServiceStart(intent); if (intent == null) { TinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring."); return; } String path = getPatchPathExtra(intent); if (path == null) { TinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring."); return; } File patchFile = new File(path); long begin = SystemClock.elapsedRealtime(); boolean result; long cost; Throwable e = null; increasingPriority(); PatchResult patchResult = new PatchResult(); try { if (upgradePatchProcessor == null) { throw new TinkerRuntimeException("upgradePatchProcessor is null."); } result = upgradePatchProcessor.tryPatch(context, path, patchResult); } catch (Throwable throwable) { e = throwable; result = false; tinker.getPatchReporter().onPatchException(patchFile, e); } cost = SystemClock.elapsedRealtime() - begin; tinker.getPatchReporter(). onPatchResult(patchFile, result, cost); patchResult.isSuccess = result; patchResult.rawPatchFilePath = path; patchResult.costTime = cost; patchResult.e = e; AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent)); }

先進行了參數校驗,increasingPriority(),這個方法用于提高進程優先級,防止被回收:

private void increasingPriority() {// if (Build.VERSION.SDK_INT > 24) {// TinkerLog.i(TAG, "for Android 7.1, we just ignore increasingPriority job");// return;// } TinkerLog.i(TAG, "try to increase patch process priority"); try { Notification notification = new Notification(); if (Build.VERSION.SDK_INT < 18) { startForeground(notificationId, notification); } else { startForeground(notificationId, notification); // start InnerService startService(new Intent(this, InnerService.class)); } } catch (Throwable e) { TinkerLog.i(TAG, "try to increase patch process priority error:" + e); } }

然后調用result = upgradePatchProcessor.tryPatch(context, path, patchResult);進行合成補丁,返回一個結果碼,這里下面再詳細說,先繼續往下看, 最后會啟動另一個Service:

AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));

這個Service就是之前傳進來的DefaultTinkerResultService,并且將合成結果帶給它回調onPatchResult方法:

public class DefaultTinkerResultService extends AbstractResultService { private static final String TAG = "Tinker.DefaultTinkerResultService"; /** * we may want to use the new patch just now!! * * @param result */ @Override public void onPatchResult(PatchResult result) { if (result == null) { TinkerLog.e(TAG, "DefaultTinkerResultService received null result!!!!"); return; } TinkerLog.i(TAG, "DefaultTinkerResultService received a result:%s ", result.toString()); //first, we want to kill the recover process TinkerServiceInternals.killTinkerPatchServiceProcess(getApplicationContext()); // if success and newPatch, it is nice to delete the raw file, and restart at once // only main process can load an upgrade patch! if (result.isSuccess) { File rawFile = new File(result.rawPatchFilePath); if (rawFile.exists()) { TinkerLog.i(TAG, "save delete raw patch file"); SharePatchFileUtil.safeDeleteFile(rawFile); } if (checkIfNeedKill(result)) { android.os.Process.killProcess(android.os.Process.myPid()); } else { TinkerLog.i(TAG, "I have already install the newly patch version!"); } } } public boolean checkIfNeedKill(PatchResult result) { Tinker tinker = Tinker.with(getApplicationContext()); if (tinker.isTinkerLoaded()) { TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent(); if (tinkerLoadResult != null) { String currentVersion = tinkerLoadResult.currentVersion; if (result.patchVersion != null && result.patchVersion.equals(currentVersion)) { return false; } } } return true; }}

在onPatchResult方法中會殺死補丁合成的進程,如果補丁合成成功,會將原始數據刪掉,并且殺死當前進程。當然用戶也可以自定義這個類,實現更好的邏輯,比如不直接殺死當前進程,而是當用戶退出應用,切到后臺,或者關閉屏幕的時候殺死應用,達到重啟的目的,具體實現可以參考Simple中的實現。這樣整個補丁的合成過程就結束了。目前為止大致Tinker初始化以及補丁合成流程已經講完了,有興趣的繼續往下看真正合成補丁的調用

result = upgradePatchProcessor.tryPatch(context, path, patchResult);

還記得之前初始化的方法嗎:

public void install(Intent intentResult) { install(intentResult, DefaultTinkerResultService.class, new UpgradePatch());}

這里的UpgradePatch對象便會賦值給upgradePatchProcessor,合成補丁的時候調用它的tryPatch方法:

@Override public boolean tryPatch(Context context, String tempPatchPath, PatchResult patchResult) { Tinker manager = Tinker.with(context); final File patchFile = new File(tempPatchPath); //check the signature, we should create a new checker ShareSecurityCheck signatureCheck = new ShareSecurityCheck(context); int returnCode = ShareTinkerInternals.checkTinkerPackage(context, manager.getTinkerFlags(), patchFile, signatureCheck); //it is a new patch, so we should not find a exist SharePatchInfo oldInfo = manager.getTinkerLoadResultIfPresent().patchInfo; String patchMd5 = SharePatchFileUtil.getMD5(patchFile); //use md5 as version patchResult.patchVersion = patchMd5; SharePatchInfo newInfo; //already have patch if (oldInfo != null) { newInfo = new SharePatchInfo(oldInfo.oldVersion, patchMd5, Build.FINGERPRINT); } else { newInfo = new SharePatchInfo("", patchMd5, Build.FINGERPRINT); } //check ok, we can real recover a new patch final String patchDirectory = manager.getPatchDirectory().getAbsolutePath(); final String patchName = SharePatchFileUtil.getPatchVersionDirectory(patchMd5); final String patchVersionDirectory = patchDirectory + "/" + patchName; //it is a new patch, we first delete if there is any files //don't delete dir for faster retry// SharePatchFileUtil.deleteDir(patchVersionDirectory); //copy file File destPatchFile = new File(patchVersionDirectory + "/" + SharePatchFileUtil.getPatchVersionFile(patchMd5)); try { SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile); TinkerLog.w(TAG, "UpgradePatch after %s size:%d, %s size:%d", patchFile.getAbsolutePath(), patchFile.length(), destPatchFile.getAbsolutePath(), destPatchFile.length()); } catch (IOException e) {// e.printStackTrace(); TinkerLog.e(TAG, "UpgradePatch tryPatch:copy patch file fail from %s to %s", patchFile.getPath(), destPatchFile.getPath()); manager.getPatchReporter().onPatchTypeExtractFail(patchFile, destPatchFile, patchFile.getName(), ShareConstants.TYPE_PATCH_FILE); return false; } //we use destPatchFile instead of patchFile, because patchFile may be deleted during the patch process if (!DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)) { TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, try patch dex failed"); return false; } final File patchInfoFile = manager.getPatchInfoFile(); if (!SharePatchInfo.rewritePatchInfoFileWithLock(patchInfoFile, newInfo, SharePatchFileUtil.getPatchInfoLockFile(patchDirectory))) { TinkerLog.e(TAG, "UpgradePatch tryPatch:new patch recover, rewrite patch info failed"); manager.getPatchReporter().onPatchInfoCorrupted(patchFile, newInfo.oldVersion, newInfo.newVersion); return false; } TinkerLog.w(TAG, "UpgradePatch tryPatch: done, it is ok"); return true; }

這個方法比較長,刪除了一些校驗的代碼以及合成資源文件等方法,主要看dex文件的合成過程。開始是初始化一些目錄,再將補丁文件拷貝到目標目錄中:

SharePatchFileUtil.copyFileUsingStream(patchFile, destPatchFile);

再調用以下方法:

DexDiffPatchInternal.tryRecoverDexFiles(manager, signatureCheck, context, patchVersionDirectory, destPatchFile)

看具體實現:

protected static boolean tryRecoverDexFiles(Tinker manager, ShareSecurityCheck checker, Context context, String patchVersionDirectory, File patchFile) { if (!manager.isEnabledForDex()) { TinkerLog.w(TAG, "patch recover, dex is not enabled"); return true; } String dexMeta = checker.getMetaContentMap().get(DEX_META_FILE); if (dexMeta == null) { TinkerLog.w(TAG, "patch recover, dex is not contained"); return true; } long begin = SystemClock.elapsedRealtime(); boolean result = patchDexExtractViaDexDiff(context, patchVersionDirectory, dexMeta, patchFile); long cost = SystemClock.elapsedRealtime() - begin; TinkerLog.i(TAG, "recover dex result:%b, cost:%d", result, cost); return result; }

主要就是計算耗時,最終方法是patchDexExtractViaDexDiff:

private static boolean patchDexExtractViaDexDiff(Context context, String patchVersionDirectory, String meta, final File patchFile) { String dir = patchVersionDirectory + "/" + DEX_PATH + "/"; if (!extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)) { TinkerLog.w(TAG, "patch recover, extractDiffInternals fail"); return false; } final Tinker manager = Tinker.with(context); File dexFiles = new File(dir); File[] files = dexFiles.listFiles(); if (files != null) { final String optimizeDexDirectory = patchVersionDirectory + "/" + DEX_OPTIMIZE_PATH + "/"; File optimizeDexDirectoryFile = new File(optimizeDexDirectory); if (!optimizeDexDirectoryFile.exists() && !optimizeDexDirectoryFile.mkdirs()) { TinkerLog.w(TAG, "patch recover, make optimizeDexDirectoryFile fail"); return false; } TinkerLog.w(TAG, "patch recover, try to optimize dex file count:%d", files.length); boolean isSuccess = TinkerParallelDexOptimizer.optimizeAll( files, optimizeDexDirectoryFile, new TinkerParallelDexOptimizer.ResultCallback() { long startTime; @Override public void onStart(File dexFile, File optimizedDir) { startTime = System.currentTimeMillis(); TinkerLog.i(TAG, "start to optimize dex %s", dexFile.getPath()); } @Override public void onSuccess(File dexFile, File optimizedDir) { // Do nothing. TinkerLog.i(TAG, "success to optimize dex %s use time %d", dexFile.getPath(), (System.currentTimeMillis() - startTime)); } @Override public void onFailed(File dexFile, File optimizedDir, Throwable thr) { TinkerLog.i(TAG, "fail to optimize dex %s use time %d", dexFile.getPath(), (System.currentTimeMillis() - startTime)); SharePatchFileUtil.safeDeleteFile(dexFile); manager.getPatchReporter().onPatchDexOptFail(patchFile, dexFile, optimizeDexDirectory, dexFile.getName(), thr); } } ); //list again if (isSuccess) { for (File file : files) { try { if (!SharePatchFileUtil.isLegalFile(file)) { TinkerLog.e(TAG, "single dex optimizer file %s is not exist, just return false", file); return false; } String outputPathName = SharePatchFileUtil.optimizedPathFor(file, optimizeDexDirectoryFile); File outputFile = new File(outputPathName); if (!SharePatchFileUtil.isLegalFile(outputFile)) { TinkerLog.e(TAG, "parallel dex optimizer file %s fail, optimize again", outputPathName); long start = System.currentTimeMillis(); DexFile.loadDex(file.getAbsolutePath(), outputPathName, 0); TinkerLog.i(TAG, "success single dex optimize file, path: %s, use time: %d", file.getPath(), (System.currentTimeMillis() - start)); if (!SharePatchFileUtil.isLegalFile(outputFile)) { manager.getPatchReporter() .onPatchDexOptFail(patchFile, file, optimizeDexDirectory, file.getName(), new TinkerRuntimeException("dexOpt file:" + outputPathName + " is not exist")); return false; } } } catch (Throwable e) { TinkerLog.e(TAG, "dex optimize or load failed, path:" + file.getPath()); //delete file SharePatchFileUtil.safeDeleteFile(file); manager.getPatchReporter().onPatchDexOptFail(patchFile, file, optimizeDexDirectory, file.getName(), e); return false; } } } return isSuccess; } return true; }

首先extractDexDiffInternals(context, dir, meta, patchFile, TYPE_DEX)是合成全量補丁,后面是通過DexFile.loadDex生成優化后的dex文件,這個過程貌似做了兩遍。主要看extractDexDiffInternals,哎,不貼代碼了,代碼好多,自己看吧。這個方法中會拿到兩個文件,一個原始包文件,一個是補丁文件:

apk = new ZipFile(apkPath);patch = new ZipFile(patchFile);

安全性校驗完了之后會分別調用extractDexFile(zipFile, entryFile,extractTo, dexInfo) 或者 patchDexFile(baseApk, patchPkg, oldDexEntry, patchFileEntry, patchInfo, patchedDexFile);這里分了三種情況,

第一種情況是直接將補丁包中的dex文件拷貝到了目標文件夾下,這種情況應該是下發的補丁包就是全量包;

第二種情況是直接拷貝原apk包的dex文件,有這么一段注釋:

// Small patched dex generating strategy was disabled, we copy full original dex directly now.

為什么要把原始Apk包里的dex文件復制過去呢?我也想不明白,問了一下張紹文老大,他的回答是:

因為內聯以及地址錯亂的問題

對,就是這個原因(因回答過于簡潔,還是不太明白)。有知道的小伙伴歡迎留言告知,說的稍微詳細一點。

這兩種情況調用的是extractDexFile,不同的是傳進去的包不一樣,一個是補丁包,一個是原始包。

第三種情況是將原始dex與補丁dex合成全量dex,調用patchDexFile,最終調用如下方法合成補?。?/p>new DexPatchApplier(zis, (int) entry.getSize(), patchFileStream).executeAndSaveTo(zos);

繼續往下看DexPatchApplier類,這個可是合成dex文件的核心所在

額。。。不看了,看不下去了,有點想吐,暈代碼。。。

等不暈的時候再來看吧,分析Tinker源碼的文章就暫時告一段落了,對這系列文章有疑問的,或者發現寫的有錯誤的歡迎在下方留言;如果接入遇到問題的也可以留言。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲第一区第二区| 亚洲第一色在线| 国产精品成人aaaaa网站| 欧美大尺度电影在线观看| 日韩日本欧美亚洲| 欧美午夜xxx| 91最新在线免费观看| 国产精品电影网站| 91久久夜色精品国产网站| 成人春色激情网| 不卡毛片在线看| 中文字幕视频一区二区在线有码| www欧美xxxx| 在线成人激情黄色| 欧美国产乱视频| 国产区精品在线观看| 欧美激情喷水视频| 日韩在线中文字幕| 亚洲最大福利视频网| 国产男人精品视频| 日韩成人激情在线| 日韩精品中文字幕在线观看| 欧美激情va永久在线播放| 日韩av一区二区在线观看| 精品国产91久久久| 精品视频偷偷看在线观看| 俺去啦;欧美日韩| 九九久久久久99精品| 成人性教育视频在线观看| 91av视频在线| 久久免费高清视频| 亚洲色图综合久久| 日本电影亚洲天堂| 日韩美女视频中文字幕| 久久综合亚洲社区| 日韩亚洲第一页| 亚洲人成电影网| xxx一区二区| 成人啪啪免费看| 97国产精品免费视频| 亚洲视频专区在线| 不卡在线观看电视剧完整版| 亚洲精品福利资源站| 欧美午夜www高清视频| 人人做人人澡人人爽欧美| 九九热在线精品视频| 亚洲最大的网站| 福利精品视频在线| 亚洲91精品在线观看| 日韩高清a**址| 日韩在线观看免费高清完整版| 亚洲奶大毛多的老太婆| 亚洲美女av在线| 亚洲成人a**站| 夜夜嗨av一区二区三区免费区| 在线观看日韩av| 亚洲欧美在线一区| 久久亚洲精品一区| 久久久久亚洲精品国产| 亚洲成人久久一区| 2018中文字幕一区二区三区| 亚洲丝袜一区在线| 欧美色播在线播放| 中文字幕综合一区| 成人在线视频网| 国产精品久久久久久久久久久久久久| 亚洲中国色老太| 国产精品99久久久久久久久| 久久久午夜视频| 欧美极品少妇xxxxⅹ喷水| 97在线视频免费观看| 欧美激情精品久久久久久大尺度| 91精品91久久久久久| 亚洲国产成人91精品| 91久久嫩草影院一区二区| 热99精品只有里视频精品| 欧美成年人视频网站| 亚洲欧美日韩综合| 欧美性受xxxx白人性爽| 国产情人节一区| 久久久免费观看视频| 日韩欧美在线播放| 欧美专区日韩视频| 一区二区三区精品99久久| 亚洲视频专区在线| 青青草原一区二区| 欧美精品在线观看91| 95av在线视频| 国产午夜精品免费一区二区三区| 欧美乱大交xxxxx另类电影| 国产美女91呻吟求| 亚洲永久免费观看| 成人在线播放av| 91在线免费视频| 日韩男女性生活视频| 97国产精品人人爽人人做| 国产69精品99久久久久久宅男| 亚洲二区中文字幕| 精品国内产的精品视频在线观看| 国产成人一区二| 亚洲精品天天看| 18一19gay欧美视频网站| 国内精品美女av在线播放| 欧美天堂在线观看| 91精品国产综合久久香蕉922| 清纯唯美亚洲激情| 大荫蒂欧美视频另类xxxx| 国产日韩欧美中文在线播放| 精品色蜜蜜精品视频在线观看| 国产精品视频999| 在线电影欧美日韩一区二区私密| 欧美亚洲国产日韩2020| 最近2019中文免费高清视频观看www99| 久久婷婷国产麻豆91天堂| 在线视频欧美日韩| 欧美成年人视频网站欧美| 国产综合在线视频| 国产日韩欧美中文| 91在线高清免费观看| 欧美一区二区三区免费视| 国产精品自拍偷拍| 欧美成人免费视频| 在线日韩日本国产亚洲| 青青精品视频播放| 最近2019中文字幕大全第二页| 亚洲精品福利免费在线观看| 亚洲桃花岛网站| 国产欧美一区二区三区在线| 国产在线视频一区| 国产成人精品国内自产拍免费看| 亚洲精品国产精品自产a区红杏吧| 欧美成人精品不卡视频在线观看| 亚洲成人黄色网| 久久久久久中文字幕| 一区二区三区日韩在线| 国产精品一区二区久久精品| 亚洲性线免费观看视频成熟| 国产成人精彩在线视频九色| 国内精品久久久久久影视8| 亚洲国产精品久久久久秋霞蜜臀| 国产精品丝袜白浆摸在线| 欧美精品videosex极品1| 在线一区二区日韩| 久久久噜噜噜久噜久久| 伊人久久久久久久久久久久久| 国产日产欧美a一级在线| 综合av色偷偷网| 一区二区在线视频| 国产精品日韩在线观看| 亚洲九九九在线观看| 国产精品久久久久久久久久| 亚洲午夜国产成人av电影男同| 国产免费观看久久黄| 国产成人精品久久亚洲高清不卡| 91高清免费在线观看| 日韩在线观看免费全集电视剧网站| 久久国产精品影视| 精品国产福利在线| 日韩欧美在线免费观看| 91精品国产综合久久香蕉| 欧美日韩国产精品专区| 91av视频在线观看| 欧美又大又粗又长| 亚洲欧美激情四射在线日|