日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Flutter 长截屏适配 Miui 系统,一点都不难

發布時間:2023/12/10 windows 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter 长截屏适配 Miui 系统,一点都不难 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

背景

現有 App 大部分業務場景都是以長列表呈現,為更好滿足用戶內容分享的訴求,Android 各大廠商都在系統層面提供十分便捷的長截屏能力。然而我們發現 Flutter 長列表頁面在部分 Android 手機上無法截長屏,Flutter 官方和社區也沒有提供框架層面的長截屏能力。
閑魚作為 Flutter 在國內業務落地的代表作,大部分頁面都以 Flutter 承接。為了閑魚用戶也能享受廠商系統的長截屏能力,更好的滿足商品、社區內容分享的訴求,閑魚技術團隊主動做了分析和適配。

針對線上輿情做了統計分析,發現小米用戶輿情反饋量占比最多,其次少量是華為用戶。為此我們針對 Miui 長截屏功能做了適配。

這里華為、OPPO、VIVO 基于無障礙服務實現,長截屏功能已經適配 Flutter 頁面。這里少量用戶反饋,是因為截屏反饋小把手 PopupWindow 有可能出現遮擋,導致系統無法驅動長列表滾動。通過重寫 isImportantForAccessibility 便能解決。

小米長截屏解讀

操作和表現

小米手機可通過音量鍵+電源鍵、或頂部下拉功能菜單“截屏”,觸發截屏。經過簡單嘗試,可以發現,原生長列表頁面支持截長屏,原生頁面無長列表不支持,閑魚 Flutter 長列表頁面(如詳情頁、搜索結果頁)不支持。點擊“截長屏”后,能看到長列表頁面會自動滾動,點擊結束或者觸底的時候,自動打開圖片編輯頁面,能看到生成的長截圖。那小米系統是如何無侵入的實現以下關鍵點:
  • 1.?當前頁面是否支持滾動截屏(長截屏 按鈕是否置灰)

  • 2.?如何觸發 App 長列表頁面滾動

  • 3.?如何判斷是否已經滾動觸底

  • 4.?如何合成長截圖

  • 系統源碼獲取

    小米廠商能判斷前臺 App 頁面能否滾動,必然需要調用前臺 App 視圖的關鍵接口來獲取信息。編寫一個自定義 RecyclerView 列表頁面,日志輸出 RecycleView 方法調用:已知長截屏需要調用的方法,再查看堆棧,可以看到調用方是系統類:miui.util.LongScreenshotUtils&ContentPort

    使用低版本 miui(這里 miui8)手機,獲取對應的代碼:/system/framework/framework.jar 或 github 查找 miui 開放代碼。

    實現原理介紹

    整體流程:查找滾動視圖 → 驅動視圖滾動 → 分段截圖→截圖內容合并

    查找滾動視圖

    其中檢查條件:

  • 1.?View visibility == View.VISIBLE

  • 2.?canScrollVertically(1) == true

  • 3.?View 在屏幕內的寬度 > 屏幕寬度/3

  • 4.?View 在屏幕內的高度 > 屏幕高度/2

  • 觸發視圖滾動

  • 1.?每次滾動前,使用 canScrollVertically(1) 判斷是否向下滾動

  • 2.?觸發滾動邏輯

  • a.?特殊視圖: dispatchFakeTouchEvent(2);private?boolean?checkNeedFakeTouchForScroll()?{
    ?if?((this.mMainScrollView?instanceof?AbsListView)?||?
    ??(this.mMainScrollView?instanceof?ScrollView)?||?
    ??isRecyclerView(this.mMainScrollView.getClass())?||?
    ??isNestedScrollView(this.mMainScrollView.getClass()))?{?
    ??return?false;
    ?}
    ?return?!(this.mMainScrollView?instanceof?AbsoluteLayout)?||?
    ??(Build.VERSION.SDK_INT?>?19?&&
    ???!"com.ucmobile".equalsIgnoreCase(this.mMainScrollView.getContext().getPackageName())?&&
    ???!"com.eg.android.AlipayGphone".equalsIgnoreCase(this.mMainScrollView.getContext().getPackageName()));
    }

  • b.?AbsListView: scrollListBy(distance);

  • c. 其他:view.scrollBy(0, distance);

  • 3.?滾動結束,對比 scrollY 和 mPrevScrolledY 是否相同,相同則認為觸底,停止滾動流程

  • 生成長截圖

    每次滾動后廣播,觸發 mMainScrollView 局部截圖,最后生成多個 Bitmap,最后合成 File 文件。在適配 Flutter 頁面,這里并沒有差異,所以這里就不做源碼解讀(不同 Miui 版本實現也有所不同)。

    閑魚適配方案

    Flutter 長截屏不適配原因

    通過分析源碼可知,Flutter 容器(SurfaceView/TextureView) canScrollVertically 方法并未被重寫,為此無法被找到作為 mMainScrollView。假如我們重寫 Flutter 容器,我們需要真實實現 getScrollY 才能保證觸發滾動后 scrollY 和 mPrevScrolledY 不相等。不幸的是,getScrollY 是 final 類型,無法被繼承類重寫,為此我們無法在 Flutter 容器上做處理。

    @InspectableProperty public?final?int?getScrollY()?{return?mScrollY; }

    系統事件代理

    轉變思路,我們并不需要讓 Flutter 容器被 Miui 系統識為可滾動視圖,而是讓 Flutter 接收到 Miui 系統指令。為此,我們構建一個不可見、不影響交互的滾動視圖 ControlView 被 Miui 系統識別,并接收系統指令。ControlView 最后把指令傳遞給 Flutter,最終建立了 Miui 系統(ContentPort)和閑魚 Flutter(可滾動 RenderObject)之間的通信。

    其中通信事件:

  • 1.?void scrollBy(View view, int x, int y)

  • 2.?boolean canScrollVertically(View view, int direction, boolean startScreenshot)

  • 3.?int getScrollY(View view)

  • 關鍵實現源碼如下

    public?static?FrameLayout?setupLongScreenshotSupport(FrameLayout?parent,View?targetChild,IMiuiLongScreenshotViewDelegate?delegate)?{Context?context?=?targetChild.getContext();MiuiLongScreenshotView?screenshotView?=?new?MiuiLongScreenshotView(context);screenshotView.setDelegate(delegate);screenshotView.addView(targetChild,?new?FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.MATCH_PARENT));MiuiLongScreenshotControlView?controlView?=?new?MiuiLongScreenshotControlView(context);controlView.bindRealScrollView(screenshotView);if?(parent?==?null)?{parent?=?new?FrameLayout(context);}parent.addView(screenshotView,?new?FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,?ViewGroup.LayoutParams.WRAP_CONTENT));parent.addView(controlView);return?parent; }public?class?MiuiLongScreenshotControlView?extends?ScrollViewimplements?MiuiScreenshotBroadcast.IListener?{private?IMiuiLongScreenshotView?mRealView;...public?void?bindRealScrollView(IMiuiLongScreenshotView?v)?{mRealView?=?v;removeAllViews();Context?context?=?getContext();LinearLayout?ll?=?new?LinearLayout(context);addView(ll);View?btn?=?new?View(context);LinearLayout.LayoutParams?lp?=?new?LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,UIUtil.dp2px(context,?20000));ll.addView(btn,?lp);resetScrollY(true);}public?void?resetScrollY(boolean?startScreenshot)?{if?(mRealView?!=?null)?{setScrollY(0);if?(getWindowVisibility()?==?VISIBLE)?{ThreadUtil.runOnUI(()?->?mRealView.canScrollVertically(1,?startScreenshot));}}}@Overridepublic?void?onReceiveScreenshot()?{//?每次收到截屏廣播,將?ControlView?滾動距離置?0//?提前查找滾動?RenderObject?并緩存//?提前計算?canScrollVerticallyresetScrollY(true);}@Overrideprotected?void?onAttachedToWindow()?{super.onAttachedToWindow();mContext?=?getContext();//?截屏廣播監聽MiuiScreenshotBroadcast.register(mContext,?this);}@Overrideprotected?void?onDetachedFromWindow()?{super.onDetachedFromWindow();MiuiScreenshotBroadcast.unregister(mContext,?this);}@Overridepublic?boolean?canScrollVertically(int?direction)?{if?(mRealView?!=?null)?{return?mRealView.canScrollVertically(direction,?false);}return?super.canScrollVertically(direction);}@Overridepublic?void?scrollBy(int?x,?int?y)?{super.scrollBy(x,?y);if?(mRealView?!=?null)?{mRealView.scrollBy(x,?y);}}//?代理獲取?DrawingCache@Overridepublic?void?setDrawingCacheEnabled(boolean?enabled)?{super.setDrawingCacheEnabled(enabled);if?(mRealView?!=?null)?{mRealView.setDrawingCacheEnabled(enabled);}}@Overridepublic?boolean?isDrawingCacheEnabled()?{if?(mRealView?!=?null)?{return?mRealView.isDrawingCacheEnabled();}return?super.isDrawingCacheEnabled();}@Overridepublic?Bitmap?getDrawingCache(boolean?autoScale)?{Bitmap?result?=?(mRealView?!=?null)??mRealView.getDrawingCache(autoScale):?super.getDrawingCache(autoScale);return?result;}@Overridepublic?void?destroyDrawingCache()?{super.destroyDrawingCache();if?(mRealView?!=?null)?{mRealView.destroyDrawingCache();}}@Overridepublic?void?buildDrawingCache(boolean?autoScale)?{super.buildDrawingCache(autoScale);if?(mRealView?!=?null)?{mRealView.buildDrawingCache(autoScale);}}//?不消費屏幕操作事件@Overridepublic?boolean?onInterceptTouchEvent(MotionEvent?ev)?{return?false;}@Overridepublic?boolean?onTouchEvent(MotionEvent?ev)?{return?false;} }

    無侵入識別滾動區域

    獲取 RenderObject 根節點

    使用 mixin 擴展 WidgetsFlutterBinding,進而獲取 RenderView

    關鍵實現源碼如下:

    mixin?NativeLongScreenshotFlutterBinding?on?WidgetsFlutterBinding?{@overridevoid?initInstances()?{super.initInstances();//?初始化FlutterMiuiLongScreenshotPlugin.inst;}@overridevoid?handleDrawFrame()?{super.handleDrawFrame();try?{NativeLongScreenshot.singleInstance._renderView?=?renderView;}?catch?(error,?stack)?{}} }

    計算前臺滾動 RenderObject

    其中第 2 步條件檢查:

  • 1.?width >= RenderView.width/2

  • 2.?height >= RenderView.height/2

  • 3.?類型是 RenderViewportBase

  • 4.?axis == Axis.vertical

  • 實現源碼如下:

    RenderViewportBase??findTopVerticalScrollRenderObject(RenderView??root)?{Size?rootSize?=?size(root,?Size.zero);//?if?(root?!=?null)?{//?_debugGetRenderTree(root,?0);//?}RenderViewportBase??result?=?_recursionFindTopVerticalScrollRenderObject(root,?rootSize);if?(_hitTest(root,?result))?{return?result;}return?null; }RenderViewportBase??_recursionFindTopVerticalScrollRenderObject(RenderObject??renderObject,?Size?rootSize)?{if?(renderObject?==?null)?{return?null;}///get?RenderObject?Sizeif?(_tooSmall(rootSize,?size(renderObject,?rootSize)))?{return?null;}if?(renderObject?is?RenderViewportBase)?{if?(renderObject.axis?==?Axis.vertical)?{return?renderObject;}}final?ListQueue<RenderObject>?children?=?ListQueue<RenderObject>();if?(renderObject.runtimeType.toString()?==?'_RenderTheatre')?{renderObject.visitChildrenForSemantics((RenderObject??child)?{if?(child?!=?null)?{children.addLast(child);}});}?else?{renderObject.visitChildren((RenderObject??child)?{if?(child?!=?null)?{children.addLast(child);}});}for?(var?child?in?children)?{RenderViewportBase??viewport?=?_recursionFindTopVerticalScrollRenderObject(child,?rootSize);if?(viewport?!=?null)?{return?viewport;}}return?null; }

    找到首個滿足條件的 RenderViewportBase 并不一定是我們需要的對象,如下圖所示:閑魚詳情頁通過上述方法能找到紅色框的 RenderViewportBase,在左圖情況下,能滿足滾動截圖要求;但在右圖情況下,留言面板遮擋了長列表,此時紅色框 RenderObject 并不是我們想要的。此刻我們需要檢測 Widget 可見性/可交互檢測能力。查看 Flutter 官方?visibility_detector?組件并不滿足我們的要求,其通過在子 Widget 上放置一個 Layer 來間接檢測可見狀態,但因為通過在屏幕內的寬高判斷,無法檢測 Widget 被遮擋的情況。

    左圖長列表沒有被遮擋,可以被操作;右圖被留言面板遮擋,事件無法傳遞到長列表,無法被操作;為此,我們模擬用戶的點擊能否被觸達來檢測 RenderViewportBase 是否被遮擋,能否用來做長截屏滾動。
    特別注意的是,當 Widget 被 Listener 包裝,事件消費會被 RenderPointerListener 攔截,如下圖所示。

    查看 Flutter Framework 源碼,Scrollable Widget 包裝了 Listener,Semantics,IgnorePointer;閑魚 PowerScrollView 使用了 ShrinkWrappingViewPort。為此,遞歸找到的 RenderSliverList 和點擊測試找到的 RenderPointerListener 的距離為?5,如上圖所示。

    點擊測試校驗代碼如下

    bool?_hitTest(RenderView??root,?RenderViewportBase??result)?{if?(root?==?null?||?result?==?null)?{return?false;}Size?rootSize?=?size(root,?Size.zero);HitTestResult?hitResult?=?HitTestResult();root.hitTest(hitResult,?position:?Offset(rootSize.width/2,?rootSize.height/2));for?(HitTestEntry?entry?in?hitResult.path)?{if?(entry.target?==?result)?{return?true;}}/***?處理如下?case*?RenderPointerListener?2749d135RenderSemanticsAnnotations?1cd639bfRenderIgnorePointer?7e33fffRenderShrinkWrappingViewport?1167ca33*/RenderPointerListener??pointerListenerParent;AbstractNode??parent?=?result.parent;const?int?lookUpLimit?=?5;int?lookupCount?=?0;while?(parent?!=?null?&&lookupCount?<?lookUpLimit?&&parent.runtimeType.toString()?!=?'_RenderTheatre')?{lookupCount?++;if?(parent?is?RenderPointerListener)?{pointerListenerParent?=?parent;}parent?=?parent.parent;}if?(pointerListenerParent?!=?null)?{for?(HitTestEntry?entry?in?hitResult.path)?{if?(entry.target?==?pointerListenerParent)?{return?true;}}}return?false; }

    異步 Channel 通信方案

    Flutter channel 通信方案如上圖所示,其中 EventChannel 和 MethodChannel 運行在 Java 主線程,同 Dart Platform Isolate,而 Dart 層事件處理邏輯在 UI Isolate,為此并不在同一線程。可以發現,Java → Dart → Java 發生了 2 次線程切換。
    使用小米 K50 測試性能,從 EventChannel 發送事件 到 MethodChannel 接收返回值,記錄耗時。可見,首次 canScrollVertically (由截屏廣播觸發)需要遞歸查找滾動組件,耗時為 10-30ms,之后耗時均在 5ms 以內。

    08-08?16:15:56.060?11079?11079?E?longscreenshot:?canScrollVertically?use_time=25 08-08?16:15:56.278?11079?11079?E?longscreenshot:?canScrollVertically?use_time=2 08-08?16:16:05.342?11079?11079?E?longscreenshot:?canScrollVertically?use_time=10 08-08?16:16:05.562?11079?11079?E?longscreenshot:?canScrollVertically?use_time=1

    為保證在異步調用的情況下,MIUI ContentPort 下發命令均能獲取到最新值,這里做以下特殊處理

  • 1.?截屏廣播提前計算 canScrollVerticallly 并緩存結果

  • 2.?MIUI ContentPort 調用 canScrollVerticallly 直接返回最新緩存值,異步觸發計算

  • 3.?MIUI ContentPort 調用 scrollBy 后,及時更新 canScrollVerticallly 和 getScrollY 緩存值

  • 同步 FFI 通信方案

    異步調用方案,在高端機且 App 任務隊列無阻塞情況下,能正確且準確運行,但在低端機和 App 任務較重時,可能存在返回 ContentPort 數據非最新的情況,為此我們考慮使用 FFI 同步通信的方案。

    以上同步方案,一次同步調用性能分析,基本在 5ms 以內:

    關鍵實現代碼如下:

    @Keep public?class?NativeLongScreenshotJni?implements?Serializable?{static?{System.loadLibrary("flutter_longscreenshot");}public?static?native?void?nativeCanScrollVertically(int?direction,?boolean?startScreenshot,int?callbackId);public?static?native?void?nativeGetScrollY(int?screenWidth,?int?callbackId);public?static?native?void?nativeScrollBy(int?screenWidth,?int?x,?int?y);public?static?boolean?canScrollVertically(final?int?direction,final?boolean?startScreenshot)?{FlutterLongScreenshotCallbacks.AwaitCallback?callback?=FlutterLongScreenshotCallbacks.newCallback();nativeCanScrollVertically(direction,?startScreenshot,?callback.id());int?result?=?callback.waitCallback().getResult();return?result?==?1;}public?static?int?getScrollY(final?int?screenWidth)?{FlutterLongScreenshotCallbacks.AwaitCallback?callback?=FlutterLongScreenshotCallbacks.newCallback();nativeGetScrollY(screenWidth,?callback.id());//?waitCallback?同步等待?C++?調用?FlutterLongScreenshotCallbacks.handleDartCallint?result?=?callback.waitCallback().getResult();return?result;}public?static?void?scrollBy(int?screenWidth,?int?x,?int?y)?{nativeScrollBy(screenWidth,?x,?y);} }@Keep public?class?FlutterLongScreenshotCallbacks?implements?Serializable?{public?static?AwaitCallback?newCallback()?{AwaitCallback?callback?=?new?AwaitCallback();CALLBACKS.put(callback.id(),?callback);return?callback;}//?C++?DART_EXPORT?void?resultCallback(int?callbackId,?int?result)?反射調用public?static?void?handleDartCall(int?id,?int?result)?{AwaitCallback?callback?=?CALLBACKS.get(id);if?(callback?!=?null)?{CALLBACKS.remove(id);callback.release(result);}}private?static?final?SparseArray<AwaitCallback>?CALLBACKS?=?new?SparseArray<>();@Keeppublic?static?class?AwaitCallback?{public?static?final?int?RESULT_ERR?=?-1;private?final?CountDownLatch?mLatch?=?new?CountDownLatch(1);private?int?mResult?=?RESULT_ERR;public?int?id()?{return?hashCode();}public?AwaitCallback?waitCallback()?{try?{mLatch.await(100,?TimeUnit.MILLISECONDS);}?catch?(Throwable?e)?{e.printStackTrace();}return?this;}public?void?release(int?result)?{mResult?=?result;mLatch.countDown();}public?int?getResult()?{return?mResult;}} }void?setDartInt(Dart_CObject&?dartObj,?int?value)?{dartObj.type?=?Dart_CObject_kInt32;dartObj.value.as_int32?=?value; }JNIEXPORT?void?JNICALL nativeCanScrollVertically(JNIEnv?*env,?jclass?cls,jint?direction,?jboolean?startScreenshot,?jint?callbackId)?{Dart_CObject*?dart_args[4];Dart_CObject?dart_arg0;Dart_CObject?dart_arg1;Dart_CObject?dart_arg2;Dart_CObject?dart_arg3;setDartString(dart_arg0,?strdup("canScrollVertically"));setDartInt(dart_arg1,?direction);setDartBool(dart_arg2,?startScreenshot);setDartLong(dart_arg3,?callbackId);dart_args[0]?=?&dart_arg0;dart_args[1]?=?&dart_arg1;dart_args[2]?=?&dart_arg2;dart_args[3]?=?&dart_arg3;Dart_CObject?dart_object;dart_object.type?=?Dart_CObject_kArray;dart_object.value.as_array.length?=?4;dart_object.value.as_array.values?=?dart_args;Dart_PostCObject_DL(send_port_,?&dart_object); }//?getScrollY?和?scrollBy?實現類似DART_EXPORT?void?resultCallback(int?callbackId,?int?result)?{JNIEnv?*env?=?_getEnv();if?(env?!=?nullptr)?{auto?cls?=?_findClass(env,?jCallbackClassName);jmethodID?handleDartCallMethod?=?nullptr;if?(cls?!=?nullptr)?{//?調用?java?代碼?FlutterLongScreenshotCallbacks.handleDartCall(int?id,?int?result)handleDartCallMethod?=?env->GetStaticMethodID(cls,"handleDartCall",?"(II)V");}if?(cls?!=?nullptr?&&?handleDartCallMethod?!=?nullptr)?{env->CallStaticVoidMethod(cls,?handleDartCallMethod,callbackId,?result);}?else?{print("resultCallback.?find?method?handleDartCall?is?nullptr");}} }class?NativeLongScreenshot?extends?Object?{...late?final?NativeLongScreenshotLibrary?_nativeLibrary;late?final?ReceivePort?_receivePort;late?final?StreamSubscription?_subscription;NativeLongScreenshot()?{..._nativeLibrary?=?initLibrary();_receivePort?=?ReceivePort();var?nativeInited?=?_nativeLibrary.initializeApi(ffi.NativeApi.initializeApiDLData);assert(nativeInited?==?0,?'DART_API_DL_MAJOR_VERSION?!=?2');_subscription?=?_receivePort.listen(_handleNativeMessage);_nativeLibrary.registerSendPort(_receivePort.sendPort.nativePort);}void?_handleNativeMessage(dynamic?inArgs)?{List<dynamic>?args?=?inArgs;String?method?=?args[0];switch?(method)?{case?'canScrollVertically':?{int?direction?=?args[1];bool?startScreenshot?=?args[2];int?callbackId?=?args[3];final?bool?canScroll?=?canScrollVertically(direction,?startScreenshot);int?result?=?canScroll???1?:?0;_nativeLibrary.resultCallback(callbackId,?result);}?break;case?'getScrollY':?{int?nativeScreenWidth?=?args[1];int?callbackId?=?args[2];int?result?=?getScrollY(nativeScreenWidth);_nativeLibrary.resultCallback(callbackId,?result);}?break;case?'scrollBy':?{int?nativeScreenWidth?=?args[1];int?nativeX?=?args[2];int?nativeY?=?args[3];scrollBy(nativeY,?nativeScreenWidth);}?break;}} }

    總結

    完成國內主要機型適配,現在線上幾乎不再有用戶反饋 Flutter 頁面不支持長截屏。閑魚 Android 用戶已經能用系統長截屏能力,分享自己喜歡的商品、圈子內容,賣家能使用一張圖片推廣自己的全部商品,買家能幫助家里不會用 App 的老人找商品。
    面對系統功能適配,業務 App 側也并不是完全束手無策。通過以下過程便有可能找到解決之道:

    • ??合理猜想(系統模塊會調用業務視圖接口)

    • ??工具輔助分析和驗證(ASM 代碼 hook,日志輸出)

    • ??源碼查找和截圖(代碼查找和反編譯)

    • ??發散思考(ControlView 頂替 Flutter 容器,瞞天過海)

    • ??方案實現(業務無侵入,一次實現全部業務頁面適配)

    總結

    以上是生活随笔為你收集整理的Flutter 长截屏适配 Miui 系统,一点都不难的全部內容,希望文章能夠幫你解決所遇到的問題。

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

    主站蜘蛛池模板: 福利在线视频导航 | 啪啪综合 | 国产高清在线视频 | 日韩影院一区二区 | 欧美日韩人妻精品一区在线 | 国产乱一区二区三区 | 亚洲图色av| 日韩精品成人一区 | 日本精品一区在线观看 | 午夜免费看视频 | 奇米影视首页 | 手机在线毛片 | 一区二区三区四区五区视频 | 亚洲五十路 | 日本一区二区在线不卡 | 免费成人精品 | 亚洲香蕉中文网 | 成人免费视频国产免费麻豆 | 亚洲一区二区蜜桃 | 午夜影院试看 | 欧美日韩精品二区 | 嫩草嫩草嫩草嫩草嫩草 | 色视频网| 美女av免费 | 91免费观看视频在线 | 夜夜添无码一区二区三区 | 国产福利专区 | 黑丝一区二区三区 | 少妇高潮一区二区三区99小说 | 国产精品999久久久 在线青草 | 欧美在线性爱视频 | 夜夜躁狠狠躁日日躁 | 成人一区二区三区仙踪林 | 国产亚洲精品精品国产亚洲综合 | 亚洲天堂福利 | av片一区二区 | 中文视频在线 | 日韩在线黄色 | 中国女人黄色大片 | 黄色一级片免费 | 日日摸夜夜添夜夜添高潮喷水 | 欧美性视频一区二区 | 国产女主播喷水视频在线观看 | 日韩黄色片在线观看 | 男人用嘴添女人下身免费视频 | 护士的小嫩嫩好紧好爽 | 亚洲天堂影视 | 99国内揄拍国内精品人妻免费 | 涩涩网站在线 | 一级黄色片看看 | 男女日批视频 | 精品日韩欧美 | 国产亚洲精品自拍 | 亚洲涩涩图 | 丁香婷婷亚洲 | 亚洲一区二区偷拍 | 亚洲欧美自拍偷拍 | 日本一区二区三区免费视频 | 涩色网站| 在线草| 偷拍亚洲精品 | 国产综合精品在线 | 国产黄页 | 亚洲第一视频网 | 日韩欧美一级大片 | 鲁丝av| 蜜臀av一区二区三区 | 污的网站 | 亚洲男人天堂2023 | 国产一级片免费播放 | 色婷婷a| 免费看三级黄色片 | 久久久69| 国产中文字幕av | 中文字幕人成乱码熟女香港 | 北岛玲在线| 亚洲性视频网站 | 成年人小视频在线观看 | 日韩a在线观看 | 亚洲日本japanese丝袜 | 精品国产黄色片 | 中文字幕有码在线视频 | 九九视频免费看 | 欧美日韩 一区二区三区 | 欧洲色网 | 国产一区二区网站 | 肉色超薄丝袜脚交一区二区图片 | 久久高清毛片 | 老司机深夜福利在线观看 | 欧美xxxx视频 | 91大神视频在线播放 | 成人一区二区三区四区 | 免费在线观看视频a | 91成人福利在线 | 国产成人无码精品久久 | 精品无码人妻少妇久久久久久 | 亚洲成年人网站在线观看 | 99精彩视频| 日日夜夜综合网 |