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

歡迎訪問 生活随笔!

生活随笔

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

windows

decorview、window、dialog关系详解

發布時間:2023/12/20 windows 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 decorview、window、dialog关系详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄介紹

https://segmentfault.com/a/1190000019111141

  • 10.0.0.1 Window是什么?如何通過WindowManager添加Window(代碼實現)?WindowManager的主要功能是什么?
  • 10.0.0.2 Window概念解析?WindowSession的創建過程是怎樣的?WindowSession的作用?Token的使用場景?
  • 10.0.0.3 Activity、View、Window三者之間的關系,Window有哪幾種類型?
  • 10.0.0.5 Activity的啟動過程是怎樣的?Activity創建和Dialog創建過程的異同?
  • 10.0.0.6 如何處理快速連續點擊了多次按鈕時Toast就觸發了多次而關閉不掉?
  • 10.0.0.7 DecorView何時才被WindowManager真正添加到Window中?Window的addView源碼分析?
  • 10.0.0.8 Dialog的Window創建過程?為什么Dialog不能用Application的Context?
  • 10.0.0.9 什么是DecorView?如何獲取到DecorView?DecorView的職責是什么?DecorView如何被加載到Window中?
  • 10.0.1.0 DecorView如何顯示出來,為什么setContentView()設置的界面,為什么在onResume()之后才對用戶可見呢?
  • 10.0.1.1 什么是ViewRoot?ViewRoot屬于View樹的一份子嗎?ViewRoot的工作流程是怎么樣的?
  • 10.0.1.2 吐司為何會出現內存泄漏?在Toast構造方法中創建NT對象是干什么用的?Toast是怎么show出來的?
  • 10.0.1.3 連續吐司是如何確定吐司的先后順序?為什么Toast執行show后過了一會兒就自動銷毀?
  • 10.0.1.4 如何理解普通應用的Toast顯示數量是有限制的?為什么要判斷是否是系統吐司?為何Activity銷毀后Toast仍會顯示?
  • 10.0.1.5 為什么說Toast盡量用全局上下文?說一下Toast的顯示和隱藏重點邏輯,說下你的理解?
  • 10.0.1.6 Toast報錯Unable to add window是什么意思?Toast運行在子線程會問題,在子線程或者service中能運行嗎?
  • 10.0.1.7 為什么建議用DialogFragment替代Dialog?如何定義DialogFragment樣式?使用dialogFragment有何好處?
  • 10.0.1.8 Dialog的Window創建過程是怎樣的?為什么Dialog不能用Application的Context,說一下原因?
  • 10.0.1.9 Dialog和Window有什么關系?Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區別?
  • 10.0.2.0 PopupWindow中不設置為什么必須設置寬高?PopupWindow和Dialog有什么區別?說下創建和銷毀的大概流程?
  • 10.0.2.1 Snackbar與吐司有何區別在哪里?Snackbar控件show時為何從下往上移出來?為什么顯示在最下面?
  • 10.0.2.2 說一下Snackbar和SnackbarManager類的設計有哪些奧妙的地方,如何處理消息的顯示順序?

好消息

  • 博客筆記大匯總【15年10月到至今】,包括Java基礎及深入知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請注明出處,謝謝!
  • 鏈接地址:https://github.com/yangchong2...
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起于忽微,量變引起質變!

彈窗博客筆記匯總

  • 02.Toast源碼深度分析

    • 最簡單的創建,簡單改造避免重復創建,show()方法源碼分析,scheduleTimeoutLocked吐司如何自動銷毀的,TN類中的消息機制是如何執行的,普通應用的Toast顯示數量是有限制的,用代碼解釋為何Activity銷毀后Toast仍會顯示,Toast偶爾報錯Unable to add window是如何產生的,Toast運行在子線程問題,Toast如何添加系統窗口的權限等等
  • 03.DialogFragment源碼分析

    • 最簡單的使用方法,onCreate(@Nullable Bundle savedInstanceState)源碼分析,重點分析彈窗展示和銷毀源碼,使用中show()方法遇到的IllegalStateException分析
  • 04.Dialog源碼分析

    • AlertDialog源碼分析,通過AlertDialog.Builder對象設置屬性,Dialog生命周期,Dialog中show方法展示彈窗分析,Dialog的dismiss銷毀彈窗,Dialog彈窗問題分析等等
  • 05.PopupWindow源碼分析

    • 顯示PopupWindow,注意問題寬和高屬性,showAsDropDown()源碼,dismiss()源碼分析,PopupWindow和Dialog有什么區別?為何彈窗點擊一下就dismiss呢?
  • 06.Snackbar源碼分析

    • 最簡單的創建,Snackbar的make方法源碼分析,Snackbar的show顯示與點擊消失源碼分析,顯示和隱藏中動畫源碼分析,Snackbar的設計思路,為什么Snackbar總是顯示在最下面
  • 07.彈窗常見問題

    • DialogFragment使用中show()方法遇到的IllegalStateException,什么常見產生的?Toast偶爾報錯Unable to add window,Toast運行在子線程導致崩潰如何解決?
  • 08.Builder模式

    • 你會發現,在這個彈窗封裝庫中,很多地方用到了builder模式,那么可以先了解下Builder模式使用場景,簡單案例,Builder模式實際案例Demo展示,看看AlertDialog.Builder源代碼如何實現,為什么AlertDialog要使用builder模式呢?builder模式優缺點分析。

10.0.0.1 Window是什么?如何通過WindowManager添加Window(代碼實現)?WindowManager的主要功能是什么?

  • Window是什么?

    • 表示一個窗口的概念,是所有View的直接管理者,任何視圖都通過Window呈現(點擊事件由Window->DecorView->View; Activity的setContentView底層通過Window完成)
    • Window是一個抽象類,具體實現是PhoneWindow
    • 創建Window需要通過WindowManager創建
    • WindowManager是外界訪問Window的入口
    • Window具體實現位于WindowManagerService中
    • WindowManager和WindowManagerService的交互是通過IPC完成
  • 如何通過WindowManager添加Window(代碼實現)?

    • 如下所示

      //1. 控件 Button button = new Button(this); button.setText("Window Button"); //2. 布局參數 WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT); layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; layoutParams.gravity = Gravity.LEFT | Gravity.TOP; layoutParams.x = 100; layoutParams.y = 300; // 必須要有type不然會異常: the specified window type 0 is not valid layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; //3. 獲取WindowManager并添加控件到Window中 WindowManager windowManager = getWindowManager(); windowManager.addView(button, layoutParams);
  • WindowManager的主要功能是什么?

    • 添加、更新、刪除View

      public interface ViewManager{ public void addView(View view, ViewGroup.LayoutParams params); //添加View public void updateViewLayout(View view, ViewGroup.LayoutParams params); //更新View public void removeView(View view); //刪除View }

10.0.0.2 Window概念解析?WindowSession的創建過程是怎樣的?WindowSession的作用?Token的使用場景?

  • Window概念解析?

    • Window和View通過ViewRootImpl建立聯系
    • Window并不是實際存在的,而是以View的形式存在
    • WindowManager的三個接口方法也是針對View的
    • 實際使用中無法直接訪問Window,必須通過WindowManager
    • View是視圖的呈現方式,但是不能單獨存在,必須依附在Window這個抽象的概念上
    • WMS把所有的用戶消息發給View/ViewGroup,但是在View/ViewGroup處理消息的過程中,有一些操作是公共的, Window把這些公共行為抽象出來, 這就是Window。
  • WindowSession的創建過程是怎樣的?

    • 在WindowManager的addView中會創建ViewRootImpl,內部會通過WMS去獲取WindowSession
    • WindowSession的類型是IWindowSession,本身是Binder對象,真正實現類是Session
    • ?
  • WindowSession的作用?博客

    • 表示一個Active Client Session
    • 每個進程一般都有一個Session對象
    • 用于WindowManager交互
  • Token的使用場景?

    • Popupwindow的showAtLocation第一個參數需要傳入View,這個View就是用來獲取Token的。
    • Android 5.0新增空間SnackBar同理也需要一個View來獲取Token
  • Token是什么?

    • 類型為IBinder,是一個Binder對象。
    • 主要分兩種Token:

      • 指向Window的token: 主要是實現WmS和應用所在進程通信。
      • 指向ActivityRecord的token: 主要是實現WmS和AmS通信的。
  • Activity中的Token

    • ActivityRecord是AmS中用來保存一個Activity信息的輔助類。
    • AMS中需要根據Token去找到對應的ActivityRecord。

10.0.0.3 Activity、View、Window三者之間的關系,Window有哪幾種類型?

  • Activity、View、Window三者之間的關系

    • 在Activity啟動過程其中的attach()方法中初始化了PhoneWindow,而PhoneWindow是Window的唯一實現類,然后Activity通過setContentView將View設置到了PhoneWindow上,而View通過WindowManager的addView()、removeView()、updateViewLayout()對View進行管理。
  • Window有哪幾種類型

    • 應用Window:對應一個Activity。
    • 子Window:不能單獨存在,需附屬特定的父Window。如Dialog。
    • 系統Window: 需申明權限才能創建。如Toast。
  • Activity 與 PhoneWindow 與 DecorView 關系圖

    • ?

10.0.0.5 Activity的啟動過程是怎樣的?Activity的視圖加載的源碼分析?Activity創建和Dialog創建過程的異同?

  • Activity的啟動過程是怎樣的?

    • 最終會由ActivityThread中的performLauchActivity來完成整個啟動過程
    • performLauchActivity內部會通過類加載器創建Activity的實例對象
    • 并為Activity的實例對象調用attach方法,為其關聯運行過程中所以來的上下文環境變量
    • attch方法中,系統會創建Activity所屬的Window對象,并為其設置回調接口
    • Window對象的創建是通過PolicyManager的makeNewWindow方法實現。博客
    • Activity實現了window的callback接口,因此外界狀態改變時會回調Activity的方法(onAttachedToWindow、dispatchTouchEvent等等)
  • Activity的視圖加載的源碼分析

    • ?
  • Dialog的Window創建過程

    • 創建WindowDialog。和Activity類似,同樣是通過PolicyManager.makeNewWindow() 來實現。
    • 初始化DecorView并將Dialog的視圖添加到DecorView中去。和Activity類似,同樣是通過Window.setContentView() 來實現。
    • 將DecorView添加到Window中顯示。和Activity一樣,都是在自身要出現在前臺時才會將添加Window。

      • Dialog.show() 方法:完成DecorView的顯示。
      • WindowManager.remoteViewImmediate() 方法:當Dialog被dismiss時移除DecorView。

10.0.0.6 如何處理快速連續點擊了多次按鈕時Toast就觸發了多次而關閉不掉?

  • 使用中遇到的問題

    • 例如:當點擊有些按鈕,需要吐司進行提示時;快速連續點擊了多次按鈕,Toast就觸發了多次。可能導致Toast就長時間關閉不掉了。又或者我們其實已在進行其他操作了,應該彈出新的Toast提示,而上一個Toast卻還沒顯示結束。博客
  • 解決的辦法

    創建工具類: /** * 吐司工具類 避免點擊多次導致吐司多次,最后導致Toast就長時間關閉不掉了 * @param context
*/ private static Toast toast; public static void showToast(Context context, String content) {if (toast == null) {toast = Toast.makeText(context.getApplicationContext(), content, Toast.LENGTH_SHORT);} else {toast.setText(content);}toast.show(); } ```
  • 這樣用的原理

    • 先判斷Toast對象是否為空,如果是空的情況下才會調用makeText()方法來去生成一個Toast對象,否則就直接調用setText()方法來設置顯示的內容,最后再調用show()方法將Toast顯示出來。由于不會每次調用的時候都生成新的Toast對象,因此剛才我們遇到的問題在這里就不會出現

10.0.0.7 DecorView何時才被WindowManager真正添加到Window中?Window的addView源碼分析?

  • DecorView何時才被WindowManager真正添加到Window中?

    • 即使Activity的布局已經成功添加到DecorView中,DecorView此時還沒有添加到Window中
    • ActivityThread的handleResumeActivity方法中,首先會調用Activity的onResume方法,接著調用Activity的makeVisible()方法
    • makeVisible()中完成了DecorView的添加和顯示兩個過程
    • ?
  • Window的addView源碼分析?

    • WindowManager是一個接口,真正實現類是WindowManagerImpl,并最終以代理模式交給WindowManagerGlobal實現。
    • addView: 1-創建ViewRootImpl;2-將ViewRoor、DecorView、布局參數保存到WM的內部列表中;3-ViewRoot.setView()建立ViewRoot和DecorView的聯系。
    • setView:1-進行View繪制三大流程;2-會通過WindowSession完成Window的添加過程(一次IPC調用)
    • requestLayout:內部調用scheduleTraversals(), 底層通過mChoreographer去監聽下一幀的刷新信號。
    • mWindowSession.addToDisplay: 執行WindowManangerService的addWindow
    • addWindow: 檢查參數等設置;檢查Token;將Token、Window保存到WMS中;將WindowState保存到Session中。
  • Window的remove源碼與解析

    • WindowManager中提供了兩種刪除接口:removeView異步刪除、removeViewImmediate同步刪除(不建議使用)
    • 調用WMGlobal的removeView
    • 調用到WMGlobal的removeViewLocked進行真正的移除
    • 執行ViewRoot的die方法(): 1-同步方法直接調用doDie 2-異步方法直接發送Message
    • doDie(): 調用dispatchDetachedFromWindow()和WindowManagerGlobal.getInstance().doRemoveView(this)
    • dispatchDetachedFromWindow:博客

      • 1回調onDetachedFromeWindow;
      • 2垃圾回收相關操作;
      • 3通過Session的remove()在WMS中刪除Window;
      • 4通過Choreographer移除監聽器

10.0.0.8 Dialog的Window創建過程?為什么Dialog不能用Application的Context?

  • Dialog的Window創建過程?

    • 創建Window——同樣是通過PolicyManager的makeNewWindow方法完成,與Activity創建過程一致
    • 初始化DecorView并將Dialog的視圖添加到DecorView中——和Activity一致(setContentView)
    • 將DecorView添加到Window中并顯示——在Dialog的show方法中,通過WindowManager將DecorView添加到Window中(mWindowManager.addView(mDecor, 1))
    • Dialog關閉時會通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor)
    • Dialog必須采用Activity的Context,因為有應用token(Application的Context沒有應用token),也可以將Dialog的Window通過type設置為系統Window就不再需要token。
  • 為什么Dialog不能用Application的Context?

    • Dialog本身的Token為null,在初始化時如果是使用Application或者Service的Context,在獲取到WindowManager時,獲取到的token依然是null。
    • Dialog如果采用Activity的Context,獲取到的WindowManager是在activity.attach()方法中創建,token指向了activity的token。
    • 因為通過Application和Service的Context將無法獲取到Token從而導致失敗。

10.0.0.9 什么是DecorView?如何獲取到DecorView?DecorView的職責是什么?DecorView如何被加載到Window中?

  • 什么是DecorView

    • DecorView是FrameLayout的子類,它可以被認為是Android視圖樹的根節點視圖。
    • DecorView作為頂級View,一般情況下它內部包含一個豎直方向的LinearLayout,在這個LinearLayout里面有上下三個部分,上面是個ViewStub,延遲加載的視圖(應該是設置ActionBar,根據Theme設置),中間的是標題欄(根據Theme設置,有的布局沒有),下面的是內容欄。
  • 如何獲取到DecorView

    • 在Activity中通過setContentView所設置的布局文件其實就是被加到內容欄之中的,成為其唯一子View,就是上面的id為content的FrameLayout中,在代碼中可以通過content來得到對應加載的布局。
    ViewGroup content = (ViewGroup)findViewById(android.R.id.content); ViewGroup rootView = (ViewGroup) content.getChildAt(0);
  • DecorView的職責是什么

    • 通過源碼了解可以知道,Activity就像個控制器,不負責視圖部分。Window像個承載器,裝著內部視圖。DecorView就是個頂層視圖,是所有View的最外層布局。ViewRoot像個連接器,負責溝通,通過硬件的感知來通知視圖,進行用戶之間的交互。
  • DecorView如何被加載到Window中?博客

    • 從Activity中的setContentView()開始。在Activity中的attach()方法中,生成了PhoneWindow實例。既然有了Window對象,那么我們就可以**設置DecorView給Window對象了。
    • 從中獲取mContentParent。獲得到之后,然后通過installDecor方法,然后生成DecorView,不過這里操作很復雜,大概流程先從主題中獲取樣式,然后根據樣式,加載對應的布局到DecorView中,為mContentParent添加View,即Activity中的布局。
    • 具體可以看這篇文章:10.DecorView介紹

10.0.1.0 DecorView如何顯示出來,為什么setContentView()設置的界面,為什么在onResume()之后才對用戶可見呢?

  • 通過setContentView()設置的界面,為什么在onResume()之后才對用戶可見呢?這就要從ActivityThread開始說起。

    private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {//就是在這里調用了Activity.attach()呀,接著調用了Activity.onCreate()和Activity.onStart()生命周期,//但是由于只是初始化了mDecor,添加了布局文件,還沒有把//mDecor添加到負責UI顯示的PhoneWindow中,所以這時候對用戶來說,是不可見的Activity a = performLaunchActivity(r, customIntent);......if (a != null) {//這里面執行了Activity.onResume()handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);if (!r.activity.mFinished && r.startsNotResumed) {try {r.activity.mCalled = false;//執行Activity.onPause()mInstrumentation.callActivityOnPause(r.activity);}}} }
  • 重點看下handleResumeActivity(),在這其中,DecorView將會顯示出來,同時重要的一個角色:ViewRoot也將登場。

    • 這個方法里面會調用performResumeActivity方法,這個時候,Activity.onResume()已經調用了,但是現在界面還是不可見的
    • 接著講decorView添加進WindowManager了,但是這個時候,還是不可見的
    • 最后執行makeVisible,執行了重要的操作,使得DecorView可見
  • 當我們執行了Activity.makeVisible()方法之后,界面才對我們是可見的。博客

    void makeVisible() {if (!mWindowAdded) {ViewManager wm = getWindowManager();wm.addView(mDecor, getWindow().getAttributes());//將DecorView添加到WindowManagermWindowAdded = true;}mDecor.setVisibility(View.VISIBLE);//DecorView可見 }
    • 到此DecorView便可見,顯示在屏幕中。但是在這其中,wm.addView(mDecor, getWindow().getAttributes());起到了重要的作用,因為其內部創建了一個ViewRootImpl對象,負責繪制顯示各個子View。
  • 最后通過WindowManagerImpl的addView方法將DecorView加載出來

    • 看到其中實例化了ViewRootImpl對象,然后調用其setView()方法。其中setView()方法經過一些列折騰,最終調用了performTraversals()方法,然后依照下圖流程層層調用,完成繪制,最終界面才顯示出來。
    • 具體更加詳細的過程,可以看10.DecorView介紹

10.0.1.1 什么是ViewRoot?ViewRoot屬于View樹的一份子嗎?ViewRoot的工作流程是怎么樣的?

  • 什么是ViewRoot

    • ViewRoot可能比較陌生,但是其作用非常重大。所有View的繪制以及事件分發等交互都是通過它來執行或傳遞的。
    • ViewRoot對應ViewRootImpl類,它是連接WindowManagerService和DecorView的紐帶,View的三大流程(測量(measure),布局(layout),繪制(draw))均通過ViewRoot來完成。
  • ViewRoot屬于View樹的一份子嗎?

    • ViewRoot并不屬于View樹的一份子。
    • 從源碼實現上來看,它既非View的子類,也非View的父類,但是,它實現了ViewParent接口,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類,可以接收事件并分發,Android的所有觸屏事件、按鍵事件、界面刷新等事件都是通過ViewRoot進行分發的。博客
  • 下面結構圖可以清晰的揭示四者之間的關系:

10.0.1.2 吐司為何會出現內存泄漏?在Toast構造方法中創建NT對象是干什么用的?Toast是怎么show出來的?

  • 吐司為何會出現內存泄漏

    • 原因在于:如果在 Toast 消失之前,Toast 持有了當前 Activity,而此時,用戶點擊了返回鍵,導致 Activity 無法被 GC 銷毀, 這個 Activity 就引起了內存泄露。
  • 在Toast構造方法中創建NT對象是干什么用的?

    • TN是屬于Toast內部一個私有靜態類,它是通過aidl進行通信,主要作用是實現吐司的show和hide功能。
    • 在構造方法中,創建了NT對象,那么有人便會問,NT是什么東西呢?看看NT的源碼,可以發現NT實現了ITransientNotification.Stub,提到這個感覺是不是很熟悉,沒錯,在aidl中就會用到這個。

      public Toast(Context context) {mContext = context;mTN = new TN();mTN.mY = context.getResources().getDimensionPixelSize(com.android.internal.R.dimen.toast_y_offset);mTN.mGravity = context.getResources().getInteger(com.android.internal.R.integer.config_toastDefaultGravity); }
      • ?
    • 在TN類中,可以看到,實現了AIDL的show與hide方法

      • TN是Toast內部的一個私有靜態類,繼承自ITransientNotification.Stub,ITransientNotification.Stub是出現在服務端實現的Service中,就是一個Binder對象,也就是對一個aidl文件的實現而已
      @Override public void show(IBinder windowToken) {if (localLOGV) Log.v(TAG, "SHOW: " + this);mHandler.obtainMessage(0, windowToken).sendToTarget(); }@Override public void hide() {if (localLOGV) Log.v(TAG, "HIDE: " + this);mHandler.post(mHide); }
    • 接著看下這個ITransientNotification.aidl文件

      /** @hide */ oneway interface ITransientNotification {void show();void hide(); }
  • Toast是怎么show出來的?

    • 通過AIDL(Binder)通信拿到NotificationManagerService的服務訪問接口,然后把TN對象和一些參數傳遞到遠程NotificationManagerService中去
    • 當 Toast在show的時候,然后把這個請求放在 NotificationManager 所管理的隊列中,并且為了保證 NotificationManager 能跟進程交互,會傳遞一個TN類型的Binder對象給NotificationManager系統服
    • 然后通過service.enqueueToast方法,record是將Toast封裝成ToastRecord對象,放入mToastQueue中。通過下面代碼可以得知:通過isSystemToast判斷是否為系統Toast。如果當前Toast所屬的進程的包名為“android”,則為系統Toast。如果是系統Toast一定可以進入到系統Toast隊列中,不會被黑名單阻止。

10.0.1.3 連續吐司是如何確定吐司的先后順序?為什么Toast執行show后過了一會兒就自動銷毀?

  • 連續吐司是如何確定吐司的先后順序?

    • 主要是說一下showNextToastLocked()方法中的源代碼

      • 首先獲取吐司消息隊列中第一個ToastRecord對象,然后判斷該對象如果不為null的話,就開始通過callback進行show,且傳遞了token參數,注意這個show是通知進程顯示。然后再調用scheduleTimeoutLocked(record)方法執行超時后自動取消的邏輯。同時需要注意的時,如果出現了異常,則會從吐司消息隊列中移除該record……
      • 那么callback是干嘛的呢,一般印象中callback是處理回調的?從ITransientNotification callback得知,這個callback哥們竟然是是一個 ITransientNotification 類型的對象,也就是前面說到的TN的Binder代理對象。
    • 簡而言之,也就是說,TN中的消息機制也是通過handler進行實現的。在show方法中發送消息,當mHandler接受到消息之后,就調用handleShow(token)處理邏輯,通過WindowManager將view添加進來,同時在該方法中也設置了大量的布局屬性。
  • 為什么Toast執行show后過了一會兒就自動銷毀?博客

    • 回調了Toast的TN的show,當timeout可能就是hide呢。分析NotificationManagerService源碼中的showNextToastLocked()的scheduleTimeoutLocked(record)源碼,可以知道在NotificationManagerService通過handler延遲delay時間發送消息,然后通過callback調用hide,由于callback是TN中Binder的代理對象, 所以便可以調用到TN中的hide方法達到銷毀吐司的目的。
    • handleHide()源碼如下所示,可知當銷毀后先將view移除,然后在置空操作。
    public void handleHide() {if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);if (mView != null) {// note: checking parent() just to make sure the view has// been added... i have seen cases where we get here when// the view isn't yet added, so let's try not to crash.if (mView.getParent() != null) {if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);mWM.removeViewImmediate(mView);}mView = null;} }

10.0.1.4 如何理解普通應用的Toast顯示數量是有限制的?為什么要判斷是否是系統吐司?為何Activity銷毀后Toast仍會顯示?

  • 如何理解普通應用的Toast顯示數量是有限制的?

    • 如何判斷是否是系統吐司呢?如果當前Toast所屬的進程的包名為“android”,則為系統Toast,或者調用isCallerSystem()方法
    final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
    • 接著看看isCallerSystem()方法源碼,isCallerSystem的源碼也比較簡單,就是判斷當前Toast所屬進程的uid是否為SYSTEM_UID、0、PHONE_UID中的一個,如果是,則為系統Toast;如果不是,則不為系統Toast。
    private static boolean isUidSystem(int uid) {final int appid = UserHandle.getAppId(uid);return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0); }private static boolean isCallerSystem() {return isUidSystem(Binder.getCallingUid()); }
  • 為什么要判斷是否是系統吐司?

    • 從源碼可知:首先系統Toast一定可以進入到系統Toast隊列中,不會被黑名單阻止。然后系統Toast在系統Toast隊列中沒有數量限制,而普通pkg所發送的Toast在系統Toast隊列中有數量限制。
    • 那么關于數量限制這個結果從何而來,大概是多少呢?查看將要入隊的Toast是否已經在系統Toast隊列中。這是通過比對pkg和callback來實現的。通過下面源碼分析可知:只要Toast的pkg名稱和tn對象是一致的,則系統把這些Toast認為是同一個Toast。
    • 然后再看看下面這個源碼截圖,可知,非系統Toast,每個pkg在當前mToastQueue中Toast有總數限制,不能超過MAX_PACKAGE_NOTIFICATIONS,也就是50
    • ?
    • ?
  • 為何Activity銷毀后Toast仍會顯示

    • 記得以前昊哥問我,為何toast在activity銷毀后仍然會彈出呢,我毫不思索地說,因為toast是系統級別的呀。那么是如何實現的呢,我就無言以對呢……今天終于可以回答呢!
    • 還是回到NotificationManagerService類中的enqueueToast方法中,直接查看keepProcessAliveIfNeededLocked(callingPid)方法。這段代碼的意思是將當前Toast所在進程設置為前臺進程,這里的mAm = ActivityManager.getService(),調用了setProcessImportant方法將當前pid的進程置為前臺進程,保證不會系統殺死。這也就解釋了為什么當我們finish當前Activity時,Toast還可以顯示,因為當前進程還在執行。
    • ?

10.0.1.5 為什么說Toast盡量用全局上下文?說一下Toast的顯示和隱藏重點邏輯,說下你的理解?

  • 為什么說Toast盡量用全局上下文?

    • 在使用Toast時context參數盡量使用getApplicationContext(),可以有效的防止靜態引用導致的內存泄漏。
    • 有時候我們會發現Toast彈出過多就會延遲顯示,因為上面源碼分析可以看見Toast.makeText是一個靜態工廠方法,每次調用這個方法都會產生一個新的Toast對象,當我們在這個新new的對象上調用show方法就會使這個對象加入到NotificationManagerService管理的mToastQueue消息顯示隊列里排隊等候顯示;所以如果我們不每次都產生一個新的Toast對象(使用單例來處理)就不需要排隊,也就能及時更新呢。
  • 說一下Toast的顯示和隱藏重點邏輯,說下你的理解?博客

    • Toast調用show方法 ,其實就是是將自己納入到NotificationManager的Toast管理中去,期間傳遞了一個本地的TN類型或者是 ITransientNotification.Stub的Binder對象
    • NotificationManager 收到 Toast 的顯示請求后,將生成一個 Binder 對象,將它作為一個窗口的 token 添加到 WMS 對象,并且類型是 TOAST
    • NotificationManager 將這個窗口token通過ITransientNotification的show方法傳遞給遠程的TN對象,并且拋出一個超時監聽消息 scheduleTimeoutLocked
    • TN 對象收到消息以后將往 Handler 對象中 post 顯示消息,然后調用顯示處理函數將 Toast 中的 View 添加到了 WMS 管理中,Toast窗口顯示
    • NotificationManager的WorkerHandler收到MESSAGE_TIMEOUT消息, NotificationManager遠程調用hide方法進程隱藏Toast 窗口,然后將窗口token從WMS中刪除,并且判斷吐司消息隊列中是否還有消息,如果有,則繼續吐司!

10.0.1.6 Toast報錯Unable to add window是什么意思?Toast運行在子線程會問題,在子線程或者service中能運行嗎?

  • Toast偶爾報錯Unable to add window

    • 報錯日志,是不是有點眼熟呀?更多可以看我的開源項目:https://github.com/yangchong211

      android.view.WindowManager$BadTokenExceptionUnable to add window -- token android.os.BinderProxy@7f652b2 is not valid; is your activity running?
    • 查詢報錯日志是從哪里來的

      • ?
    • 發生該異常的原因

      • 這個異常發生在Toast顯示的時候,原因是因為token失效。通常情況下,一般是不會出現這種異常。但是由于在某些情況下, Android進程某個UI線程的某個消息阻塞。導致 TN 的 show 方法 post 出來 0 (顯示) 消息位于該消息之后,遲遲沒有執行。這時候,NotificationManager 的超時檢測結束,刪除了 WMS 服務中的 token 記錄。刪除 token 發生在 Android 進程 show 方法之前。這就導致了上面的異常。
      • 測試代碼。模擬一下異常的發生場景,其實很容易,只需要這樣做就可以出現上面這個問題
      Toast.makeText(this,"瀟湘劍雨-yc",Toast.LENGTH_SHORT).show();try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}
    • 解決辦法,目前見過好幾種,思考一下那種比較好……

      • 第一種,既然是報is your activity running,那可以不可以在吐司之前先判斷一下activity是否running呢?
      • 第二種,拋出異常增加try-catch,代碼如下所示,最后仍然無法解決問題

        • 按照源碼分析,異常是發生在下一個UI線程消息中,因此在上一個ui線程消息中加入try-catch是沒有意義的。而且用到吐司地方這么多,這樣做也不方便啦!
      • 第三種,那就是自定義類似吐司Toast的view控件。個人建議除非要求非常高,不然不要這樣做。畢竟發生這種異常還是比較少見的
    • 哪些情況會發生該問題?

      • UI 線程執行了一條非常耗時的操作,比如加載圖片等等,就類似上面用 sleep 模擬情況
      • 進程退后臺或者息屏了,系統為了減少電量或者某種原因,分配給進程的cpu時間減少,導致進程內的指令并不能被及時執行,這樣一樣會導致進程看起來”卡頓”的現象
      • 當TN拋出消息的時候,前面有大量的 UI 線程消息等待執行,而每個 UI 線程消息雖然并不卡頓,但是總和如果超過了 NotificationManager 的超時時間,還是會出現問題
  • Toast運行在子線程問題

    • 先來看看問題代碼,會出現什么問題呢?
    new Thread(new Runnable() {@Overridepublic void run() {ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");} }).start();
    • 報錯日志如下所示:
    • ?
  • 子線程中吐司的正確做法,代碼如下所示

    new Thread(new Runnable() {@Overridepublic void run() {Looper.prepare();ToastUtils.showRoundRectToast("瀟湘劍雨-楊充");Looper.loop();} }).start();
  • 得出的結論

    • Toast也可以在子線程執行,不過需要手動提供Looper環境的。
    • Toast在調用show方法顯示的時候,內部實現是通過Handler執行的,因此自然是不阻塞Binder線程,另外,如果addView的線程不是Loop線程,執行完就結束了,當然就沒機會執行后續的請求,這個是由Hanlder的構造函數保證的。可以看看handler的構造函數,如果Looper==null就會報錯,而Toast對象在實例化的時候,也會為自己實例化一個Hanlder,這就是為什么說“一定要在主線程”,其實準確的說應該是 “一定要在Looper非空的線程”。博客

10.0.1.7 為什么建議用DialogFragment替代Dialog?如何定義DialogFragment樣式?使用dialogFragment有何好處?

  • 為什么建議用DialogFragment替代Dialog

    • Android比較推薦采用DialogFragment實現對話框,它完全能夠實現Dialog的所有需求,并且還能復用Fragment的生命周期管理,被后臺殺死后,可以恢復重建。
    • 在手機配置變化導致 Activity 需要重新創建時,例如旋轉屏幕,基于 DialogFragment 的對話框將會由 FragmentManager 自動重建,然而基于 Dialog 實現的對話框卻沒有這樣的能力。
  • 如何定義DialogFragment樣式

    @Override public void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (local == BOTTOM) {setStyle(DialogFragment.STYLE_NO_TITLE, R.style.BottomDialog);} else if (local == CENTER || local == TOP) {setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog);} }
  • 創建theme主題樣式,并且進行設置

    • 設置樣式,以DialogFragment為例,只需要在onCreate中setStyle(DialogFragment.STYLE_NO_TITLE, R.style.CenterDialog)即可。
    • 注意,CenterDialog中可以設置彈窗的動畫效果。
    • 注意一下style常量,這里只是展示常用的。

      STYLE_NORMAL:會顯示一個普通的dialog STYLE_NO_TITLE:不帶標題的dialog STYLE_NO_FRAME:無框的dialog STYLE_NO_INPUT:無法輸入內容的dialog,即不接收輸入的焦點,而且觸摸無效。
    • 注意動畫設置如下所示

      <style name="CenterDialog" parent="@android:style/Theme.Dialog"><item name="android:windowTitleStyle">@null</item><item name="android:windowBackground">@android:color/transparent</item><item name="android:colorBackgroundCacheHint">@null</item><item name="android:windowAnimationStyle">@style/CenterDialogAnimationStyle</item><item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item> </style>
  • 使用dialogFragment有何好處?

    • DialogFragment是繼承Fragment,具有Fragment的生命周期,本質上說就是Fragment,只是其內部還有一個dialog而已。你既可以當它是Dialog使用,也可以把它作為Fragment使用。
    • onCreateView可以加載客戶化更高的對話框,onCreateDialog加載系統AlertDialog類型對話框比較合適。
    • DialogFragmnet對話框橫屏時對話框不會關閉,因為DailogFragment有Fragment屬性,會在屏幕發生變化時重新創建DialogFragment。博客
    • setStyle的調用點,要放在onCreateView前,一般是放在onCreat方法中執行,否則,設置的style和theme將不起作用!setStyle中,style的參數是不可以相互一起使用的,只能用一個,如果還不滿足你使用,可以通過設置theme來滿足。

10.0.1.8 Dialog的Window創建過程是怎樣的?為什么Dialog不能用Application的Context,說一下原因?

  • Dialog的Window創建過程是怎樣的?

    • 創建Window——同樣是通過PolicyManager的makeNewWindow方法完成,與Activity創建過程一致
    • 初始化DecorView并將Dialog的視圖添加到DecorView中——和Activity一致(setContentView)
    • 將DecorView添加到Window中并顯示——在Dialog的show方法中,通過WindowManager將DecorView添加到Window中(mWindowManager.addView(mDecor, 1))
    • Dialog關閉時會通過WindowManager來移除DecorView:mWindowManager.removeViewImmediate(mDecor)
    • Dialog必須采用Activity的Context,因為有應用token(Application的Context沒有應用token),也可以將Dialog的Window通過type設置為系統Window就不再需要token。
  • 為什么Dialog不能用Application的Context,說一下原因?

    • Dialog本身的Token為null,在初始化時如果是使用Application或者Service的Context,在獲取到WindowManager時,獲取到的token依然是null。
    • Dialog如果采用Activity的Context,獲取到的WindowManager是在activity.attach()方法中創建,token指向了activity的token。
    • 因為通過Application和Service的Context將無法獲取到Token從而導致失敗。

10.0.1.9 Dialog和Window有什么關系?Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區別?

  • Dialog和Window有什么關系?

    • 看源碼可知在Dialog的構造方法中直接直接構造了一個PhoneWindow,并賦值給Dialog的成員變量mWindow,從這里可以看出其實Dialog和Activity的顯示邏輯都是類似的,都是通過對應的Window變量來實現窗口的加載與顯示的。然后我們執行了一些Window對象的初始化操作,比如設置回調函數為本身,然后調用Window類的setWindowManager方法,并傳入了WindowManager。然后創建一個對話框監聽handler對象。
    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {if (createContextThemeWrapper) {if (themeResId == 0) {final TypedValue outValue = new TypedValue();context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);themeResId = outValue.resourceId;}//創建一個ContextmContext = new ContextThemeWrapper(context, themeResId);} else {mContext = context;}//獲取一個WindowManager對象mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);//創建一個Window對象final Window w = new PhoneWindow(mContext);//將Window對象w賦值給mWindowmWindow = w;//為Windowd對象設置回調,并且它本身實現了這些回調函數w.setCallback(this);w.setOnWindowDismissedCallback(this);//為Window對象設置WindowManager對象w.setWindowManager(mWindowManager, null, null);w.setGravity(Gravity.CENTER);//創建一個對話框監聽HandlermListenersHandler = new ListenersHandler(this); }
  • Dialog的dismiss和cancel()方法都可銷毀彈窗,它們有什么區別?

    • 調用alertDialog.cancel()或者alertDialog.dismiss()都可以達到銷毀彈窗的效果。
    • 如果沒有設置setOnCancelListener事件,則兩個方法是等效的。為什么這樣說呢?
    • 首先看一下Dialog的cannel方法的具體實現:可以看到方法體中,若當前Dialog沒有取消,并且設置了取消message,則調用Message.obtain(mCancelMessage).sendToTarget()。而這個mCancelMessage則是在setOnCancelListener方法中創建的。調用的是設置的OnCancelListener的onCancel方法,也就是說調用dialog.cancel方法時首先會判斷dialog是否調用了setOnCancelListener若設置了,則先調用OnCancelListener的onCancel方法,然后再次執行dismiss方法,若我們沒有為Dialog.Builder設置OnCancelListener那么cancel方法和dismiss方法是等效的。博客
    public void cancel() {if (!mCanceled && mCancelMessage != null) {mCanceled = true;// Obtain a new message so this dialog can be re-usedMessage.obtain(mCancelMessage).sendToTarget();}dismiss(); }public void setOnCancelListener(final OnCancelListener listener) {if (listener != null) {mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener);} else {mCancelMessage = null;} }private static final class ListenersHandler extends Handler {private WeakReference<DialogInterface> mDialog;public ListenersHandler(Dialog dialog) {mDialog = new WeakReference<DialogInterface>(dialog);}@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case DISMISS:((OnDismissListener) msg.obj).onDismiss(mDialog.get());break;case CANCEL:((OnCancelListener) msg.obj).onCancel(mDialog.get());break;case SHOW:((OnShowListener) msg.obj).onShow(mDialog.get());break;}} }
  • dismiss方法主要是做了什么?

    • 可以看到,這里首先判斷當前線程的Looper是否是主線程的Looper(由于mHandler是在主線程中創建的,所以mHandler.getLooper返回的是主線程中創建的Looper對象),若是的話,則直接執行dismissDialog()方法,否則的話,通過mHandler發送異步消息至主線程中,簡單來說就是判斷當前線程是否是主線程,若是主線程則執行dismissDialog方法否則發送異步消息。而這里的異步消息最終也是調用的dismissDialog方法
    • ?
    public void dismiss() {if (Looper.myLooper() == mHandler.getLooper()) {dismissDialog();} else {mHandler.post(mDismissAction);} }

10.0.2.0 PopupWindow中不設置為什么必須設置寬高?PopupWindow和Dialog有什么區別?說下創建和銷毀的大概流程?

  • PopupWindow中不設置為什么必須設置寬高?

    • 先看問題代碼,下面這個不會出現彈窗,思考:為什么?

      PopupWindow popupWindow = new PopupWindow(this); View inflate = LayoutInflater.from(this).inflate(R.layout.view_pop_custom, null); popupWindow.setContentView(inflate); popupWindow.setAnimationStyle(R.style.BottomDialog); popupWindow.showAsDropDown(mTv1);
    • 注意:必須設置寬和高,否則不顯示任何東西

      • 這里的WRAP_CONTENT可以換成fill_parent 也可以是具體的數值,它是指PopupWindow的大小,也就是contentview的大小,注意popupwindow根據這個大小顯示你的View,如果你的View本身是從xml得到的,那么xml的第一層view的大小屬性將被忽略。相當于popupWindow的width和height屬性直接和第一層View相對應。
  • PopupWindow和Dialog有什么區別?

    • 兩者最根本的區別在于有沒有新建一個window,PopupWindow沒有新建,而是將view加到DecorView;Dialog是新建了一個window,相當于走了一遍Activity中創建window的流程
    • 從源碼中可以看出,PopupWindow最終是執行了mWindowManager.addView方法,全程沒有新建window
  • 說下創建和銷毀的大概流程?

    • 創建PopupWindow的時候,先創建WindowManager,因為WIndowManager擁有控制view的添加和刪除、修改的能力。這一點關于任主席的藝術探索書上寫的很詳細……博客
    • 然后是setContentView,保存contentView,這個步驟就做了這個
    • 顯示PopupWindow,這個步驟稍微復雜點,創建并初始化LayoutParams,設置相關參數,作為以后PopupWindow在應用DecorView里哪里顯示的憑據。然后創建PopupView,并且將contentView插入其中。最后使用WindowManager將PopupView添加到應用DecorView里。
    • 銷毀PopupView,WindowManager把PopupView移除,PopupView再把contentView移除,最后把對象置為null
  • 為何彈窗點擊一下就dismiss呢?

    • PopupWindow通過為傳入的View添加一層包裹的布局,并重寫該布局的點擊事件,實現點擊PopupWindow之外的區域PopupWindow消失的效果

10.0.2.1 Snackbar與吐司有何區別在哪里?Snackbar控件show時為何從下往上移出來?為什么顯示在最下面?

  • Snackbar與吐司有何區別

    • 與Toast進行比較,SnackBar有優勢:
    • 1.SnackBar可以自動消失,也可以手動取消(側滑取消,但是需要在特殊的布局中,后面會仔細說)
    • 2.SnackBar可以通過setAction()來與用戶進行交互
    • 3.通過CallBack我們可以獲取SnackBar的狀態
    • ?
  • Snackbar控件show時為何從下往上移出來?

    • 至于說Snackbar控件show時為何從下往上移出來,看下面這段代碼就知道呢,如下所示
    • ?
  • 為什么顯示在最下面?

    • 直接找到make方法中的填充布局,然后去看design_layout_snackbar_include的布局參數,結果如下:
    • ?
  • Snackbar顯示會導致FloatingActionButton上移?

    • 為什么CoordinatorLayout + FloatingActionButton,當Snackbar顯示的時候FloatingActionButton會上移呢,這個是怎么實現的?
    • 把CoordinatorLayout替換成FrameLayout確不行。這個問題我們還沒說。其實這個不是在Snackbar里面處理的,是通過CoordinatorLayout和Behavior來處理的。那具體的處理在哪里呢。FloatingActionButton類里面Behavior類。正是Behavior里面的兩個函數layoutDependsOn()和onDependentViewChanged()函數作用的結果。直接進去看下FloatingActionButton內部類Behavior里面這兩個函數的代碼。

10.0.2.2 說一下Snackbar和SnackbarManager類的設計有哪些奧妙的地方,如何處理消息的顯示順序?

  • Snackbar和SnackbarManager,SnackbarManager內部有兩個SnackbarRecord,一個mCurrentSnackbar,一個mNextSnackbar,SnackbarManager通過這兩個對象實現Snackbar的順序顯示,如果在一個Snackbar顯示之前有Snackbar正在顯示,那么使用mNextSnackbar保存第二個Snackbar,然后讓第一個Snackbar消失,然后消失之后再調用SnackbarManager顯示下一個Snackbar,如此循環,實現了Snackbar的順序顯示。 博客
  • Snackbar負責顯示和消失,具體來說其實就是添加和移除View的過程。Snackbar和SnackbarManager的設計很巧妙,利用一個SnackbarRecord對象保存Snackbar的顯示時間以及SnackbarManager.Callback對象,前面說到每一個Snackbar都有一個叫做mManagerCallback的SnackbarManager.Callback對象,下面看一下SnackRecord類的定義:

    • ?
  • Snackbar向SnackbarManager發送消息主要是調用SnackbarManager.getInstace()返回一個單例對象;而SnackManager向Snackbar發送消息就是通過show方法傳入的Callback對象。SnackbarManager中的Handler只處理一個MSG_TIMEOUT事件,最后是調用Snackbar的hideView消失的;Snackbar的sHandler處理兩個消息,showView和hideView,而消息的發送者是mManagerCallback,控制者是SnackbarManager。

總結

以上是生活随笔為你收集整理的decorview、window、dialog关系详解的全部內容,希望文章能夠幫你解決所遇到的問題。

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

主站蜘蛛池模板: 91在线观看免费高清 | 不卡的日韩av | 无码人妻精品一区二区三区夜夜嗨 | www麻豆| 久久久久久无码精品人妻一区二区 | 日韩一区二区a片免费观看 伊人网综合在线 | 在线观看国产一区 | 玖玖综合网| 日本乱轮视频 | www性| 亚洲激情在线视频 | 日韩av电影网站 | 久久成人亚洲 | 日本成人免费观看 | www.96av| 亚洲欧美另类一区 | 午夜看黄神器 | 日韩激情 | 大屁股白浆一区二区 | 手机在线免费观看av | 78日本xxxxxxxxx59| 激情在线观看视频 | 开心激情综合网 | 青青草老司机 | 亚洲一区h | 亚洲色图一区二区三区 | 男人和女人日批视频 | 久久久久亚洲AV成人无在 | 国产精品无码久久久久高潮 | 国产成人精品久久 | 国产精品成人在线 | 亚洲最新av| 艳妇臀荡乳欲伦交换在线播放 | 黄色日本视频 | 成人毛片软件 | 亚洲精品18p| 日韩女优在线观看 | 亚洲精品中文字幕成人片 | 国产又粗又黄视频 | 性色av网 | 久久人妻少妇嫩草av蜜桃 | 中文字幕亚洲一区二区三区 | 少妇特黄a一区二区三区 | 国产一区二区三区在线免费观看 | 东京久久久| 怡红院久久 | 自拍超碰 | 欧美午夜不卡 | 午夜激情在线播放 | 亚洲精品v天堂中文字幕 | 国产av一区二区三区精品 | 日韩黄片一区二区 | 丰满岳乱妇国产精品一区 | 91九色丨porny丨肉丝 | 日日躁夜夜躁狠狠久久av | 手机在线观看毛片 | 成人在线观看亚洲 | 久久乐视频 | 曰批又黄又爽免费视频 | 日韩在线观看一区二区 | 久久综合导航 | 五月婷婷丁香综合 | 黄片毛片在线看 | 韩国av免费在线观看 | 亚洲欧美高清在线 | www狠狠操 | 偷偷操网站 | 男生和女生靠逼视频 | 久久y| 香蕉视频在线网站 | 亚洲午夜精品久久久久久app | 国产天天操 | 日韩免费av一区二区 | 天天综合精品 | 久久国产一级 | aaaa免费视频 | av最新| 国产精品国产馆在线真实露脸 | 人妻一区二区三区在线 | 爆操少妇| 美女试爆场恐怖电影在线观看 | 成人福利在线播放 | 九九夜 | 午夜免费剧场 | 秋霞在线视频 | 欧美激情一级精品国产 | 国产精品久久av | 国产精品亲子伦对白 | 浪漫樱花在线观看高清动漫 | 久久夜色精品国产噜噜亚洲av | 亚洲精品欧洲 | 潘金莲一级淫片免费放动漫 | 污网站在线观看免费 | 少妇熟女一区二区 | 揉我啊嗯~喷水了h视频 | 国产一二三在线视频 | 在线成人毛片 | 久久精品a亚洲国产v高清不卡 | 在线色|