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

Flutter 地圖在攜程的最佳實踐

人工智能 新聞
本文將重點突出基于 flutter-boost 的混合工程,單引擎模式下接入 Flutter 地圖插件遇到的問題和解決方案。

作者簡介

Leo,攜程高級移動開發工程師,關注跨端技術,致力于高效、高性能開發。

Jarmon,攜程高級移動開發工程師,專注 Flutter、iOS 開發。

本文將重點突出基于 flutter-boost 的混合工程,單引擎模式下接入 Flutter 地圖插件遇到的問題和解決方案。

一、背景

隨著各種多端技術的蓬勃發展,項目主體從純 Native 項目,到 Native+RN,到現在的 Native+RN+Flutter。基于我們的業務都在 Flutter 技術棧上面,這要求我們需要嵌套展示地圖。目前,實現嵌套展示地圖的主要方案有二個:

接入官方提供的 Flutter 地圖插件,主要面臨的問題有:

  • 官方提供的插件成熟度不夠,有一些 Native 已有的 API 在 Flutter 上不支持;
  • 目前接入 Flutter 地圖插件的應用很少,我們需要去蹚雷。
  • 由于官方適配的是純 Flutter 項目,混合工程可能遇到很多未知棘手問題。

直接在 Flutter 頁面上展示 Native 的地圖

  • Native 地圖成熟,不會遇到很大的坑;
  • 主要問題在于業務在 Flutter上,Flutter 需要大量的和地圖組件進行交互、請求數據、聯動。需要通過大量的橋方法去傳遞操作數據;
  • 要嵌套 Native 地圖需要定制容器,Android 和 IOS 上各自得實現一遍橋、容器和地圖邏輯,增加了維護成本。

考慮維護成本、權衡再三我們還是選擇接入 Flutter 地圖插件。為了能更好的定制一些 API 和更快速的修復一些官方沒有及時更新的問題。我們采用的是源碼接入 Flutter 地圖插件。本文將重點突出基于 flutter-boost 的混合工程,單引擎模式下接入 Flutter 地圖插件遇到的問題和解決方案。

二、如何源碼集成

在混合項目中集成插件主要分 flutter 和原生兩側,集成 Flutter 插件時,官方 demo 中可以直接下載到插件的源碼。本文以接入 flutter 地圖插件 3.3.1 版本示例。

2.1 Flutter 端集成

獲取到官方 demo 后在該目錄下執行 flutter pub get,然后去 flutter SDK 下找到 pub-cache 依賴緩存文件目錄,根據業務需要將每個插件 src 文件下的代碼導入到 flutter 工程中。

2.2 IOS 端集成

執行完 flutter pub get 后,根據需要將每個插件 iOS/Classes/ 目錄下的代碼導入工程中。

2.3 Android 端集成

Android 的 Native 側的集成和 IOS 端是類似的。在 Native 工程中新建一個地圖 Module。把地圖 Demo 中的地圖插件源碼 Android 部分放入工程即可。

三、地圖插件實現原理:platformView

地圖插件按功能分為 Map、Search、Util 等模塊,其基本實現類似,使用 MethodChannel 與 native 通信,我們以 Map 為例分析其實現。插件使用了 PlatformView 將原生地圖嵌入到 flutter 頁面中,在 flutter 層為 UIKitView、AndroidView,native 在生成地圖后根據 viewId 初始化 BMFMapViewController,包含對應的 MethodChannel。BMFMapViewController 聚合了對地圖操作,派發到不同模塊調用地圖 native 方法。

3.1 什么是PlatformView

PlatformView 是允許原生組件嵌入到 Flutter 頁面的一種技術,能夠讓我們將一些原生成熟組件、flutter UI 框架難以實現的地圖、WebView 等組件展示在 flutter 頁面中。

Flutter 提供了 Virtual Display、Hybrid Composition 兩種方式實現 PlatformView。Virtual Display 模式將 native view 加載到內存當中,隨著 flutter Widget 一起渲染出來。Hybrid Composition 模式是直接將 native view 添加到 flutter view 圖層上。iOS采用了 Hybrid Composition 模式,Android 采用了 Virtual Display 和 Hybrid Composition 兩種模式。

3.2 PlatformView 實現原理

1)flutter 渲染流程

在介紹 Hybrid Composition 實現之前,先通過下圖大致了解下 flutter 的渲染流程。

在收到 VSync 信號之后,Dart 層在 UI Thread 完成 Widget Tree、Element Tree、RenderObject Tree 三棵樹的更新與生成,然后生成包含繪制信息的 layer Tree 交給 Engine 去渲染,最后在 GPU Thread 經歷 Compositor、Skia 將 flutter 視圖渲染出來。

2)Hybrid Composition 模式分析

以 iOS 為例逐步分析 Hybird Composition 模式執行流程。首先 Dart 層提供了 UIKitView 組件來展示 native view,didChangeDependencies 方法中通過 channel 初始化一次 native view,生成唯一標識 native view 的 viewId,并將 native view 緩存在 root_views_ 中。在實際組裝 layer 層時,dart 層會傳輸給 engine 展示 native view 的坐標和大小,并生成一個 PlatformViewLayer,也就是說 native view 的位置、大小信息是由 dart 層控制的。

void FlutterPlatformViewsController::OnCreate(FlutterMethodCall* call, FlutterResult& result) {
  NSDictionary<NSString*, id>* args = [call arguments];
  long viewId = [args[@"id"] longValue];
  NSObject<FlutterPlatformView>* embedded_view = [factory createWithFrame:CGRectZero                                          viewIdentifier:viewId                                               arguments:params]; // 初始化
  UIView* platform_view = [embedded_view view]; 


  FlutterTouchInterceptingView* touch_interceptor = [[[FlutterTouchInterceptingView alloc]
                  initWithEmbeddedView:platform_view
               platformViewsController:GetWeakPtr()
gestureRecognizersBlockingPolicy:gesture_recognizers_blocking_policies[viewType]]
      autorelease];
  ChildClippingView* clipping_view =
      [[[ChildClippingView alloc] initWithFrame:CGRectZero] autorelease];
  [clipping_view addSubview:touch_interceptor];
  root_views_[viewId] = fml::scoped_nsobject<UIView>([clipping_view retain]); // 緩存
}

生成當前幀的 Layer Tree 之后,會進入到 Rasterizer 流程。首先會調用 BeginFrame 渲染一幀,觸發 PlatformViewLayer::Preroll,PlatformViewLayer 標記出當前幀有 PlatformView ,然后調用 FlutterPlatformViewsController::PrerollCompositeEmbeddedView 更新 view_params_,包含 Platform View 坐標、size 等信息,最后在 SubmitFrame 方法中取出 native view 添加到 flutter view 中,完成渲染。

void PlatformViewLayer::Preroll(PrerollContext* context,
                                const SkMatrix& matrix) {
  set_paint_bounds(SkRect::MakeXYWH(offset_.x(), offset_.y(), size_.width(),
                                    size_.height()));
  context->has_platform_view = true;
  set_subtree_has_platform_view(true); // 標記當前幀存在Platform View
  std::unique_ptr<EmbeddedViewParams> params =
      std::make_unique<EmbeddedViewParams>(matrix, size_,
                                           context->mutators_stack);  context->view_embedder->PrerollCompositeEmbeddedView(view_id_,
                                                       std::move(params));
}

3.3 PlatformView 是如何實現幀同步?

在原生開發中,我們知道UI操作不能在其他線程執行,會出現幀不同步的問題。flutter Engine 中有 platform、ui、raster、io四個線程,native view 是在 Platform Thread(主線程)渲染,而 flutter 渲染正常情況在 Raster Thread 執行的,flutter 又是如何保證幀同步的呢? 

flutter 解決幀同步是通過線程合并的方案。上圖 Raster 流程 PostPrerollAction 方法中,會判斷如果有 PlatformView 存在,在接下來的繪制過程中 Raster Thread 與 Platform Thread 會合并,將 Raster 隊列任務放到 Platform 隊列中。這樣所有的渲染任務都在 Platform Thread 中執行,保證了畫面的同步。

PostPrerollResult FlutterPlatformViewsController::PostPrerollAction(
    fml::RefPtr<fml::RasterThreadMerger> raster_thread_merger) {
  if (!HasPlatformViewThisOrNextFrame()) { // 沒有Platform View不用處理
    return PostPrerollResult::kSuccess;
  }
  if (!raster_thread_merger->IsMerged()) { // 線程還沒有并不用處理
    CancelFrame(); // 取消繪制當前幀
    return PostPrerollResult::kSkipAndRetryFrame; // 合并后完成當前幀
  }
  BeginCATransaction();
  raster_thread_merger->ExtendLeaseTo(kDefaultMergedLeaseDuration);
  return PostPrerollResult::kSuccess;
}
// 合并隊列
bool MessageLoopTaskQueues::Merge(TaskQueueId owner, TaskQueueId subsumed) {
  if (owner == subsumed) {
    return true;
  }
  std::lock_guard guard(queue_mutex_);
  auto& owner_entry = queue_entries_.at(owner);
  auto& subsumed_entry = queue_entries_.at(subsumed);
  auto& subsumed_set = owner_entry->owner_of;
  if (subsumed_set.find(subsumed) != subsumed_set.end()) {
    return true;
  }
  owner_entry->owner_of.insert(subsumed);
  subsumed_entry->subsumed_by = owner;
  if (HasPendingTasksUnlocked(owner)) {
    WakeUpUnlocked(owner, GetNextWakeTimeUnlocked(owner));
  }
  return true;
}

四、問題及解決方案

4.1 IOS 頁面切換 Map 組件白屏問題

在使用 flutter_boost 混合開發時,當 A 頁面中使用 platformview,開啟新容器跳轉到 flutter B 頁面,platformView 會出現短暫的白屏,從 A 頁面跳轉 native 頁面不會出現。根據表象首先猜測是單引擎導致的。flutter A頁面跳轉到其他頁面時都會觸發 SceneBuilder::pushTransform 重新渲染一次 A 頁面。

void SceneBuilder::pushTransform(Dart_Handle layer_handle,
                                 tonic::Float64List& matrix4,
                                 fml::RefPtr<EngineLayer> oldLayer) {
  SkMatrix sk_matrix = ToSkMatrix(matrix4);
  auto layer = std::make_shared<flutter::TransformLayer>(sk_matrix);
  PushLayer(layer);
  // matrix4 has to be released before we can return another Dart object
  matrix4.Release();
  EngineLayer::MakeRetained(layer_handle, layer);
  if (oldLayer && oldLayer->Layer()) {
    layer->AssignOldLayer(oldLayer->Layer().get());
  }
}

flutter  A頁面在創建新容器 push 到 flutter B 頁面時,首先會觸發 viewDidLayoutSubviews,方法內部會修改 engine 對應的 viewController flutterView,SceneBuilder::pushTransform 是在 viewDidLayoutSubviews 之后還會觸發,而 platformView 是在 native 渲染,重新渲染 A 頁面時就找不到對應的 platformView,導致白屏的問題。push 到非 flutter 頁面時不會觸發 surfaceUpdated,所以不會出現該問題。

- (void)viewDidLayoutSubviews {
  ...
  if (firstViewBoundsUpdate && applicationIsActive && _engine) {
    [self surfaceUpdated:YES];
  }
  ...
}
- (void)surfaceUpdated:(BOOL)appeared {
  if (appeared) {
    [self installFirstFrameCallback];
    [_engine.get() platformViewsController]->SetFlutterView(_flutterView.get());
    [_engine.get()     platformViewsController]->SetFlutterViewController(self);
    [_engine.get() iosPlatformView]->NotifyCreated();
  }
}

一開始的方案是在 viewWillAppear 中調用 sufaceUpdated,但是在 release 環境中會出現卡死的現象。另一方案是 [super bridge_viewWillAppear:animated]; 改為 [super viewWillAppear:animated];  [super viewWillAppear:animated]; 會調用父類的方法,父類方法又會調用 sufaceUpdated,就可以解決白屏的問題。

4.2 Android 地圖卡死不能操作問題

1)問題描述

A 頁面內嵌地圖,跳轉到 B 頁面。然后返回 A 頁面,地圖就不能滑動。

結合上文提到的 Flutter 地圖插件其實是通過 MathodChannel 將操作傳遞到 Native 的地圖視圖處理的。我們調試 Native 的代碼發現 PlatformViewsController 類里面的 onTouch()方法中,context 報了一個Attempt to invoke virtual method 'android.content.res.Resources android.content.Context.getResources()' on a null object reference。

public void onTouch(@NonNull PlatformViewsChannel.PlatformViewTouch touch) {
          final float density = context.getResources().getDisplayMetrics().density;
          }

2)分析問題

由于 context 對象被回收,造成的報錯。現在我們只有分析出來為什么 context 對象會被回收掉了就能找出問題了,讀源碼發現只有在 detach() 方法中才會回收 context 對象。

public void detach() {
    context = null;
  }

結合日志輸出,確實發現回到 A 頁面是執行了 attach() 方法,但是馬上又執行了 detach() 方法。現在就是要找出,為什么 A 頁面的 PlatformViewsController 會被執行 datach()。

從B頁面 返回A頁面
2022-08-22 15:13:08.126 21878-21878/ctrip.flutter.demo D/PlatformViewsController: B===>detach()
2022-08-22 15:13:08.135 21878-21878/ctrip.flutter.demo D/PlatformViewsController: A====>attach()
2022-08-22 15:13:08.249 21878-21878/ctrip.flutter.demo D/PlatformViewsController: A=====>detach()

查看調用鏈:

逐個類讀源碼我們發現在 FlutterActivityAndFragmentDelegate的OnDetach() 方法中如果引擎的生命周期和 Activity 的生命周期是綁定的。頁面結束時,引擎就會被銷毀掉。

void onDetach() {
    if (host.shouldAttachEngineToActivity()) {
      if (host.getActivity().isChangingConfigurations()) {
flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges();
} else {
flutterEngine.getActivityControlSurface().detachFromActivity();
      }
    }

3)解決問題

設置 shouldAttachEngineToActivity 返回 flase 使得 Flutter 引擎將在應用程序的整個生命周期內持久化存在,并獨立于 Activity,當 Activity 被銷毀時,Flutter 引擎不被銷毀 。問題就解決了。產生問題的原因是我們新開 B 頁面是通過新開容器的方式創建的。B 頁面 FlutterFragment 中 onDetach() 方法在 A 頁面 onAttach() 之后被執行的。純 Flutter 工程或者是采用 Push 的方式打開新頁面,不新開容器都能規避掉這個問題。

public boolean shouldAttachEngineToActivity() {
        return false;
    }

4.3 Android 地圖內存溢出問題

1)問題描述

多次打開 Android Flutter 地圖頁面會越來越卡,到后面整個地圖都黑一下,顯然是有內存溢出了。通過 Android Studio IDE 自帶的內存工具 Android Profiler 可以很明顯的看出來,每打開一次頁面,內存占有都會上升,結束頁面內存沒有得到釋放。

2)分析問題

Flutter Boost 和地圖插件如此大量的第三方代碼,我們如何去定位問題呢?是插件引起的,還是框架引起的呢?借助 LeakCanary 就能很好的找到內存泄露的地方了。

接入也非常的簡單,在 Android build.gradle引入leakcanary。

debugImplementation'com.squareup.leakcanary:leakcanary-android:2.6'

然后運行應用,反復操作問題復現流程,直到 LeakCanary 提示。查看 leaks 內存溢出的堆棧信息。是由于 SingleViewPresentation 一直持有了容器 TripFlutterActivity  的 context 對象。懷疑是 MapView 的生命周期有問題。是不是沒有執行 dispose。調試下來的情況 PlatformViewsHandler handler 對象空了,后面的流程都不會執行。

3)解決問題

查看源碼只有 PaltformViewsController detach() 方法會把 handler 設置為 null。

public void detach() {
    if (platformViewsChannel != null) {
      platformViewsChannel.setPlatformViewsHandler(null);
    }
    }

調試下來 FlutterActivity 容器結束,調用了 onDestroy() 方法的時候  PaltformViewsController detach() 就已經被執行了。容器的 onDestroy() 在 MapView 的 dispos e之前,造成了 handler 對象空了。

解決問題的思路很簡單,在 onDestroy() 的時候先保留 handler 對象,然后找個時機清除一下。采用 viewIdSet 自己維護一份 View 的數據。在 creat 方法中  disposeArgs.get("id")  執行過 dispose 方法的就刪除掉 viewIdSet.remove(viewId)。setPlatformViewsHandler 為空的情況判斷一下,有沒有執行 dispose 的 view handler  先不回收。如下:

public void setPlatformViewsHandler(@Nullable PlatformViewsHandler handler) {
    if(handler == null && viewIdSet != null && viewIdSet.size() > 0) {
      needReset = true;
      return;
    }
    this.handler = handler;
  }

目前是執行 dispose 的時候 needReset 為 true 時會將 handler 設置為 null。為什么官方的 Demo 是沒有問題的呢?主要原因還是我們接入了 FlutterBoost 默認是單引擎的,官方 Demo 是的純 Flutter 項目多引擎。頁面結束,通過銷毀 engine 把問題覆蓋了,所以內存回收表現的很平滑。 

五、自定義文本 BitMap Marker

地圖業務中自定義 marker 是比較常見的需求,由于地圖是通過 PlatformView 實現的,最容易想到的做法是,通過 Channel 傳入 marker 對應的樣式 Id 和展示所需數據,在各端繪制 marker,這種做法會增加人工成本,樣式也可能存在不一致的情況,失去了 flutter 框架的優勢。

地圖插件在 v3.0(v3.0 之前需要自己實現)提供了 iconData 參數傳入圖片 data 信息,在 flutter 側將文本、圖片繪制出來生成一張圖,將生成圖片 Data 傳遞給原生,該實現并不需要改動各端代碼,繪制時要注意視圖大小是物理像素點,而不是邏輯像素點。

Future<Uint8List?> customMark(String name, BuildContext context) async {
  final scale = MediaQuery.of(context).devicePixelRatio;
  final recorder = PictureRecorder();
  final canvas = Canvas(recorder);
  final paint = Paint();
  final textPainter = TextPainter(textDirection: TextDirection.ltr);
  ...
  final path = Path();
  canvas.drawPath(path, paint);
  // 繪制圖片
  final imageInfo = await UIImageLoader.imageInfoByAsset(HotelListImage.mapPoiMark);
  paintImage(canvas: canvas,rect: rect,image: imageInfo.image);
  // 生成繪制圖片
  final image = await recorder.endRecording().toImage(
      width.toInt(), (textBgHeight + arrowHeight + iconHeight + 2).toInt());
  final data = await image.toByteData(format: ImageByteFormat.png);
  return data?.buffer.asUint8List();
}

從 flutter 2 升級到 flutter 3 出現了小插曲,iOS debug 環境調用 toImage 進程會被終止。flutter 升級之后對弱引用指針調用做了線程檢查,創建和使用不是在同一線程在 debug 環境進程會被終止。toImage() 方法內使用了 fml::WeakPtr<SnapshotDelegate> snapshot_delegate 弱引用指針,由于 snapshot_delegate 在 raster 線程中被創建,正常調用也應該是在 raster 線程,當在 flutter 頁面中嵌入 PlatformView 時,為了保證渲染的一致性,會將 raster 線程與主線程合并,造成了 snapshot_delegate 在主線程調用的情況,觸發了線程檢查終止進程,但并不影響 release 環境。

class WeakPtr {
    T* operator->() const {
    CheckThreadSafety();
    return get();
  }
}


if (0 == pthread_getname_np(current_thread, actual_thread,
                                  buffer_length) &&
          0 == pthread_getname_np(self_, expected_thread, buffer_length)) {
        FML_DLOG(ERROR) << "IsCreationThreadCurrent expected thread: '"
                        << expected_thread << "' actual thread:'" // Object被創建的線程
                        << actual_thread << "'";  // 實際執行線程
}

六、自定義讓 Marker 展示在可見范圍

在地圖上添加 marker 之后,將已添加的 marker 全部展示在可視范圍內也是常見的需求。插件提供了支持 iOS 的 showmarkers 方法,這顯然不能夠滿足需求。我們思考通過 setVisibleMapRectWithPadding 指定顯示地圖地理范圍,該方法要求我們傳入參數 visibleMapBounds,設置地理范圍的東北坐標、西南坐標。由于右上角、左下角經緯度分為可視地理范圍最大、最小,即可拿到東北、西南坐標。

BMFCoordinateBounds? getMarkersVisibleMapBounds(List<BMFMarker> markers) {
  if (markers.isEmpty) return null;
  final firstPosition = markers.first.position;
  double maxLatitude = firstPosition.latitude;
  double minLatitude = firstPosition.latitude;
  double maxLongitude = firstPosition.longitude;
  double minLongitude = firstPosition.longitude;
  for (final marker in markers) {
    final lat = marker.position.latitude;
    final lon = marker.position.longitude;
    maxLatitude = max(maxLatitude, lat);
    minLatitude = min(minLatitude, lat);
    maxLongitude = max(maxLongitude, lon);
    minLongitude = min(minLongitude, lon);
  }
  return BMFCoordinateBounds(
      northeast: BMFCoordinate(maxLatitude, maxLongitude),
      southwest: BMFCoordinate(minLatitude, minLongitude));
}

隨著業務的迭代,需要將大地圖融合到列表中。為了將大地圖與小地圖切換動畫更加流暢,當小地圖被加載時,地圖 size 實際已經渲染成和大地圖同樣大小,下半部分被列表遮擋。這意味小地圖需要設置可見范圍的偏移量,但 inserts 參數 iOS、Android 計算方式不一樣,iOS 是根據 point 計算,Android 是通過 pixel 計算,要區分平臺做一次轉換。

Future<bool> setAllMarkersVisibleWithPadding(
  List<BMFMarker> markers,
  BuildContext context, {
  EdgeInsets insets = const EdgeInsets.all(20.0),
}) async {
  final bounds = getMarkersVisibleMapBounds(markers);
  if (bounds == null) return false;
  if (Util.isAndroid()) {
    final scale = MediaQuery.of(context).devicePixelRatio;
    insets = EdgeInsets.only(
        top: insets.top * scale,
        bottom: insets.bottom * scale,
        left: insets.left * scale,
        right: insets.right * scale);
  }
  return await setVisibleMapRectWithPadding(
      visibleMapBounds: bounds, insets: insets, animated: true);
}

七、總結

Flutter 地圖插件基于Native地圖 Android 和 iOS SDK 二次封裝而成,通過在 Flutter 使用MethodChannel交互實現地圖的顯示、交互、覆蓋物繪制和事件響應等功能。混合項目接入Flutter地圖容易發生問題的點,基本集中在PlatformView這一塊。通常是容器和View的事件、生命周期同步問題。

本文主要介紹FlutterBoost的混合工程,在接入Flutter地圖插件遇到的各種問題和解決方案。闡述了PlatformView的工作原理,方便我們更好的理解Flutter地圖插件。同時也介紹了如何用Android Studio 自帶的工具直觀地看內存異常。并且推薦leakcanary定位內存溢出的類和方法,希望對你接入Flutter地圖插件有一定的幫助。

責任編輯:張燕妮 來源: 攜程技術
相關推薦

2022-07-08 09:38:27

攜程酒店Flutter技術跨平臺整合

2023-02-08 16:34:05

數據庫工具

2022-04-07 17:30:31

Flutter攜程火車票渲染

2023-06-06 16:01:00

Web優化

2022-11-29 20:32:07

2022-08-06 08:23:47

云計算公有云廠商成本

2017-10-09 09:12:35

攜程運維架構

2022-07-15 12:58:02

鴻蒙攜程華為

2022-05-13 09:27:55

Widget機票業務App

2022-03-30 18:39:51

TiDBHTAPCDP

2022-08-20 07:46:03

Dynamo攜程數據庫

2022-08-12 08:34:32

攜程數據庫上云

2022-07-15 09:20:17

性能優化方案

2024-04-26 09:38:36

2022-06-17 10:44:49

實體鏈接系統旅游AI知識圖譜攜程

2024-07-05 15:05:00

2022-05-27 09:52:36

攜程TS運營AI

2023-08-18 10:49:14

開發攜程

2023-12-15 10:05:58

攜程網絡

2023-04-14 10:29:24

小程序實踐
點贊
收藏

51CTO技術棧公眾號

欧美激情论坛| 欧美中文字幕视频| 无码人妻丰满熟妇区毛片蜜桃精品| 成人区精品一区二区不卡| 国产自产视频一区二区三区| 欧美老妇交乱视频| 国产精品一级黄片| 欧美日韩在线精品一区二区三区激情综合 | 国产高清一区在线观看| 蜜臀久久久久久久| 国产做受高潮69| 国产欧美小视频| 成人福利免费在线观看| 91久久香蕉国产日韩欧美9色| 中文字幕一区二区三区5566| 人妻中文字幕一区| 蜜桃久久av一区| 久久久久久久一区二区| 日韩精品久久久久久久的张开腿让| 久久免费精品| 色久优优欧美色久优优| 69精品丰满人妻无码视频a片| 天堂av中文在线资源库| 国产在线精品不卡| 国产成人av网址| 九九热精品免费视频| 精品国内自产拍在线观看视频 | 国产精品一区二区三区在线播放 | 一本色道久久加勒比精品 | 日韩午夜av在线| 裸体女人亚洲精品一区| 免费看黄色的视频| 国产精品久久久网站| 在线观看免费亚洲| 天堂…中文在线最新版在线| 国产1区在线| 国产精品麻豆欧美日韩ww| 国产精品美女诱惑| a级片在线播放| 美女网站色91| 国产成人午夜视频网址| 在线观看亚洲欧美| 欧美午夜电影在线观看 | 亚洲日本精品国产第一区| 污污网站在线免费观看| 国产东北露脸精品视频| 国产啪精品视频| 五月天中文字幕| 三级久久三级久久| 欧美一级黄色网| 日韩免费观看一区二区| 欧美日韩精品| 欧美伦理91i| 日韩a级片在线观看| 99精品视频在线| 日韩中文视频免费在线观看| 国产一区二区三区四区五区六区| 日韩电影在线观看完整免费观看| 精品久久久久久最新网址| 国产又黄又嫩又滑又白| 一区二区免费| 亚洲成色777777女色窝| 五月天丁香社区| 波多野结衣在线一区二区| 欧美成人免费网站| 亚洲一区二区三区四区av| 亚洲一区二区三区在线免费| 欧美白人最猛性xxxxx69交| 下面一进一出好爽视频| 一区二区三区欧洲区| 精品蜜桃在线看| 欧美大喷水吹潮合集在线观看| 999久久久久久久久6666| 欧美tk丨vk视频| 国产+高潮+白浆+无码| 秋霞综合在线视频| 国产丝袜精品第一页| 久久av无码精品人妻系列试探| 国产成人精品一区二区免费看京| 亚洲石原莉奈一区二区在线观看| 人妻aⅴ无码一区二区三区 | 国产在线xxxx| 欧美激情网站| 欧美亚洲一区二区在线观看| 色91精品久久久久久久久| 美女国产精品久久久| 亚洲第一精品福利| 黄色正能量网站| 久久高清精品| 欧美激情精品久久久久久蜜臀| 国产一级视频在线播放| 久久久久久一区二区| 国产精品午夜视频| 成人av手机在线| 337p粉嫩大胆色噜噜噜噜亚洲| 亚洲第一综合| 婷婷丁香在线| 在线看国产日韩| 日本少妇一级片| 九九精品在线| 欧美理论电影在线观看| 国产www在线| 激情综合色播激情啊| 国产乱码一区| 日本a级在线| 欧美日韩亚洲国产一区| 国内国产精品天干天干| 大型av综合网站| 日韩性xxxx爱| 国产精品视频免费播放| 精品一区二区综合| 蜜桃av噜噜一区二区三| 成人av黄色| 欧美在线一区二区三区| 国产人成视频在线观看| 欧美少妇xxxx| 欧美一级bbbbb性bbbb喷潮片| 国产尤物视频在线观看| 成人国产视频在线观看| 综合国产精品久久久| 国产免费不卡| 精品国产三级a在线观看| 成人性生交大片免费看无遮挡aⅴ| 在线观看一区视频| 成人美女av在线直播| 青草久久伊人| 午夜精品福利久久久| 佐山爱在线视频| 日本一二区不卡| 日本久久亚洲电影| 日韩一级免费毛片| 亚洲精品第1页| 中文字幕国产免费| 少妇精品久久久| 91精品国产91久久久久| 精品国产999久久久免费| 中文字幕va一区二区三区| 欧美亚洲一二三区| 国产欧美三级电影| 欧美—级a级欧美特级ar全黄| 国产原创中文av| 国产精品伦理在线| 国产视频一区二区视频| 中国av一区| 日本久久久久久久久| 香蕉视频免费看| 福利一区视频在线观看| 老司机免费视频| 一区二区三区导航| 好吊色欧美一区二区三区视频| 图片区小说区亚洲| 欧美videossexotv100| 久久久久免费看| 国产99久久久国产精品潘金网站| 青青在线免费视频| 日韩精品一区国产| 欧美精品在线观看91| www天堂在线| 一区二区三区日韩欧美精品| xxx中文字幕| 91精品国产自产拍在线观看蜜| 91九色精品视频| av激情在线| 精品奇米国产一区二区三区| 国产成人精品av久久| 99麻豆久久久国产精品免费| 国产精品自拍片| 国产影视一区| 国产精品久久久久久久久粉嫩av| 在线观看国产原创自拍视频| 欧美日韩精品系列| 一区二区在线观看免费视频| 粉嫩aⅴ一区二区三区四区五区 | 午夜影院免费在线| 亚洲大胆人体视频| 黄瓜视频在线免费观看| 国产精品国产精品国产专区不蜜| 亚洲综合婷婷久久| 国产精品啊啊啊| 久久综合九色综合久99| 粉嫩av一区二区三区四区五区 | 欧美精品一区二区蜜桃| 成人毛片视频在线观看| 亚洲熟妇av一区二区三区| 欧美精品一区二区三区中文字幕| 国产日韩欧美成人| 日韩av官网| 亚洲免费一在线| 国产精品九九九九| 午夜精品一区二区三区电影天堂 | 一级黄色特级片| 欧美片第1页综合| 热re99久久精品国99热蜜月| 国产999精品在线观看| 性色av一区二区三区红粉影视| 久热av在线| 日韩视频在线你懂得| 狠狠人妻久久久久久| 亚洲欧美另类综合偷拍| 99久久人妻精品免费二区| 蜜乳av一区二区| 久久久亚洲精品无码| 成人中文视频| 久久精品99| 91精品国产一区二区在线观看| 欧美一级bbbbb性bbbb喷潮片| 免费在线观看黄色| 亚洲精品视频二区| 国产成人精品一区二区无码呦| 日韩欧美国产一区二区| 欧美丰满艳妇bbwbbw| 亚洲国产精品t66y| 中文字幕一区二区三区乱码不卡| 九九国产精品视频| 国产a级片免费观看| 欧美久久影院| 欧美 日韩 国产 在线观看| 免费一区二区三区视频导航| 97人人模人人爽人人少妇| 日本黄色一区| 欧美亚洲另类视频| 丝袜在线视频| 久久精品亚洲热| av中文字幕一区二区三区| 亚洲国产精品成人av| a天堂中文在线观看| 欧美色涩在线第一页| 日韩毛片一区二区三区| 五月激情丁香一区二区三区| 久热这里有精品| 成人欧美一区二区三区在线播放| 欧美熟妇激情一区二区三区| 不卡av电影在线播放| 18深夜在线观看免费视频| 久久国产尿小便嘘嘘| 国产高清视频网站| 久久九九电影| 久久久久人妻精品一区三寸| 亚洲激情二区| 欧美视频在线第一页| 欧美伊人影院| 看全色黄大色大片| 99久久综合狠狠综合久久aⅴ| 日本高清久久一区二区三区| 亚洲综合福利| 久久99久久精品国产| 欧美成人午夜77777| 国产欧美日韩综合精品二区| 加勒比久久高清| 国产精品高清一区二区三区| 国产精品高清一区二区| 91在线看www| 高清一区二区| www.成人av| 动漫视频在线一区| 国产一区二区不卡视频| 九九热hot精品视频在线播放| 国产一区二区高清不卡| 国产欧美三级电影| 麻豆传媒一区| 波多野结衣在线观看一区二区三区| 色综合电影网| 久久精品久久久| wwwwww欧美| 99精品99| 99视频精品免费| 久久超碰97人人做人人爱| 国产欧美一区二| 国产麻豆一精品一av一免费| 亚洲AV成人精品| 北岛玲一区二区三区四区| 国产毛片毛片毛片毛片毛片毛片| 久久久噜噜噜久久人人看| 国产综合精品久久久久成人av| 中文字幕制服丝袜成人av | 精品久久久久久久久久久| 欧美三级一区二区三区| 欧美性大战久久| 国产成人精品a视频| 日韩国产高清污视频在线观看| 久久手机免费观看| 日韩在线播放av| 欧美人与牲禽动交com| 97久久精品人人澡人人爽缅北| 成人黄色免费短视频| 91精品免费看| 北条麻妃一区二区三区在线| 欧洲精品久久| 欧美在线亚洲| 日本a级片免费观看| 精品一区二区三区不卡| a天堂视频在线观看| 欧美韩国日本一区| 久一视频在线观看| 91成人免费在线| 精品人妻一区二区三区浪潮在线| 亚洲美女福利视频网站| 黄色网页在线免费看| 91国产精品91| 国产不卡精品| 欧美日韩在线观看一区| 欧美激情1区2区| 成年人免费大片| 丁香婷婷综合网| 欧美成人久久久免费播放| 亚洲一二三区不卡| 依依成人在线视频| 亚洲精品美女在线观看| 麻豆系列在线观看| 日本精品久久中文字幕佐佐木| 国产va免费精品观看精品| 欧美一区2区三区4区公司二百 | 日日摸天天爽天天爽视频| 韩国午夜理伦三级不卡影院| 亚洲熟妇无码av| 亚洲高清在线精品| 91尤物国产福利在线观看| 亚洲男人第一网站| ****av在线网毛片| 91丝袜美腿美女视频网站| 精品一二三区| 免费看国产曰批40分钟| 国产一区二区伦理片| 综合 欧美 亚洲日本| 色综合久久综合网欧美综合网| 国产成人手机在线| 超碰91人人草人人干| 欧美性aaa| 清纯唯美一区二区三区| 国产日韩视频| 少妇激情一区二区三区视频| 亚洲精品亚洲人成人网在线播放| 在线观看中文字幕av| 亚洲图片在线综合| 波多视频一区| 久久一区二区精品| 亚洲黄色高清| 亚洲精品乱码久久| 一区二区三区精品视频| 国产精品久久久国产盗摄| 日韩在线免费高清视频| 另类中文字幕国产精品| 日本不卡一区二区三区在线观看| 亚洲一区二区三区高清| 国产又粗又长又爽| 午夜成人在线视频| 神马一区二区三区| 97欧美精品一区二区三区| 国产精品极品| 内射国产内射夫妻免费频道| 不卡的av在线| 色一情一乱一伦| 亚洲人免费视频| 国精产品一区一区三区四川| 热舞福利精品大尺度视频| 日韩精品福利网| 天天操天天摸天天舔| 欧美日韩国产影片| 国产高清一区二区三区视频| 91亚洲精品久久久| 欧美.www| 国产ts丝袜人妖系列视频| 色综合久久66| 中文字幕在线视频区| 国产综合福利在线| 欧美成人一品| 亚洲の无码国产の无码步美| 欧美日韩亚洲91| 黑人与亚洲人色ⅹvideos| 国产欧美中文字幕| 欧美在线免费| 亚洲中文字幕一区| 欧美曰成人黄网| caopon在线免费视频| 国产日产精品一区二区三区四区| 亚洲一区二区伦理| 日本黄区免费视频观看| 欧美一区二区大片| 97人澡人人添人人爽欧美| 欧美日韩在线高清| 久久99久久精品| 国产精品suv一区二区| 亚洲片在线观看| 成人51免费| 波多野结衣乳巨码无在线| 欧美激情一区在线观看| jizz中国少妇| 欧美在线一级视频| 天天射天天综合网| 天堂www中文在线资源| 欧美亚日韩国产aⅴ精品中极品| 国产黄色在线免费观看| 九九九九精品九九九九| 久久精品国产**网站演员| 国产真实乱偷精品视频| 一区二区福利视频| 中文字幕一区二区三区中文字幕| 日本精品免费在线观看| 亚洲视频一区在线观看| 亚州av在线播放|