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

用兩張圖告訴你,為什么你的App會卡頓?

移動開發(fā) Android
Window從字面看它是一個窗口,意思和PC上的窗口概念有點像。但也不是那么準確。看圖說。可以看到,我們要顯示的布局是被放到它的屬性mDecor中的,這個mDecor就是DecorView的一個實例。下面會專門擼DecorView,現(xiàn)在先把關注點放到Window上。Window還有一個比較重要的屬性mWindowManager,它是WindowManager(這是個接口)的一個實現(xiàn)類的一個實例。我們平時通過getWindowManager()方法獲得的東西就是這個mWindowManager。

[[192710]]

有什么料?

從這篇文章中你能獲得這些料:

  • 知道setContentView()之后發(fā)生了什么?
  • 知道Android究竟是如何在屏幕上顯示我們期望的畫面的?
  • 對Android的視圖架構(gòu)有整體把握。
  • 學會從根源處分析畫面卡頓的原因。
  • 掌握如何編寫一個流暢的App的技巧。
  • 從源碼中學習Android的細想。
  • 收獲兩張自制圖,幫助你理解Android的視圖架構(gòu)。

[[192711]] 

從setContentView()說起

  1. public class AnalyzeViewFrameworkActivity extends Activity { 
  2.   @Override 
  3.   protected void onCreate(Bundle savedInstanceState) { 
  4.     super.onCreate(savedInstanceState); 
  5.     setContentView(R.layout.activity_analyze_view_framwork); 
  6.   } 
  7.  

上面這段代碼想必Androider們大都已經(jīng)不能再熟悉的更多了。但是你知道這樣寫了之后發(fā)生什么了嗎?這個布局到底被添加到哪了?我的天,知識點來了!

可能很多同學也知道這個布局是被放到了一個叫做DecorView的父布局里,但是我還是要再說一遍。且看下圖

 

這個圖可能和伙伴們在書上或者網(wǎng)上常見的不太一樣,為什么不太一樣呢?因為是我自己畫的,哈哈哈...

下面就來看著圖捋一捋Android最基本的視圖框架。

PhoneWindow

估計很多同學都知道,每一個Activity都擁有一個Window對象的實例。這個實例實際是PhoneWindow類型的。那么PhoneWindow從名字很容易看出,它應該是Window的兒子(即子類)!

知識點:每一個Activity都有一個PhoneWindow對象。

那么,PhoneWindow有什么用呢?它在Activity充當什么角色呢?下面我就姑且把PhoneWindow等同于Window來稱呼吧。

[[192712]] 

Window從字面看它是一個窗口,意思和PC上的窗口概念有點像。但也不是那么準確。看圖說。可以看到,我們要顯示的布局是被放到它的屬性mDecor中的,這個mDecor就是DecorView的一個實例。下面會專門擼DecorView,現(xiàn)在先把關注點放到Window上。Window還有一個比較重要的屬性mWindowManager,它是WindowManager(這是個接口)的一個實現(xiàn)類的一個實例。我們平時通過getWindowManager()方法獲得的東西就是這個mWindowManager。顧名思義,它是Window的管理者,負責管理著窗口及其中顯示的內(nèi)容。它的實際實現(xiàn)類是WindowManagerImpl。可能童鞋們現(xiàn)在正在PhoneWindow中尋找著這個mWindowManager是在哪里實例化的,是不是上下來回滾動著這個類都找不見?STOP!mWindowManager是在它爹那里就實例化好的。下面代碼是在Window.java中的。

  1. public void setWindowManager(WindowManager wm,  
  2.     IBinder appToken,  
  3.     String appName,  
  4.     boolean hardwareAccelerated) { 
  5.         ... 
  6.         if (wm == null) { 
  7.             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
  8.             //獲取了一個WindowManager 
  9.         } 
  10.         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); 
  11.         //通過這里我們可以知道,上面獲取到的wm實際是WindowManagerImpl類型的。 
  12.     }  

通過上面的介紹,我們已經(jīng)知道了Window中有負責承載布局的DecorView,有負責管理的WindowManager(事實上它只是個代理,后面會講它代理的是誰)。

DecorView

前面提到過,在Activity的onCreate()中通過setContentView()設置的布局實際是被放到DecorView中的。我們在圖中找到DecorView。

從圖中可以看到,DecorView繼承了FrameLayout,并且一般情況下,它會在先添加一個預設的布局。比如DecorCaptionView,它是從上到下放置自己的子布局的,相當于一個LinearLayout。通常它會有一個標題欄,然后有一個容納內(nèi)容的mContentRoot,這個布局的類型視情況而定。我們希望顯示的布局就是放到了mContentRoot中。

知識點:通過setContentView()設置的布局是被放到DecorView中,DecorView是視圖樹的最頂層。

WindowManager

前面已經(jīng)提到過,WindowManager在Window中具有很重要的作用。我們先在圖中找到它。這里需要先說明一點,在PhoneWindow中的mWindowManager實際是WindowManagerImpl類型的。WindowManagerImpl自然就是接口WindowManager的一個實現(xiàn)類嘍。這一點是我沒有在圖中反映的。

WindowManager是在Activity執(zhí)行attach()時被創(chuàng)建的,attach()方法是在onCreate()之前被調(diào)用的。關于Activity的創(chuàng)建可以看看我的這篇:【可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52】。

Activity.java

  1. final void attach(Context context, ActivityThread aThread, 
  2.     Instrumentation instr, IBinder token, int ident, 
  3.     Application application, Intent intent, ActivityInfo info, 
  4.     CharSequence title, Activity parent, String id, 
  5.     NonConfigurationInstances lastNonConfigurationInstances, 
  6.     Configuration config, String referrer, IVoiceInteractor voiceInteractor, 
  7.     Window window){ 
  8.         ... 
  9.         mWindow = new PhoneWindow(this, window); 
  10.         //創(chuàng)建Window 
  11.         ... 
  12.         mWindow.setWindowManager( 
  13.          (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), 
  14.          mToken, mComponent.flattenToString(), 
  15.          (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); 
  16.         //注意!這里就是在創(chuàng)建WindowManager。 
  17.         //這個方法在前面已經(jīng)說過了。 
  18.         if (mParent != null) { 
  19.            mWindow.setContainer(mParent.getWindow()); 
  20.         } 
  21.         mWindowManager = mWindow.getWindowManager(); 
  22.             }  

繼續(xù)看圖。WindowManagerImpl持有了PhoneWindow的引用,因此它可以對PhoneWindow進行管理。同時它還持有一個非常重要的引用mGlobal。這個mGlobal指向一個WindowManagerGlobal類型的單例對象,這個單例每個應用程序只有唯一的一個。在圖中,我說明了WindowManagerGlobal維護了本應用程序內(nèi)所有Window的DecorView,以及與每一個DecorView對應關聯(lián)的ViewRootImpl。這也就是為什么我前面提到過,WindowManager只是一個代理,實際的管理功能是通過WindowManagerGlobal實現(xiàn)的。我們來看個源碼的例子就比較清晰了。開始啦!

[[192713]] 

WimdowManagerImpl.java

  1. public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { 
  2.     ... 
  3.     mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); 
  4.     //實際是通過WindowManagerGlobal實現(xiàn)的。 
  5.  

從上面的代碼可以看出,WindowManagerImpl確實只是WindowManagerGlobal的一個代理而已。同時,上面這個方法在整個Android的視圖框架流程中十分的重要。我們知道,在Activity執(zhí)行onResume()后界面就要開始渲染了。原因是在onResume()時,會調(diào)用WindowManager的addView()方法(實際最后調(diào)用的是WindowManagerGlobal的addView()方法),把視圖添加到窗口上。結(jié)合我的這篇【可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52】看,可以幫助你更好的理解Android的視圖框架。

ActivityThread.java

  1. final void handleResumeActivity(IBinder token, 
  2.     boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { 
  3.     ... 
  4.     ViewManager wm = a.getWindowManager(); 
  5.     //獲得WindowManager,實際是WindowManagerImpl 
  6.     ... 
  7.     wm.addView(decor, l); 
  8.     //添加視圖 
  9.     ... 
  10.     wm.updateViewLayout(decor, l); 
  11.     //需要刷新的時候會走這里 
  12.     ... 
  13.  

從上面可以看到,當Activity執(zhí)行onResume()的時候就會添加視圖,或者刷新視圖。需要解釋一點:WindowManager實現(xiàn)了ViewManager接口。

如圖中所說,WindowManagerGlobal調(diào)用addView()的時候會把DecorView添加到它維護的數(shù)組中去,并且會創(chuàng)建另一個關鍵且極其重要的ViewRootImpl(這個必須要專門講一下)類型的對象,并且也會把它存到一個數(shù)組中維護。

WindowManagerGlobal.java

  1. public void addView(View view, ViewGroup.LayoutParams params, 
  2.     Display display, Window parentWindow) { 
  3.     ... 
  4.     root = new ViewRootImpl(view.getContext(), display); 
  5.     //重要角色登場 
  6.     view.setLayoutParams(wparams); 
  7.     mViews.add(view); 
  8.     mRoots.add(root); 
  9.     //保存起來維護 
  10.     mParams.add(wparams); 
  11.     ... 
  12.     root.setView(view, wparams, panelParentView); 
  13.     //設置必要屬性view是DecorView,panelParentView是PhoneWindow 
  14.     ... 
  15.  

可以看出ViewRootImpl是在Activity執(zhí)行onResume()的時候才被創(chuàng)建的,并且此時才把DecorView傳進去讓它管理。

知識點:WindowManager是在onCreate()時被創(chuàng)建。它對窗口的管理能力實際是通過WindowManagerGlobal實現(xiàn)的。在onResume()是視圖才通過WindowManager被添加到窗口上。

ViewRootImpl

ViewRootImpl能夠和系統(tǒng)的WindowManagerService進行交互,并且管理著DecorView的繪制和窗口狀態(tài)。非常的重要。趕緊在圖中找到對應位置吧!

ViewRootImpl并不是一個View,而是負責管理視圖的。它配合系統(tǒng)來完成對一個Window內(nèi)的視圖樹的管理。從圖中也可以看到,它持有了DecorView的引用,并且視圖樹它是視圖樹繪制的起點。因此,ViewRootImpl會稍微復雜一點,需要我們更深入的去了解,在圖中我標出了它比較重要的組成Surface和Choreographer等都會在后面提到。

到此,我們已經(jīng)一起把第一張圖擼了一遍了,現(xiàn)在童鞋們因該對Android視圖框架有了大致的了解。下面將更進一步的去了解Android的繪制機制。

App總是卡頓到底是什么原因?

下面將會詳細的講解為什么我們設置的視圖能夠被繪制到屏幕上?這中間究竟隱藏著怎樣的離奇?看完之后,你自然就能夠從根源知道為什么你的App會那么卡,以及開始有思路著手解決這些卡頓。 

 

同樣用一張圖來展示這個過程。由于Android繪制機制確實有點復雜,所以第一眼看到的時候你的內(nèi)心中可能蹦騰了一萬只草泥馬😂。不要怕!我們從源頭開始,一點一點的梳理這個看似復雜的繪制機制。為什么說看似復雜呢?因為這個過程只需要幾分鐘。Just Do It!

CPU、GPU是搞什么鬼的?

整天聽到CPU、GPU的,你知道他們是干什么的嗎?這里簡單的提一下,幫助理解后面的內(nèi)容。

在Android的繪制架構(gòu)中,CPU主要負責了視圖的測量、布局、記錄、把內(nèi)容計算成Polygons多邊形或者Texture紋理,而GPU主要負責把Polygons或者Textture進行Rasterization柵格化,這樣才能在屏幕上成像。在使用硬件加速后,GPU會分擔CPU的計算任務,而CPU會專注處理邏輯,這樣減輕CPU的負擔,使得整個系統(tǒng)效率更高。

 

RefreshRate刷新率和FrameRate幀率

RefreshRate刷新率是屏幕每秒刷新的次數(shù),是一個與硬件有關的固定值。在Android平臺上,這個值一般為60HZ,即屏幕每秒刷新60次。

FrameRate幀率是每秒繪制的幀數(shù)。通常只要幀數(shù)和刷新率保持一致,就能夠看到流暢的畫面。在Android平臺,我們應該盡量維持60FPS的幀率。但有時候由于視圖的復雜,它們可能就會出現(xiàn)不一致的情況。

 

 

如圖,當幀率小于刷新率時,比如圖中的30FPS < 60HZ,就會出現(xiàn)相鄰兩幀看到的是同一個畫面,這就造成了卡頓。這就是為什么我們總會說,要盡量保證一幀畫面能夠在16ms內(nèi)繪制完成,就是為了和屏幕的刷新率保持同步。

下面將會介紹Android是如何來確保刷新率和幀率保持同步的。

Vsync(垂直同步)是什么?

你可能在游戲的設置中見過Vsync,開啟它通常能夠提高游戲性能。在Android中,同樣使用Vsync垂直同步來提高顯示性能。它能夠使幀率FrameRate和硬件的RefreshRate刷新強制保持一致。

HWComposer與Vsync不得不說的事

看圖啦看圖啦。首先在最左邊我們看到有個叫HWComposer的類,這是一個c++編寫的類。它Android系統(tǒng)初始化時就被創(chuàng)建,然后開始配合硬件產(chǎn)生Vsync信號,也就是圖中的HW_Vsync信號。當然它不是一直不停的在產(chǎn)生,這樣會導致Vsync信號的接收者不停的接收到繪制、渲染命令,即使它們并不需要,這樣會帶來嚴重的性能損耗,因為進行了很多無用的繪制。所以它被設計設計成能夠喚醒和睡眠的。這使得HWComposer在需要時才產(chǎn)生Vsync信號(比如當屏幕上的內(nèi)容需要改變時),不需要時進入睡眠狀態(tài)(比如當屏幕上的內(nèi)容保持不變時,此時屏幕每次刷新都是顯示緩沖區(qū)里沒發(fā)生變化的內(nèi)容)。

如圖,Vsync的兩個接收者,一個是SurfaceFlinger(負責合成各個Surface),一個是Choreographer(負責控制視圖的繪制)。我們稍后再介紹,現(xiàn)在先知道它們是干什么的就行了。

Vsync offset機制

為了提高效率,盡量減少卡頓,在Android 4.1時引入了Vsync機制,并在隨后的4.4版本中加入Vsync offset偏移機制。

 

圖1. 為4.1時期的Vsync機制。可以看到,當一個Vsync信號到來時,SurfaceFlinger和UI繪制進程會同時啟動,導致它們競爭CPU資源,而CPU分配資源會耗費時間,著降低系統(tǒng)性能。同時當收到一個Vsync信號時,第N幀開始繪制。等再收到一個Vsync信號時,第N幀才被SurfaceFlinger合成。而需要顯示到屏幕上,需要等都第三個Vsync信號。這是比較低效率。于是才有了圖2. 4.4版本加入的Vsync offset機制。

圖2. Google加入Vsync offset機制后,原本的HW_Vsync信號會經(jīng)過DispSync會分成Vsync和SF_Vsync兩個虛擬化的Vsync信號。其中Vsync信號會發(fā)送到Choreographer中,而SF_Vsync會發(fā)送到SurfaceFlinger中。理論上只要phase_app和phase_sf這兩個偏移參數(shù)設置合理,在繪制階段消耗的時間控制好,那么畫面就會像圖2中的前幾幀那樣有序流暢的進行。理想總是美好的。實際上很難一直維持這種有序和流暢,比如frame_3是比較復雜的一幀,它的繪制完成的時間超過了SurfaceFlinger開始合成的時間,所以它必須要等到下一個Vsync信號到來時才能被合成。這樣便造成了一幀的丟失。但即使是這樣,如你所見,加入了Vsync offset機制后,繪制效率還是提高了很多。

從圖中可以看到,Vsync和SF_Vsync的偏移量分別由phase_app和phase_sf控制,這兩個值是可以調(diào)節(jié)的,默認為0,可為負值。你只需要找到BoardConfig.mk文件,就可以對這兩個值進行調(diào)節(jié)。

回到ViewRootImpl

前面介紹了幾個關鍵的概念,現(xiàn)在我們回到ViewRootImpl中去,在圖中找到ViewRootImpl的對應位置。

前面說過,ViewRootImpl控制著一個Window中的整個視圖樹的繪制。那它是如何進行控制的呢?一次繪制究竟是如何開始的呢?

 

[[192717]] 

在ViewRootImpl創(chuàng)建的時候,會獲取到前面提到過過的一個關鍵對象Choreographer。Choreographer在一個線程中僅存在一個實例,因此在UI線程只有一個Choreographer存在。也就說,通常情況下,它相當于一個應用中的單例。

在ViewRootImpl初始化時,會實現(xiàn)一個Choreographer.FrameCallback(這是一個Choreographer中的內(nèi)部類),并向Choreographer中post。顧名思義,F(xiàn)rameCallback會在每次接收到Vsync信號時被回調(diào)。

Choreographer.java

  1. public interface FrameCallback { 
  2.     public void doFrame(long frameTimeNanos); 
  3.     //一旦注冊到CallbackQueue中,那么 
  4.     //每次Choreographer接收到Vsync信號時都會回調(diào)。 
  5.     }  

FrameCallback一旦被注冊,那么每次收到Vsync信號時它都會被回調(diào)。利用它,我們可以實現(xiàn)會幀率的監(jiān)聽。

ViewRootImpl.java

  1. //這個方法只有在ViewRootImpl初始化時才會被調(diào)用 
  2. private void profileRendering(boolean enabled) { 
  3.     ... 
  4.     mRenderProfiler = new Choreographer.FrameCallback() { 
  5.     @Override 
  6.     public void doFrame(long frameTimeNanos) { 
  7.         ... 
  8.         scheduleTraversals(); 
  9.         //請求一個Vsync信號,后面還會提到這個方法 
  10.         mChoreographer.postFrameCallback(mRenderProfiler); 
  11.         //每次回調(diào)時,重新將FrameCallback post到Choreographer中 
  12.         ... 
  13.     } 
  14.     }; 
  15.     ... 
  16.     mChoreographer.postFrameCallback(mRenderProfiler); 
  17.     //將FrameCallback post到Choreographer中 
  18.     ... 
  19.  

上面代碼出現(xiàn)了一個重要方法scheduleTraversals()。下面我們看看它究竟為何重要。 ViewRootImpl.java

  1. void scheduleTraversals() { 
  2.     ... 
  3.     mChoreographer.postCallback( 
  4.         Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); 
  5.     //向Choreographer中post一個TraversalRunnable 
  6.     //這又是一個十分重要的對象 
  7.     ... 
  8.     }  

可以看出scheduleTraversals()每次調(diào)用時會向Choreographer中post一個TraversalRunnable,它會促使Choreographer去請求一個Vsync信號。所以這個方法的作用就是用來請求一次Vsync信號刷新界面的。事實上,你可以看到,在invalidate()、requestLayout()等操作中,都能夠看到它被調(diào)用。原因就是這些操作需要刷新界面,所以需要請求一個Vsync信號來出發(fā)新界面的繪制。

ViewRootImpl.java

  1. final class TraversalRunnable implements Runnable { 
  2.     @Override 
  3.     public void run() { 
  4.         doTraversal(); 
  5.         //開始遍歷視圖樹,這意味著開始繪制一幀內(nèi)容了 
  6.     } 
  7.  

從圖中可以看到,每當doTraversal()被調(diào)用時,一系列的測量、布局和繪制操作就開始了。在繪制時,會通過Surface來獲取一個Canvas內(nèi)存塊交給DecorView,用于視圖的繪制。整個View視圖的內(nèi)容都是被繪制到這個Canvas中。

Choreographer中的風起云涌

前面反復提到向Choreographer中post回調(diào),那么post過去發(fā)生了些什么呢?從圖中可以看到,所有的post操作最終都進入到postCallbackDelayedInternal()中。

[[192718]] 

Choreographer.java

  1. private void postCallbackDelayedInternal(int callbackType, 
  2.     Object action, Object token, long delayMillis) { 
  3.     ... 
  4.     synchronized (mLock) { 
  5.         final long now = SystemClock.uptimeMillis(); 
  6.         final long dueTime = now + delayMillis; 
  7.         mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
  8.         //將Callback添加到CallbackQueue[]中 
  9.         if (dueTime <= now) { 
  10.             scheduleFrameLocked(now); 
  11.             //如果回調(diào)時間到了,請求一個Vsync信號 
  12.             //在接收到后會調(diào)用doFrame()回調(diào)這個Callback。 
  13.         } else { 
  14.             Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action); 
  15.             msg.arg1 = callbackType; 
  16.             msg.setAsynchronous(true); 
  17.             //異步消息,避免被攔截器攔截 
  18.             mHandler.sendMessageAtTime(msg, dueTime); 
  19.             //如果還沒到回調(diào)的時間,向FrameHandelr中發(fā)送 
  20.             //MSG_DO_SCHEDULE_CALLBACK消息 
  21.         } 
  22.     } 
  23.     ... 
  24.  

上面這段代碼會把post到Choreographer中的Callback添加到Callback[]中,并且當它因該被回調(diào)時,請求一個Vsync信號,在接收到下一個Vsync信號時回調(diào)這個Callback。如果沒有到回調(diào)的時間,則向FrameHandler中發(fā)送一個MSG_DO_SCHEDULE_CALLBACK消息,但最終還是會請求一個Vsync信號,然后回調(diào)這個Callback。

簡單提一下CallbackQueue:簡單說一下CallbackQueue。它和MessageQueue差不多,都是單鏈表結(jié)構(gòu)。在我的這篇【驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀http://www.jianshu.com/p/8862bd2b6a29】文章中,你能夠看到更多關于MessageQueue和Handler機制的內(nèi)容。不同的是它同時還是一個一維數(shù)組,下標表示Callback類型。事實上,算上每種類型的單鏈表結(jié)構(gòu),它更像是二維數(shù)組的樣子。簡單點描述,假設有一個MessageQueue[]數(shù)組,里面存了幾個MessageQueue。來看看它的創(chuàng)建你可能就明白,它是在Choreographer初始化時創(chuàng)建的。

  1. private Choreographer(Looper looper) { 
  2.     mCallbackQueues = new CallbackQueue[CALLBACK_LAST + 1]; 
  3.     //CALLBACK_LAST值為3。 
  4.     for (int i = 0; i <= CALLBACK_LAST; i++) { 
  5.         mCallbackQueues[i] = new CallbackQueue(); 
  6.     } 
  7.  

現(xiàn)在來看看前面代碼中調(diào)用的scheduleFrameLocked()是如何請求一個Vsync信號的。

  1. private void scheduleFrameLocked(long now) { 
  2.     ... 
  3.     //先判斷當前是不是在UI線程 
  4.     if (isRunningOnLooperThreadLocked()) { 
  5.         scheduleVsyncLocked(); 
  6.         //是UI線程就請求一個Vsync信號 
  7.     } else { 
  8.         Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC); 
  9.         msg.setAsynchronous(true); 
  10.         mHandler.sendMessageAtFrontOfQueue(msg); 
  11.         //不在UI線程向FrameHandler發(fā)送一個MSG_DO_SCHEDULE_VSYNC消息 
  12.         //來請求一個Vsync信號 
  13.     } 
  14.  
  15. private void scheduleVsyncLocked() { 
  16.     mDisplayEventReceiver.scheduleVsync(); 
  17.     //通過DisplayEventReceiver請求一個Vsync信號 
  18.     //這是個恨角色,待會兒會聊聊它。 
  19.     //MSG_DO_SCHEDULE_VSYNC消息也是通過調(diào)用這個方法請求Vsync信號的。 
  20.  

上面我們提到過,Choreographer在一個線程中只有一個。所以,如果在其它線程,需要通過Handler來切換到UI線程,然后再請求Vsync信號。

下面看看剛剛出場的mDisplayEventReceiver是個什么鬼?

[[192719]] 

 

  1. private final class FrameDisplayEventReceiver extends DisplayEventReceiver 
  2.     implements Runnable { 
  3.  
  4.     //這個方法用于接收Vsync信號 
  5.     public void onVsync(){ 
  6.         ... 
  7.         Message msg = Message.obtain(mHandler, this); 
  8.         msg.setAsynchronous(true); 
  9.         mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); 
  10.         //這里并沒有設置消息的類型 
  11.         //其實就是默認為0,即MSG_DO_FRAME類型的消息 
  12.         //它其實就是通知Choreographer開始回調(diào)CallbackQueue[]中的Callback了 
  13.         //也就是開始繪制下一幀的內(nèi)容了 
  14.     } 
  15.  
  16.     //這個方法是在父類中的,寫在這方便看 
  17.     public void scheduleVsync() { 
  18.         ... 
  19.         nativeScheduleVsync(mReceiverPtr); 
  20.         //請求一個Vsync信號 
  21.     } 
  22.  

這給類功能比較明確,而且很重要! 

[[192720]] 

上面一直在說向FrameHandler中發(fā)消息,搞得神神秘秘的。接下來就來看看FrameHandler本尊。請在圖中找到對應位置哦。

  1. private final class FrameHandler extends Handler { 
  2.     public FrameHandler(Looper looper) { 
  3.         super(looper); 
  4.     } 
  5.  
  6.     @Override 
  7.     public void handleMessage(Message msg) { 
  8.         switch (msg.what) { 
  9.             case MSG_DO_FRAME: 
  10.                 //開始回調(diào)Callback,以開始繪制下一幀內(nèi)容 
  11.                 doFrame(System.nanoTime(), 0); 
  12.                 break; 
  13.             case MSG_DO_SCHEDULE_VSYNC: 
  14.                 //請求一個Vsync信號 
  15.                 doScheduleVsync(); 
  16.                 break; 
  17.             case MSG_DO_SCHEDULE_CALLBACK: 
  18.                 //實際也是請求一個Vsync信號 
  19.                 doScheduleCallback(msg.arg1); 
  20.                 break; 
  21.             } 
  22.         } 
  23.     }  

FrameHandler主要在UI線程處理3種類型的消息。

  • MSG_DO_FRAME:值為0。當接收到一個Vsync信號時會發(fā)送該種類型的消息,然后開始回調(diào)CallbackQueue[]中的Callback。比如上面說過,在ViewRootImpl有兩個重要的Callback,F(xiàn)rameCallback(請求Vsync并再次注冊回調(diào))和TraversalRunnable(執(zhí)行doTraversal()開始繪制界面)頻繁被注冊。
  • MSG_DO_SCHEDULE_VSYNC:值為1。當需要請求一個Vsync消息(即屏幕上的內(nèi)容需要更新時)會發(fā)送這個消息。接收到Vsync后,同上一步。
  • MSG_DO_SCHEDULE_CALLBACK:值為2。請求回調(diào)一個Callback。實際上會先請求一個Vsync信號,然后再發(fā)送MSG_DO_FRAME消息,然后再回調(diào)。

FrameHandler并不復雜,但在UI的繪制過程中具有重要的作用,所以一定要結(jié)合圖梳理下這個流程。

SurfaceFlinger和Surface簡單說

在介紹Vsync的時候,我們可能已經(jīng)看到了,現(xiàn)在Android系統(tǒng)會將HW_VSYNC虛擬化為兩個Vsync信號。一個是VSYNC,被發(fā)送給上面一直在講的Choreographer,用于觸發(fā)視圖樹的繪制渲染。另一個是SF_VSYNC,被發(fā)送給我接下來要講的SurfaceFlinger,用于觸發(fā)Surface的合成,即各個Window窗口畫面的合成。接下來我們就簡單的看下SurfaceFlinger和Surface。由于這部分基本是c++編寫的,我著重講原理。

隱藏在背后的Surface

平時同學們都知道,我們的視圖需要被繪制。那么它們被繪制到那了呢?也許很多童鞋腦海里立即浮現(xiàn)出一個詞:Canvas。但是,~沒錯!就是繪制到了Canvas上。那么Canvas又是怎么來的呢?是的,它可以New出來的。但是前面提到過,我們Window中的視圖樹都是被繪制到一個由Surface提供的Canvas上。忘了的童鞋面壁思過😄。 

[[192721]] 

Canvas實際代表了一塊內(nèi)存,用于儲存繪制出來的數(shù)據(jù)。在Canvas的構(gòu)造器中你可以看到:

  1. public Canvas() { 
  2.     ... 
  3.     mNativeCanvasWrapper = initRaster(null); 
  4.     //申請一塊內(nèi)存,并且返回該內(nèi)存的一個long類型的標記或者索引。 
  5.     ... 
  6.  

可以看到,Canvas實際主要就是持有了一塊用于繪制的內(nèi)存塊的索引long mNativeCanvasWrapper。每次繪制時就通過這個索引找到對應的內(nèi)存塊,然后將數(shù)據(jù)繪制到內(nèi)存中。比如:

  1. public void drawRect(@NonNull RectF rect, @NonNull Paint paint) { 
  2.     native_drawRect(mNativeCanvasWrapper, 
  3.         rect.left, rect.top, rect.right, rect.bottom, paint.getNativeInstance()); 
  4.     //在mNativeCanvasWrapper標記的內(nèi)存中繪制一個矩形。 
  5.     }  

簡單的說一下。Android繪制圖形是通過圖形庫Skia(主要針對2D)或OpenGL(主要針對3D)進行。圖形庫是個什么概念?就好比你在PC上用畫板畫圖,此時畫板就相當于Android中的圖形庫,它提供了一系列標準化的工具供我們畫圖使用。比如我們drawRect()實際就是操作圖形庫在內(nèi)存上寫入了一個矩形的數(shù)據(jù)。

扯多了,我們繼續(xù)回到Surface上。當ViewRootImpl執(zhí)行到draw()方法(即開始繪制圖形數(shù)據(jù)了),會根據(jù)是否開啟了硬件(從Android 4.0開始默認是開啟的)加速來決定是使用CPU軟繪制還是使用GPU硬繪制。如果使用軟繪制,圖形數(shù)據(jù)會繪制在Surface默認的CompatibleCanvas上(和普通Canvas的唯一區(qū)別就是對Matrix進行了處理,提高在不同設備上的兼容性)。如果使用了硬繪制,圖形數(shù)據(jù)會被繪制在DisplayListCanvas上。DisplayListCanvas會通過GPU使用openGL圖形庫進行繪制,因此具有更高的效率。

前面也簡單說了一下,每一個Window都會有一個自己的Surface,也就是說一個應用程序中會存在多個Surface。通過上面的講解,童鞋們也都知道了Surface的作用就是管理用于繪制視圖樹的Canvas的。這個Surface是和SurfaceFlinger共享,從它實現(xiàn)了Parcelable接口也可以才想到它會被序列化傳遞。事實上,Surface中的繪制數(shù)據(jù)是通過匿名共享內(nèi)存的方式和SurfaceFlinger共享的,這樣SurfaceFlinger可以根據(jù)不同的Surface,找到它所對應的內(nèi)存區(qū)域中的繪制數(shù)據(jù),然后進行合成。

合成師SurfaceFlinger

SurfaceFlinger是系統(tǒng)的一個服務。前面也一直在提到它專門負責把每個Surface中的內(nèi)容合成緩存,以待顯示到屏幕上。SurfaceFlinger在合成Surface時是根據(jù)Surface的Z-order順序一層一層進行。比如一個Dialog的Surface就會在Activity的Surface上面。然后這個東西不多提了。

終于可以說說你的App為什么這么卡的原因了

通過對Android繪制機制的了解,我們知道造成應用卡頓的根源就在于16ms內(nèi)不能完成繪制渲染合成過程,因為Android平臺的硬件刷新率為60HZ,大概就是16ms刷新一次。如果沒能在16ms內(nèi)完成這個過程,就會使屏幕重復顯示上一幀的內(nèi)容,即造成了卡頓。在這16ms內(nèi),需要完成視圖樹的所有測量、布局、繪制渲染及合成。而我們的優(yōu)化工作主要就是針對這個過程的。

復雜的視圖樹

如果視圖樹復雜,會使整個Traversal過程變長。因此,我們在開發(fā)過程中要控制視圖樹的復雜程度。減少不必要的層級嵌套。比如使用RelativeLayout可以減少復雜布局的嵌套。比如使用【震驚!這個控件絕對值得收藏。輕松實現(xiàn)圓角、文字描邊、狀態(tài)指示等效果http://www.jianshu.com/p/cfe18cbc6924】😄,這個控件可以減少既需要顯示文字,又需要圖片和特殊背景的需求的布局復雜程度,所有的東西由一個控件實現(xiàn)。

頻繁的requestlayout()

如果頻繁的觸發(fā)requestLayout(),就可能會導致在一幀的周期內(nèi),頻繁的發(fā)生布局計算,這也會導致整個Traversal過程變長。有的ViewGroup類型的控件,比如RelativeLayout,在一幀的周期內(nèi)會通過兩次layout()操作來計算確認子View的位置,這種少量的操作并不會引起能夠被注意到的性能問題。但是如果在一幀的周期內(nèi)頻繁的發(fā)生layout()計算,就會導致嚴重的性能,每次計算都是要消耗時間的!而requestLayout()操作,會向ViewRootImpl中一個名為mLayoutRequesters的List集合里添加需要重新Layout的View,這些View將在下一幀中全部重新layout()一遍。通常在一個控件加載之后,如果沒什么變化的話,它不會在每次的刷新中都重新layout()一次,因為這是一個費時的計算過程。所以,如果每一幀都有許多View需要進行l(wèi)ayout()操作,可想而知你的界面將會卡到爆!卡到爆!需要注意,setLayoutParams()最終也會調(diào)用requestLayout(),所以也不能爛用!同學們在寫代碼的過程中一定要謹慎注意那些可能引起requestLayout()的地方啊!

UI線程被阻塞

如果UI線程受到阻塞,顯而易見的是,我們的Traversal過程也將受阻塞!畫面卡頓是妥妥的發(fā)生啊。這就是為什么大家一直在強調(diào)不要在UI線程做耗時操作的原因。通常UI線程的阻塞和以下原因脫不了關系。

在UI線程中進行IO讀寫數(shù)據(jù)的操作。這是一個很費時的過程好嗎?千萬別這么干。如果不想獲得一個卡到爆的App的話,把IO操作統(tǒng)統(tǒng)放到子線程中去。

在UI線程中進行復雜的運算操作。運算本身是一個耗時的操作,當然簡單的運算幾乎瞬間完成,所以不會讓你感受到它在耗時。但是對于十分復雜的運算,對時間的消耗是十分辣眼睛的!如果不想獲得一個卡到爆的App的話,把復雜的運算操作放到子線程中去。

在UI線程中進行復雜的數(shù)據(jù)處理。我說的是比如數(shù)據(jù)的加密、解密、編碼等等。這些操作都需要進行復雜運算,特別是在數(shù)據(jù)比較復雜的時候。如果不想獲得一個卡到爆的App的話,把復雜數(shù)據(jù)的處理工作放到子線程中去。

頻繁的發(fā)生GC,導致UI線程被頻繁中斷。在Java中,發(fā)生GC(垃圾回收)意味著Stop-The-World,就是說其它線程全部會被暫停啊。好可怕!正常的GC導致偶然的畫面卡頓是可以接受的,但是頻繁發(fā)生就讓人很蛋疼了!頻繁GC的罪魁禍首是內(nèi)存抖動,這個時候就需要看下我的這篇【Android內(nèi)存基礎——內(nèi)存抖動http://www.jianshu.com/p/69e6f894c698】文章了。簡單的說就是在短時間內(nèi)頻繁的創(chuàng)建大量對象,導致達到GC的閥值,然后GC就發(fā)生了。如果不想獲得一個卡到爆的App的話,把內(nèi)存的管理做好,即使這是Java。

故意阻塞UI線程。好吧,相信沒人會這么干吧。比如sleep()一下?

總結(jié)

抽出空余時間寫文章分享需要動力,還請各位看官動動小手點個贊,鼓勵下嘍😄

我一直在不定期的創(chuàng)作新的干貨,想要上車只需進到我的個人主頁點個關注就好了哦。發(fā)車嘍~

整篇下來,相信童鞋對Android的繪制機制也有了一個比較全面的了解。現(xiàn)在回過頭來再寫代碼時是不是有種知根知底的自信呢?😄

參考鏈接

  1. Implementing VSYNC:https://source.android.com/devices/graphics/implement-vsync
  2. SurfaceFlinger and Hardware Composer:https://source.android.com/devices/graphics/arch-sf-hwc
  3. Surface and SurfaceHolder:https://source.android.com/devices/graphics/arch-sh
  4. Implementing the Hardware Composer HAL:https://source.android.com/devices/graphics/implement-hwc
  5. 可能是史上最簡單的!一張圖3分鐘讓你明白Activity啟動流程,不看后悔!http://www.jianshu.com/p/9ecea420eb52
  6. 驚天秘密!從Thread開始,揭露Android線程通訊的詭計和主線程的陰謀http://www.jianshu.com/p/8862bd2b6a29
  7. 震驚!這個控件絕對值得收藏。輕松實現(xiàn)圓角、文字描邊、狀態(tài)指示等效果http://www.jianshu.com/p/cfe18cbc6924
  8. Android內(nèi)存基礎——內(nèi)存抖動http://www.jianshu.com/p/69e6f894c698
  9. Android性能優(yōu)化之渲染篇http://hukai.me/android-performance-render/
  10. Android硬件加速原理與實現(xiàn)簡介http://tech.meituan.com/hardware-accelerate.html
  11. Android SurfaceFlinger對VSync信號的處理過程分析http://blog.csdn.net/yangwen123/article/details/17001405
  12. Android Vsync 原理http://www.10tiao.com/html/431/201601/401709603/1.html
  13. Android Choreographer 源碼分析http://www.jianshu.com/p/996bca12eb1d?utm_campaign=hugo&utm_medium=reader_share&utm_content=note
  14. Android應用程序窗口(Activity)的視圖對象(View)的創(chuàng)建過程分析:http://blog.csdn.net/luoshengyang/article/details/8245546
  15. Android 4.4(KitKat)中VSync信號的虛擬化http://blog.csdn.net/jinzhuojun/article/details/17293325
  16. Understanding necessity of Android VSYNC signals:http://stackoverflow.com/questions/27947848/understanding-necessity-of-android-vsync-signals 

 

責任編輯:龐桂玉 來源: CoorChice的博客
相關推薦

2014-01-23 16:27:44

域名解析異常互聯(lián)網(wǎng)癱瘓DNS

2021-04-13 15:51:46

服務治理流量

2022-08-01 10:43:11

RocketMQZookeeper注冊中心

2021-02-27 10:38:56

Python結(jié)構(gòu)數(shù)據(jù)

2012-07-20 17:24:51

HTML5

2013-11-29 10:09:41

物聯(lián)網(wǎng)

2022-09-26 10:43:13

RocketMQ保存消息

2019-05-08 14:24:04

區(qū)塊鏈CosmosPolkadot

2018-05-28 21:17:57

大數(shù)據(jù)分析軟件

2021-04-13 18:16:07

多線程安全代碼

2019-07-16 08:57:15

kafka應用Broker

2012-03-14 20:59:32

iPad

2020-09-09 08:30:42

內(nèi)網(wǎng)隱蔽端口

2013-10-10 17:22:51

開源開源軟件

2015-01-22 11:37:44

Android

2015-11-06 09:41:03

圖標可視化

2021-03-18 14:34:34

達達集團京東云電商

2018-03-07 17:47:16

藍屏計算機死機

2012-07-03 16:56:12

Hadoop

2017-07-18 13:09:20

互聯(lián)網(wǎng)
點贊
收藏

51CTO技術(shù)棧公眾號

97人妻精品一区二区三区| 国产18无套直看片| 天堂√8在线中文| 99在线热播精品免费| 国产精品2018| 唐朝av高清盛宴| 最新精品国偷自产在线| 在线成人免费观看| 成人在线观看你懂的| 国产三级在线免费| 国内精品伊人久久久久av影院| 高清欧美电影在线| 免费一级suv好看的国产网站| 波多野结衣在线一区二区 | 成人高清视频在线观看| 国产精品入口福利| 青青草成人av| 欧美不卡视频| 日韩中文字幕在线视频| 亚洲成人日韩在线| 日本免费一区二区视频| 欧美日韩一区二区电影| 日韩欧美精品在线观看视频| 伊人精品影院| 国产精品不卡在线观看| 麻豆视频成人| 日本激情一区二区| 国产精品一区一区| 国产综合在线观看视频| 无码人妻精品一区二区三区蜜桃91| 国产精品久久久乱弄 | 在线看一级片| 国产精品美女久久久久aⅴ国产馆| 精品一区二区日本| 超碰福利在线观看| 国产精品77777| 成人激情视频免费在线| 中文字幕日产av| 老司机免费视频久久| 51精品在线观看| 国产第一页在线播放| 欧美69wwwcom| 欧美国产日本在线| 日本天堂中文字幕| 午夜精品久久| 欧美大片在线看| a在线视频播放观看免费观看| 日韩a一区二区| 色99之美女主播在线视频| 欧洲美熟女乱又伦| 日韩免费av| 久久视频中文字幕| 国产天堂av在线| 亚洲国产一成人久久精品| 日韩小视频在线观看| frxxee中国xxx麻豆hd| 伊人色**天天综合婷婷| 欧美成人中文字幕| 久久久久久久中文字幕| 伊人久久大香线蕉av超碰演员| 欧美激情一区二区久久久| 劲爆欧美第一页| 精品91视频| 日本不卡免费高清视频| 精品人妻无码一区二区性色| 天堂成人国产精品一区| 国产精品va在线播放| 亚洲一区二区人妻| 国产一区二区三区在线观看精品 | 日韩一级理论片| 97精品国产99久久久久久免费| 欧美少妇一区二区| 久久艹这里只有精品| 涩爱av色老久久精品偷偷鲁| 亚洲а∨天堂久久精品喷水 | 国产精品久久久久一区| 99久re热视频精品98| 超碰97免费在线| 一本大道久久精品懂色aⅴ| 婷婷激情四射五月天| crdy在线观看欧美| 亚洲福利视频网| 国产一级久久久久毛片精品| 亚欧美无遮挡hd高清在线视频 | 国产a级毛片一区| 看欧美日韩国产| 欧美成人高清在线| 午夜久久久久久| 成人性生生活性生交12| 久久99成人| 日韩成人av网址| 国产日产在线观看| 亚洲精品在线二区| 国产日韩在线免费| 天天干天天草天天射| 国产精品乱人伦| 青青青在线视频播放| 精品三区视频| 亚洲丁香婷深爱综合| 男人的天堂官网| 亚洲韩日在线| 成人在线观看视频网站| 天堂av在线免费观看| 综合久久综合久久| 女性女同性aⅴ免费观女性恋| 亚洲欧美在线人成swag| 日韩成人性视频| 国产一区二区播放| 久久精品99久久久| 欧美精品亚洲| 男人添女人下部高潮视频在线观看| 91高清视频在线| 亚洲欧美日韩色| 99久久.com| 国产精品成人av在线| 蜜桃91麻豆精品一二三区| 国产精品久久久久久久久久免费看| 日韩a∨精品日韩在线观看| 国产午夜久久av| 中文字幕在线成人| 黄色污污视频软件| 99re热这里只有精品视频| 国产树林野战在线播放| 成人国产精品| 亚洲视频一区二区| 9i精品福利一区二区三区| 成人综合婷婷国产精品久久 | 波多野结依一区| 宅男噜噜噜66一区二区66| 日本成人免费视频| 视频在线观看国产精品| 久久久com| 精精国产xxxx视频在线播放| 日韩免费福利电影在线观看| 登山的目的在线| 精品一区二区成人精品| 一本一道久久a久久综合精品| 日韩大尺度黄色| 亚洲精品网站在线播放gif| 成年人午夜视频| 成人激情小说乱人伦| 国产精品无码免费专区午夜| 欧美成人一级| 欧美人与性动交| www.欧美国产| 亚洲一区二区av电影| 日本黄色大片在线观看| 欧美特黄a级高清免费大片a级| 国产精品一区二区三区免费视频| fc2在线中文字幕| 欧美日韩免费高清一区色橹橹| 魔女鞋交玉足榨精调教| 久久人人超碰| 午夜一区二区三视频在线观看| 国产韩日精品| 精品国产一区二区三区久久狼黑人 | 欧美日韩三级一区二区| 国产一级淫片久久久片a级| 美女在线视频一区| 在线观看免费黄色片| 精品中文字幕一区二区三区| 欧美激情在线狂野欧美精品| 蜜臀久久99精品久久久| 色婷婷精品久久二区二区蜜臀av | 四虎永久在线精品免费网址| 精品国产91乱高清在线观看| 国产ts在线播放| 麻豆久久久久久| 日本a在线天堂| 欧美理论电影在线精品| 国产精品成人品| 国产黄网站在线观看| 日韩免费性生活视频播放| 1级黄色大片儿| 国产日韩欧美一区二区三区乱码 | 亚洲精品视频一区| 丰满少妇xbxb毛片日本| 麻豆成人在线| 亚洲黄色网址在线观看| 欧美精品中文字幕亚洲专区| 国产精品久久久久久久久| 成人在线视频亚洲| 亚洲男人的天堂网站| 一级淫片免费看| 午夜精品久久久| 日韩精品久久久久久久的张开腿让| 国产精品亚洲第一| 日本一极黄色片| 欧美91福利在线观看| 欧洲一区二区在线 | 美女免费视频一区二区| 免费看欧美黑人毛片| 狠狠色狠狠色综合婷婷tag| 亚洲字幕在线观看| 久久91导航| 欧美精品www在线观看| 国产一级在线观看| 日韩精品一区二区三区中文精品| 久久久蜜桃一区二区| 亚洲自拍欧美精品| 91导航在线观看| av一本久道久久综合久久鬼色| 亚洲综合婷婷久久| 亚洲综合日本| 日本a在线天堂| 日韩欧美高清| 欧美亚洲丝袜| 另类图片第一页| 亚洲伊人久久综合| 97精品国产99久久久久久免费| 91av在线视频观看| 影音先锋在线播放| 久久久国产一区| 国产区av在线| 日韩精品中文字幕视频在线| 亚洲精品一区二区三区四区| 欧美色综合天天久久综合精品| aaa人片在线| 亚洲在线视频一区| 强制高潮抽搐sm调教高h| 久久丝袜美腿综合| 国产一级伦理片| 国产91丝袜在线观看| 免费在线观看污网站| 日本亚洲一区二区| 国产精品wwwww| 亚洲三级观看| 缅甸午夜性猛交xxxx| 欧美三区不卡| 丰满人妻一区二区三区53号| 婷婷激情综合| 自拍偷拍一区二区三区| 日韩在线视频精品| 亚洲国产另类久久久精品极度| 国产aⅴ精品一区二区三区久久| 黄色小网站91| 老牛影视av一区二区在线观看| 国产富婆一区二区三区 | 欧美videofree性高清杂交| 国产又粗又猛又黄| 欧美老年两性高潮| 国产麻豆91视频| 日韩一级完整毛片| 亚洲av无码乱码国产麻豆| 日韩网站在线看片你懂的| 国内老熟妇对白xxxxhd| 日韩视频在线永久播放| 午夜免费福利视频| 欧美精品一区二区在线观看| 五月婷婷六月丁香综合| 日韩电视剧免费观看网站| 肉丝一区二区| 国产亚洲视频在线| 91在线视频| 久久伊人91精品综合网站| 91香蕉在线观看| 久久男人的天堂| www.8ⅹ8ⅹ羞羞漫画在线看| 欧美中文在线字幕| 91国拍精品国产粉嫩亚洲一区| 国产精品爽黄69| 精品国产亚洲一区二区在线观看 | 亚洲美女性囗交| 国产麻豆精品theporn| av天堂一区二区| 91麻豆高清视频| 美国精品一区二区| 亚洲伦理在线精品| 国产欧美日韩另类| 在线观看三级视频欧美| 国产精品久久久久久久免费看 | 91免费福利视频| 亚洲一区电影| 欧美性大战久久久久| 91精品一区国产高清在线gif| 欧美亚洲黄色片| 久久久久国产精品一区三寸| 亚洲综合欧美激情| 成人一级片网址| 97人妻人人揉人人躁人人| 樱花草国产18久久久久| 日本三级小视频| 欧美人成免费网站| 色噜噜在线播放| 最新国产精品亚洲| 超碰在线最新网址| 国产女同一区二区| 日韩欧美黄色| 中文字幕一区二区三区5566| 精品不卡视频| 日韩在线不卡一区| 99久久免费精品高清特色大片| 久久久久99精品成人| 亚洲va国产天堂va久久en| 中文字幕av影视| 日韩成人在线视频| 性国产高清在线观看| 国产精品美女网站| 国产精品45p| 视频一区二区综合| 亚洲久色影视| www.偷拍.com| 国产欧美精品一区| 草久久免费视频| 日韩亚洲欧美一区| 在线观看的av| 国产91精品最新在线播放| 亚洲视频三区| 国产三级中文字幕| 男人的天堂亚洲一区| 中文精品在线观看| 亚洲一区二区三区国产| 一级二级三级视频| 亚洲无亚洲人成网站77777| 91超碰免费在线| 97在线中文字幕| 外国成人激情视频| 天天干天天爽天天射| 久久久精品人体av艺术| 日韩网红少妇无码视频香港| 日韩情涩欧美日韩视频| 日本高清中文字幕在线| 国产精品久久久91| 亚洲va久久| 国产99久久九九精品无码| 丁香亚洲综合激情啪啪综合| 青青青在线免费观看| 欧美精品aⅴ在线视频| www在线免费观看| 国产成人极品视频| 怕怕欧美视频免费大全| ww国产内射精品后入国产| 成人黄色一级视频| 亚洲国产精品午夜在线观看| 精品国产一区久久| 韩国成人免费视频| 国产成人精品免费视频大全最热 | 91精品视频国产| 中文字幕一区不卡| 91亚洲国产成人精品一区| 中文字幕日韩精品在线| 精品久久福利| 成年人黄色在线观看| 紧缚捆绑精品一区二区| 免费看一级大片| 日韩精品一区二区三区在线播放 | 中国毛片在线观看| 一本色道久久加勒比精品 | 毛片毛片毛片毛片毛片毛片毛片毛片毛片| 91网页版在线| av片免费观看| 最近2019年好看中文字幕视频| ww久久综合久中文字幕| 在线免费观看成人网| 国产综合久久久久影院| 免费在线观看黄色av| 精品电影一区二区三区 | 好吊日精品视频| 亚洲视频在线播放免费| 欧美午夜影院在线视频| 国产三级电影在线观看| 国产在线视频2019最新视频| 欧美 亚欧 日韩视频在线 | 亚洲一区精品在线| 天天操天天干天天舔| 国产精品福利观看| 久久精品一区二区不卡| 麻豆短视频在线观看| 日韩欧美在线免费观看| 国产精品一区在线看| 91老司机在线| 国产精品日韩久久久| 日本精品久久久久中文| 日韩一级大片在线| 亚洲永久av| 在线观看精品视频| 国产成人免费视频网站| 亚洲 欧美 成人| zzjj国产精品一区二区| 欧美理论电影在线精品| 污污的视频免费| 精品久久久久久久久中文字幕 | 亚洲成在人线在线播放| 免费在线黄色网址| 亚洲已满18点击进入在线看片| 亚洲精品护士| 北条麻妃在线观看视频| 亚洲国产日韩一区| 亚洲三级在线| 97超碰青青草| 亚洲婷婷综合色高清在线| 天天干天天做天天操| 成人国产亚洲精品a区天堂华泰| 亚洲国产二区| 中文字幕电影av| 亚洲男人天堂视频| 51亚洲精品| aaa一级黄色片| 欧美天堂在线观看| 欧美videos另类精品|