成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

iOS 之如何利用 RunLoop 原理去監控卡頓?

移動開發 iOS
通過 Runloop 來檢測卡頓,還是很有必要的。對提高 app 的用戶使用體驗還是很有幫助的。畢竟卡頓是偶顯的不容易復現。所以檢測卡頓來來抓取堆棧信息,分析并解決卡頓,還是很有必要的。

[[397057]]

1. 前言

卡頓問題,就是在主線程上無法響應用戶交互的問題。如果一個 App 時不時地就給你卡一下,有 時還長時間無響應,這時你還愿意繼續用它嗎?所以說,卡頓問題對 App 的傷害是巨大的,也是 我們必須要重點解決的一個問題。

2. 卡頓原因

現在,我們先來看一下導致卡頓問題的幾種原因:

  • 復雜 UI 、圖文混排的繪制量過大;
  • 在主線程上做網絡同步請求;
  • 在主線程做大量的 IO 操作;
  • 運算量過大,CPU 持續高占用;
  • 死鎖和主子線程搶鎖。

那么,我們如何監控到什么時候會出現卡頓呢?是要監視FPS嗎?

FPS 是一秒顯示的幀數,也就是一秒內畫面變化數量。當FPS達到60,說明界面很流程,當FPS低于24,頁面流暢度不是那么流暢,但是不能說卡主了。

由此可見,簡單地通過監視 FPS 是很難確定是否會出現卡頓問題了,所以我就果斷棄了通過監視 FPS 來監控卡頓的方案。

那么,我們到底應該使用什么方案來監控卡頓呢?

3. 使用RunLoop來檢控卡頓

對于 iOS 開發來說,監控卡頓就是要去找到主線程上都做了哪些事兒。我們都知道,線程的消息 事件是依賴于 NSRunLoop 的,所以從 NSRunLoop 入手,就可以知道主線程上都調用了哪些方 法。我們通過監聽 NSRunLoop 的狀態,就能夠發現調用方法是否執行時間過長,從而判斷出是 否會出現卡頓。

所以,我推薦的監控卡頓的方案是:通過監控 RunLoop 的狀態來判斷是否會出現卡頓。

3.1 Runloop

RunLoop是iOS開發中的一個基礎概念,為了幫助你理解并用好這個對象,接下來我會先和你介紹一下它可以做哪些事兒,以及它為什么可以做成這些事兒。

RunLoop 這個對象,在 iOS 里由 CFRunLoop 實現。簡單來說,RunLoop 是用來監聽輸入源,進 行調度處理的。這里的輸入源可以是輸入設備、網絡、周期性或者延遲時間、異步回調。

RunLoop 會接收兩種類型的輸入源:

  • 一種是來自另一個線程或者來自不同應用的異步消息;
  • 另一 種是來自預訂時間或者重復間隔的同步事件。

RunLoop 的目的是,當有事件要去處理時保持線程忙,當沒有事件要處理時讓線程進入休眠。所 以,了解 RunLoop 原理不光能夠運用到監控卡頓上,還可以提高用戶的交互體驗。通過將那些 繁重而不緊急會大量占用 CPU 的任務(比如圖片加載),放到空閑的 RunLoop 模式里執行,這 樣就可以避開在 UITrackingRunLoopMode 這個 RunLoop 模式時是執行。

UITrackingRunLoopMode 是用戶進行滾動操作時會切換到的 RunLoop 模式,避免在這個 RunLoop 模式執行繁重的 CPU 任務,就能避免影響用戶交互操作上體驗。

接下來,我就通過 CFRunLoop 的源碼來跟你分享下 RunLoop 的原理吧。

 3.2 RunLoop原理

其內部代碼整理如下:

  1. /// 用DefaultMode啟動 
  2. void CFRunLoopRun(void) { 
  3.     CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); 
  4.   
  5. /// 用指定的Mode啟動,允許設置RunLoop超時時間 
  6. int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { 
  7.     return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); 
  8.   
  9. /// RunLoop的實現 
  10. int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { 
  11.      
  12.     /// 首先根據modeName找到對應mode 
  13.     CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); 
  14.     /// 如果mode里沒有source/timer/observer, 直接返回。 
  15.     if (__CFRunLoopModeIsEmpty(currentMode)) return
  16.      
  17.     /// 1. 通知 Observers: RunLoop 即將進入 loop。 
  18.     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); 
  19.      
  20.     /// 內部函數,進入loop 
  21.     __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { 
  22.          
  23.         Boolean sourceHandledThisLoop = NO
  24.         int retVal = 0; 
  25.         do { 
  26.   
  27.             /// 2. 通知 Observers: RunLoop 即將觸發 Timer 回調。 
  28.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); 
  29.             /// 3. 通知 Observers: RunLoop 即將觸發 Source0 (非port) 回調。 
  30.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); 
  31.             /// 執行被加入的block 
  32.             __CFRunLoopDoBlocks(runloop, currentMode); 
  33.              
  34.             /// 4. RunLoop 觸發 Source0 (非port) 回調。 
  35.             sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); 
  36.             /// 執行被加入的block 
  37.             __CFRunLoopDoBlocks(runloop, currentMode); 
  38.   
  39.             /// 5. 如果有 Source1 (基于port) 處于 ready 狀態,直接處理這個 Source1 然后跳轉去處理消息。 
  40.             if (__Source0DidDispatchPortLastTime) { 
  41.                 Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) 
  42.                 if (hasMsg) goto handle_msg; 
  43.             } 
  44.              
  45.             /// 通知 Observers: RunLoop 的線程即將進入休眠(sleep)。 
  46.             if (!sourceHandledThisLoop) { 
  47.                 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); 
  48.             } 
  49.              
  50.             /// 7. 調用 mach_msg 等待接受 mach_port 的消息。線程將進入休眠, 直到被下面某一個事件喚醒。 
  51.             /// • 一個基于 port 的Source 的事件。 
  52.             /// • 一個 Timer 到時間了 
  53.             /// • RunLoop 自身的超時時間到了 
  54.             /// • 被其他什么調用者手動喚醒 
  55.             __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { 
  56.                 mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg 
  57.             } 
  58.   
  59.             /// 8. 通知 Observers: RunLoop 的線程剛剛被喚醒了。 
  60.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); 
  61.              
  62.             /// 收到消息,處理消息。 
  63.             handle_msg: 
  64.   
  65.             /// 9.1 如果一個 Timer 到時間了,觸發這個Timer的回調。 
  66.             if (msg_is_timer) { 
  67.                 __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) 
  68.             }  
  69.   
  70.             /// 9.2 如果有dispatch到main_queue的block,執行block。 
  71.             else if (msg_is_dispatch) { 
  72.                 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); 
  73.             }  
  74.   
  75.             /// 9.3 如果一個 Source1 (基于port) 發出事件了,處理這個事件 
  76.             else { 
  77.                 CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); 
  78.                 sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); 
  79.                 if (sourceHandledThisLoop) { 
  80.                     mach_msg(reply, MACH_SEND_MSG, reply); 
  81.                 } 
  82.             } 
  83.              
  84.             /// 執行加入到Loop的block 
  85.             __CFRunLoopDoBlocks(runloop, currentMode); 
  86.              
  87.   
  88.             if (sourceHandledThisLoop && stopAfterHandle) { 
  89.                 /// 進入loop時參數說處理完事件就返回。 
  90.                 retVal = kCFRunLoopRunHandledSource; 
  91.             } else if (timeout) { 
  92.                 /// 超出傳入參數標記的超時時間了 
  93.                 retVal = kCFRunLoopRunTimedOut; 
  94.             } else if (__CFRunLoopIsStopped(runloop)) { 
  95.                 /// 被外部調用者強制停止了 
  96.                 retVal = kCFRunLoopRunStopped; 
  97.             } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { 
  98.                 /// source/timer/observer一個都沒有了 
  99.                 retVal = kCFRunLoopRunFinished; 
  100.             } 
  101.              
  102.             /// 如果沒超時,mode里沒空,loop也沒被停止,那繼續loop。 
  103.         } while (retVal == 0); 
  104.     } 
  105.      
  106.     /// 10. 通知 Observers: RunLoop 即將退出。 
  107.     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); 

可以看到,實際上 RunLoop 就是這樣一個函數,其內部是一個 do-while 循環。當你調用 CFRunLoopRun() 時,線程就會一直停留在這個循環里;直到超時或被手動停止,該函數才會返回。

RunLoop內部的邏輯圖:

RunLoop內部原理.png

4. 如何檢測卡頓

4.1 首先知道RunLoop的六個狀態

  1. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  2.     kCFRunLoopEntry         = (1UL << 0), // 即將進入Loop 
  3.     kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer 
  4.     kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source 
  5.     kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠 
  6.     kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒 
  7.     kCFRunLoopExit          = (1UL << 7), // 即將退出Loop 
  8. kCFRunLoopAllActivities // loop所有狀態改變 
  9.  
  10. }; 

要想監聽RunLoop,你就首先需要創建一個 CFRunLoopObserverContext 觀察者,代碼如下:

  1. - (void)registerObserver { 
  2.      
  3.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  4.     //創建Run loop observer對象 
  5.     //第一個參數用于分配observer對象的內存 
  6.     //第二個參數用以設置observer所要關注的事件,詳見回調函數myRunLoopObserver中注釋 
  7.     //第三個參數用于標識該observer是在第一次進入run loop時執行還是每次進入run loop處理時均執行 
  8.     //第四個參數用于設置該observer的優先級 
  9.     //第五個參數用于設置該observer的回調函數 
  10.     //第六個參數用于設置該observer的運行環境 
  11.     CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  12.                                                             kCFRunLoopAllActivities, 
  13.                                                             YES, 
  14.                                                             0, 
  15.                                                             &runLoopObserverCallBack, 
  16.                                                             &context); 
  17.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  18.  

實時獲取變化的回調的方法:

  1. //每當runloop狀態變化的觸發這個回調方法 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     MyClass *object = (__bridge MyClass*)info; 
  4.     object->activity = activity; 

其中UI主要集中在

_CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION(source0)和CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION(source1)之前。

獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態就可以知道是否有卡頓的情況。

4.2 檢測卡頓的思路

只需要另外再開啟一個線程,實時計算這兩個狀態區域之間的耗時是否到達某個閥值,便能揪出這些性能殺手。

  • 監聽runloop狀態變化回調方法
  1. // 就是runloop有一個狀態改變 就記錄一下 
  2. static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { 
  3.     BGPerformanceMonitor *monitor = (__bridge BGPerformanceMonitor*)info; 
  4.      
  5.     // 記錄狀態值 
  6.     monitor->activity = activity; 
  7.      
  8.     // 發送信號 
  9.     dispatch_semaphore_t semaphore = monitor->semaphore; 
  10.     long st = dispatch_semaphore_signal(semaphore); 
  11.     NSLog(@"dispatch_semaphore_signal:st=%ld,time:%@",st,[BGPerformanceMonitor getCurTime]); 
  12.      
  13.  
  14.        /* Run Loop Observer Activities */ 
  15. //    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { 
  16. //        kCFRunLoopEntry = (1UL << 0),    // 進入RunLoop循環(這里其實還沒進入) 
  17. //        kCFRunLoopBeforeTimers = (1UL << 1),  // RunLoop 要處理timer了 
  18. //        kCFRunLoopBeforeSources = (1UL << 2), // RunLoop 要處理source了 
  19. //        kCFRunLoopBeforeWaiting = (1UL << 5), // RunLoop要休眠了 
  20. //        kCFRunLoopAfterWaiting = (1UL << 6),   // RunLoop醒了 
  21. //        kCFRunLoopExit = (1UL << 7),           // RunLoop退出(和kCFRunLoopEntry對應) 
  22. //        kCFRunLoopAllActivities = 0x0FFFFFFFU //RunLoop狀態變化 
  23. //    }; 
  24.     if (activity == kCFRunLoopEntry) {  // 即將進入RunLoop 
  25.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopEntry"); 
  26.     } else if (activity == kCFRunLoopBeforeTimers) {    // 即將處理Timer 
  27.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeTimers"); 
  28.     } else if (activity == kCFRunLoopBeforeSources) {   // 即將處理Source 
  29.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeSources"); 
  30.     } else if (activity == kCFRunLoopBeforeWaiting) {   //即將進入休眠 
  31.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopBeforeWaiting"); 
  32.     } else if (activity == kCFRunLoopAfterWaiting) {    // 剛從休眠中喚醒 
  33.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAfterWaiting"); 
  34.     } else if (activity == kCFRunLoopExit) {    // 即將退出RunLoop 
  35.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopExit"); 
  36.     } else if (activity == kCFRunLoopAllActivities) { 
  37.         NSLog(@"runLoopObserverCallBack - %@",@"kCFRunLoopAllActivities"); 
  38.     } 
  • 開啟runloop監聽
  1. // 開始監聽 
  2. - (void)startMonitor { 
  3.     if (observer) { 
  4.         return
  5.     } 
  6.      
  7.     // 創建信號 
  8.     semaphore = dispatch_semaphore_create(0); 
  9.     NSLog(@"dispatch_semaphore_create:%@",[BGPerformanceMonitor getCurTime]); 
  10.      
  11.     // 注冊RunLoop狀態觀察 
  12.     CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; 
  13.     //創建Run loop observer對象 
  14.     //第一個參數用于分配observer對象的內存 
  15.     //第二個參數用以設置observer所要關注的事件,詳見回調函數myRunLoopObserver中注釋 
  16.     //第三個參數用于標識該observer是在第一次進入run loop時執行還是每次進入run loop處理時均執行 
  17.     //第四個參數用于設置該observer的優先級 
  18.     //第五個參數用于設置該observer的回調函數 
  19.     //第六個參數用于設置該observer的運行環境 
  20.     observer = CFRunLoopObserverCreate(kCFAllocatorDefault, 
  21.                                        kCFRunLoopAllActivities, 
  22.                                        YES, 
  23.                                        0, 
  24.                                        &runLoopObserverCallBack, 
  25.                                        &context); 
  26.     CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes); 
  27.      
  28.     // 在子線程監控時長 
  29.     dispatch_async(dispatch_get_global_queue(0, 0), ^{ 
  30.         while (YES) {   // 有信號的話 就查詢當前runloop的狀態 
  31.             // 假定連續5次超時50ms認為卡頓(當然也包含了單次超時250ms) 
  32.             // 因為下面 runloop 狀態改變回調方法runLoopObserverCallBack中會將信號量遞增 1,所以每次 runloop 狀態改變后,下面的語句都會執行一次 
  33.             // dispatch_semaphore_wait:Returns zero on success, or non-zero if the timeout occurred. 
  34.             long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC)); 
  35.             NSLog(@"dispatch_semaphore_wait:st=%ld,time:%@",st,[self getCurTime]); 
  36.             if (st != 0) {  // 信號量超時了 - 即 runloop 的狀態長時間沒有發生變更,長期處于某一個狀態下 
  37.                 if (!observer) { 
  38.                     timeoutCount = 0; 
  39.                     semaphore = 0; 
  40.                     activity = 0; 
  41.                     return
  42.                 } 
  43.                 NSLog(@"st = %ld,activity = %lu,timeoutCount = %d,time:%@",st,activity,timeoutCount,[self getCurTime]); 
  44.                 // kCFRunLoopBeforeSources - 即將處理source kCFRunLoopAfterWaiting - 剛從休眠中喚醒 
  45.                 // 獲取kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting再到kCFRunLoopAfterWaiting的狀態就可以知道是否有卡頓的情況。 
  46.                 // kCFRunLoopBeforeSources:停留在這個狀態,表示在做很多事情 
  47.                 if (activity == kCFRunLoopBeforeSources || activity == kCFRunLoopAfterWaiting) {    // 發生卡頓,記錄卡頓次數 
  48.                     if (++timeoutCount < 5) { 
  49.                         continue;   // 不足 5 次,直接 continue 當次循環,不將timeoutCount置為0 
  50.                     } 
  51.                      
  52.                      
  53.                     // 收集Crash信息也可用于實時獲取各線程的調用堆棧 
  54.                     PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  55.                      
  56.                     PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  57.                      
  58.                     NSData *data = [crashReporter generateLiveReport]; 
  59.                     PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  60.                     NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter withTextFormat:PLCrashReportTextFormatiOS]; 
  61.                      
  62.                     NSLog(@"---------卡頓信息\n%@\n--------------",report); 
  63.                 } 
  64.             } 
  65.             NSLog(@"dispatch_semaphore_wait timeoutCount = 0,time:%@",[self getCurTime]); 
  66.             timeoutCount = 0; 
  67.         } 
  68.     }); 

記錄卡頓的函數調用

監控到了卡頓現場,當然下一步便是記錄此時的函數調用信息,此處可以使用一個第三方Crash 收集組件 PLCrashReporter,它不僅可以收集 Crash 信息也可用于實時獲取各線程的調用堆棧,示例如下:

  1. PLCrashReporterConfig *config = [[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD 
  2.                                                                    symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]; 
  3. PLCrashReporter *crashReporter = [[PLCrashReporter alloc] initWithConfiguration:config]; 
  4. NSData *data = [crashReporter generateLiveReport]; 
  5. PLCrashReport *reporter = [[PLCrashReport alloc] initWithData:data error:NULL]; 
  6. NSString *report = [PLCrashReportTextFormatter stringValueForCrashReport:reporter 
  7.                                                           withTextFormat:PLCrashReportTextFormatiOS]; 
  8. NSLog(@"------------\n%@\n------------", report); 

當檢測到卡頓時,抓取堆棧信息,然后在客戶端做一些過濾處理,便可以上報到服務器,通過收集一定量的卡頓數據后經過分析便能準確定位需要優化的邏輯,這個實時卡頓監控就大功告成了!

5. 結尾

 

通過 Runloop 來檢測卡頓,還是很有必要的。對提高 app 的用戶使用體驗還是很有幫助的。畢竟卡頓是偶顯的不容易復現。所以檢測卡頓來來抓取堆棧信息,分析并解決卡頓,還是很有必要的。

 

責任編輯:武曉燕 來源: 網羅開發
相關推薦

2025-04-29 08:20:00

無線監控網絡無線網絡

2013-03-27 10:32:53

iOS多線程原理runloop介紹GCD

2018-07-27 18:47:01

數據庫MySQL線程

2021-08-03 16:35:04

AndroidANR內存

2011-08-15 14:27:51

CocoaRunLoop

2021-04-02 14:23:12

WiFi網絡技術

2021-03-31 21:20:15

WiFi網絡漫游

2023-02-03 15:14:15

2021-11-28 21:26:39

Windows 7Windows微軟

2017-11-21 09:25:23

2021-03-15 10:31:48

手機安卓蘋果

2017-03-02 12:39:04

移動端iOS監控體系

2021-08-31 23:09:50

微信功能技巧

2013-12-27 10:37:01

2015-06-03 16:33:23

手機銀行應用性能APP

2012-08-20 09:45:18

SQL Server

2011-03-25 15:01:26

Cacti監控memcache

2011-03-29 15:35:14

cactimemcache

2022-05-02 08:30:46

網絡Wi-Fi

2016-03-30 09:58:16

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91亚洲精品在线 | 欧美成人专区 | 不卡一区 | 国产精品久久av | 祝你幸福电影在线观看 | 欧美日韩在线一区二区 | 一级毛片免费视频 | 精品国产一区一区二区三亚瑟 | 成人在线一级片 | 欧美色综合 | av综合站| 免费的色网站 | 成人激情视频 | 欧美一级毛片久久99精品蜜桃 | 亚洲激情专区 | 蜜臀久久99精品久久久久久宅男 | 色偷偷888欧美精品久久久 | 日韩av在线一区 | 欧美在线视频二区 | 日韩av电影院 | 色婷婷久久久亚洲一区二区三区 | www.黄色在线观看 | 国产精品久久免费观看 | 精品三级在线观看 | 黑人精品xxx一区一二区 | 久久天天躁狠狠躁夜夜躁2014 | 久久久久久免费看 | 欧美精品乱码久久久久久按摩 | 中文字幕视频在线观看 | 久久在线视频 | aa级毛片毛片免费观看久 | 中文字幕在线观看第一页 | 国产欧美精品区一区二区三区 | 99亚洲视频 | 日本中文字幕一区 | 免费av毛片 | 欧美日韩一区二区在线播放 | 中文字幕不卡视频在线观看 | 亚洲最大av网站 | 久久精品色欧美aⅴ一区二区 | 亚洲高清电影 |