亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 編程 > Java > 正文

詳解Android中的Toast源碼

2019-11-26 15:02:43
字體:
來源:轉載
供稿:網友

Toast源碼實現
Toast入口
    我們在應用中使用Toast提示的時候,一般都是一行簡單的代碼調用,如下所示:
[java] view plaincopyprint?在CODE上查看代碼片派生到我的代碼片

  Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); 

    makeText就是Toast的入口,我們從makeText的源碼來深入理解Toast的實現。源碼如下(frameworks/base/core/java/android/widget/Toast.java):

  public static Toast makeText(Context context, CharSequence text, int duration) {     Toast result = new Toast(context);        LayoutInflater inflate = (LayoutInflater)         context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);     View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);     TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);     tv.setText(text);          result.mNextView = v;     result.mDuration = duration;        return result;   } 

    從makeText的源碼里,我們可以看出Toast的布局文件是transient_notification.xml,位于frameworks/base/core/res/res/layout/transient_notification.xml:

  <?xml version="1.0" encoding="utf-8"?>   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"     android:layout_width="match_parent"     android:layout_height="match_parent"     android:orientation="vertical"     android:background="?android:attr/toastFrameBackground">        <TextView       android:id="@android:id/message"       android:layout_width="wrap_content"       android:layout_height="wrap_content"       android:layout_weight="1"       android:layout_gravity="center_horizontal"       android:textAppearance="@style/TextAppearance.Toast"       android:textColor="@color/bright_foreground_dark"       android:shadowColor="#BB000000"       android:shadowRadius="2.75"       />      </LinearLayout> 

    系統Toast的布局文件非常簡單,就是在垂直布局的LinearLayout里放置了一個TextView。接下來,我們繼續跟到show()方法,研究一下布局形成之后的展示代碼實現:

  

 public void show() {     if (mNextView == null) {       throw new RuntimeException("setView must have been called");     }        INotificationManager service = getService();     String pkg = mContext.getPackageName();     TN tn = mTN;     tn.mNextView = mNextView;        try {       service.enqueueToast(pkg, tn, mDuration);     } catch (RemoteException e) {       // Empty     }   } 

    show方法中有兩點是需要我們注意的。(1)TN是什么東東?(2)INotificationManager服務的作用。帶著這兩個問題,繼續我們Toast源碼的探索。
TN源碼
    很多問題都能通過閱讀源碼找到答案,關鍵在與你是否有與之匹配的耐心和堅持。mTN的實現在Toast的構造函數中,源碼如下:

  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類的源碼出發,探尋TN的作用。TN源碼如下:

 

  private static class TN extends ITransientNotification.Stub {     final Runnable mShow = new Runnable() {       @Override       public void run() {         handleShow();       }     };        final Runnable mHide = new Runnable() {       @Override       public void run() {         handleHide();         // Don't do this in handleHide() because it is also invoked by handleShow()         mNextView = null;       }     };        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();     final Handler mHandler = new Handler();          int mGravity;     int mX, mY;     float mHorizontalMargin;     float mVerticalMargin;           View mView;     View mNextView;        WindowManager mWM;        TN() {       // XXX This should be changed to use a Dialog, with a Theme.Toast       // defined that sets up the layout params appropriately.       final WindowManager.LayoutParams params = mParams;       params.height = WindowManager.LayoutParams.WRAP_CONTENT;       params.width = WindowManager.LayoutParams.WRAP_CONTENT;       params.format = PixelFormat.TRANSLUCENT;       params.windowAnimations = com.android.internal.R.style.Animation_Toast;       params.type = WindowManager.LayoutParams.TYPE_TOAST;       params.setTitle("Toast");       params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON           | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE           | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;       /// M: [ALPS00517576] Support multi-user       params.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;     }        /**      * schedule handleShow into the right thread      */     @Override     public void show() {       if (localLOGV) Log.v(TAG, "SHOW: " + this);       mHandler.post(mShow);     }        /**      * schedule handleHide into the right thread      */     @Override     public void hide() {       if (localLOGV) Log.v(TAG, "HIDE: " + this);       mHandler.post(mHide);     }        public void handleShow() {       if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView           + " mNextView=" + mNextView);       if (mView != mNextView) {         // remove the old view if necessary         handleHide();         mView = mNextView;         Context context = mView.getContext().getApplicationContext();         if (context == null) {           context = mView.getContext();         }         mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);         // We can resolve the Gravity here by using the Locale for getting         // the layout direction         final Configuration config = mView.getContext().getResources().getConfiguration();         final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());         mParams.gravity = gravity;         if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {           mParams.horizontalWeight = 1.0f;         }         if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {           mParams.verticalWeight = 1.0f;         }         mParams.x = mX;         mParams.y = mY;         mParams.verticalMargin = mVerticalMargin;         mParams.horizontalMargin = mHorizontalMargin;         if (mView.getParent() != null) {           if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);           mWM.removeView(mView);         }         if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);         mWM.addView(mView, mParams);         trySendAccessibilityEvent();       }     }        private void trySendAccessibilityEvent() {       AccessibilityManager accessibilityManager =           AccessibilityManager.getInstance(mView.getContext());       if (!accessibilityManager.isEnabled()) {         return;       }       // treat toasts as notifications since they are used to       // announce a transient piece of information to the user       AccessibilityEvent event = AccessibilityEvent.obtain(           AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);       event.setClassName(getClass().getName());       event.setPackageName(mView.getContext().getPackageName());       mView.dispatchPopulateAccessibilityEvent(event);       accessibilityManager.sendAccessibilityEvent(event);     }            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.removeView(mView);         }            mView = null;       }     }   } 

    通過源碼,我們能很明顯的看到繼承關系,TN類繼承自ITransientNotification.Stub,用于進程間通信。這里假設讀者都有Android進程間通信的基礎(不太熟的建議學習羅升陽關于Binder進程通信的一系列博客)。既然TN是用于進程間通信,那么我們很容易想到TN類的具體作用應該是Toast類的回調對象,其他進程通過調用TN類的具體對象來操作Toast的顯示和消失。
    TN類繼承自ITransientNotification.Stub,ITransientNotification.aidl位于frameworks/base/core/java/android/app/ITransientNotification.aidl,源碼如下:

  package android.app;      /** @hide */   oneway interface ITransientNotification {     void show();     void hide();   } 

    ITransientNotification定義了兩個方法show()和hide(),它們的具體實現就在TN類當中。TN類的實現為:

  /**    * schedule handleShow into the right thread    */   @Override   public void show() {     if (localLOGV) Log.v(TAG, "SHOW: " + this);     mHandler.post(mShow);   }      /**    * schedule handleHide into the right thread    */   @Override   public void hide() {     if (localLOGV) Log.v(TAG, "HIDE: " + this);     mHandler.post(mHide);   } 

    這里我們就能知道,Toast的show和hide方法實現是基于Handler機制。而TN類中的Handler實現是:

  final Handler mHandler = new Handler();   

    而且,我們在TN類中沒有發現任何Looper.perpare()和Looper.loop()方法。說明,mHandler調用的是當前所在線程的Looper對象。所以,當我們在主線程(也就是UI線程中)可以隨意調用Toast.makeText方法,因為Android系統幫我們實現了主線程的Looper初始化。但是,如果你想在子線程中調用Toast.makeText方法,就必須先進行Looper初始化了,不然就會報出java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() 。Handler機制的學習可以參考我之前寫過的一篇博客:http://blog.csdn.net/wzy_1988/article/details/38346637。
    接下來,繼續跟一下mShow和mHide的實現,它倆的類型都是Runnable。

 

  final Runnable mShow = new Runnable() {     @Override     public void run() {       handleShow();     }   };      final Runnable mHide = new Runnable() {     @Override     public void run() {       handleHide();       // Don't do this in handleHide() because it is also invoked by handleShow()       mNextView = null;     }   }; 

    可以看到,show和hide的真正實現分別是調用了handleShow()和handleHide()方法。我們先來看handleShow()的具體實現:
   

 public void handleShow() {     if (mView != mNextView) {       // remove the old view if necessary       handleHide();       mView = mNextView;       Context context = mView.getContext().getApplicationContext();       if (context == null) {         context = mView.getContext();       }       mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);       // We can resolve the Gravity here by using the Locale for getting       // the layout direction       final Configuration config = mView.getContext().getResources().getConfiguration();       final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());       mParams.gravity = gravity;       if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {         mParams.horizontalWeight = 1.0f;       }       if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {         mParams.verticalWeight = 1.0f;       }       mParams.x = mX;       mParams.y = mY;       mParams.verticalMargin = mVerticalMargin;       mParams.horizontalMargin = mHorizontalMargin;       if (mView.getParent() != null) {         mWM.removeView(mView);       }       mWM.addView(mView, mParams);       trySendAccessibilityEvent();     }   } 

    從源碼中,我們知道Toast是通過WindowManager調用addView加載進來的。因此,hide方法自然是WindowManager調用removeView方法來將Toast視圖移除。
    總結一下,通過對TN類的源碼分析,我們知道了TN類是回調對象,其他進程調用tn類的show和hide方法來控制這個Toast的顯示和消失。
NotificationManagerService
    回到Toast類的show方法中,我們可以看到,這里調用了getService得到INotificationManager服務,源碼如下:

  private static INotificationManager sService;      static private INotificationManager getService() {     if (sService != null) {       return sService;     }     sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));     return sService;   } 

    得到INotificationManager服務后,調用了enqueueToast方法將當前的Toast放入到系統的Toast隊列中。傳的參數分別是pkg、tn和mDuration。也就是說,我們通過Toast.makeText(context, msg, Toast.LENGTH_SHOW).show()去呈現一個Toast,這個Toast并不是立刻顯示在當前的window上,而是先進入系統的Toast隊列中,然后系統調用回調對象tn的show和hide方法進行Toast的顯示和隱藏。
    這里INofiticationManager接口的具體實現類是NotificationManagerService類,位于frameworks/base/services/java/com/android/server/NotificationManagerService.java。
    首先,我們來分析一下Toast入隊的函數實現enqueueToast,源碼如下:

  public void enqueueToast(String pkg, ITransientNotification callback, int duration)   {     // packageName為null或者tn類為null,直接返回,不進隊列     if (pkg == null || callback == null) {       return ;     }        // (1) 判斷是否為系統Toast     final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));        // 判斷當前toast所屬的pkg是否為系統不允許發生Toast的pkg.NotificationManagerService有一個HashSet數據結構,存儲了不允許發生Toast的包名     if (ENABLE_BLOCKED_TOASTS && !noteNotificationOp(pkg, Binder.getCallingUid()) && !areNotificationsEnabledForPackageInt(pkg)) {       if (!isSystemToast) {         return;       }     }        synchronized (mToastQueue) {       int callingPid = Binder.getCallingPid();       long callingId = Binder.clearCallingIdentity();       try {         ToastRecord record;         // (2) 查看該Toast是否已經在隊列當中         int index = indexOfToastLocked(pkg, callback);         // 如果Toast已經在隊列中,我們只需要更新顯示時間即可         if (index >= 0) {           record = mToastQueue.get(index);           record.update(duration);         } else {           // 非系統Toast,每個pkg在當前mToastQueue中Toast有總數限制,不能超過MAX_PACKAGE_NOTIFICATIONS           if (!isSystemToast) {             int count = 0;             final int N = mToastQueue.size();             for (int i=0; i<N; i++) {                final ToastRecord r = mToastQueue.get(i);                if (r.pkg.equals(pkg)) {                  count++;                  if (count >= MAX_PACKAGE_NOTIFICATIONS) {                    Slog.e(TAG, "Package has already posted " + count                       + " toasts. Not showing more. Package=" + pkg);                    return;                  }                }             }           }              // 將Toast封裝成ToastRecord對象,放入mToastQueue中           record = new ToastRecord(callingPid, pkg, callback, duration);           mToastQueue.add(record);           index = mToastQueue.size() - 1;           // (3) 將當前Toast所在的進程設置為前臺進程           keepProcessAliveLocked(callingPid);         }         // (4) 如果index為0,說明當前入隊的Toast在隊頭,需要調用showNextToastLocked方法直接顯示         if (index == 0) {           showNextToastLocked();         }       } finally {         Binder.restoreCallingIdentity(callingId);       }     }   } 

    可以看到,我對上述代碼做了簡要的注釋。代碼相對簡單,但是還有4點標注代碼需要我們來進一步探討。
    (1) 判斷是否為系統Toast。如果當前Toast所屬的進程的包名為“android”,則為系統Toast,否則還可以調用isCallerSystem()方法來判斷。該方法的實現源碼為:

 

  boolean isUidSystem(int uid) {     final int appid = UserHandle.getAppId(uid);     return (appid == Process.SYSTEM_UID || appid == Process.PHONE_UID || uid == 0);   }   boolean isCallerSystem() {     return isUidSystem(Binder.getCallingUid());   } 

    isCallerSystem的源碼也比較簡單,就是判斷當前Toast所屬進程的uid是否為SYSTEM_UID、0、PHONE_UID中的一個,如果是,則為系統Toast;如果不是,則不為系統Toast。
    是否為系統Toast,通過下面的源碼閱讀可知,主要有兩點優勢:

    系統Toast一定可以進入到系統Toast隊列中,不會被黑名單阻止。
    系統Toast在系統Toast隊列中沒有數量限制,而普通pkg所發送的Toast在系統Toast隊列中有數量限制。

    (2) 查看將要入隊的Toast是否已經在系統Toast隊列中。這是通過比對pkg和callback來實現的,具體源碼如下所示:

 

  private int indexOfToastLocked(String pkg, ITransientNotification callback)   {     IBinder cbak = callback.asBinder();     ArrayList<ToastRecord> list = mToastQueue;     int len = list.size();     for (int i=0; i<len; i++) {       ToastRecord r = list.get(i);       if (r.pkg.equals(pkg) && r.callback.asBinder() == cbak) {         return i;       }     }     return -1;   } 

    通過上述代碼,我們可以得出一個結論,只要Toast的pkg名稱和tn對象是一致的,則系統把這些Toast認為是同一個Toast。
    (3) 將當前Toast所在進程設置為前臺進程。源碼如下所示:

  private void keepProcessAliveLocked(int pid)   {     int toastCount = 0; // toasts from this pid     ArrayList<ToastRecord> list = mToastQueue;     int N = list.size();     for (int i=0; i<N; i++) {       ToastRecord r = list.get(i);       if (r.pid == pid) {         toastCount++;       }     }     try {       mAm.setProcessForeground(mForegroundToken, pid, toastCount > 0);     } catch (RemoteException e) {       // Shouldn't happen.     }   } 

    這里的mAm=ActivityManagerNative.getDefault(),調用了setProcessForeground方法將當前pid的進程置為前臺進程,保證不會系統殺死。這也就解釋了為什么當我們finish當前Activity時,Toast還可以顯示,因為當前進程還在執行。
    (4) index為0時,對隊列頭的Toast進行顯示。源碼如下:

 

  private void showNextToastLocked() {     // 獲取隊列頭的ToastRecord     ToastRecord record = mToastQueue.get(0);     while (record != null) {       try {         // 調用Toast的回調對象中的show方法對Toast進行展示         record.callback.show();         scheduleTimeoutLocked(record);         return;       } catch (RemoteException e) {         Slog.w(TAG, "Object died trying to show notification " + record.callback             + " in package " + record.pkg);         // remove it from the list and let the process die         int index = mToastQueue.indexOf(record);         if (index >= 0) {           mToastQueue.remove(index);         }         keepProcessAliveLocked(record.pid);         if (mToastQueue.size() > 0) {           record = mToastQueue.get(0);         } else {           record = null;         }       }     }   } 

    這里Toast的回調對象callback就是tn對象。接下來,我們看一下,為什么系統Toast的顯示時間只能是2s或者3.5s,關鍵在于scheduleTimeoutLocked方法的實現。原理是,調用tn的show方法展示完Toast之后,需要調用scheduleTimeoutLocked方法來將Toast消失。(如果大家有疑問:不是說tn對象的hide方法來將Toast消失,為什么要在這里調用scheduleTimeoutLocked方法將Toast消失呢?是因為tn類的hide方法一執行,Toast立刻就消失了,而平時我們所使用的Toast都會在當前Activity停留幾秒。如何實現停留幾秒呢?原理就是scheduleTimeoutLocked發送MESSAGE_TIMEOUT消息去調用tn對象的hide方法,但是這個消息會有一個delay延遲,這里也是用了Handler消息機制)。

 

  private static final int LONG_DELAY = 3500; // 3.5 seconds   private static final int SHORT_DELAY = 2000; // 2 seconds   private void scheduleTimeoutLocked(ToastRecord r)   {     mHandler.removeCallbacksAndMessages(r);     Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);     long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;     mHandler.sendMessageDelayed(m, delay);   } 

    首先,我們看到這里并不是直接發送了MESSAGE_TIMEOUT消息,而是有個delay的延遲。而delay的時間從代碼中“long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;”看出只能為2s或者3.5s,這也就解釋了為什么系統Toast的呈現時間只能是2s或者3.5s。自己在Toast.makeText方法中隨意傳入一個duration是無作用的。
    接下來,我們來看一下WorkerHandler中是如何處理MESSAGE_TIMEOUT消息的。mHandler對象的類型為WorkerHandler,源碼如下:

  private final class WorkerHandler extends Handler   {     @Override     public void handleMessage(Message msg)     {       switch (msg.what)       {         case MESSAGE_TIMEOUT:           handleTimeout((ToastRecord)msg.obj);           break;       }     }   } 

    可以看到,WorkerHandler對MESSAGE_TIMEOUT類型的消息處理是調用了handlerTimeout方法,那我們繼續跟蹤handleTimeout源碼:

  private void handleTimeout(ToastRecord record)   {     synchronized (mToastQueue) {       int index = indexOfToastLocked(record.pkg, record.callback);       if (index >= 0) {         cancelToastLocked(index);       }     }   } 

    handleTimeout代碼中,首先判斷當前需要消失的Toast所屬ToastRecord對象是否在隊列中,如果在隊列中,則調用cancelToastLocked(index)方法。真相就要浮現在我們眼前了,繼續跟蹤源碼:

  private void cancelToastLocked(int index) {     ToastRecord record = mToastQueue.get(index);     try {       record.callback.hide();     } catch (RemoteException e) {       // don't worry about this, we're about to remove it from       // the list anyway     }     mToastQueue.remove(index);     keepProcessAliveLocked(record.pid);     if (mToastQueue.size() > 0) {       // Show the next one. If the callback fails, this will remove       // it from the list, so don't assume that the list hasn't changed       // after this point.       showNextToastLocked();     }   } 

    哈哈,看到這里,我們回調對象的hide方法也被調用了,同時也將該ToastRecord對象從mToastQueue中移除了。到這里,一個Toast的完整顯示和消失就講解結束了。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲 日韩 国产第一| 日韩大片在线观看视频| 亚洲女人天堂成人av在线| 国产伦精品一区二区三区精品视频| 久热99视频在线观看| 日韩美女激情视频| 亚洲精品国产品国语在线| 精品国产乱码久久久久久婷婷| 91天堂在线视频| 亚洲国产欧美一区二区丝袜黑人| 精品日本高清在线播放| 国产欧美亚洲视频| 一个人看的www欧美| 久久精品电影一区二区| 国产亚洲欧美日韩美女| 欧美国产在线视频| 国产美女91呻吟求| 国产在线观看精品| 国产亚洲欧洲高清| 亚洲精品少妇网址| 91中文在线观看| 日韩在线视频免费观看| 91久久精品在线| 精品国产一区二区三区四区在线观看| 欧美高清无遮挡| 亚洲午夜精品久久久久久性色| 国产精品美女www爽爽爽视频| 国产欧美日韩综合精品| 永久免费毛片在线播放不卡| 国产精品视频免费在线观看| 久久久综合免费视频| 国产成人aa精品一区在线播放| 精品动漫一区二区三区| 午夜欧美大片免费观看| 欧美刺激性大交免费视频| 亚洲国产精品99久久| 亚洲风情亚aⅴ在线发布| 国模gogo一区二区大胆私拍| 51ⅴ精品国产91久久久久久| 日韩中文字在线| 欧美日韩国产限制| 亚洲小视频在线观看| 久久久久久久久久久免费精品| 亚洲精品一区二区久| 最新中文字幕亚洲| 另类少妇人与禽zozz0性伦| 欧美成人精品不卡视频在线观看| 国产精品美女免费| 国产日韩av在线播放| 66m—66摸成人免费视频| 久久久免费高清电视剧观看| 日韩欧美精品免费在线| 中文字幕自拍vr一区二区三区| 欧美裸体xxxx极品少妇| 中文字幕亚洲综合久久| 在线看福利67194| 91精品国产高清自在线看超| 精品欧美国产一区二区三区| 精品一区二区三区电影| 日韩成人激情在线| 亚洲精品美女久久久久| 欧美国产日产韩国视频| 国产欧美亚洲精品| 日本久久久久亚洲中字幕| 亚洲女在线观看| 成人福利在线视频| 伊人伊成久久人综合网小说| 日韩精品福利网站| 久久久精品2019中文字幕神马| 欧美激情欧美激情| 国产98色在线| 欧美成年人在线观看| 91成人在线视频| 欧美成人四级hd版| 国产欧美日韩中文字幕在线| 日韩乱码在线视频| 国产一区二区成人| 欧美一区二区三区四区在线| 91高清免费视频| 亚洲国产成人久久综合| 欧美精品成人在线| 日韩一区二区在线视频| 亚洲精品日韩久久久| 国产精品视频免费在线观看| 欧美刺激性大交免费视频| 欧美视频专区一二在线观看| 米奇精品一区二区三区在线观看| 国产精品99免视看9| 欧美日韩国产一区中文午夜| 国产国产精品人在线视| 欧美激情精品久久久久| 麻豆国产精品va在线观看不卡| 久久噜噜噜精品国产亚洲综合| 亚洲韩国日本中文字幕| 91精品国产色综合久久不卡98| 欧美成人精品不卡视频在线观看| 日韩免费在线播放| 情事1991在线| 91精品国产高清久久久久久久久| 午夜精品久久久久久久99黑人| 九九热这里只有精品免费看| 欧美精品电影在线| 久久亚洲影音av资源网| 中文字幕精品一区久久久久| 欧美亚洲视频一区二区| 欧美日韩国产影院| 日韩欧美在线观看视频| 亚洲一区二区精品| 亚洲国产91色在线| 中日韩美女免费视频网站在线观看| 少妇激情综合网| 亚洲大胆人体视频| 欧美大尺度电影在线观看| 亚洲综合国产精品| 国语自产精品视频在线看一大j8| 在线观看欧美日韩| 国产精品欧美一区二区三区奶水| 久久夜色撩人精品| 韩国精品久久久999| 91久久在线观看| 日韩麻豆第一页| 亚洲福利在线看| 亚洲精品国产电影| 成人免费观看a| 成人久久久久久久| 国产欧美婷婷中文| 日韩在线欧美在线国产在线| 国模gogo一区二区大胆私拍| 亚洲美女喷白浆| 日韩电影大片中文字幕| 日韩中文字幕免费| 国产美女久久久| 国产97免费视| 热草久综合在线| 国产精品爽爽ⅴa在线观看| 国产精品日韩欧美综合| 萌白酱国产一区二区| 国产91精品久久久久久久| 高跟丝袜一区二区三区| 精品国产拍在线观看| 久色乳综合思思在线视频| 日本久久亚洲电影| 91精品国产高清自在线看超| 国产精品igao视频| 欧美日韩综合视频| 久久精品精品电影网| 在线观看91久久久久久| 欧美夜福利tv在线| 亚洲免费av网址| 国产日韩欧美夫妻视频在线观看| 日韩在线免费视频| 欧美影院成年免费版| 亚洲无av在线中文字幕| 欧美洲成人男女午夜视频| www.欧美视频| 最近2019年中文视频免费在线观看| 成人黄色午夜影院| 91精品在线一区| 成人午夜黄色影院| 国产xxx69麻豆国语对白| 亚洲福利在线视频| 国产视频精品久久久| 国内精品久久久久久影视8| 久久中文精品视频|